Example #1
0
def create_pex_and_get_pex_info(
    rule_runner: RuleRunner,
    *,
    pex_type: type[Pex | VenvPex] = Pex,
    requirements: PexRequirements | Lockfile
    | LockfileContent = PexRequirements(),
    main: MainSpecification | None = None,
    interpreter_constraints: InterpreterConstraints = InterpreterConstraints(),
    platforms: PexPlatforms = PexPlatforms(),
    sources: Digest | None = None,
    additional_pants_args: tuple[str, ...] = (),
    additional_pex_args: tuple[str, ...] = (),
    internal_only: bool = True,
) -> Mapping[str, Any]:
    return create_pex_and_get_all_data(
        rule_runner,
        pex_type=pex_type,
        requirements=requirements,
        main=main,
        interpreter_constraints=interpreter_constraints,
        platforms=platforms,
        sources=sources,
        additional_pants_args=additional_pants_args,
        additional_pex_args=additional_pex_args,
        internal_only=internal_only,
    ).info
Example #2
0
async def setup_user_lockfile_requests(
        requested: RequestedPythonUserResolveNames, all_targets: AllTargets,
        python_setup: PythonSetup) -> UserGenerateLockfiles:
    if not (python_setup.enable_resolves
            and python_setup.resolves_generate_lockfiles):
        return UserGenerateLockfiles()

    resolve_to_requirements_fields = defaultdict(set)
    for tgt in all_targets:
        if not tgt.has_fields(
            (PythonRequirementResolveField, PythonRequirementsField)):
            continue
        resolve = tgt[PythonRequirementResolveField].normalized_value(
            python_setup)
        resolve_to_requirements_fields[resolve].add(
            tgt[PythonRequirementsField])

    return UserGenerateLockfiles(
        GeneratePythonLockfile(
            requirements=PexRequirements.create_from_requirement_fields(
                resolve_to_requirements_fields[resolve],
                constraints_strings=(),
            ).req_strings,
            interpreter_constraints=InterpreterConstraints(
                python_setup.resolves_to_interpreter_constraints.get(
                    resolve, python_setup.interpreter_constraints)),
            resolve_name=resolve,
            lockfile_dest=python_setup.resolves[resolve],
            use_pex=python_setup.generate_lockfiles_with_pex,
        ) for resolve in requested)
Example #3
0
def test_venv_pex_resolve_info(rule_runner: RuleRunner,
                               pex_type: type[Pex | VenvPex]) -> None:
    constraints = [
        "requests==2.23.0",
        "certifi==2020.12.5",
        "chardet==3.0.4",
        "idna==2.10",
        "urllib3==1.25.11",
    ]
    rule_runner.write_files({"constraints.txt": "\n".join(constraints)})
    pex = create_pex_and_get_all_data(
        rule_runner,
        pex_type=pex_type,
        requirements=PexRequirements(["requests==2.23.0"],
                                     constraints_strings=constraints),
        additional_pants_args=(
            "--python-requirement-constraints=constraints.txt", ),
    ).pex
    dists = rule_runner.request(PexResolveInfo, [pex])
    assert dists[0] == PexDistributionInfo("certifi", Version("2020.12.5"),
                                           None, ())
    assert dists[1] == PexDistributionInfo("chardet", Version("3.0.4"), None,
                                           ())
    assert dists[2] == PexDistributionInfo(
        "idna", Version("2.10"),
        SpecifierSet("!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"), ())
    assert dists[3].project_name == "requests"
    assert dists[3].version == Version("2.23.0")
    # requires_dists is parsed from metadata written by the pex tool, and is always
    #   a set of valid pkg_resources.Requirements.
    assert Requirement.parse(
        'PySocks!=1.5.7,>=1.5.6; extra == "socks"') in dists[3].requires_dists
    assert dists[4].project_name == "urllib3"
Example #4
0
async def resolve_plugins(
        request: PluginsRequest,
        global_options: GlobalOptions) -> ResolvedPluginDistributions:
    """This rule resolves plugins using a VenvPex, and exposes the absolute paths of their dists.

    NB: This relies on the fact that PEX constructs venvs in a stable location (within the
    `named_caches` directory), but consequently needs to disable the process cache: see the
    ProcessCacheScope reference in the body.
    """
    requirements = PexRequirements(
        req_strings=sorted(global_options.plugins),
        constraints_strings=(str(constraint)
                             for constraint in request.constraints),
    )
    if not requirements:
        return ResolvedPluginDistributions()

    python: PythonExecutable | None = None
    if not request.interpreter_constraints:
        python = cast(
            PythonExecutable,
            PythonExecutable.fingerprinted(
                sys.executable,
                ".".join(map(str, sys.version_info[:3])).encode("utf8")),
        )

    plugins_pex = await Get(
        VenvPex,
        PexRequest(
            output_filename="pants_plugins.pex",
            internal_only=True,
            python=python,
            requirements=requirements,
            interpreter_constraints=request.interpreter_constraints,
            description=
            f"Resolving plugins: {', '.join(requirements.req_strings)}",
        ),
    )

    # NB: We run this Process per-restart because it (intentionally) leaks named cache
    # paths in a way that invalidates the Process-cache. See the method doc.
    cache_scope = (ProcessCacheScope.PER_SESSION
                   if global_options.plugins_force_resolve else
                   ProcessCacheScope.PER_RESTART_SUCCESSFUL)

    plugins_process_result = await Get(
        ProcessResult,
        VenvPexProcess(
            plugins_pex,
            argv=
            ("-c",
             "import os, site; print(os.linesep.join(site.getsitepackages()))"
             ),
            description="Extracting plugin locations",
            level=LogLevel.DEBUG,
            cache_scope=cache_scope,
        ),
    )
    return ResolvedPluginDistributions(
        plugins_process_result.stdout.decode().strip().split("\n"))
def test_issue_12222(rule_runner: RuleRunner) -> None:
    constraints = ["foo==1.0", "bar==1.0"]
    rule_runner.write_files(
        {
            "constraints.txt": "\n".join(constraints),
            "a.py": "",
            "BUILD": dedent(
                """
                python_requirement(name="foo",requirements=["foo"])
                python_requirement(name="bar",requirements=["bar"])
                python_sources(name="lib",dependencies=[":foo"])
                """
            ),
        }
    )
    request = PexFromTargetsRequest(
        [Address("", target_name="lib")],
        output_filename="demo.pex",
        internal_only=False,
        platforms=PexPlatforms(["some-platform-x86_64"]),
    )
    rule_runner.set_options(
        [
            "--python-requirement-constraints=constraints.txt",
            "--python-resolve-all-constraints",
        ],
        env_inherit={"PATH"},
    )
    result = rule_runner.request(PexRequest, [request])

    assert result.requirements == PexRequirements(["foo"], constraints_strings=constraints)
