Beispiel #1
0
    def get_asdf_paths(env: Environment,
                       *,
                       asdf_local: bool = False) -> list[str]:
        """Returns a list of paths to Python interpreters managed by ASDF.

        :param env: The environment to use to look up ASDF.
        :param bool asdf_local: If True, only use the interpreter specified by
                                '.tool-versions' file under `build_root`.
        """
        asdf_dir = get_asdf_data_dir(env)
        if not asdf_dir:
            return []

        asdf_dir = Path(asdf_dir)

        # Ignore ASDF if the python plugin isn't installed.
        asdf_python_plugin = asdf_dir / "plugins" / "python"
        if not asdf_python_plugin.exists():
            return []

        # Ignore ASDF if no python versions have ever been installed (the installs folder is
        # missing).
        asdf_installs_dir = asdf_dir / "installs" / "python"
        if not asdf_installs_dir.exists():
            return []

        # Find all installed versions.
        asdf_installed_paths: list[str] = []
        for child in asdf_installs_dir.iterdir():
            # Aliases, and non-cpython installs may have odd names.
            # Make sure that the entry is a subdirectory of the installs directory.
            if child.is_dir():
                # Make sure that the subdirectory has a bin directory.
                bin_dir = child / "bin"
                if bin_dir.exists():
                    asdf_installed_paths.append(str(bin_dir))

        # Ignore ASDF if there are no installed versions.
        if not asdf_installed_paths:
            return []

        asdf_paths: list[str] = []
        asdf_versions: OrderedDict[str, str] = OrderedDict()
        tool_versions_file = None

        # Support "shell" based ASDF configuration
        ASDF_PYTHON_VERSION = env.get("ASDF_PYTHON_VERSION")
        if ASDF_PYTHON_VERSION:
            asdf_versions.update([
                (v, "ASDF_PYTHON_VERSION")
                for v in re.split(r"\s+", ASDF_PYTHON_VERSION)
            ])

        # Target the local tool-versions file.
        if asdf_local:
            tool_versions_file = Path(get_buildroot(), ".tool-versions")
            if not tool_versions_file.exists():
                logger.warning(
                    "No `.tool-versions` file found in the build root, but <ASDF_LOCAL> was set in"
                    " `[python-bootstrap].search_paths`.")
                tool_versions_file = None
        # Target the home directory tool-versions file.
        else:
            home = env.get("HOME")
            if home:
                tool_versions_file = Path(home) / ".tool-versions"
                if not tool_versions_file.exists():
                    tool_versions_file = None

        if tool_versions_file:
            # Parse the tool-versions file.
            # A tool-versions file contains multiple lines, one or more per tool.
            # Standardize that the last line for each tool wins.
            #
            # The definition of a tool-versions file can be found here:
            # https://asdf-vm.com/#/core-configuration?id=tool-versions
            tool_versions_lines = tool_versions_file.read_text().splitlines()
            last_line = None
            for line in tool_versions_lines:
                # Find the last python line.
                if line.lower().startswith("python"):
                    last_line = line
            if last_line:
                _, _, versions = last_line.partition("python")
                for v in re.split(r"\s+", versions.strip()):
                    if ":" in v:
                        key, _, value = v.partition(":")
                        if key.lower() == "path":
                            asdf_paths.append(value)
                        elif key.lower() == "ref":
                            asdf_versions[value] = str(tool_versions_file)
                        else:
                            logger.warning(
                                f"Unknown version format `{v}` from ASDF configured by "
                                "`[python-bootstrap].search_path`, ignoring. This "
                                "version will not be considered when determining which Python "
                                f"interpreters to use. Please check that `{tool_versions_file}` "
                                "is accurate.")
                    elif v == "system":
                        logger.warning(
                            "System python set by ASDF configured by "
                            "`[python-bootstrap].search_path` is unsupported, ignoring. "
                            "This version will not be considered when determining which Python "
                            "interpreters to use. Please remove 'system' from "
                            f"`{tool_versions_file}` to disable this warning.")
                    else:
                        asdf_versions[v] = str(tool_versions_file)

        for version, source in asdf_versions.items():
            install_dir = asdf_installs_dir / version / "bin"
            if install_dir.exists():
                asdf_paths.append(str(install_dir))
            else:
                logger.warning(
                    f"Trying to use ASDF version `{version}` configured by "
                    f"`[python-bootstrap].search_path` but `{install_dir}` does not "
                    "exist. This version will not be considered when determining which Python "
                    f"interpreters to use. Please check that `{source}` is accurate."
                )

        # For non-local, if no paths have been defined, fallback to every version installed
        if not asdf_local and len(asdf_paths) == 0:
            # This could be appended to asdf_paths, but there isn't any reason to
            return asdf_installed_paths
        else:
            return asdf_paths
