Esempio n. 1
0
 async def a_rule() -> TrueResult:
     proc = Process(
         ["/bin/sh", "-c", "true"],
         description="always true",
     )
     _ = await Get(ProcessResult, MultiPlatformProcess({None: proc}))
     return TrueResult()
Esempio n. 2
0
 async def a_rule() -> TrueResult:
     proc = Process(
         ["/bin/sh", "-c", "true"],
         description="always true",
         cache_scope=ProcessCacheScope.PER_SESSION,
     )
     _ = await Get(ProcessResult, MultiPlatformProcess({None: proc}))
     return TrueResult()
Esempio n. 3
0
async def build_pex(
    request: PexRequest,
    python_setup: PythonSetup,
    python_repos: PythonRepos,
    platform: Platform,
    pex_runtime_env: PexRuntimeEnvironment,
) -> BuildPexResult:
    """Returns a PEX with the given settings."""
    argv = ["--output-file", request.output_filename, *request.additional_args]

    repository_pex = (request.requirements.repository_pex if isinstance(
        request.requirements, PexRequirements) else None)
    if repository_pex:
        argv.extend(["--pex-repository", repository_pex.name])
    else:
        # NB: In setting `--no-pypi`, we rely on the default value of `--python-repos-indexes`
        # including PyPI, which will override `--no-pypi` and result in using PyPI in the default
        # case. Why set `--no-pypi`, then? We need to do this so that
        # `--python-repos-repos=['custom_url']` will only point to that index and not include PyPI.
        argv.extend([
            "--no-pypi",
            *(f"--index={index}" for index in python_repos.indexes),
            *(f"--repo={repo}" for repo in python_repos.repos),
            "--resolver-version",
            "pip-2020-resolver",
        ])

    python: PythonExecutable | None = None

    # NB: If `--platform` is specified, this signals that the PEX should not be built locally.
    # `--interpreter-constraint` only makes sense in the context of building locally. These two
    # flags are mutually exclusive. See https://github.com/pantsbuild/pex/issues/957.
    if request.platforms:
        # TODO(#9560): consider validating that these platforms are valid with the interpreter
        #  constraints.
        argv.extend(request.platforms.generate_pex_arg_list())
    elif request.python:
        python = request.python
    elif request.internal_only:
        # NB: If it's an internal_only PEX, we do our own lookup of the interpreter based on the
        # interpreter constraints, and then will run the PEX with that specific interpreter. We
        # will have already validated that there were no platforms.
        python = await Get(PythonExecutable, InterpreterConstraints,
                           request.interpreter_constraints)
    else:
        # `--interpreter-constraint` options are mutually exclusive with the `--python` option,
        # so we only specify them if we have not already located a concrete Python.
        argv.extend(request.interpreter_constraints.generate_pex_arg_list())

    if python:
        argv.extend(["--python", python.path])

    argv.append("--no-emit-warnings")

    if python_setup.resolver_jobs:
        argv.extend(["--jobs", str(python_setup.resolver_jobs)])

    if python_setup.manylinux:
        argv.extend(["--manylinux", python_setup.manylinux])
    else:
        argv.append("--no-manylinux")

    if request.main is not None:
        argv.extend(request.main.iter_pex_args())

    # TODO(John Sirois): Right now any request requirements will shadow corresponding pex path
    #  requirements, which could lead to problems. Support shading python binaries.
    #  See: https://github.com/pantsbuild/pants/issues/9206
    if request.pex_path:
        argv.extend(
            ["--pex-path", ":".join(pex.name for pex in request.pex_path)])

    source_dir_name = "source_files"
    argv.append(f"--sources-directory={source_dir_name}")
    sources_digest_as_subdir = await Get(
        Digest, AddPrefix(request.sources or EMPTY_DIGEST, source_dir_name))

    additional_inputs_digest = request.additional_inputs or EMPTY_DIGEST
    repository_pex_digest = repository_pex.digest if repository_pex else EMPTY_DIGEST
    constraint_file_digest = EMPTY_DIGEST
    requirements_file_digest = EMPTY_DIGEST

    # TODO(#12314): Capture the resolve name for multiple user lockfiles.
    resolve_name = (request.requirements.options_scope_name if isinstance(
        request.requirements,
        (ToolDefaultLockfile, ToolCustomLockfile)) else None)

    if isinstance(request.requirements, Lockfile):
        is_monolithic_resolve = True
        argv.extend(["--requirement", request.requirements.file_path])
        argv.append("--no-transitive")
        globs = PathGlobs(
            [request.requirements.file_path],
            glob_match_error_behavior=GlobMatchErrorBehavior.error,
            description_of_origin=request.requirements.
            file_path_description_of_origin,
        )
        if python_setup.invalid_lockfile_behavior in {
                InvalidLockfileBehavior.warn,
                InvalidLockfileBehavior.error,
        }:
            requirements_file_digest_contents = await Get(
                DigestContents, PathGlobs, globs)
            metadata = LockfileMetadata.from_lockfile(
                requirements_file_digest_contents[0].content,
                request.requirements.file_path,
                resolve_name,
            )
            _validate_metadata(metadata, request, request.requirements,
                               python_setup)
        requirements_file_digest = await Get(Digest, PathGlobs, globs)

    elif isinstance(request.requirements, LockfileContent):
        is_monolithic_resolve = True
        file_content = request.requirements.file_content
        argv.extend(["--requirement", file_content.path])
        argv.append("--no-transitive")
        if python_setup.invalid_lockfile_behavior in {
                InvalidLockfileBehavior.warn,
                InvalidLockfileBehavior.error,
        }:
            metadata = LockfileMetadata.from_lockfile(
                file_content.content, resolve_name=resolve_name)
            _validate_metadata(metadata, request, request.requirements,
                               python_setup)
        requirements_file_digest = await Get(Digest,
                                             CreateDigest([file_content]))
    else:
        assert isinstance(request.requirements, PexRequirements)
        is_monolithic_resolve = request.requirements.is_all_constraints_resolve

        if (request.requirements.apply_constraints
                and python_setup.requirement_constraints is not None):
            argv.extend(
                ["--constraints", python_setup.requirement_constraints])
            constraint_file_digest = await Get(
                Digest,
                PathGlobs(
                    [python_setup.requirement_constraints],
                    glob_match_error_behavior=GlobMatchErrorBehavior.error,
                    description_of_origin=
                    "the option `[python].requirement_constraints`",
                ),
            )

        argv.extend(request.requirements.req_strings)

    merged_digest = await Get(
        Digest,
        MergeDigests((
            sources_digest_as_subdir,
            additional_inputs_digest,
            constraint_file_digest,
            requirements_file_digest,
            repository_pex_digest,
            *(pex.digest for pex in request.pex_path),
        )),
    )

    output_files: Iterable[str] | None = None
    output_directories: Iterable[str] | None = None
    if request.internal_only or is_monolithic_resolve:
        # This is a much friendlier layout for the CAS than the default zipapp.
        argv.extend(["--layout", "packed"])
        output_directories = [request.output_filename]
    else:
        output_files = [request.output_filename]

    process = await Get(
        Process,
        PexCliProcess(
            python=python,
            argv=argv,
            additional_input_digest=merged_digest,
            description=_build_pex_description(request),
            output_files=output_files,
            output_directories=output_directories,
        ),
    )

    # NB: Building a Pex is platform dependent, so in order to get a PEX that we can use locally
    # without cross-building, we specify that our PEX command should be run on the current local
    # platform.
    result = await Get(ProcessResult, MultiPlatformProcess({platform:
                                                            process}))

    if pex_runtime_env.verbosity > 0:
        log_output = result.stderr.decode()
        if log_output:
            logger.info("%s", log_output)

    digest = (await Get(
        Digest,
        MergeDigests(
            (result.output_digest, *(pex.digest for pex in request.pex_path))))
              if request.pex_path else result.output_digest)

    return BuildPexResult(result=result,
                          pex_filename=request.output_filename,
                          digest=digest,
                          python=python)
