Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(yarnv1): read package.json & yarnlock #705

Conversation

ben-alkov
Copy link
Member

@ben-alkov ben-alkov commented Oct 23, 2024

Closes GH Issue #627

What

  1. Ensure that 'package.json' and 'yarn.lock' files exist
  2. Read from 'package.json' and 'yarn.lock' files
  • Reject packages as unsupported if PnP (Plug n' Play) is being used

    i.e. any of

    • installConfig.pnp: true in 'package.json'
    • file(s) with glob name '*.pnp.cjs' exist
    • any expanded 'node_modules' directory exists

How

  • add Project class
  • add class hierarchy for config files (CommonConfigFile ABC)
  • implement _check_pnp_installs() in 'main.py'
  • add 'pyarn' to requirements (for parsing 'yarn.lock')

N.B. RE: mypy

  • I had to add type: ignore for the 'pyarn' import, as 'pyarn' is untyped
  • Removed altogether main.py::_resolve_yarn_project() has a temporary type: ignore for its return type, as it has no return yet (coming later from a different issue)

Maintainers will complete the following section

  • Commit messages are descriptive enough
  • Code coverage from testing does not decrease and new code is covered
  • Docs updated (if applicable)
  • Docs links in the code are still valid (if docs were updated)

Note: if the contribution is external (not from an organization member), the CI
pipeline will not run automatically. After verifying that the CI is safe to run:

Comment on lines 265 to 268
# if installConfig.pnp:
# if self.yarn_cache.path.exists() and self.yarn_cache.path.is_dir():
# # in this case the cache folder will be populated with downloaded ZIP dependencies
# return any(file.suffix == ".zip" for file in self.yarn_cache.path.iterdir())

Check notice

Code scanning / CodeQL

Commented-out code

This comment appears to contain commented-out code.
@ben-alkov ben-alkov marked this pull request as draft October 23, 2024 13:54
@ben-alkov
Copy link
Member Author

yarn.cache will be removed; handling yarnrc will be removed to a separate PR

Copy link
Collaborator

@a-ovchinnikov a-ovchinnikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor suggestions and some questions.

cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
@ben-alkov
Copy link
Member Author

Some fixes and changes mentioned by Alexey are in place; removed YarnRC and yarn.cache

def _verify_repository(project: Project) -> None:
# _check_for_pnp(project)
# _check_lockfile(project)
pass

Check warning

Code scanning / CodeQL

Unnecessary pass

Unnecessary 'pass' statement.
@ben-alkov
Copy link
Member Author

Implemented _check_pnp_installs, is_pnp_install; removed all write() methods; added basic unit tests

return "package_json"

@property
def package_manager(self) -> Optional[str]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least for the initial implementation, I don't think we'll need to get/set the packageManager field in package.json

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

def _check_pnp_installs(project: Project) -> None:
if project.is_pnp_install:
raise PackageRejected(
reason=("Yarn zero install detected, PnP zero installs are unsupported by cachi2"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "zero-installs" are a term/feature that only really exists in Yarn v2+, so we might want to avoid using it here for Yarn 1.x. Same with the solution text mention of .yarn/cache

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gah. Copy-pasta.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@ben-alkov ben-alkov force-pushed the feat-yarnv1-read-pkg-json-yarn-lock branch from 63b7946 to 903bd5b Compare October 25, 2024 21:08
@ben-alkov
Copy link
Member Author

We've got 80% coverage of new code.

I figure somewhere between 2-5 more unit tests should cover everything, and they'll all be pretty straightforward.

@ben-alkov ben-alkov force-pushed the feat-yarnv1-read-pkg-json-yarn-lock branch from ea7eeb4 to 9321a9f Compare October 30, 2024 15:53
@ben-alkov
Copy link
Member Author

Not ready to merge, as I need to increase coverage, but I'm confident in the implementation.

@ben-alkov ben-alkov marked this pull request as ready for review October 30, 2024 15:59
@ben-alkov ben-alkov force-pushed the feat-yarnv1-read-pkg-json-yarn-lock branch 2 times, most recently from b06b279 to 0e585af Compare October 31, 2024 19:18
@ben-alkov
Copy link
Member Author

/retest

Comment on lines 115 to 132
def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> list[Component]: # type: ignore
# Temporary type ignore
"""Process a request for a single yarn source directory.

:param project: the directory to be processed.
:param output_dir: the directory where the prefetched dependencies will be placed.
:raises PackageManagerError: if fetching dependencies fails
"""
log.info(f"Fetching the yarn-classic dependencies at the subpath {project.source_dir}")

_verify_repository(project)

# Placeholders for implementations in other PRs
# _set_yarnrc_configuration(project, output_dir)
# packages = resolve_packages(project.source_dir)
# _fetch_dependencies(project.source_dir)

# return create_components(packages, project, output_dir)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is not used anywhere

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

@property
def config_kind(self) -> ConfigKind:
"""Return kind of this ConfigFile."""
return "yarnlock"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, this property should be dropped and replaced with isinstance(<class-name>) somewhere else in the code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case here is being able to use the pattern what_are_you = foo.config_kind rather than being forced to use

if isinstance(foo):
    what_are_you = i_am_foo
elif isinstance(bar):
    what_are_you = i_am_bar
elif isinstance(baz):
    what_are_you = i_am_baz

every time.

If we're truly switching a different code block for each and all classes, then yeah, the if-tree is necessary.

Copy link
Collaborator

@slimreaper35 slimreaper35 Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the real use case here ? __CommonConfigFile is the abstract class so you can't instantiate it anyway.
kind = PackageJson.config_kind == "package_json" this feels awkward to me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: So my point about isinstance is not valid, ConfigKind is just Literal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But still don't understand the use case of such property.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT 2: I understand, but using the class as type insted of literal is much clearer IMO.

return cls(path, package_json_data)


class YarnLock(_CommonConfigFile):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the inheritance here, data and path attributes are not used anywhere inside YarnLock class.

I would say package.json and yarn.lock are quite different from each other.

Copy link
Member Author

@ben-alkov ben-alkov Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They will be eventually. Also, they are siblings of the yarnrc class which will be implemented later

cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
if not yarn_lockfile:
raise PackageRejected(
reason="The yarn.lock file must not be empty",
solution=("Please verify the content of the file."),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

( and ) are not necessary here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor fallout :rolleyes:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
@slimreaper35
Copy link
Collaborator

As I often get these recommendations, you could split the changes into more commits. For example

  • implement yarn.lock
  • implement package.json
    (package.json has nothing to do with pyarn being added to requirements.txt)

return None


def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> list[Component]: # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should just drop this function in this PR and I'll introduce it in mine. That way the unit tests for detecting pnp installs could just test _verify_repository rather than (what will be) the more expansive _resolve_yarn_project

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

),
],
)
def test_pnp_installs_detection(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It might make this simpler to make the error case a separate test

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is applicable to all test cases that have branching in them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

--hash=sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016 \
--hash=sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504
# via yarl
pyarn==0.2.0 \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pyarn is pinned to different versions in requirements.txt and requirements-extras.txt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

)
except ValueError as e:
raise PackageRejected(
reason=(f"Can't parse the {path.subpath_from_root} file.\n" f"{e}"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output here could potentially be lengthy, so I think we definitely want the exceptions chained but without repeating the output in the new PackageRejected exception we are raising

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

cachi2/core/package_managers/yarn_classic/main.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/main.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/main.py Outdated Show resolved Hide resolved
),
],
)
def test_pnp_installs_detection(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is applicable to all test cases that have branching in them.

cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
@ben-alkov ben-alkov force-pushed the feat-yarnv1-read-pkg-json-yarn-lock branch from 0e585af to 1ffda55 Compare November 11, 2024 19:42
@ben-alkov
Copy link
Member Author

As I often get these recommendations, you could split the changes into more commits. For example

* implement yarn.lock

* implement package.json
  (package.json has nothing to do with `pyarn`  being added to requirements.txt)

Fixed

@ben-alkov ben-alkov force-pushed the feat-yarnv1-read-pkg-json-yarn-lock branch from 1ffda55 to c0892c3 Compare November 12, 2024 14:04
Copy link
Collaborator

@a-ovchinnikov a-ovchinnikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

99% LGTM (with a couple small and trivial fixes).

cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
cachi2/core/package_managers/yarn_classic/project.py Outdated Show resolved Hide resolved
## What

- Ensure that 'package.json' file exists
- Read from 'package.json'

## How

- introduce ABC `_CommonConfigFile` dataclass to represent config files
- implement `PackageJson` CommonConfigFile subclass

Signed-off-by: Ben Alkov <[email protected]>
## What

- a class representing a yarn project
- can create Projects from a source dir
- can check for (disallowed) PnP installs

## How

- implement `Project` as a frozen dataclass

Signed-off-by: Ben Alkov <[email protected]>
## What

- Ensure that 'yarn.lock' file exists
- Read from 'yarn.lock'

## How

- add 'pyarn' to dependencies (to parse 'yarn.lock' file)
- implement `YarnLock` CommonConfigFile subclass

Signed-off-by: Ben Alkov <[email protected]>
@ben-alkov ben-alkov force-pushed the feat-yarnv1-read-pkg-json-yarn-lock branch from c0892c3 to 8a2e7d3 Compare November 12, 2024 16:48
@ben-alkov ben-alkov added this pull request to the merge queue Nov 13, 2024
Merged via the queue into containerbuildsystem:main with commit 58faa67 Nov 13, 2024
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants