예제 #1
0
def test_find_binary_file_path(rule_runner: RuleRunner,
                               tmp_path: Path) -> None:
    binary_path_abs = MyBin.create(tmp_path)

    binary_paths = rule_runner.request(
        BinaryPaths,
        [
            BinaryPathRequest(
                binary_name=MyBin.binary_name,
                search_path=[str(binary_path_abs)],
            )
        ],
    )
    assert binary_paths.first_path is None, "By default, PATH file entries should not be checked."

    binary_paths = rule_runner.request(
        BinaryPaths,
        [
            BinaryPathRequest(
                binary_name=MyBin.binary_name,
                search_path=[str(binary_path_abs)],
                check_file_entries=True,
            )
        ],
    )
    assert binary_paths.first_path is not None
    assert binary_paths.first_path.path == str(binary_path_abs)
예제 #2
0
파일: desktop.py 프로젝트: matze999/pants
async def find_open_program(request: OpenFilesRequest,
                            plat: Platform) -> OpenFiles:
    open_program_name = "open" if plat == Platform.darwin else "xdg-open"
    open_program_paths = await Get(
        BinaryPaths,
        BinaryPathRequest(binary_name=open_program_name,
                          search_path=("/bin", "/usr/bin")),
    )
    if not open_program_paths.first_path:
        error = (
            f"Could not find the program '{open_program_name}' on `/bin` or `/usr/bin`, so cannot "
            f"open the files {sorted(request.files)}.")
        if request.error_if_open_not_found:
            raise OSError(error)
        logger.error(error)
        return OpenFiles(())

    if plat == Platform.darwin:
        processes = [
            InteractiveProcess(
                argv=(open_program_paths.first_path,
                      *(str(f) for f in request.files)),
                run_in_workspace=True,
            )
        ]
    else:
        processes = [
            InteractiveProcess(argv=(open_program_paths.first_path, str(f)),
                               run_in_workspace=True) for f in request.files
        ]

    return OpenFiles(tuple(processes))
예제 #3
0
async def determine_shunit2_shell(request: Shunit2RunnerRequest,
                                  shell_setup: ShellSetup) -> Shunit2Runner:
    if request.shell_field.value is not None:
        tgt_shell = Shunit2Shell(request.shell_field.value)
    else:
        parse_result = Shunit2Shell.parse_shebang(
            request.test_file_content.content)
        if parse_result is None:
            raise ShellNotConfigured(
                f"Could not determine which shell to use to run shunit2 on {request.address}.\n\n"
                f"Please either specify the `{Shunit2ShellField.alias}` field or add a "
                f"shebang to {request.test_file_content.path} with one of the supported shells in "
                f"the format `!#/path/to/shell` or `!#/path/to/env shell`"
                f"(run `./pants help {Shunit2Tests.alias}` for valid shells).")
        tgt_shell = parse_result

    env = await Get(Environment, EnvironmentRequest(["PATH"]))
    path_request = BinaryPathRequest(
        binary_name=tgt_shell.name,
        search_path=shell_setup.executable_search_path(env),
        test=tgt_shell.binary_path_test,
    )
    paths = await Get(BinaryPaths, BinaryPathRequest, path_request)
    first_path = paths.first_path
    if not first_path:
        raise BinaryNotFoundError(
            path_request, rationale=f"run shunit2 on {request.address}")
    return Shunit2Runner(tgt_shell, first_path)
예제 #4
0
def test_find_binary_non_existent(rule_runner: RuleRunner) -> None:
    with temporary_dir() as tmpdir:
        search_path = [tmpdir]
        binary_paths = rule_runner.request(
            BinaryPaths,
            [BinaryPathRequest(binary_name="anybin", search_path=search_path)])
        assert binary_paths.first_path is None
예제 #5
0
파일: rules.py 프로젝트: patricklaw/pants
async def package_debian_package(field_set: DebianPackageFieldSet) -> BuiltPackage:
    dpkg_deb_path = await Get(
        BinaryPaths,
        BinaryPathRequest(
            binary_name="touch",
            search_path=["/usr/bin"],
        ),
    )
    if not dpkg_deb_path.first_path:
        raise EnvironmentError("Could not find the `touch` program on search paths ")

    output_filename = field_set.output_path.value_or_default(file_ending="deb")

    # TODO(alexey): add Debian packaging logic
    result = await Get(
        ProcessResult,
        Process(
            argv=(
                "touch",
                output_filename,
            ),
            description="Create a Debian package from the produced packages.",
            output_files=(output_filename,),
        ),
    )
    return BuiltPackage(result.output_digest, artifacts=(BuiltPackageArtifact(output_filename),))
예제 #6
0
def test_find_binary_respects_search_path_order(rule_runner: RuleRunner,
                                                tmp_path: Path) -> None:
    binary_path_abs1 = MyBin.create(tmp_path / "bin1")
    binary_path_abs2 = MyBin.create(tmp_path / "bin2")
    binary_path_abs3 = MyBin.create(tmp_path / "bin3")

    binary_paths = rule_runner.request(
        BinaryPaths,
        [
            BinaryPathRequest(
                binary_name=MyBin.binary_name,
                search_path=[
                    str(binary_path_abs1.parent),
                    str(binary_path_abs2),
                    str(binary_path_abs3.parent),
                ],
                check_file_entries=True,
            )
        ],
    )
    assert binary_paths.first_path is not None
    assert binary_paths.first_path.path == str(binary_path_abs1)
    assert [
        str(p) for p in (binary_path_abs1, binary_path_abs2, binary_path_abs3)
    ] == [binary_path.path for binary_path in binary_paths.paths]
예제 #7
0
async def find_pex_python(
    python_setup: PythonSetup,
    pex_runtime_environment: PexRuntimeEnvironment,
    subprocess_environment: SubprocessEnvironment,
) -> PexEnvironment:
    # PEX files are compatible with bootstrapping via python2.7 or python 3.5+. The bootstrap
    # code will then re-exec itself if the underlying PEX user code needs a more specific python
    # interpreter. As such, we look for many Pythons usable by the PEX bootstrap code here for
    # maximum flexibility.
    all_python_binary_paths = await MultiGet([
        Get(
            BinaryPaths,
            BinaryPathRequest(
                search_path=python_setup.interpreter_search_paths,
                binary_name=binary_name),
        )
        for binary_name in pex_runtime_environment.bootstrap_interpreter_names
    ])

    def first_python_binary() -> Optional[str]:
        for binary_paths in all_python_binary_paths:
            if binary_paths.first_path:
                return binary_paths.first_path
        return None

    return PexEnvironment(
        path=pex_runtime_environment.path,
        interpreter_search_paths=tuple(python_setup.interpreter_search_paths),
        subprocess_environment_dict=FrozenDict(
            subprocess_environment.environment_dict),
        bootstrap_python=first_python_binary(),
    )
예제 #8
0
def test_find_binary_non_existent(rule_runner: RuleRunner,
                                  tmp_path: Path) -> None:
    binary_paths = rule_runner.request(BinaryPaths, [
        BinaryPathRequest(binary_name="nonexistent-bin",
                          search_path=[str(tmp_path)])
    ])
    assert binary_paths.first_path is None
예제 #9
0
파일: archive.py 프로젝트: hephex/pants
async def find_tar() -> TarBinary:
    request = BinaryPathRequest(binary_name="tar",
                                search_path=SEARCH_PATHS,
                                test=BinaryPathTest(args=["--version"]))
    paths = await Get(BinaryPaths, BinaryPathRequest, request)
    first_path = paths.first_path_or_raise(
        request, rationale="download the tools Pants needs to run")
    return TarBinary(first_path.path, first_path.fingerprint)
예제 #10
0
파일: archive.py 프로젝트: hephex/pants
async def find_zip() -> ZipBinary:
    request = BinaryPathRequest(binary_name="zip",
                                search_path=SEARCH_PATHS,
                                test=BinaryPathTest(args=["-v"]))
    paths = await Get(BinaryPaths, BinaryPathRequest, request)
    first_path = paths.first_path_or_raise(request,
                                           rationale="create `.zip` archives")
    return ZipBinary(first_path.path, first_path.fingerprint)
예제 #11
0
async def find_unzip() -> UnzipBinary:
    request = BinaryPathRequest(binary_name="unzip",
                                search_path=SEARCH_PATHS,
                                test=BinaryPathTest(args=["-v"]))
    paths = await Get(BinaryPaths, BinaryPathRequest, request)
    first_path = paths.first_path
    if not first_path:
        raise BinaryNotFoundError(
            request, rationale="download the tools Pants needs to run")
    return UnzipBinary(first_path.path, first_path.fingerprint)
예제 #12
0
def test_find_binary_non_existent(rule_runner: RuleRunner,
                                  which_path: str | None) -> None:
    with temporary_dir() as tmpdir:
        if which_path:
            os.symlink(which_path, os.path.join(tmpdir, "which"))
        binary_paths = rule_runner.request(BinaryPaths, [
            BinaryPathRequest(binary_name="nonexistent-bin",
                              search_path=[tmpdir])
        ])
        assert binary_paths.first_path is None
예제 #13
0
async def find_docker(docker_request: DockerBinaryRequest) -> DockerBinary:
    request = BinaryPathRequest(
        binary_name="docker",
        search_path=docker_request.search_path,
        test=BinaryPathTest(args=["-v"]),
    )
    paths = await Get(BinaryPaths, BinaryPathRequest, request)
    first_path = paths.first_path
    if not first_path:
        raise BinaryNotFoundError.from_request(request, rationale="interact with the docker daemon")
    return DockerBinary(first_path.path, first_path.fingerprint)
예제 #14
0
async def package_bash_binary(field_set: BashBinaryFieldSet,
                              bash_setup: BashSetup) -> BuiltPackage:
    # We first locate the `zip` program using `BinaryPaths`. We use the option
    # `--bash-executable-search-paths` to determine which paths to search, such as `/bin` and
    # `/usr/bin`. See https://www.pantsbuild.org/v2.0/docs/rules-api-installing-tools.
    zip_program_paths = await Get(
        BinaryPaths,
        BinaryPathRequest(
            binary_name="zip",
            search_path=bash_setup.executable_search_path,
            # This will run `zip --version` to ensure it's a valid binary and to allow
            # invalidating the cache if the version changes.
            test=BinaryPathTest(args=["-v"]),
        ),
    )
    if not zip_program_paths.first_path:
        raise EnvironmentError(
            f"Could not find the `zip` program on search paths "
            f"{list(bash_setup.executable_search_path)}, so cannot create a binary for "
            f"{field_set.address}. Please check that `zip` is installed and possibly modify the "
            "option `executable_search_paths` in the `[bash-setup]` options scope."
        )

    # We need to include all relevant transitive dependencies in the zip. See
    # https://www.pantsbuild.org/v2.0/docs/rules-api-and-target-api.
    transitive_targets = await Get(
        TransitiveTargets, TransitiveTargetsRequest([field_set.address]))
    sources = await Get(
        SourceFiles,
        SourceFilesRequest(
            (tgt.get(Sources) for tgt in transitive_targets.closure),
            for_sources_types=(BashSources, FilesSources, ResourcesSources),
        ),
    )

    output_filename = field_set.output_path.value_or_default(
        field_set.address, file_ending="zip", use_legacy_format=False)
    result = await Get(
        ProcessResult,
        Process(
            argv=(
                zip_program_paths.first_path.path,
                output_filename,
                *sources.snapshot.files,
            ),
            input_digest=sources.snapshot.digest,
            description=f"Zip {field_set.address} and its dependencies.",
            output_files=(output_filename, ),
        ),
    )
    return BuiltPackage(result.output_digest,
                        artifacts=(BuiltPackageArtifact(output_filename), ))
예제 #15
0
def test_find_binary_on_path_without_bash(rule_runner: RuleRunner,
                                          tmp_path: Path) -> None:
    # Test that locating a binary on a PATH which does not include bash works (by recursing to
    # locate bash first).
    binary_dir_abs = tmp_path / "bin"
    binary_path_abs = MyBin.create(binary_dir_abs)

    binary_paths = rule_runner.request(
        BinaryPaths,
        [
            BinaryPathRequest(binary_name=MyBin.binary_name,
                              search_path=[str(binary_dir_abs)])
        ],
    )
    assert binary_paths.first_path is not None
    assert binary_paths.first_path.path == str(binary_path_abs)
예제 #16
0
def test_find_binary_on_path_without_bash(rule_runner: RuleRunner) -> None:
    # Test that locating a binary on a PATH which does not include bash works (by recursing to
    # locate bash first).
    binary_name = "mybin"
    binary_dir = "bin"
    with temporary_dir() as tmpdir:
        binary_dir_abs = os.path.join(tmpdir, binary_dir)
        binary_path_abs = os.path.join(binary_dir_abs, binary_name)
        safe_mkdir(binary_dir_abs)
        touch(binary_path_abs)

        search_path = [binary_dir_abs]
        binary_paths = rule_runner.request(BinaryPaths, [
            BinaryPathRequest(binary_name=binary_name, search_path=search_path)
        ])
        assert os.path.exists(os.path.join(binary_dir_abs, binary_name))
        assert binary_paths.first_path is not None
        assert binary_paths.first_path.path == binary_path_abs