Esempio n. 4
0
async def build_pex(
    request: PexRequest,
    python_setup: PythonSetup,
    python_repos: PythonRepos,
    platform: Platform,
    pex_runtime_env: PexRuntimeEnvironment,
) -> BuildPexResult:
    """Returns a PEX with the given settings."""

    argv = [
        "--output-file",
        request.output_filename,
        # NB: In setting `--no-pypi`, we rely on the default value of `--python-repos-indexes`
        # including PyPI, which will override `--no-pypi` and result in using PyPI in the default
        # case. Why set `--no-pypi`, then? We need to do this so that
        # `--python-repos-repos=['custom_url']` will only point to that index and not include PyPI.
        "--no-pypi",
        *(f"--index={index}" for index in python_repos.indexes),
        *(f"--repo={repo}" for repo in python_repos.repos),
        "--resolver-version",
        python_setup.resolver_version.value,
        *request.additional_args,
    ]

    python: PythonExecutable | None = None

    # NB: If `--platform` is specified, this signals that the PEX should not be built locally.
    # `--interpreter-constraint` only makes sense in the context of building locally. These two
    # flags are mutually exclusive. See https://github.com/pantsbuild/pex/issues/957.
    if request.platforms:
        # TODO(#9560): consider validating that these platforms are valid with the interpreter
        #  constraints.
        argv.extend(request.platforms.generate_pex_arg_list())
    else:
        # NB: If it's an internal_only PEX, we do our own lookup of the interpreter based on the
        # interpreter constraints, and then will run the PEX with that specific interpreter. We
        # will have already validated that there were no platforms.
        # Otherwise, we let Pex resolve the constraints.
        if request.internal_only:
            python = await Get(
                PythonExecutable, PexInterpreterConstraints, request.interpreter_constraints
            )
        else:
            argv.extend(request.interpreter_constraints.generate_pex_arg_list())

    argv.append("--no-emit-warnings")

    if python_setup.resolver_jobs:
        argv.extend(["--jobs", str(python_setup.resolver_jobs)])

    if python_setup.manylinux:
        argv.extend(["--manylinux", python_setup.manylinux])
    else:
        argv.append("--no-manylinux")

    if request.main is not None:
        argv.extend(request.main.iter_pex_args())

    source_dir_name = "source_files"
    argv.append(f"--sources-directory={source_dir_name}")

    argv.extend(request.requirements)

    constraint_file_digest = EMPTY_DIGEST
    if request.apply_requirement_constraints and python_setup.requirement_constraints is not None:
        argv.extend(["--constraints", python_setup.requirement_constraints])
        constraint_file_digest = await Get(
            Digest,
            PathGlobs(
                [python_setup.requirement_constraints],
                glob_match_error_behavior=GlobMatchErrorBehavior.error,
                conjunction=GlobExpansionConjunction.all_match,
                description_of_origin="the option `--python-setup-requirement-constraints`",
            ),
        )

    sources_digest_as_subdir = await Get(
        Digest, AddPrefix(request.sources or EMPTY_DIGEST, source_dir_name)
    )
    additional_inputs_digest = request.additional_inputs or EMPTY_DIGEST

    merged_digest = await Get(
        Digest,
        MergeDigests(
            (
                sources_digest_as_subdir,
                additional_inputs_digest,
                constraint_file_digest,
                *(pex.digest for pex in request.pex_path),
            )
        ),
    )
    # TODO(John Sirois): Right now any request requirements will shadow corresponding pex path
    #  requirements, which could lead to problems. Support shading python binaries.
    #  See: https://github.com/pantsbuild/pants/issues/9206
    if request.pex_path:
        argv.extend(["--pex-path", ":".join(pex.name for pex in request.pex_path)])

    description = request.description
    if description is None:
        if request.requirements:
            description = (
                f"Building {request.output_filename} with "
                f"{pluralize(len(request.requirements), 'requirement')}: "
                f"{', '.join(request.requirements)}"
            )
        else:
            description = f"Building {request.output_filename}"

    process = await Get(
        Process,
        PexCliProcess(
            python=python,
            argv=argv,
            additional_input_digest=merged_digest,
            description=description,
            output_files=[request.output_filename],
        ),
    )

    # NB: Building a Pex is platform dependent, so in order to get a PEX that we can use locally
    # without cross-building, we specify that our PEX command should be run on the current local
    # platform.
    result = await Get(ProcessResult, MultiPlatformProcess({platform: process}))

    if pex_runtime_env.verbosity > 0:
        log_output = result.stderr.decode()
        if log_output:
            logger.info("%s", log_output)

    digest = (
        await Get(
            Digest, MergeDigests((result.output_digest, *(pex.digest for pex in request.pex_path)))
        )
        if request.pex_path
        else result.output_digest
    )

    return BuildPexResult(
        result=result, pex_filename=request.output_filename, digest=digest, python=python
    )