Example #6
0
def test_resolves_dependencies(rule_runner: RuleRunner) -> None:
    requirements = PexRequirements(
        ["six==1.12.0", "jsonschema==2.6.0", "requests==2.23.0"])
    pex_info = create_pex_and_get_pex_info(rule_runner,
                                           requirements=requirements)
    # NB: We do not check for transitive dependencies, which PEX-INFO will include. We only check
    # that at least the dependencies we requested are included.
    assert set(parse_requirements(requirements.req_strings)).issubset(
        set(parse_requirements(pex_info["requirements"])))
Example #7
0
def _create_pex(
    rule_runner: RuleRunner,
    interpreter_constraints: InterpreterConstraints,
) -> Pex:
    request = PexRequest(
        output_filename="setup-py-runner.pex",
        internal_only=True,
        requirements=PexRequirements(["setuptools==44.0.0", "wheel==0.34.2"]),
        interpreter_constraints=interpreter_constraints,
    )
    return rule_runner.request(Pex, [request])
Example #8
0
def test_use_packed_pex_requirements(rule_runner: RuleRunner,
                                     is_all_constraints_resolve: bool,
                                     internal_only: bool) -> None:
    requirements = PexRequirements(
        ["six==1.12.0"], is_all_constraints_resolve=is_all_constraints_resolve)
    pex_data = create_pex_and_get_all_data(rule_runner,
                                           requirements=requirements,
                                           internal_only=internal_only)
    # If this is either internal_only, or an all_constraints resolve, we should use packed.
    should_use_packed = is_all_constraints_resolve or internal_only
    assert (not pex_data.is_zipapp) == should_use_packed
Example #9
0
async def determine_requirement_strings_in_closure(
    request: _PexRequirementsRequest, global_requirement_constraints: GlobalRequirementConstraints
) -> PexRequirements:
    transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest(request.addresses))
    return PexRequirements.create_from_requirement_fields(
        (
            tgt[PythonRequirementsField]
            for tgt in transitive_targets.closure
            if tgt.has_field(PythonRequirementsField)
        ),
        constraints_strings=(str(constraint) for constraint in global_requirement_constraints),
    )
Example #10
0
def test_requirement_constraints(rule_runner: RuleRunner) -> None:
    direct_deps = ["requests>=1.0.0,<=2.23.0"]

    def assert_direct_requirements(pex_info):
        assert {PipRequirement.parse(r)
                for r in pex_info["requirements"]
                } == {PipRequirement.parse(d)
                      for d in direct_deps}

    # Unconstrained, we should always pick the top of the range (requests 2.23.0) since the top of
    # the range is a transitive closure over universal wheels.
    direct_pex_info = create_pex_and_get_pex_info(
        rule_runner, requirements=PexRequirements(direct_deps))
    assert_direct_requirements(direct_pex_info)
    assert "requests-2.23.0-py2.py3-none-any.whl" in set(
        direct_pex_info["distributions"].keys())

    constraints = [
        "requests==2.16.0",
        "certifi==2019.6.16",
        "chardet==3.0.2",
        "idna==2.5",
        "urllib3==1.21.1",
    ]
    rule_runner.write_files({"constraints.txt": "\n".join(constraints)})
    constrained_pex_info = create_pex_and_get_pex_info(
        rule_runner,
        requirements=PexRequirements(direct_deps,
                                     constraints_strings=constraints),
        additional_pants_args=(
            "--python-requirement-constraints=constraints.txt", ),
    )
    assert_direct_requirements(constrained_pex_info)
    assert {
        "certifi-2019.6.16-py2.py3-none-any.whl",
        "chardet-3.0.2-py2.py3-none-any.whl",
        "idna-2.5-py2.py3-none-any.whl",
        "requests-2.16.0-py2.py3-none-any.whl",
        "urllib3-1.21.1-py2.py3-none-any.whl",
    } == set(constrained_pex_info["distributions"].keys())
Example #11
0
    def pex_requirements(
        self,
        *,
        extra_requirements: Iterable[str] = (),
    ) -> PexRequirements | EntireLockfile:
        """The requirements to be used when installing the tool.

        If the tool supports lockfiles, the returned type will install from the lockfile rather than
        `all_requirements`.
        """

        requirements = (*self.all_requirements, *extra_requirements)

        if not self.uses_lockfile:
            return PexRequirements(requirements)

        hex_digest = calculate_invalidation_digest(requirements)

        lockfile: ToolDefaultLockfile | ToolCustomLockfile
        if self.lockfile == DEFAULT_TOOL_LOCKFILE:
            assert self.default_lockfile_resource is not None
            lockfile = ToolDefaultLockfile(
                file_content=FileContent(
                    f"{self.options_scope}_default.lock",
                    importlib.resources.read_binary(
                        *self.default_lockfile_resource),
                ),
                lockfile_hex_digest=hex_digest,
                resolve_name=self.options_scope,
                uses_project_interpreter_constraints=(
                    not self.register_interpreter_constraints),
                uses_source_plugins=self.uses_requirements_from_source_plugins,
            )
        else:
            lockfile = ToolCustomLockfile(
                file_path=self.lockfile,
                file_path_description_of_origin=
                f"the option `[{self.options_scope}].lockfile`",
                lockfile_hex_digest=hex_digest,
                resolve_name=self.options_scope,
                uses_project_interpreter_constraints=(
                    not self.register_interpreter_constraints),
                uses_source_plugins=self.uses_requirements_from_source_plugins,
            )
        return EntireLockfile(lockfile,
                              complete_req_strings=tuple(requirements))
Example #12
0
async def pylint_first_party_plugins(
        pylint: Pylint) -> PylintFirstPartyPlugins:
    if not pylint.source_plugins:
        return PylintFirstPartyPlugins(FrozenOrderedSet(), FrozenOrderedSet(),
                                       EMPTY_DIGEST)

    plugin_target_addresses = await Get(Addresses, UnparsedAddressInputs,
                                        pylint.source_plugins)
    transitive_targets = await Get(
        TransitiveTargets, TransitiveTargetsRequest(plugin_target_addresses))

    requirements_fields: OrderedSet[PythonRequirementsField] = OrderedSet()
    interpreter_constraints_fields: OrderedSet[
        InterpreterConstraintsField] = OrderedSet()
    for tgt in transitive_targets.closure:
        if tgt.has_field(PythonRequirementsField):
            requirements_fields.add(tgt[PythonRequirementsField])
        if tgt.has_field(InterpreterConstraintsField):
            interpreter_constraints_fields.add(
                tgt[InterpreterConstraintsField])

    # NB: Pylint source plugins must be explicitly loaded via PYTHONPATH (i.e. PEX_EXTRA_SYS_PATH).
    # The value must point to the plugin's directory, rather than to a parent's directory, because
    # `load-plugins` takes a module name rather than a path to the module; i.e. `plugin`, but
    # not `path.to.plugin`. (This means users must have specified the parent directory as a
    # source root.)
    stripped_sources = await Get(
        StrippedPythonSourceFiles,
        PythonSourceFilesRequest(transitive_targets.closure))
    prefixed_sources = await Get(
        Digest,
        AddPrefix(stripped_sources.stripped_source_files.snapshot.digest,
                  PylintFirstPartyPlugins.PREFIX),
    )

    return PylintFirstPartyPlugins(
        requirement_strings=PexRequirements.create_from_requirement_fields(
            requirements_fields,
            constraints_strings=(),
        ).req_strings,
        interpreter_constraints_fields=FrozenOrderedSet(
            interpreter_constraints_fields),
        sources_digest=prefixed_sources,
    )