Beispiel #2
0
 def get_environment_paths(env: Environment):
     """Returns a list of paths specified by the PATH env var."""
     pathstr = env.get("PATH")
     if pathstr:
         return pathstr.split(os.pathsep)
     return []
Beispiel #3
0
async def package_python_dist(
    field_set: PythonDistributionFieldSet,
    python_setup: PythonSetup,
    union_membership: UnionMembership,
) -> BuiltPackage:
    transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest([field_set.address]))
    exported_target = ExportedTarget(transitive_targets.roots[0])

    dist_tgt = exported_target.target
    wheel = dist_tgt.get(WheelField).value
    sdist = dist_tgt.get(SDistField).value
    if not wheel and not sdist:
        raise NoDistTypeSelected(
            softwrap(
                f"""
                In order to package {dist_tgt.address.spec} at least one of {WheelField.alias!r} or
                {SDistField.alias!r} must be `True`.
                """
            )
        )

    wheel_config_settings = dist_tgt.get(WheelConfigSettingsField).value or FrozenDict()
    sdist_config_settings = dist_tgt.get(SDistConfigSettingsField).value or FrozenDict()
    backend_env_vars = dist_tgt.get(BuildBackendEnvVarsField).value
    if backend_env_vars:
        extra_build_time_env = await Get(Environment, EnvironmentRequest(sorted(backend_env_vars)))
    else:
        extra_build_time_env = Environment()

    interpreter_constraints = InterpreterConstraints.create_from_targets(
        transitive_targets.closure, python_setup
    ) or InterpreterConstraints(python_setup.interpreter_constraints)
    chroot = await Get(
        DistBuildChroot,
        DistBuildChrootRequest(
            exported_target,
            interpreter_constraints=interpreter_constraints,
        ),
    )

    # Find the source roots for the build-time 1stparty deps (e.g., deps of setup.py).
    source_roots_result = await Get(
        SourceRootsResult,
        SourceRootsRequest(
            files=[], dirs={PurePath(tgt.address.spec_path) for tgt in transitive_targets.closure}
        ),
    )
    source_roots = tuple(sorted({sr.path for sr in source_roots_result.path_to_root.values()}))

    # Get any extra build-time environment (e.g., native extension requirements).
    build_env_requests = []
    build_env_request_types = union_membership.get(DistBuildEnvironmentRequest)
    for build_env_request_type in build_env_request_types:
        if build_env_request_type.is_applicable(dist_tgt):
            build_env_requests.append(
                build_env_request_type(
                    tuple(tt.address for tt in transitive_targets.closure), interpreter_constraints
                )
            )

    build_envs = await MultiGet(
        [
            Get(DistBuildEnvironment, DistBuildEnvironmentRequest, build_env_request)
            for build_env_request in build_env_requests
        ]
    )
    extra_build_time_requirements = tuple(
        itertools.chain.from_iterable(
            build_env.extra_build_time_requirements for build_env in build_envs
        )
    )
    input_digest = await Get(
        Digest,
        MergeDigests(
            [chroot.digest, *(build_env.extra_build_time_inputs for build_env in build_envs)]
        ),
    )

    # We prefix the entire chroot, and run with this prefix as the cwd, so that we can capture
    # any changes setup made within it without also capturing other artifacts of the pex
    # process invocation.
    chroot_prefix = "chroot"
    working_directory = os.path.join(chroot_prefix, chroot.working_directory)
    prefixed_input = await Get(Digest, AddPrefix(input_digest, chroot_prefix))
    build_system = await Get(BuildSystem, BuildSystemRequest(prefixed_input, working_directory))

    setup_py_result = await Get(
        DistBuildResult,
        DistBuildRequest(
            build_system=build_system,
            interpreter_constraints=interpreter_constraints,
            build_wheel=wheel,
            build_sdist=sdist,
            input=prefixed_input,
            working_directory=working_directory,
            build_time_source_roots=source_roots,
            target_address_spec=exported_target.target.address.spec,
            wheel_config_settings=wheel_config_settings,
            sdist_config_settings=sdist_config_settings,
            extra_build_time_requirements=extra_build_time_requirements,
            extra_build_time_env=extra_build_time_env,
        ),
    )
    dist_snapshot = await Get(Snapshot, Digest, setup_py_result.output)
    return BuiltPackage(
        setup_py_result.output,
        tuple(BuiltPackageArtifact(path) for path in dist_snapshot.files),
    )
