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)
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, ), )
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", ), )
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)
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`")
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)
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", )