Esempio n. 5
0
async def create_pex(
    request: PexRequest,
    pex_bin: DownloadedPexBin,
    python_setup: PythonSetup,
    python_repos: PythonRepos,
    subprocess_encoding_environment: SubprocessEncodingEnvironment,
    pex_build_environment: PexBuildEnvironment,
    platform: Platform,
    log_level: LogLevel,
) -> Pex:
    """Returns a PEX with the given requirements, optional entry point, optional interpreter
    constraints, and optional requirement constraints."""

    argv = [
        "--output-file",
        request.output_filename,
        # NB: In setting `--no-pypi`, we rely on the default value of `--python-repos-indexes`
        # including PyPI, which will override `--no-pypi` and result in using PyPI in the default
        # case. Why set `--no-pypi`, then? We need to do this so that
        # `--python-repos-repos=['custom_url']` will only point to that index and not include PyPI.
        "--no-pypi",
        *(f"--index={index}" for index in python_repos.indexes),
        *(f"--repo={repo}" for repo in python_repos.repos),
        *request.additional_args,
    ]

    # NB: If `--platform` is specified, this signals that the PEX should not be built locally.
    # `--interpreter-constraint` only makes sense in the context of building locally. These two
    # flags are mutually exclusive. See https://github.com/pantsbuild/pex/issues/957.
    if request.platforms:
        # TODO(#9560): consider validating that these platforms are valid with the interpreter
        #  constraints.
        argv.extend(request.platforms.generate_pex_arg_list())
    else:
        argv.extend(request.interpreter_constraints.generate_pex_arg_list())

    pex_debug = PexDebug(log_level)
    argv.extend(pex_debug.iter_pex_args())

    if python_setup.resolver_jobs:
        argv.extend(["--jobs", str(python_setup.resolver_jobs)])

    if python_setup.manylinux:
        argv.extend(["--manylinux", python_setup.manylinux])
    else:
        argv.append("--no-manylinux")

    if request.entry_point is not None:
        argv.extend(["--entry-point", request.entry_point])

    if python_setup.requirement_constraints is not None:
        argv.extend(["--constraints", python_setup.requirement_constraints])

    source_dir_name = "source_files"
    argv.append(f"--sources-directory={source_dir_name}")

    argv.extend(request.requirements)

    constraint_file_snapshot = EMPTY_SNAPSHOT
    if python_setup.requirement_constraints is not None:
        constraint_file_snapshot = await Get[Snapshot](
            PathGlobs(
                [python_setup.requirement_constraints],
                glob_match_error_behavior=GlobMatchErrorBehavior.error,
                conjunction=GlobExpansionConjunction.all_match,
                description_of_origin="the option `--python-setup-requirement-constraints`",
            )
        )

    sources_digest_as_subdir = await Get[Digest](
        AddPrefix(request.sources or EMPTY_DIGEST, source_dir_name)
    )
    additional_inputs_digest = request.additional_inputs or EMPTY_DIGEST

    merged_digest = await Get[Digest](
        MergeDigests(
            (
                pex_bin.digest,
                sources_digest_as_subdir,
                additional_inputs_digest,
                constraint_file_snapshot.digest,
            )
        )
    )

    # NB: PEX outputs are platform dependent so in order to get a PEX that we can use locally, without
    # cross-building, we specify that our PEX command be run on the current local platform. When we
    # support cross-building through CLI flags we can configure requests that build a PEX for our
    # local platform that are able to execute on a different platform, but for now in order to
    # guarantee correct build we need to restrict this command to execute on the same platform type
    # that the output is intended for. The correct way to interpret the keys
    # (execution_platform_constraint, target_platform_constraint) of this dictionary is "The output of
    # this command is intended for `target_platform_constraint` iff it is run on `execution_platform
    # constraint`".
    description = request.description
    if description is None:
        if request.requirements:
            description = (
                f"Building {request.output_filename} with "
                f"{pluralize(len(request.requirements), 'requirement')}: "
                f"{', '.join(request.requirements)}"
            )
        else:
            description = f"Building {request.output_filename}"
    process = MultiPlatformProcess(
        {
            (
                PlatformConstraint(platform.value),
                PlatformConstraint(platform.value),
            ): pex_bin.create_process(
                python_setup=python_setup,
                subprocess_encoding_environment=subprocess_encoding_environment,
                pex_build_environment=pex_build_environment,
                pex_args=argv,
                input_digest=merged_digest,
                description=description,
                output_files=(request.output_filename,),
            )
        }
    )

    result = await Get[ProcessResult](MultiPlatformProcess, process)

    if pex_debug.might_log:
        lines = result.stderr.decode().splitlines()
        if lines:
            pex_debug.log(f"Debug output from Pex for: {process}")
            for line in lines:
                pex_debug.log(line)

    return Pex(digest=result.output_digest, output_filename=request.output_filename)