Example #13
0
def test_platforms(rule_runner: RuleRunner) -> None:
    # We use Python 2.7, rather than Python 3, to ensure that the specified platform is
    # actually used.
    platforms = PexPlatforms(["linux-x86_64-cp-27-cp27mu"])
    constraints = InterpreterConstraints(["CPython>=2.7,<3", "CPython>=3.6"])
    pex_data = create_pex_and_get_all_data(
        rule_runner,
        requirements=PexRequirements(["cryptography==2.9"]),
        platforms=platforms,
        interpreter_constraints=constraints,
        internal_only=
        False,  # Internal only PEXes do not support (foreign) platforms.
    )
    assert any("cryptography-2.9-cp27-cp27mu-manylinux2010_x86_64.whl" in fp
               for fp in pex_data.files)
    assert not any("cryptography-2.9-cp27-cp27m-" in fp
                   for fp in pex_data.files)
    assert not any("cryptography-2.9-cp35-abi3" in fp for fp in pex_data.files)

    # NB: Platforms override interpreter constraints.
    assert pex_data.info["interpreter_constraints"] == []
Example #14
0
def create_pex_and_get_all_data(
    rule_runner: RuleRunner,
    *,
    pex_type: type[Pex | VenvPex] = Pex,
    requirements: PexRequirements | EntireLockfile = PexRequirements(),
    main: MainSpecification | None = None,
    interpreter_constraints: InterpreterConstraints = InterpreterConstraints(),
    platforms: PexPlatforms = PexPlatforms(),
    sources: Digest | None = None,
    additional_inputs: Digest | None = None,
    additional_pants_args: tuple[str, ...] = (),
    additional_pex_args: tuple[str, ...] = (),
    env: Mapping[str, str] | None = None,
    internal_only: bool = True,
    layout: PexLayout | None = None,
) -> PexData:
    request = PexRequest(
        output_filename="test.pex",
        internal_only=internal_only,
        requirements=requirements,
        interpreter_constraints=interpreter_constraints,
        platforms=platforms,
        main=main,
        sources=sources,
        additional_inputs=additional_inputs,
        additional_args=additional_pex_args,
        layout=layout,
    )
    rule_runner.set_options(
        ["--backend-packages=pants.backend.python", *additional_pants_args],
        env=env,
        env_inherit={"PATH", "PYENV_ROOT", "HOME"},
    )

    pex: Pex | VenvPex
    if pex_type == Pex:
        pex = rule_runner.request(Pex, [request])
    else:
        pex = rule_runner.request(VenvPex, [request])
    return get_all_data(rule_runner, pex)
Example #15
0
async def get_mypyc_build_environment(
    request: MyPycDistBuildEnvironmentRequest,
    first_party_plugins: MyPyFirstPartyPlugins,
    mypy_config_file: MyPyConfigFile,
    mypy: MyPy,
) -> DistBuildEnvironment:
    mypy_pex_get = Get(
        Pex,
        PexRequest,
        mypy.to_pex_request(
            interpreter_constraints=request.interpreter_constraints,
            extra_requirements=first_party_plugins.requirement_strings,
        ),
    )
    requirements_pex_get = Get(
        Pex,
        RequirementsPexRequest(
            addresses=request.target_addresses,
            hardcoded_interpreter_constraints=request.interpreter_constraints,
        ),
    )
    extra_type_stubs_pex_get = Get(
        Pex,
        PexRequest(
            output_filename="extra_type_stubs.pex",
            internal_only=True,
            requirements=PexRequirements(mypy.extra_type_stubs),
            interpreter_constraints=request.interpreter_constraints,
        ),
    )
    (mypy_pex, requirements_pex,
     extra_type_stubs_pex) = await MultiGet(mypy_pex_get, requirements_pex_get,
                                            extra_type_stubs_pex_get)
    return DistBuildEnvironment(
        extra_build_time_requirements=(mypy_pex, requirements_pex,
                                       extra_type_stubs_pex),
        extra_build_time_inputs=mypy_config_file.digest,
    )
Example #16
0
async def mypy_first_party_plugins(mypy: MyPy, ) -> MyPyFirstPartyPlugins:
    if not mypy.source_plugins:
        return MyPyFirstPartyPlugins(FrozenOrderedSet(), EMPTY_DIGEST, ())

    plugin_target_addresses = await Get(Addresses, UnparsedAddressInputs,
                                        mypy.source_plugins)
    transitive_targets = await Get(
        TransitiveTargets, TransitiveTargetsRequest(plugin_target_addresses))

    requirements = PexRequirements.create_from_requirement_fields(
        (plugin_tgt[PythonRequirementsField]
         for plugin_tgt in transitive_targets.closure
         if plugin_tgt.has_field(PythonRequirementsField)),
        constraints_strings=(),
    )

    sources = await Get(PythonSourceFiles,
                        PythonSourceFilesRequest(transitive_targets.closure))
    return MyPyFirstPartyPlugins(
        requirement_strings=requirements.req_strings,
        sources_digest=sources.source_files.snapshot.digest,
        source_roots=sources.source_roots,
    )
Example #17
0
async def find_build_system(request: BuildSystemRequest,
                            setuptools: Setuptools) -> BuildSystem:
    digest_contents = await Get(
        DigestContents,
        DigestSubset(
            request.digest,
            PathGlobs(
                globs=[
                    os.path.join(request.working_directory, "pyproject.toml")
                ],
                glob_match_error_behavior=GlobMatchErrorBehavior.ignore,
            ),
        ),
    )
    ret = None
    if digest_contents:
        file_content = next(iter(digest_contents))
        settings: Mapping[str, Any] = toml.loads(file_content.content.decode())
        build_system = settings.get("build-system")
        if build_system is not None:
            build_backend = build_system.get("build-backend")
            if build_backend is None:
                raise InvalidBuildConfigError(
                    f"No build-backend found in the [build-system] table in {file_content.path}"
                )
            requires = build_system.get("requires")
            if requires is None:
                raise InvalidBuildConfigError(
                    f"No requires found in the [build-system] table in {file_content.path}"
                )
            ret = BuildSystem(PexRequirements(requires), build_backend)
    # Per PEP 517: "If the pyproject.toml file is absent, or the build-backend key is missing,
    #   the source tree is not using this specification, and tools should revert to the legacy
    #   behaviour of running setup.py."
    if ret is None:
        ret = BuildSystem.legacy(setuptools)
    return ret
