示例#1
0
def test_lockfile_validation(rule_runner: RuleRunner) -> None:
    """Check that we properly load and validate lockfile metadata for both types of locks.

    Note that we don't exhaustively test every source of lockfile failure nor the different options
    for `--invalid-lockfile-behavior`, as those are already tested in pex_requirements_test.py.
    """

    # We create a lockfile that claims it works with no requirements. It should fail when we try
    # to build a PEX with a requirement.
    lock_content = PythonLockfileMetadata.new(InterpreterConstraints(),
                                              set()).add_header_to_lockfile(
                                                  b"",
                                                  regenerate_command="regen",
                                                  delimeter="#")
    rule_runner.write_files({"lock.txt": lock_content.decode()})

    lockfile = Lockfile(
        "lock.txt",
        file_path_description_of_origin="a test",
        resolve_name="a",
        req_strings=FrozenOrderedSet("ansicolors"),
    )
    with engine_error(InvalidLockfileError):
        create_pex_and_get_all_data(rule_runner, requirements=lockfile)

    lockfile_content = LockfileContent(
        FileContent("lock.txt", lock_content),
        resolve_name="a",
        req_strings=FrozenOrderedSet("ansicolors"),
    )
    with engine_error(InvalidLockfileError):
        create_pex_and_get_all_data(rule_runner, requirements=lockfile_content)
示例#2
0
async def choose_python_resolve(
        request: ChosenPythonResolveRequest,
        python_setup: PythonSetup) -> ChosenPythonResolve:
    transitive_targets = await Get(TransitiveTargets,
                                   TransitiveTargetsRequest(request.addresses))

    def maybe_get_resolve(t: Target) -> str | None:
        if not t.has_field(PythonResolveField):
            return None
        return t[PythonResolveField].normalized_value(python_setup)

    # First, choose the resolve by inspecting the root targets.
    root_resolves = {
        root[PythonResolveField].normalized_value(python_setup)
        for root in transitive_targets.roots
        if root.has_field(PythonResolveField)
    }
    if root_resolves:
        if len(root_resolves) > 1:
            raise NoCompatibleResolveException.bad_input_roots(
                transitive_targets.roots,
                maybe_get_resolve=maybe_get_resolve,
                doc_url_slug=
                "python-third-party-dependencies#multiple-lockfiles",
                workaround=None,
            )

        chosen_resolve = next(iter(root_resolves))

        # Then, validate that all transitive deps are compatible.
        for tgt in transitive_targets.dependencies:
            if (tgt.has_field(PythonResolveField)
                    and tgt[PythonResolveField].normalized_value(python_setup)
                    != chosen_resolve):
                raise NoCompatibleResolveException.bad_dependencies(
                    maybe_get_resolve=maybe_get_resolve,
                    doc_url_slug=
                    "python-third-party-dependencies#multiple-lockfiles",
                    root_resolve=chosen_resolve,
                    root_targets=transitive_targets.roots,
                    dependencies=transitive_targets.dependencies,
                )

    else:
        # If there are no relevant targets, we fall back to the default resolve. This is relevant,
        # for example, when running `./pants repl` with no specs or only on non-Python targets.
        chosen_resolve = python_setup.default_resolve

    return ChosenPythonResolve(
        name=chosen_resolve,
        lockfile=Lockfile(
            file_path=python_setup.resolves[chosen_resolve],
            file_path_description_of_origin=(
                f"the resolve `{chosen_resolve}` (from `[python].resolves`)"),
            resolve_name=chosen_resolve,
        ),
    )
示例#3
0
 def create_lock(path: str) -> None:
     lock = Lockfile(
         path,
         file_path_description_of_origin="foo",
         resolve_name="a",
     )
     create_pex_and_get_pex_info(
         rule_runner,
         requirements=EntireLockfile(lock, ("ansicolors", )),
         additional_pants_args=(
             "--python-invalid-lockfile-behavior=ignore", ),
     )
示例#4
0
async def get_repository_pex(
    request: _RepositoryPexRequest, python_setup: PythonSetup
) -> OptionalPexRequest:

    interpreter_constraints = await Get(
        InterpreterConstraints,
        InterpreterConstraintsRequest,
        request.to_interpreter_constraints_request(),
    )

    repository_pex_request: PexRequest | None = None
    if python_setup.requirement_constraints:
        constraints_repository_pex_request = await Get(
            OptionalPexRequest,
            _ConstraintsRepositoryPexRequest(request),
        )
        repository_pex_request = constraints_repository_pex_request.maybe_pex_request
    elif (
        python_setup.resolve_all_constraints
        and python_setup.resolve_all_constraints_was_set_explicitly()
    ):
        raise ValueError(
            "`[python].resolve_all_constraints` is enabled, so "
            "`[python].requirement_constraints` must also be set."
        )
    elif python_setup.enable_resolves:
        chosen_resolve = await Get(
            ChosenPythonResolve, ChosenPythonResolveRequest(request.addresses)
        )
        repository_pex_request = PexRequest(
            description=(
                f"Installing {chosen_resolve.lockfile_path} for the resolve `{chosen_resolve.name}`"
            ),
            output_filename=f"{path_safe(chosen_resolve.name)}_lockfile.pex",
            internal_only=request.internal_only,
            requirements=Lockfile(
                file_path=chosen_resolve.lockfile_path,
                file_path_description_of_origin=(
                    f"the resolve `{chosen_resolve.name}` (from `[python].resolves`)"
                ),
                resolve_name=chosen_resolve.name,
                req_strings=request.requirements.req_strings,
            ),
            interpreter_constraints=interpreter_constraints,
            platforms=request.platforms,
            complete_platforms=request.complete_platforms,
            additional_args=request.additional_lockfile_args,
        )
    return OptionalPexRequest(repository_pex_request)