Esempio n. 6
0
async def create_pex(
    request: PexRequest,
    python_setup: PythonSetup,
    python_repos: PythonRepos,
    platform: Platform,
    pex_runtime_environment: PexRuntimeEnvironment,
) -> Pex:
    """Returns a PEX with the given settings."""

    argv = [
        "--output-file",
        request.output_filename,
        # NB: In setting `--no-pypi`, we rely on the default value of `--python-repos-indexes`
        # including PyPI, which will override `--no-pypi` and result in using PyPI in the default
        # case. Why set `--no-pypi`, then? We need to do this so that
        # `--python-repos-repos=['custom_url']` will only point to that index and not include PyPI.
        "--no-pypi",
        *(f"--index={index}" for index in python_repos.indexes),
        *(f"--repo={repo}" for repo in python_repos.repos),
        *request.additional_args,
    ]

    if request.internal_only:
        # This will result in a faster build, but worse compatibility at runtime.
        argv.append("--use-first-matching-interpreter")

    # NB: If `--platform` is specified, this signals that the PEX should not be built locally.
    # `--interpreter-constraint` only makes sense in the context of building locally. These two
    # flags are mutually exclusive. See https://github.com/pantsbuild/pex/issues/957.
    if request.platforms:
        # TODO(#9560): consider validating that these platforms are valid with the interpreter
        #  constraints.
        argv.extend(request.platforms.generate_pex_arg_list())
    else:
        argv.extend(request.interpreter_constraints.generate_pex_arg_list())

    argv.append("--no-emit-warnings")
    verbosity = pex_runtime_environment.verbosity
    if verbosity > 0:
        argv.append(f"-{'v' * verbosity}")

    if python_setup.resolver_jobs:
        argv.extend(["--jobs", str(python_setup.resolver_jobs)])

    if python_setup.manylinux:
        argv.extend(["--manylinux", python_setup.manylinux])
    else:
        argv.append("--no-manylinux")

    if request.entry_point is not None:
        argv.extend(["--entry-point", request.entry_point])

    if python_setup.requirement_constraints is not None:
        argv.extend(["--constraints", python_setup.requirement_constraints])

    source_dir_name = "source_files"
    argv.append(f"--sources-directory={source_dir_name}")

    argv.extend(request.requirements)

    constraint_file_digest = EMPTY_DIGEST
    if python_setup.requirement_constraints is not None:
        constraint_file_digest = await Get(
            Digest,
            PathGlobs(
                [python_setup.requirement_constraints],
                glob_match_error_behavior=GlobMatchErrorBehavior.error,
                conjunction=GlobExpansionConjunction.all_match,
                description_of_origin=
                "the option `--python-setup-requirement-constraints`",
            ),
        )

    sources_digest_as_subdir = await Get(
        Digest, AddPrefix(request.sources or EMPTY_DIGEST, source_dir_name))
    additional_inputs_digest = request.additional_inputs or EMPTY_DIGEST

    merged_digest = await Get(
        Digest,
        MergeDigests((
            sources_digest_as_subdir,
            additional_inputs_digest,
            constraint_file_digest,
        )),
    )

    description = request.description
    if description is None:
        if request.requirements:
            description = (
                f"Building {request.output_filename} with "
                f"{pluralize(len(request.requirements), 'requirement')}: "
                f"{', '.join(request.requirements)}")
        else:
            description = f"Building {request.output_filename}"

    process = await Get(
        Process,
        PexCliProcess(
            argv=argv,
            additional_input_digest=merged_digest,
            description=description,
            output_files=[request.output_filename],
        ),
    )

    # NB: Building a Pex is platform dependent, so in order to get a PEX that we can use locally
    # without cross-building, we specify that our PEX command should be run on the current local
    # platform.
    result = await Get(
        ProcessResult,
        MultiPlatformProcess({
            (PlatformConstraint(platform.value),
             PlatformConstraint(platform.value)):
            process
        }),
    )

    if verbosity > 0:
        log_output = result.stderr.decode()
        if log_output:
            logger.info("%s", log_output)

    return Pex(
        digest=result.output_digest,
        name=request.output_filename,
        internal_only=request.internal_only,
    )