Example #18
0
    def __init__(
        self,
        *,
        output_filename: str,
        internal_only: bool,
        layout: PexLayout | None = None,
        python: PythonExecutable | None = None,
        requirements: PexRequirements | EntireLockfile = PexRequirements(),
        interpreter_constraints=InterpreterConstraints(),
        platforms=PexPlatforms(),
        complete_platforms=CompletePlatforms(),
        sources: Digest | None = None,
        additional_inputs: Digest | None = None,
        main: MainSpecification | None = None,
        additional_args: Iterable[str] = (),
        pex_path: Iterable[Pex] = (),
        description: str | None = None,
    ) -> None:
        """A request to create a PEX from its inputs.

        :param output_filename: The name of the built Pex file, which typically should end in
            `.pex`.
        :param internal_only: Whether we ever materialize the Pex and distribute it directly
            to end users, such as with the `binary` goal. Typically, instead, the user never
            directly uses the Pex, e.g. with `lint` and `test`. If True, we will use a Pex setting
            that results in faster build time but compatibility with fewer interpreters at runtime.
        :param layout: The filesystem layout to create the PEX with.
        :param python: A particular PythonExecutable to use, which must match any relevant
            interpreter_constraints.
        :param requirements: The requirements that the PEX should contain.
        :param interpreter_constraints: Any constraints on which Python versions may be used.
        :param platforms: Which abbreviated platforms should be supported. Setting this value will
            cause interpreter constraints to not be used at PEX build time because platforms already
            constrain the valid Python versions, e.g. by including `cp36m` in the platform string.
            Unfortunately this also causes interpreter constraints to not be embedded in the built
            PEX for use at runtime which can lead to problems.
            See: https://github.com/pantsbuild/pants/issues/13904.
        :param complete_platforms: Which complete platforms should be supported. Setting this value
            will cause interpreter constraints to not be used at PEX build time because complete
            platforms completely constrain the valid Python versions. Unfortunately this also causes
            interpreter constraints to not be embedded in the built PEX for use at runtime which can
            lead to problems. See: https://github.com/pantsbuild/pants/issues/13904.
        :param sources: Any source files that should be included in the Pex.
        :param additional_inputs: Any inputs that are not source files and should not be included
            directly in the Pex, but should be present in the environment when building the Pex.
        :param main: The main for the built Pex, equivalent to Pex's `-e` or '-c' flag. If
            left off, the Pex will open up as a REPL.
        :param additional_args: Any additional Pex flags.
        :param pex_path: Pex files to add to the PEX_PATH.
        :param description: A human-readable description to render in the dynamic UI when building
            the Pex.
        """
        self.output_filename = output_filename
        self.internal_only = internal_only
        # Use any explicitly requested layout, or Packed for internal PEXes (which is a much
        # friendlier layout for the CAS than Zipapp.)
        self.layout = layout or (PexLayout.PACKED
                                 if internal_only else PexLayout.ZIPAPP)
        self.python = python
        self.requirements = requirements
        self.interpreter_constraints = interpreter_constraints
        self.platforms = platforms
        self.complete_platforms = complete_platforms
        self.sources = sources
        self.additional_inputs = additional_inputs or EMPTY_DIGEST
        self.main = main
        self.additional_args = tuple(additional_args)
        self.pex_path = tuple(pex_path)
        self.description = description

        self.__post_init__()
Example #19
0
async def _setup_constraints_repository_pex(
    constraints_request: _ConstraintsRepositoryPexRequest,
    python_setup: PythonSetup,
    global_requirement_constraints: GlobalRequirementConstraints,
) -> OptionalPexRequest:
    request = constraints_request.repository_pex_request
    # NB: it isn't safe to resolve against the whole constraints file if
    # platforms are in use. See https://github.com/pantsbuild/pants/issues/12222.
    if not python_setup.resolve_all_constraints or request.platforms or request.complete_platforms:
        return OptionalPexRequest(None)

    constraints_path = python_setup.requirement_constraints
    assert constraints_path is not None

    transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest(request.addresses))

    requirements = PexRequirements.create_from_requirement_fields(
        (
            tgt[PythonRequirementsField]
            for tgt in transitive_targets.closure
            if tgt.has_field(PythonRequirementsField)
        ),
        constraints_strings=(str(constraint) for constraint in global_requirement_constraints),
    )

    # In requirement strings, Foo_-Bar.BAZ and foo-bar-baz refer to the same project. We let
    # packaging canonicalize for us.
    # See: https://www.python.org/dev/peps/pep-0503/#normalized-names
    url_reqs = set()  # E.g., 'foobar@ git+https://github.com/foo/bar.git@branch'
    name_reqs = set()  # E.g., foobar>=1.2.3
    name_req_projects = set()
    constraints_file_reqs = set(global_requirement_constraints)

    for req_str in requirements.req_strings:
        req = PipRequirement.parse(req_str)
        if req.url:
            url_reqs.add(req)
        else:
            name_reqs.add(req)
            name_req_projects.add(canonicalize_project_name(req.project_name))

    constraint_file_projects = {
        canonicalize_project_name(req.project_name) for req in constraints_file_reqs
    }
    # Constraints files must only contain name reqs, not URL reqs (those are already
    # constrained by their very nature). See https://github.com/pypa/pip/issues/8210.
    unconstrained_projects = name_req_projects - constraint_file_projects
    if unconstrained_projects:
        logger.warning(
            f"The constraints file {constraints_path} does not contain "
            f"entries for the following requirements: {', '.join(unconstrained_projects)}.\n\n"
            f"Ignoring `[python_setup].resolve_all_constraints` option."
        )
        return OptionalPexRequest(None)

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

    # To get a full set of requirements we must add the URL requirements to the
    # constraints file, since the latter cannot contain URL requirements.
    # NB: We can only add the URL requirements we know about here, i.e., those that
    #  are transitive deps of the targets in play. There may be others in the repo.
    #  So we may end up creating a few different repository pexes, each with identical
    #  name requirements but different subsets of URL requirements. Fortunately since
    #  all these repository pexes will have identical pinned versions of everything,
    #  this is not a correctness issue, only a performance one.
    all_constraints = {str(req) for req in (constraints_file_reqs | url_reqs)}
    repository_pex = PexRequest(
        description=f"Resolving {constraints_path}",
        output_filename="repository.pex",
        internal_only=request.internal_only,
        requirements=PexRequirements(
            all_constraints,
            constraints_strings=(str(constraint) for constraint in global_requirement_constraints),
            # TODO: See PexRequirements docs.
            is_all_constraints_resolve=True,
        ),
        interpreter_constraints=interpreter_constraints,
        platforms=request.platforms,
        complete_platforms=request.complete_platforms,
        additional_args=request.additional_lockfile_args,
    )
    return OptionalPexRequest(repository_pex)
