Skip to content

Commit

Permalink
yarn v1: Add dev attribute to package resolution
Browse files Browse the repository at this point in the history
Integrate dev dependency detection into the package resolution process:
- Add dev flag to YarnClassicPackageFactory
- Pass non-dev dependencies set through the resolution chain
- Update package creation to mark dependencies as dev/non-dev
- Update tests to accommodate the new dev flag

This completes the dependency classification implementation by marking
each resolved package with its development status.

Signed-off-by: Michal Šoltis <[email protected]>
  • Loading branch information
slimreaper35 committed Dec 2, 2024
1 parent 76b3024 commit 9ded4b4
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 11 deletions.
20 changes: 16 additions & 4 deletions cachi2/core/package_managers/yarn_classic/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from cachi2.core.errors import PackageRejected, UnexpectedFormat
from cachi2.core.package_managers.npm import NPM_REGISTRY_CNAMES
from cachi2.core.package_managers.yarn_classic.project import PackageJson, Project, YarnLock
from cachi2.core.package_managers.yarn_classic.utils import find_runtime_deps
from cachi2.core.package_managers.yarn_classic.workspaces import (
Workspace,
extract_workspace_metadata,
Expand Down Expand Up @@ -79,8 +80,9 @@ class LinkPackage(_BasePackage, _RelpathMixin):


class _YarnClassicPackageFactory:
def __init__(self, source_dir: RootedPath):
def __init__(self, source_dir: RootedPath, runtime_deps: set[str]) -> None:
self._source_dir = source_dir
self._runtime_deps = runtime_deps

def create_package_from_pyarn_package(self, package: PYarnPackage) -> YarnClassicPackage:
def assert_package_has_relative_path(package: PYarnPackage) -> None:
Expand All @@ -93,12 +95,16 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None:
solution="Ensure that file/link packages in yarn.lock do not have absolute paths.",
)

package_id = f"{package.name}@{package.version}"
dev = package_id not in self._runtime_deps

if _is_from_npm_registry(package.url):
return RegistryPackage(
name=package.name,
version=package.version,
integrity=package.checksum,
url=package.url,
dev=dev,
)
elif package.path is not None:
# Ensure path is not absolute
Expand All @@ -112,24 +118,28 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None:
version=package.version,
relpath=path.subpath_from_root,
integrity=package.checksum,
dev=dev,
)
return LinkPackage(
name=package.name,
version=package.version,
relpath=path.subpath_from_root,
dev=dev,
)
elif _is_git_url(package.url):
return GitPackage(
name=package.name,
version=package.version,
url=package.url,
dev=dev,
)
elif _is_tarball_url(package.url):
return UrlPackage(
name=package.name,
version=package.version,
url=package.url,
integrity=package.checksum,
dev=dev,
)
else:
raise UnexpectedFormat(
Expand Down Expand Up @@ -187,11 +197,11 @@ def _is_from_npm_registry(url: str) -> bool:


def _get_packages_from_lockfile(
source_dir: RootedPath, yarn_lock: YarnLock
source_dir: RootedPath, yarn_lock: YarnLock, runtime_deps: set[str]
) -> list[YarnClassicPackage]:
"""Return a list of Packages for all dependencies in yarn.lock."""
pyarn_packages: list[PYarnPackage] = yarn_lock.yarn_lockfile.packages()
package_factory = _YarnClassicPackageFactory(source_dir)
package_factory = _YarnClassicPackageFactory(source_dir, runtime_deps)

return [
package_factory.create_package_from_pyarn_package(package) for package in pyarn_packages
Expand Down Expand Up @@ -230,8 +240,10 @@ def resolve_packages(project: Project) -> Iterable[YarnClassicPackage]:
"""Return a list of Packages corresponding to all project dependencies."""
workspaces = extract_workspace_metadata(project.source_dir)
yarn_lock = YarnLock.from_file(project.source_dir.join_within_root("yarn.lock"))
runtime_deps = find_runtime_deps(project.package_json, yarn_lock, workspaces)

return chain(
[_get_main_package(project.package_json)],
_get_workspace_packages(project.source_dir, workspaces),
_get_packages_from_lockfile(project.source_dir, yarn_lock),
_get_packages_from_lockfile(project.source_dir, yarn_lock, runtime_deps),
)
21 changes: 14 additions & 7 deletions tests/unit/package_managers/yarn_classic/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ def test__is_from_npm_registry_can_parse_incorrect_registry_urls() -> None:
def test_create_package_from_pyarn_package(
pyarn_package: PYarnPackage, expected_package: YarnClassicPackage, rooted_tmp_path: RootedPath
) -> None:
package_factory = _YarnClassicPackageFactory(rooted_tmp_path)
package_factory = _YarnClassicPackageFactory(
rooted_tmp_path,
set({f"{pyarn_package.name}@{pyarn_package.version}"}),
)
assert package_factory.create_package_from_pyarn_package(pyarn_package) == expected_package


Expand All @@ -194,7 +197,7 @@ def test_create_package_from_pyarn_package_fail_absolute_path(rooted_tmp_path: R
f"({pyarn_package.path}), which is not permitted."
)

package_factory = _YarnClassicPackageFactory(rooted_tmp_path)
package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set())
with pytest.raises(PackageRejected, match=re.escape(error_msg)):
package_factory.create_package_from_pyarn_package(pyarn_package)

Expand All @@ -208,7 +211,7 @@ def test_create_package_from_pyarn_package_fail_path_outside_root(
path="../path/outside/root",
)

package_factory = _YarnClassicPackageFactory(rooted_tmp_path)
package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set())
with pytest.raises(PathOutsideRoot):
package_factory.create_package_from_pyarn_package(pyarn_package)