示例#5
0
def test_validate_user_lockfiles(
    invalid_reqs: bool,
    invalid_constraints: bool,
    caplog,
) -> None:
    runtime_interpreter_constraints = (InterpreterConstraints([
        "==2.7.*"
    ]) if invalid_constraints else METADATA.valid_for_interpreter_constraints)
    req_strings = FrozenOrderedSet(["bad-req"] if invalid_reqs else
                                   [str(r) for r in METADATA.requirements])
    lockfile = Lockfile(
        file_path="lock.txt",
        file_path_description_of_origin="foo",
        resolve_name="a",
    )

    # Ignore validation if resolves are manually managed.
    assert not should_validate_metadata(
        lockfile,
        create_python_setup(InvalidLockfileBehavior.warn,
                            enable_resolves=False))

    validate_metadata(
        METADATA,
        runtime_interpreter_constraints,
        lockfile,
        req_strings,
        create_python_setup(InvalidLockfileBehavior.warn),
    )

    def contains(msg: str, if_: bool = True) -> None:
        assert (msg in caplog.text) is if_

    contains(
        "You are using the lockfile at lock.txt to install the resolve `a`")
    contains(
        "The targets depend on requirements that are not in the lockfile: ['bad-req']",
        if_=invalid_reqs,
    )
    contains("The targets use interpreter constraints",
             if_=invalid_constraints)
    contains("./pants generate-lockfiles --resolve=a`")
示例#6
0
 def assert_ics(
     lockfile: str,
     expected: list[str],
     *,
     ics: RankedValue = RankedValue(Rank.HARDCODED,
                                    Black.default_interpreter_constraints),
     metadata: PythonLockfileMetadata
     | None = PythonLockfileMetadata.new(
         InterpreterConstraints(["==2.7.*"]), set()),
 ) -> None:
     black = create_subsystem(
         Black,
         lockfile=lockfile,
         interpreter_constraints=ics,
         version="v",
         extra_requirements=[],
     )
     loaded_lock = LoadedLockfile(
         EMPTY_DIGEST,
         "black.lock",
         metadata=metadata,
         requirement_estimate=1,
         is_pex_native=True,
         constraints_strings=None,
         original_lockfile=Lockfile("black.lock",
                                    file_path_description_of_origin="foo",
                                    resolve_name="black"),
     )
     result = run_rule_with_mocks(
         _find_python_interpreter_constraints_from_lockfile,
         rule_args=[black],
         mock_gets=[
             MockGet(
                 output_type=LoadedLockfile,
                 input_type=LoadedLockfileRequest,
                 mock=lambda _: loaded_lock,
             )
         ],
     )
     assert result == InterpreterConstraints(expected)
示例#7
0
def test_build_pex_description() -> None:
    def assert_description(
        requirements: PexRequirements | Lockfile | LockfileContent,
        *,
        description: str | None = None,
        expected: str,
    ) -> None:
        request = PexRequest(
            output_filename="new.pex",
            internal_only=True,
            requirements=requirements,
            description=description,
        )
        assert _build_pex_description(request) == expected

    repo_pex = Pex(EMPTY_DIGEST, "repo.pex", None)

    assert_description(PexRequirements(),
                       description="Custom!",
                       expected="Custom!")
    assert_description(PexRequirements(repository_pex=repo_pex),
                       description="Custom!",
                       expected="Custom!")

    assert_description(PexRequirements(), expected="Building new.pex")
    assert_description(PexRequirements(repository_pex=repo_pex),
                       expected="Building new.pex")

    assert_description(PexRequirements(["req"]),
                       expected="Building new.pex with 1 requirement: req")
    assert_description(
        PexRequirements(["req"], repository_pex=repo_pex),
        expected="Extracting 1 requirement to build new.pex from repo.pex: req",
    )

    assert_description(
        PexRequirements(["req1", "req2"]),
        expected="Building new.pex with 2 requirements: req1, req2",
    )
    assert_description(
        PexRequirements(["req1", "req2"], repository_pex=repo_pex),
        expected=
        "Extracting 2 requirements to build new.pex from repo.pex: req1, req2",
    )

    assert_description(
        LockfileContent(
            file_content=FileContent("lock.txt", b""),
            resolve_name="a",
            req_strings=FrozenOrderedSet(),
        ),
        expected="Building new.pex from lock.txt",
    )

    assert_description(
        Lockfile(
            file_path="lock.txt",
            file_path_description_of_origin="foo",
            resolve_name="a",
            req_strings=FrozenOrderedSet(),
        ),
        expected="Building new.pex from lock.txt",
    )