Example #20
0
async def create_pex_from_targets(request: PexFromTargetsRequest) -> PexRequest:
    interpreter_constraints = await Get(
        InterpreterConstraints,
        InterpreterConstraintsRequest,
        request.to_interpreter_constraints_request(),
    )

    transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest(request.addresses))

    sources_digests = []
    if request.additional_sources:
        sources_digests.append(request.additional_sources)
    if request.include_source_files:
        sources = await Get(PythonSourceFiles, PythonSourceFilesRequest(transitive_targets.closure))
    else:
        sources = PythonSourceFiles.empty()

    additional_inputs_digests = []
    if request.additional_inputs:
        additional_inputs_digests.append(request.additional_inputs)
    additional_args = request.additional_args
    if request.include_local_dists:
        local_dists = await Get(
            LocalDistsPex,
            LocalDistsPexRequest(
                request.addresses,
                internal_only=request.internal_only,
                interpreter_constraints=interpreter_constraints,
                sources=sources,
            ),
        )
        remaining_sources = local_dists.remaining_sources
        additional_inputs_digests.append(local_dists.pex.digest)
        additional_args += ("--requirements-pex", local_dists.pex.name)
    else:
        remaining_sources = sources

    remaining_sources_stripped = await Get(
        StrippedPythonSourceFiles, PythonSourceFiles, remaining_sources
    )
    sources_digests.append(remaining_sources_stripped.stripped_source_files.snapshot.digest)

    merged_sources_digest, additional_inputs = await MultiGet(
        Get(Digest, MergeDigests(sources_digests)),
        Get(Digest, MergeDigests(additional_inputs_digests)),
    )

    description = request.description

    if request.include_requirements:
        requirements = await Get(PexRequirements, _PexRequirementsRequest(request.addresses))
    else:
        requirements = PexRequirements()

    if requirements:
        repository_pex = await Get(
            OptionalPex,
            _RepositoryPexRequest(
                request.addresses,
                requirements=requirements,
                hardcoded_interpreter_constraints=request.hardcoded_interpreter_constraints,
                platforms=request.platforms,
                complete_platforms=request.complete_platforms,
                internal_only=request.internal_only,
                additional_lockfile_args=request.additional_lockfile_args,
            ),
        )
        requirements = dataclasses.replace(requirements, repository_pex=repository_pex.maybe_pex)

    return PexRequest(
        output_filename=request.output_filename,
        internal_only=request.internal_only,
        layout=request.layout,
        requirements=requirements,
        interpreter_constraints=interpreter_constraints,
        platforms=request.platforms,
        complete_platforms=request.complete_platforms,
        main=request.main,
        sources=merged_sources_digest,
        additional_inputs=additional_inputs,
        additional_args=additional_args,
        description=description,
    )
Example #21
0
async def mypy_typecheck_partition(
    partition: MyPyPartition,
    config_file: MyPyConfigFile,
    first_party_plugins: MyPyFirstPartyPlugins,
    mypy: MyPy,
    python_setup: PythonSetup,
) -> CheckResult:
    # MyPy requires 3.5+ to run, but uses the typed-ast library to work with 2.7, 3.4, 3.5, 3.6,
    # and 3.7. However, typed-ast does not understand 3.8+, so instead we must run MyPy with
    # Python 3.8+ when relevant. We only do this if <3.8 can't be used, as we don't want a
    # loose requirement like `>=3.6` to result in requiring Python 3.8+, which would error if
    # 3.8+ is not installed on the machine.
    tool_interpreter_constraints = (partition.interpreter_constraints if (
        mypy.options.is_default("interpreter_constraints")
        and partition.interpreter_constraints.requires_python38_or_newer(
            python_setup.interpreter_universe)) else
                                    mypy.interpreter_constraints)

    closure_sources_get = Get(PythonSourceFiles,
                              PythonSourceFilesRequest(partition.closure))
    roots_sources_get = Get(
        SourceFiles,
        SourceFilesRequest(
            tgt.get(PythonSourceField) for tgt in partition.root_targets),
    )

    # See `requirements_venv_pex` for how this will get wrapped in a `VenvPex`.
    requirements_pex_get = Get(
        Pex,
        RequirementsPexRequest(
            (tgt.address for tgt in partition.root_targets),
            hardcoded_interpreter_constraints=partition.
            interpreter_constraints,
            internal_only=True,
        ),
    )
    extra_type_stubs_pex_get = Get(
        Pex,
        PexRequest(
            output_filename="extra_type_stubs.pex",
            internal_only=True,
            requirements=PexRequirements(mypy.extra_type_stubs),
            interpreter_constraints=partition.interpreter_constraints,
        ),
    )

    mypy_pex_get = Get(
        VenvPex,
        PexRequest,
        mypy.to_pex_request(
            interpreter_constraints=tool_interpreter_constraints,
            extra_requirements=first_party_plugins.requirement_strings,
        ),
    )

    (
        closure_sources,
        roots_sources,
        mypy_pex,
        extra_type_stubs_pex,
        requirements_pex,
    ) = await MultiGet(
        closure_sources_get,
        roots_sources_get,
        mypy_pex_get,
        extra_type_stubs_pex_get,
        requirements_pex_get,
    )

    python_files = determine_python_files(roots_sources.snapshot.files)
    file_list_path = "__files.txt"
    file_list_digest_request = Get(
        Digest,
        CreateDigest(
            [FileContent(file_list_path, "\n".join(python_files).encode())]),
    )

    # This creates a venv with all the 3rd-party requirements used by the code. We tell MyPy to
    # use this venv by setting `--python-executable`. Note that this Python interpreter is
    # different than what we run MyPy with.
    #
    # We could have directly asked the `PexFromTargetsRequest` to return a `VenvPex`, rather than
    # `Pex`, but that would mean missing out on sharing a cache with other goals like `test` and
    # `run`.
    requirements_venv_pex_request = Get(
        VenvPex,
        PexRequest(
            output_filename="requirements_venv.pex",
            internal_only=True,
            pex_path=[requirements_pex, extra_type_stubs_pex],
            interpreter_constraints=partition.interpreter_constraints,
        ),
    )

    requirements_venv_pex, file_list_digest = await MultiGet(
        requirements_venv_pex_request, file_list_digest_request)

    merged_input_files = await Get(
        Digest,
        MergeDigests([
            file_list_digest,
            first_party_plugins.sources_digest,
            closure_sources.source_files.snapshot.digest,
            requirements_venv_pex.digest,
            config_file.digest,
        ]),
    )

    all_used_source_roots = sorted(
        set(
            itertools.chain(first_party_plugins.source_roots,
                            closure_sources.source_roots)))
    env = {
        "PEX_EXTRA_SYS_PATH": ":".join(all_used_source_roots),
        "MYPYPATH": ":".join(all_used_source_roots),
    }

    result = await Get(
        FallibleProcessResult,
        VenvPexProcess(
            mypy_pex,
            argv=generate_argv(
                mypy,
                venv_python=requirements_venv_pex.python.argv0,
                file_list_path=file_list_path,
                python_version=config_file.python_version_to_autoset(
                    partition.interpreter_constraints,
                    python_setup.interpreter_universe),
            ),
            input_digest=merged_input_files,
            extra_env=env,
            output_directories=(REPORT_DIR, ),
            description=f"Run MyPy on {pluralize(len(python_files), 'file')}.",
            level=LogLevel.DEBUG,
        ),
    )
    report = await Get(Digest, RemovePrefix(result.output_digest, REPORT_DIR))
    return CheckResult.from_fallible_process_result(
        result,
        partition_description=str(
            sorted(str(c) for c in partition.interpreter_constraints)),
        report=report,
    )