Expand All @@ -222,7 +225,7 @@ def test_create_package_from_pyarn_package_fail_unexpected_format(
url="ftp://some-tarball.tgz",
)

package_factory = _YarnClassicPackageFactory(rooted_tmp_path)
package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set())
with pytest.raises(UnexpectedFormat):
package_factory.create_package_from_pyarn_package(pyarn_package)

Expand All @@ -233,7 +236,6 @@ def test_create_package_from_pyarn_package_fail_unexpected_format(
def test__get_packages_from_lockfile(
mock_create_package: mock.Mock, rooted_tmp_path: RootedPath
) -> None:

# Setup lockfile instance
mock_pyarn_lockfile = mock.Mock()
mock_yarn_lock = mock.Mock(yarn_lockfile=mock_pyarn_lockfile)
Expand All @@ -250,7 +252,7 @@ def test__get_packages_from_lockfile(
mock.call(mock_pyarn_package_2),
]

output = _get_packages_from_lockfile(rooted_tmp_path, mock_yarn_lock)
output = _get_packages_from_lockfile(rooted_tmp_path, mock_yarn_lock, set())

mock_pyarn_lockfile.packages.assert_called_once()
mock_create_package.assert_has_calls(create_package_expected_calls)
Expand All @@ -262,7 +264,9 @@ def test__get_packages_from_lockfile(
@mock.patch("cachi2.core.package_managers.yarn_classic.resolver.extract_workspace_metadata")
@mock.patch("cachi2.core.package_managers.yarn_classic.resolver._get_packages_from_lockfile")
@mock.patch("cachi2.core.package_managers.yarn_classic.resolver._get_main_package")
@mock.patch("cachi2.core.package_managers.yarn_classic.resolver.find_runtime_deps")
def test_resolve_packages(
find_runtime_deps: mock.Mock,
mock_get_main_package: mock.Mock,
mock_get_lockfile_packages: mock.Mock,
mock_extract_workspaces: mock.Mock,
Expand All @@ -278,6 +282,7 @@ def test_resolve_packages(
lockfile_packages = [mock.Mock(), mock.Mock()]
expected_output = [main_package, *workspace_packages, *lockfile_packages]

find_runtime_deps.return_value = set()
mock_get_main_package.return_value = main_package
mock_get_lockfile_packages.return_value = lockfile_packages
mock_get_workspace_packages.return_value = workspace_packages
Expand All @@ -290,7 +295,9 @@ def test_resolve_packages(
rooted_tmp_path, mock_extract_workspaces.return_value
)
mock_get_lockfile_packages.assert_called_once_with(
rooted_tmp_path, mock_get_yarn_lock.return_value
rooted_tmp_path,
mock_get_yarn_lock.return_value,
find_runtime_deps.return_value,
)
assert list(output) == expected_output

Expand Down

0 comments on commit 9ded4b4

Please sign in to comment.