예제 #17
0
파일: desktop.py 프로젝트: hephex/pants
async def find_open_program(
    request: OpenFilesRequest,
    plat: Platform,
    complete_env: CompleteEnvironment,
) -> OpenFiles:
    open_program_name = "open" if plat.is_macos else "xdg-open"
    open_program_paths = await Get(
        BinaryPaths,
        BinaryPathRequest(binary_name=open_program_name,
                          search_path=("/bin", "/usr/bin")),
    )
    if not open_program_paths.first_path:
        error = (
            f"Could not find the program '{open_program_name}' on `/bin` or `/usr/bin`, so cannot "
            f"open the files {sorted(request.files)}.")
        if request.error_if_open_not_found:
            raise OSError(error)
        logger.error(error)
        return OpenFiles(())

    if plat.is_macos:
        processes = [
            InteractiveProcess(
                argv=(open_program_paths.first_path.path,
                      *(str(f) for f in request.files)),
                run_in_workspace=True,
                restartable=True,
            )
        ]
    else:
        processes = [
            InteractiveProcess(
                argv=(open_program_paths.first_path.path, str(f)),
                run_in_workspace=True,
                # The xdg-open binary needs many environment variables to work properly. In addition
                # to the various XDG_* environment variables, DISPLAY and other X11 variables are
                # required. Instead of attempting to track all of these we just export the full user
                # environment since this is not a cached process.
                env=complete_env,
                restartable=True,
            ) for f in request.files
        ]

    return OpenFiles(tuple(processes))
예제 #18
0
async def run_bash_binary(bash_setup: BashSetup) -> BashProgram:
    # We expect Bash to already be installed. See
    # https://www.pantsbuild.org/v2.0/docs/rules-api-installing-tools.
    bash_program_paths = await Get(
        BinaryPaths,
        BinaryPathRequest(
            binary_name="bash",
            search_path=bash_setup.executable_search_path,
            # This will run `bash --version` to ensure it's a valid binary and to allow
            # invalidating the cache if the version changes.
            test=BinaryPathTest(args=["--version"]),
        ),
    )
    if not bash_program_paths.first_path:
        raise EnvironmentError(
            "Could not find the `bash` program on search paths "
            f"{list(bash_setup.executable_search_path)}. Please check that `bash` is installed and "
            "possibly modify the option `executable_search_paths` in the `[bash-setup]` options "
            "scope.")
    return BashProgram(bash_program_paths.first_path.path)
예제 #19
0
async def package_into_image(
    field_set: DockerPackageFieldSet,
    union_membership: UnionMembership,
) -> BuiltPackage:
    """Build a docker image from a 'docker' build target.

    Creates a build context & dockerfile from the build target & its
    dependencies. Then builds & tags that image. (see the module
    docstring for more information)
    """
    target_name = field_set.address.target_name
    transitive_targets = await Get(
        TransitiveTargets, TransitiveTargetsRequest([field_set.address])
    )
    component_list = []
    logger.debug("Building Target %s", target_name)
    for field_set_type in union_membership[DockerComponentFieldSet]:
        for target in transitive_targets.dependencies:
            if field_set_type.is_applicable(target):
                logger.debug(
                    "Dependent Target %s applies to as component %s",
                    target.address,
                    field_set_type.__name__,
                )
                component_list.append(field_set_type.create(target))

    components = await MultiGet(
        Get(DockerComponent, DockerComponentFieldSet, fs) for fs in component_list
    )

    source_digests = []
    run_commands = []
    components = sorted(components, key=lambda c: c.order)
    for component in components:
        if component.sources:
            source_digests.append(component.sources)
        run_commands.extend(component.commands)
    source_digest = await Get(Digest, MergeDigests(source_digests))
    application_snapshot = await Get(Snapshot, AddPrefix(source_digest, "application"))

    if logger.isEnabledFor(logging.DEBUG):
        logger.debug("Files to be copied into the docker container")
        for file in application_snapshot.files:
            logger.debug("* %s", file)

    dockerfile_contents = _create_dockerfile(
        field_set.base_image.value,
        field_set.workdir.value,
        field_set.image_setup.value,
        run_commands,
        field_set.command.value,
    )
    logger.debug(dockerfile_contents)
    dockerfile = await Get(
        Digest,
        CreateDigest([FileContent("Dockerfile", dockerfile_contents.encode("utf-8"))]),
    )
    # create docker build context of all merged files & fetch docker
    # connection enviornment variables
    # and the location of the docker process
    search_path = ["/bin", "/usr/bin", "/usr/local/bin", "$HOME/"]
    docker_context, docker_env, docker_paths = await MultiGet(
        Get(Digest, MergeDigests([dockerfile, application_snapshot.digest])),
        Get(Environment, EnvironmentRequest(utils.DOCKER_ENV_VARS)),
        Get(
            BinaryPaths,
            BinaryPathRequest(
                binary_name="docker",
                search_path=search_path,
            ),
        ),
    )
    if not docker_paths.first_path:
        raise ValueError("Unable to locate Docker binary on paths: %s", search_path)
    process_path = docker_paths.first_path.path
    # build an list of arguments of the form ["-t",
    # "registry/name:tag"] to pass to the docker executable
    tag_arguments = _build_tag_argument_list(
        target_name, field_set.tags.value or [], field_set.registry.value
    )
    # create the image
    process_args = [process_path, "build"]
    if not logger.isEnabledFor(logging.DEBUG):
        process_args.append("-q")  # only output the hash of the image
    process_args.extend(tag_arguments)
    process_args.append(".")  # use current (sealed) directory as build context
    process_result = await Get(
        ProcessResult,
        Process(
            env=docker_env,
            argv=process_args,
            input_digest=docker_context,
            description=f"Creating Docker Image from {target_name}",
        ),
    )
    logger.info(process_result.stdout.decode())
    o = await Get(Snapshot, Digest, process_result.output_digest)
    return BuiltPackage(
        digest=process_result.output_digest,
        artifacts=([BuiltPackageArtifact(f, ()) for f in o.files]),
    )