def test_constraints_validation(tmp_path: Path, rule_runner: RuleRunner) -> None:
    sdists = tmp_path / "sdists"
    sdists.mkdir()
    find_links = create_dists(
        sdists,
        Project("Foo-Bar", "1.0.0"),
        Project("Bar", "5.5.5"),
        Project("baz", "2.2.2"),
        Project("QUX", "3.4.5"),
    )

    # Turn the project dir into a git repo, so it can be cloned.
    gitdir = tmp_path / "git"
    gitdir.mkdir()
    foorl_dir = create_project_dir(gitdir, Project("foorl", "9.8.7"))
    with pushd(str(foorl_dir)):
        subprocess.check_call(["git", "init"])
        subprocess.check_call(["git", "config", "user.name", "dummy"])
        subprocess.check_call(["git", "config", "user.email", "*****@*****.**"])
        subprocess.check_call(["git", "add", "--all"])
        subprocess.check_call(["git", "commit", "-m", "initial commit"])
        subprocess.check_call(["git", "branch", "9.8.7"])

    # This string won't parse as a Requirement if it doesn't contain a netloc,
    # so we explicitly mention localhost.
    url_req = f"foorl@ git+file://localhost{foorl_dir.as_posix()}@9.8.7"

    rule_runner.write_files(
        {
            "util.py": "",
            "app.py": "",
            "BUILD": dedent(
                f"""
                python_requirement(name="foo", requirements=["foo-bar>=0.1.2"])
                python_requirement(name="bar", requirements=["bar==5.5.5"])
                python_requirement(name="baz", requirements=["baz"])
                python_requirement(name="foorl", requirements=["{url_req}"])
                python_sources(name="util", sources=["util.py"], dependencies=[":foo", ":bar"])
                python_sources(name="app", sources=["app.py"], dependencies=[":util", ":baz", ":foorl"])
                """
            ),
            "constraints1.txt": dedent(
                """
                # Comment.
                --find-links=https://duckduckgo.com
                Foo._-BAR==1.0.0  # Inline comment.
                bar==5.5.5
                baz==2.2.2
                qux==3.4.5
                # Note that pip does not allow URL requirements in constraints files,
                # so there is no mention of foorl here.
                """
            ),
        }
    )

    # Create and parse the constraints file.
    constraints1_filename = "constraints1.txt"
    rule_runner.set_options(
        [f"--python-requirement-constraints={constraints1_filename}"], env_inherit={"PATH"}
    )
    constraints1_strings = [str(c) for c in rule_runner.request(GlobalRequirementConstraints, [])]

    def get_pex_request(
        constraints_file: str | None,
        resolve_all_constraints: bool | None,
        *,
        additional_args: Iterable[str] = (),
        additional_lockfile_args: Iterable[str] = (),
    ) -> PexRequest:
        args = ["--backend-packages=pants.backend.python"]
        request = PexFromTargetsRequest(
            [Address("", target_name="app")],
            output_filename="demo.pex",
            internal_only=True,
            additional_args=additional_args,
            additional_lockfile_args=additional_lockfile_args,
        )
        if resolve_all_constraints is not None:
            args.append(f"--python-resolve-all-constraints={resolve_all_constraints!r}")
        if constraints_file:
            args.append(f"--python-requirement-constraints={constraints_file}")
        args.append("--python-repos-indexes=[]")
        args.append(f"--python-repos-repos={find_links}")
        rule_runner.set_options(args, env_inherit={"PATH"})
        pex_request = rule_runner.request(PexRequest, [request])
        assert OrderedSet(additional_args).issubset(OrderedSet(pex_request.additional_args))
        return pex_request

    additional_args = ["--strip-pex-env"]
    additional_lockfile_args = ["--no-strip-pex-env"]

    pex_req1 = get_pex_request(constraints1_filename, resolve_all_constraints=False)
    assert pex_req1.requirements == PexRequirements(
        ["foo-bar>=0.1.2", "bar==5.5.5", "baz", url_req],
        constraints_strings=constraints1_strings,
    )

    pex_req2 = get_pex_request(
        constraints1_filename,
        resolve_all_constraints=True,
        additional_args=additional_args,
        additional_lockfile_args=additional_lockfile_args,
    )
    pex_req2_reqs = pex_req2.requirements
    assert isinstance(pex_req2_reqs, PexRequirements)
    assert list(pex_req2_reqs.req_strings) == ["bar==5.5.5", "baz", "foo-bar>=0.1.2", url_req]
    assert pex_req2_reqs.repository_pex is not None
    assert not info(rule_runner, pex_req2_reqs.repository_pex)["strip_pex_env"]
    repository_pex = pex_req2_reqs.repository_pex
    assert ["Foo._-BAR==1.0.0", "bar==5.5.5", "baz==2.2.2", "foorl", "qux==3.4.5"] == requirements(
        rule_runner, repository_pex
    )

    with engine_error(
        ValueError,
        contains=(
            "`[python].resolve_all_constraints` is enabled, so "
            "`[python].requirement_constraints` must also be set."
        ),
    ):
        get_pex_request(None, resolve_all_constraints=True)

    # Shouldn't error, as we don't explicitly set --resolve-all-constraints.
    get_pex_request(None, resolve_all_constraints=None)