Beispiel #4
0
def test_expand_interpreter_search_paths(rule_runner: RuleRunner) -> None:
    local_pyenv_version = "3.5.5"
    all_python_versions = [
        "2.7.14", local_pyenv_version, "3.7.10", "3.9.4", "3.9.5"
    ]
    asdf_home_versions = [0, 1, 2]
    asdf_local_versions = [2, 1, 4]
    asdf_local_versions_str = " ".join(
        materialize_indices(all_python_versions, asdf_local_versions))
    rule_runner.write_files({
        ".python-version":
        f"{local_pyenv_version}\n",
        ".tool-versions": ("nodejs 16.0.1\n"
                           "java current\n"
                           f"python {asdf_local_versions_str}\n"
                           "rust 1.52.0\n"),
    })
    with setup_pexrc_with_pex_python_path(["/pexrc/path1:/pexrc/path2"]):
        with fake_asdf_root(all_python_versions, asdf_home_versions,
                            asdf_local_versions) as (
                                home_dir,
                                asdf_dir,
                                expected_asdf_paths,
                                expected_asdf_home_paths,
                                expected_asdf_local_paths,
                            ), fake_pyenv_root(all_python_versions,
                                               local_pyenv_version) as (
                                                   pyenv_root,
                                                   expected_pyenv_paths,
                                                   expected_pyenv_local_paths,
                                               ):
            paths = [
                "/foo",
                "<PATH>",
                "/bar",
                "<PEXRC>",
                "/baz",
                "<ASDF>",
                "<ASDF_LOCAL>",
                "<PYENV>",
                "<PYENV_LOCAL>",
                "/qux",
            ]
            env = Environment({
                "HOME": home_dir,
                "PATH": "/env/path1:/env/path2",
                "PYENV_ROOT": pyenv_root,
                "ASDF_DATA_DIR": asdf_dir,
            })
            expanded_paths = PythonBootstrap.expand_interpreter_search_paths(
                paths,
                env,
            )

    expected = [
        "/foo",
        "/env/path1",
        "/env/path2",
        "/bar",
        "/pexrc/path1",
        "/pexrc/path2",
        "/baz",
        *expected_asdf_home_paths,
        *expected_asdf_local_paths,
        *expected_pyenv_paths,
        *expected_pyenv_local_paths,
        "/qux",
    ]
    assert expected == expanded_paths
Beispiel #5
0
def test_get_environment_paths() -> None:
    paths = PythonBootstrap.get_environment_paths(
        Environment({"PATH": "foo/bar:baz:/qux/quux"}))
    assert ["foo/bar", "baz", "/qux/quux"] == paths
Beispiel #6
0
 def create(
     cls,
     env: Mapping[str, str],
 ) -> DockerBuildEnvironment:
     return cls(Environment(env))