예제 #20
0
async def find_pex_python(
    python_setup: PythonSetup,
    pex_runtime_env: PexRuntimeEnvironment,
    subprocess_env_vars: SubprocessEnvironmentVars,
) -> PexEnvironment:
    # PEX files are compatible with bootstrapping via Python 2.7 or Python 3.5+. The bootstrap
    # code will then re-exec itself if the underlying PEX user code needs a more specific python
    # interpreter. As such, we look for many Pythons usable by the PEX bootstrap code here for
    # maximum flexibility.
    all_python_binary_paths = await MultiGet(
        Get(
            BinaryPaths,
            BinaryPathRequest(
                search_path=python_setup.interpreter_search_paths,
                binary_name=binary_name,
                test=BinaryPathTest(
                    args=[
                        "-c",
                        # N.B.: The following code snippet must be compatible with Python 2.7 and
                        # Python 3.5+.
                        #
                        # We hash the underlying Python interpreter executable to ensure we detect
                        # changes in the real interpreter that might otherwise be masked by Pyenv
                        # shim scripts found on the search path. Naively, just printing out the full
                        # version_info would be enough, but that does not account for supported abi
                        # changes (e.g.: a pyenv switch from a py27mu interpreter to a py27m
                        # interpreter.)
                        #
                        # When hashing, we pick 8192 for efficiency of reads and fingerprint updates
                        # (writes) since it's a common OS buffer size and an even multiple of the
                        # hash block size.
                        dedent("""\
                            import sys

                            major, minor = sys.version_info[:2]
                            if (major, minor) != (2, 7) and not (major == 3 and minor >= 5):
                                sys.exit(1)

                            import hashlib
                            hasher = hashlib.sha256()
                            with open(sys.executable, "rb") as fp:
                                for chunk in iter(lambda: fp.read(8192), b""):
                                    hasher.update(chunk)
                            sys.stdout.write(hasher.hexdigest())
                            """),
                    ],
                    fingerprint_stdout=
                    False,  # We already emit a usable fingerprint to stdout.
                ),
            ),
        ) for binary_name in pex_runtime_env.bootstrap_interpreter_names)

    def first_python_binary() -> Optional[PythonExecutable]:
        for binary_paths in all_python_binary_paths:
            if binary_paths.first_path:
                return PythonExecutable(
                    path=binary_paths.first_path.path,
                    fingerprint=binary_paths.first_path.fingerprint,
                )
        return None

    return PexEnvironment(
        path=pex_runtime_env.path,
        interpreter_search_paths=tuple(python_setup.interpreter_search_paths),
        subprocess_environment_dict=subprocess_env_vars.vars,
        bootstrap_python=first_python_binary(),
    )