Example #23
0
def create_pex_and_get_all_data(
    rule_runner: RuleRunner,
    *,
    pex_type: type[Pex | VenvPex] = Pex,
    requirements: PexRequirements | Lockfile
    | LockfileContent = PexRequirements(),
    main: MainSpecification | None = None,
    interpreter_constraints: InterpreterConstraints = InterpreterConstraints(),
    platforms: PexPlatforms = PexPlatforms(),
    sources: Digest | None = None,
    additional_inputs: Digest | None = None,
    additional_pants_args: tuple[str, ...] = (),
    additional_pex_args: tuple[str, ...] = (),
    env: Mapping[str, str] | None = None,
    internal_only: bool = True,
) -> PexData:
    request = PexRequest(
        output_filename="test.pex",
        internal_only=internal_only,
        requirements=requirements,
        interpreter_constraints=interpreter_constraints,
        platforms=platforms,
        main=main,
        sources=sources,
        additional_inputs=additional_inputs,
        additional_args=additional_pex_args,
    )
    rule_runner.set_options(
        ["--backend-packages=pants.backend.python", *additional_pants_args],
        env=env,
        env_inherit={"PATH", "PYENV_ROOT", "HOME"},
    )

    pex: Pex | VenvPex
    if pex_type == Pex:
        pex = rule_runner.request(Pex, [request])
        digest = pex.digest
        sandbox_path = pex.name
        pex_pex = rule_runner.request(PexPEX, [])
        process = rule_runner.request(
            Process,
            [
                PexProcess(
                    Pex(digest=pex_pex.digest,
                        name=pex_pex.exe,
                        python=pex.python),
                    argv=["-m", "pex.tools", pex.name, "info"],
                    input_digest=pex.digest,
                    extra_env=dict(PEX_INTERPRETER="1"),
                    description="Extract PEX-INFO.",
                )
            ],
        )
    else:
        pex = rule_runner.request(VenvPex, [request])
        digest = pex.digest
        sandbox_path = pex.pex_filename
        process = rule_runner.request(
            Process,
            [
                VenvPexProcess(
                    pex,
                    argv=["info"],
                    extra_env=dict(PEX_TOOLS="1"),
                    description="Extract PEX-INFO.",
                ),
            ],
        )

    rule_runner.scheduler.write_digest(digest)
    local_path = PurePath(rule_runner.build_root) / "test.pex"
    result = rule_runner.request(ProcessResult, [process])
    pex_info_content = result.stdout.decode()

    is_zipapp = zipfile.is_zipfile(local_path)
    if is_zipapp:
        with zipfile.ZipFile(local_path, "r") as zipfp:
            files = tuple(zipfp.namelist())
    else:
        files = tuple(
            os.path.normpath(
                os.path.relpath(os.path.join(root, path), local_path))
            for root, dirs, files in os.walk(local_path)
            for path in dirs + files)

    return PexData(
        pex=pex,
        is_zipapp=is_zipapp,
        sandbox_path=PurePath(sandbox_path),
        local_path=local_path,
        info=json.loads(pex_info_content),
        files=files,
    )
Example #24
0
async def build_local_dists(
    request: LocalDistsPexRequest,
) -> LocalDistsPex:
    transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest(request.addresses))
    applicable_targets = [
        tgt for tgt in transitive_targets.closure if PythonDistributionFieldSet.is_applicable(tgt)
    ]

    local_dists_wheels = await MultiGet(
        Get(LocalDistWheels, PythonDistributionFieldSet, PythonDistributionFieldSet.create(target))
        for target in applicable_targets
    )

    # The primary use-case of the "local dists" feature is to support consuming native extensions
    # as wheels without having to publish them first.
    # It doesn't seem very useful to consume locally-built sdists, and it makes it hard to
    # reason about possible sys.path collisions between the in-repo sources and whatever the
    # sdist will place on the sys.path when it's installed.
    # So for now we simply ignore sdists, with a warning if necessary.
    provided_files: set[str] = set()
    wheels: list[str] = []
    wheels_digests = []
    for local_dist_wheels in local_dists_wheels:
        wheels.extend(local_dist_wheels.wheel_paths)
        wheels_digests.append(local_dist_wheels.wheels_digest)
        provided_files.update(local_dist_wheels.provided_files)

    wheels_digest = await Get(Digest, MergeDigests(wheels_digests))

    dists_pex = await Get(
        Pex,
        PexRequest(
            output_filename="local_dists.pex",
            requirements=PexRequirements(wheels),
            interpreter_constraints=request.interpreter_constraints,
            additional_inputs=wheels_digest,
            internal_only=request.internal_only,
            additional_args=["--intransitive"],
        ),
    )

    if not wheels:
        # The source calculations below are not (always) cheap, so we skip them if no wheels were
        # produced. See https://github.com/pantsbuild/pants/issues/14561 for one possible approach
        # to sharing the cost of these calculations.
        return LocalDistsPex(dists_pex, request.sources)

    # We check source roots in reverse lexicographic order,
    # so we'll find the innermost root that matches.
    source_roots = sorted(request.sources.source_roots, reverse=True)
    remaining_sources = set(request.sources.source_files.files)
    unrooted_files_set = set(request.sources.source_files.unrooted_files)
    for source in request.sources.source_files.files:
        if source not in unrooted_files_set:
            for source_root in source_roots:
                source_relpath = fast_relpath_optional(source, source_root)
                if source_relpath is not None and source_relpath in provided_files:
                    remaining_sources.remove(source)
    remaining_sources_snapshot = await Get(
        Snapshot,
        DigestSubset(
            request.sources.source_files.snapshot.digest, PathGlobs(sorted(remaining_sources))
        ),
    )
    subtracted_sources = PythonSourceFiles(
        SourceFiles(remaining_sources_snapshot, request.sources.source_files.unrooted_files),
        request.sources.source_roots,
    )

    return LocalDistsPex(dists_pex, subtracted_sources)