예제 #21
0
async def prepare_shell_command_process(request: ShellCommandProcessRequest,
                                        shell_setup: ShellSetup,
                                        bash: BashBinary) -> Process:
    shell_command = request.target
    interactive = shell_command.has_field(ShellCommandRunWorkdirField)
    if interactive:
        working_directory = shell_command[
            ShellCommandRunWorkdirField].value or ""
    else:
        working_directory = shell_command.address.spec_path
    command = shell_command[ShellCommandCommandField].value
    timeout = shell_command.get(ShellCommandTimeoutField).value
    tools = shell_command.get(ShellCommandToolsField,
                              default_raw_value=()).value
    outputs = shell_command.get(ShellCommandOutputsField).value or ()

    if not command:
        raise ValueError(
            f"Missing `command` line in `{shell_command.alias}` target {shell_command.address}."
        )

    if interactive:
        command_env = {
            "CHROOT": "{chroot}",
        }
    else:
        if not tools:
            raise ValueError(
                f"Must provide any `tools` used by the `{shell_command.alias}` {shell_command.address}."
            )

        env = await Get(Environment, EnvironmentRequest(["PATH"]))
        search_path = shell_setup.executable_search_path(env)
        tool_requests = [
            BinaryPathRequest(
                binary_name=tool,
                search_path=search_path,
            ) for tool in {*tools, *["mkdir", "ln"]}
            if tool not in BASH_BUILTIN_COMMANDS
        ]
        tool_paths = await MultiGet(
            Get(BinaryPaths, BinaryPathRequest, request)
            for request in tool_requests)

        command_env = {
            "TOOLS":
            " ".join(
                _shell_tool_safe_env_name(tool.binary_name)
                for tool in tool_requests),
        }

        for binary, tool_request in zip(tool_paths, tool_requests):
            if binary.first_path:
                command_env[_shell_tool_safe_env_name(
                    tool_request.binary_name)] = binary.first_path.path
            else:
                raise BinaryNotFoundError.from_request(
                    tool_request,
                    rationale=
                    f"execute `{shell_command.alias}` {shell_command.address}",
                )

    transitive_targets = await Get(
        TransitiveTargets,
        TransitiveTargetsRequest([shell_command.address]),
    )

    sources, pkgs_per_target = await MultiGet(
        Get(
            SourceFiles,
            SourceFilesRequest(
                sources_fields=[
                    tgt.get(SourcesField)
                    for tgt in transitive_targets.dependencies
                ],
                for_sources_types=(SourcesField, FileSourceField),
                enable_codegen=True,
            ),
        ),
        Get(
            FieldSetsPerTarget,
            FieldSetsPerTargetRequest(PackageFieldSet,
                                      transitive_targets.dependencies),
        ),
    )

    packages = await MultiGet(
        Get(BuiltPackage, PackageFieldSet, field_set)
        for field_set in pkgs_per_target.field_sets)

    if interactive or not working_directory or working_directory in sources.snapshot.dirs:
        work_dir = EMPTY_DIGEST
    else:
        work_dir = await Get(Digest,
                             CreateDigest([Directory(working_directory)]))

    input_digest = await Get(
        Digest,
        MergeDigests([
            sources.snapshot.digest, work_dir,
            *(pkg.digest for pkg in packages)
        ]))

    output_files = [f for f in outputs if not f.endswith("/")]
    output_directories = [d for d in outputs if d.endswith("/")]

    if interactive:
        relpath = os.path.relpath(
            working_directory or ".",
            start="/" if os.path.isabs(working_directory) else ".")
        boot_script = f"cd {shlex.quote(relpath)}; " if relpath != "." else ""
    else:
        # Setup bin_relpath dir with symlinks to all requested tools, so that we can use PATH, force
        # symlinks to avoid issues with repeat runs using the __run.sh script in the sandbox.
        bin_relpath = ".bin"
        boot_script = ";".join(
            dedent(f"""\
                $mkdir -p {bin_relpath}
                for tool in $TOOLS; do $ln -sf ${{!tool}} {bin_relpath}; done
                export PATH="$PWD/{bin_relpath}"
                """).split("\n"))

    return Process(
        argv=(bash.path, "-c", boot_script + command),
        description=f"Running {shell_command.alias} {shell_command.address}",
        env=command_env,
        input_digest=input_digest,
        output_directories=output_directories,
        output_files=output_files,
        timeout_seconds=timeout,
        working_directory=working_directory,
    )
예제 #22
0
async def find_python(python_bootstrap: PythonBootstrap) -> PythonBinary:

    # PEX files are compatible with bootstrapping via Python 2.7 or Python 3.5+, but we select 3.6+
    # for maximum compatibility with internal scripts.
    interpreter_search_paths = python_bootstrap.interpreter_search_paths()
    all_python_binary_paths = await MultiGet(
        Get(
            BinaryPaths,
            BinaryPathRequest(
                search_path=interpreter_search_paths,
                binary_name=binary_name,
                check_file_entries=True,
                test=BinaryPathTest(
                    args=[
                        "-c",
                        # N.B.: The following code snippet must be compatible with Python 3.6+.
                        #
                        # We hash the underlying Python interpreter executable to ensure we detect
                        # changes in the real interpreter that might otherwise be masked by Pyenv
                        # shim scripts found on the search path. Naively, just printing out the full
                        # version_info would be enough, but that does not account for supported abi
                        # changes (e.g.: a pyenv switch from a py27mu interpreter to a py27m
                        # interpreter.)
                        #
                        # When hashing, we pick 8192 for efficiency of reads and fingerprint updates
                        # (writes) since it's a common OS buffer size and an even multiple of the
                        # hash block size.
                        dedent("""\
                            import sys

                            major, minor = sys.version_info[:2]
                            if not (major == 3 and minor >= 6):
                                sys.exit(1)

                            import hashlib
                            hasher = hashlib.sha256()
                            with open(sys.executable, "rb") as fp:
                                for chunk in iter(lambda: fp.read(8192), b""):
                                    hasher.update(chunk)
                            sys.stdout.write(hasher.hexdigest())
                            """),
                    ],
                    fingerprint_stdout=
                    False,  # We already emit a usable fingerprint to stdout.
                ),
            ),
        ) for binary_name in python_bootstrap.interpreter_names)

    for binary_paths in all_python_binary_paths:
        path = binary_paths.first_path
        if path:
            return PythonBinary(
                path=path.path,
                fingerprint=path.fingerprint,
            )

    raise BinaryNotFoundError(
        "Was not able to locate a Python interpreter to execute rule code.\n"
        "Please ensure that Python is available in one of the locations identified by "
        "`[python-bootstrap] search_path`, which currently expands to:\n"
        f"  {interpreter_search_paths}")
예제 #23
0
async def run_shell_command(
    request: GenerateFilesFromShellCommandRequest,
    shell_setup: ShellSetup,
    bash: BashBinary,
) -> GeneratedSources:
    shell_command = request.protocol_target
    working_directory = shell_command.address.spec_path
    command = shell_command[ShellCommandCommandField].value
    tools = shell_command[ShellCommandToolsField].value
    outputs = shell_command[ShellCommandOutputsField].value or ()

    if not command:
        raise ValueError(
            f"Missing `command` line in `shell_command` target {shell_command.address}."
        )

    if not tools:
        raise ValueError(
            f"Must provide any `tools` used by the `shell_command` {shell_command.address}."
        )

    env = await Get(Environment, EnvironmentRequest(["PATH"]))
    search_path = shell_setup.executable_search_path(env)
    tool_requests = [
        BinaryPathRequest(
            binary_name=tool,
            search_path=search_path,
        ) for tool in {*tools, *["mkdir", "ln"]}
        if tool not in BASH_BUILTIN_COMMANDS
    ]
    tool_paths = await MultiGet(
        Get(BinaryPaths, BinaryPathRequest, request)
        for request in tool_requests)

    command_env = {
        "TOOLS":
        " ".join(shlex.quote(tool.binary_name) for tool in tool_requests),
    }

    for binary, tool_request in zip(tool_paths, tool_requests):
        if binary.first_path:
            command_env[tool_request.binary_name] = binary.first_path.path
        else:
            raise BinaryNotFoundError.from_request(
                tool_request,
                rationale=
                f"execute experimental_shell_command {shell_command.address}",
            )

    transitive_targets = await Get(
        TransitiveTargets,
        TransitiveTargetsRequest([shell_command.address]),
    )

    sources = await Get(
        SourceFiles,
        SourceFilesRequest(
            sources_fields=[
                tgt.get(Sources) for tgt in transitive_targets.dependencies
            ],
            for_sources_types=(
                Sources,
                FilesSources,
            ),
            enable_codegen=True,
        ),
    )

    output_files = [f for f in outputs if not f.endswith("/")]
    output_directories = [d for d in outputs if d.endswith("/")]

    if working_directory in sources.snapshot.dirs:
        input_digest = sources.snapshot.digest
    else:
        work_dir = await Get(Digest,
                             CreateDigest([Directory(working_directory)]))
        input_digest = await Get(
            Digest, MergeDigests([sources.snapshot.digest, work_dir]))

    # Setup bin_relpath dir with symlinks to all requested tools, so that we can use PATH.
    bin_relpath = ".bin"
    setup_tool_symlinks_script = ";".join(
        dedent(f"""\
            $mkdir -p {bin_relpath}
            for tool in $TOOLS; do $ln -s ${{!tool}} {bin_relpath}/; done
            export PATH="$PWD/{bin_relpath}"
            """).split("\n"))

    result = await Get(
        ProcessResult,
        Process(
            argv=(bash.path, "-c", setup_tool_symlinks_script + command),
            description=
            f"Running experimental_shell_command {shell_command.address}",
            env=command_env,
            input_digest=input_digest,
            output_directories=output_directories,
            output_files=output_files,
            working_directory=working_directory,
        ),
    )

    if shell_command[ShellCommandLogOutputField].value:
        if result.stdout:
            logger.info(result.stdout.decode())
        if result.stderr:
            logger.warning(result.stderr.decode())

    output = await Get(Snapshot,
                       AddPrefix(result.output_digest, working_directory))
    return GeneratedSources(output)