Example #25
0
async def get_requirements(
    dep_owner: DependencyOwner,
    union_membership: UnionMembership,
    setup_py_generation: SetupPyGeneration,
) -> ExportedTargetRequirements:
    transitive_targets = await Get(
        TransitiveTargets,
        TransitiveTargetsRequest([dep_owner.exported_target.target.address]),
    )
    ownable_tgts = [
        tgt for tgt in transitive_targets.closure if is_ownable_target(tgt, union_membership)
    ]
    owners = await MultiGet(Get(ExportedTarget, OwnedDependency(tgt)) for tgt in ownable_tgts)
    owned_by_us: set[Target] = set()
    owned_by_others: set[Target] = set()
    for tgt, owner in zip(ownable_tgts, owners):
        (owned_by_us if owner == dep_owner.exported_target else owned_by_others).add(tgt)

    # Get all 3rdparty deps of our owned deps.
    #
    # Note that we need only consider requirements that are direct dependencies of our owned deps:
    # If T depends on R indirectly, then it must be via some direct deps U1, U2, ... For each such U,
    # if U is in the owned deps then we'll pick up R through U. And if U is not in the owned deps
    # then it's owned by an exported target ET, and so R will be in the requirements for ET, and we
    # will require ET.
    direct_deps_tgts = await MultiGet(
        Get(Targets, DependenciesRequest(tgt.get(Dependencies))) for tgt in owned_by_us
    )

    transitive_excludes: FrozenOrderedSet[Target] = FrozenOrderedSet()
    uneval_trans_excl = [
        tgt.get(Dependencies).unevaluated_transitive_excludes for tgt in transitive_targets.closure
    ]
    if uneval_trans_excl:
        nested_trans_excl = await MultiGet(
            Get(Targets, UnparsedAddressInputs, unparsed) for unparsed in uneval_trans_excl
        )
        transitive_excludes = FrozenOrderedSet(
            itertools.chain.from_iterable(excludes for excludes in nested_trans_excl)
        )

    direct_deps_chained = FrozenOrderedSet(itertools.chain.from_iterable(direct_deps_tgts))
    direct_deps_with_excl = direct_deps_chained.difference(transitive_excludes)

    req_strs = list(
        PexRequirements.create_from_requirement_fields(
            (
                tgt[PythonRequirementsField]
                for tgt in direct_deps_with_excl
                if tgt.has_field(PythonRequirementsField)
            ),
            constraints_strings=(),
        ).req_strings
    )

    # Add the requirements on any exported targets on which we depend.
    kwargs_for_exported_targets_we_depend_on = await MultiGet(
        Get(SetupKwargs, OwnedDependency(tgt)) for tgt in owned_by_others
    )
    req_strs.extend(
        f"{kwargs.name}{setup_py_generation.first_party_dependency_version(kwargs.version)}"
        for kwargs in set(kwargs_for_exported_targets_we_depend_on)
    )
    return ExportedTargetRequirements(req_strs)
Example #26
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",
    )
Example #27
0
async def create_pex_from_targets(request: PexFromTargetsRequest,
                                  python_setup: PythonSetup) -> PexRequest:
    requirements: PexRequirements | EntireLockfile = PexRequirements()
    if request.include_requirements:
        requirements = await Get(PexRequirements,
                                 _PexRequirementsRequest(request.addresses))

        pex_native_subsetting_supported = False
        if python_setup.enable_resolves:
            # TODO: Once `requirement_constraints` is removed in favor of `enable_resolves`,
            # `ChosenPythonResolveRequest` and `_PexRequirementsRequest` should merge and
            # do a single transitive walk to replace this method.
            chosen_resolve = await Get(
                ChosenPythonResolve,
                ChosenPythonResolveRequest(request.addresses))
            loaded_lockfile = await Get(
                LoadedLockfile, LoadedLockfileRequest(chosen_resolve.lockfile))
            pex_native_subsetting_supported = loaded_lockfile.is_pex_native
            if loaded_lockfile.constraints_strings:
                requirements = dataclasses.replace(
                    requirements,
                    constraints_strings=loaded_lockfile.constraints_strings)

        should_return_entire_lockfile = (
            python_setup.run_against_entire_lockfile and request.internal_only)
        should_request_repository_pex = (
            # The entire lockfile was explicitly requested.
            should_return_entire_lockfile
            # The legacy `resolve_all_constraints`+`requirement_constraints` options were used.
            or (
                # TODO: The constraints.txt resolve for `resolve_all_constraints` will be removed as
                # part of #12314.
                python_setup.resolve_all_constraints
                and python_setup.requirement_constraints)
            # A non-PEX-native lockfile was used, and so we cannot subset it.
            or not pex_native_subsetting_supported)

        if should_request_repository_pex:
            repository_pex_request = await Get(
                OptionalPexRequest,
                _RepositoryPexRequest(
                    request.addresses,
                    hardcoded_interpreter_constraints=request.
                    hardcoded_interpreter_constraints,
                    platforms=request.platforms,
                    complete_platforms=request.complete_platforms,
                    internal_only=request.internal_only,
                    additional_lockfile_args=request.additional_lockfile_args,
                ),
            )
            if should_return_entire_lockfile:
                if repository_pex_request.maybe_pex_request is None:
                    raise ValueError(
                        softwrap(f"""
                            [python].run_against_entire_lockfile was set, but could not find a
                            lockfile or constraints file for this target set. See
                            {doc_url('python-third-party-dependencies')} for details.
                            """))
                return repository_pex_request.maybe_pex_request

            repository_pex = await Get(OptionalPex, OptionalPexRequest,
                                       repository_pex_request)
            requirements = dataclasses.replace(
                requirements, from_superset=repository_pex.maybe_pex)
        elif python_setup.enable_resolves:
            # NB: We confirmed above that this is a PEX-native lockfile, so it can be used as a
            # superset.
            chosen_resolve = await Get(
                ChosenPythonResolve,
                ChosenPythonResolveRequest(request.addresses))
            loaded_lockfile = await Get(
                LoadedLockfile, LoadedLockfileRequest(chosen_resolve.lockfile))
            requirements = dataclasses.replace(requirements,
                                               from_superset=loaded_lockfile)

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

    transitive_targets = await Get(TransitiveTargets,
                                   TransitiveTargetsRequest(request.addresses))

    sources_digests = []
    if request.additional_sources:
        sources_digests.append(request.additional_sources)
    if request.include_source_files:
        sources = await Get(
            PythonSourceFiles,
            PythonSourceFilesRequest(transitive_targets.closure))
    else:
        sources = PythonSourceFiles.empty()

    additional_inputs_digests = []
    if request.additional_inputs:
        additional_inputs_digests.append(request.additional_inputs)
    additional_args = request.additional_args
    if request.include_local_dists:
        local_dists = await Get(
            LocalDistsPex,
            LocalDistsPexRequest(
                request.addresses,
                internal_only=request.internal_only,
                interpreter_constraints=interpreter_constraints,
                sources=sources,
            ),
        )
        remaining_sources = local_dists.remaining_sources
        additional_inputs_digests.append(local_dists.pex.digest)
        additional_args += ("--requirements-pex", local_dists.pex.name)
    else:
        remaining_sources = sources

    remaining_sources_stripped = await Get(StrippedPythonSourceFiles,
                                           PythonSourceFiles,
                                           remaining_sources)
    sources_digests.append(
        remaining_sources_stripped.stripped_source_files.snapshot.digest)

    merged_sources_digest, additional_inputs = await MultiGet(
        Get(Digest, MergeDigests(sources_digests)),
        Get(Digest, MergeDigests(additional_inputs_digests)),
    )

    description = request.description

    return PexRequest(
        output_filename=request.output_filename,
        internal_only=request.internal_only,
        layout=request.layout,
        requirements=requirements,
        interpreter_constraints=interpreter_constraints,
        platforms=request.platforms,
        complete_platforms=request.complete_platforms,
        main=request.main,
        sources=merged_sources_digest,
        additional_inputs=additional_inputs,
        additional_args=additional_args,
        description=description,
    )