예제 #24
0
async def setup_goroot(golang_subsystem: GolangSubsystem) -> GoRoot:
    env = await Get(Environment, EnvironmentRequest(["PATH"]))
    search_paths = golang_subsystem.go_search_paths(env)
    all_go_binary_paths = await Get(
        BinaryPaths,
        BinaryPathRequest(
            search_path=search_paths,
            binary_name="go",
            test=BinaryPathTest(["version"]),
        ),
    )
    if not all_go_binary_paths.paths:
        raise BinaryNotFoundError(
            "Cannot find any `go` binaries using the option "
            f"`[golang].go_search_paths`: {list(search_paths)}\n\n"
            "To fix, please install Go (https://golang.org/doc/install) with the version "
            f"{golang_subsystem.expected_version} (set by `[golang].expected_version`) and ensure "
            "that it is discoverable via `[golang].go_search_paths`.")

    # `go env GOVERSION` does not work in earlier Go versions (like 1.15), so we must run
    # `go version` and `go env GOROOT` to calculate both the version and GOROOT.
    version_results = await MultiGet(
        Get(
            ProcessResult,
            Process(
                (binary_path.path, "version"),
                description=f"Determine Go version for {binary_path.path}",
                level=LogLevel.DEBUG,
                cache_scope=ProcessCacheScope.PER_RESTART_SUCCESSFUL,
            ),
        ) for binary_path in all_go_binary_paths.paths)

    invalid_versions = []
    for binary_path, version_result in zip(all_go_binary_paths.paths,
                                           version_results):
        try:
            _raw_version = version_result.stdout.decode("utf-8").split()[
                2]  # e.g. go1.17 or go1.17.1
            _version_components = _raw_version[2:].split(
                ".")  # e.g. [1, 17] or [1, 17, 1]
            version = f"{_version_components[0]}.{_version_components[1]}"
        except IndexError:
            raise AssertionError(
                f"Failed to parse `go version` output for {binary_path}. Please open a bug at "
                f"https://github.com/pantsbuild/pants/issues/new/choose with the below data."
                f"\n\n"
                f"{version_result}")

        if version == golang_subsystem.expected_version:
            env_result = await Get(
                ProcessResult,
                Process(
                    (binary_path.path, "env", "GOROOT"),
                    description=
                    f"Determine Go version and GOROOT for {binary_path.path}",
                    level=LogLevel.DEBUG,
                    cache_scope=ProcessCacheScope.PER_RESTART_SUCCESSFUL,
                    env={"GOPATH": "/does/not/matter"},
                ),
            )
            goroot = env_result.stdout.decode("utf-8").strip()
            return GoRoot(goroot)

        logger.debug(
            f"Go binary at {binary_path.path} has version {version}, but this "
            f"project is using {golang_subsystem.expected_version} "
            "(set by `[golang].expected_version`). Ignoring.")
        invalid_versions.append((binary_path.path, version))

    invalid_versions_str = bullet_list(
        f"{path}: {version}" for path, version in sorted(invalid_versions))
    raise BinaryNotFoundError(
        "Cannot find a `go` binary with the expected version of "
        f"{golang_subsystem.expected_version} (set by `[golang].expected_version`).\n\n"
        f"Found these `go` binaries, but they had different versions:\n\n"
        f"{invalid_versions_str}\n\n"
        "To fix, please install the expected version (https://golang.org/doc/install) and ensure "
        "that it is discoverable via the option `[golang].go_search_paths`, or change "
        "`[golang].expected_version`.")