Exemplo n.º 1
0
    def test_pex_execution(self) -> None:
        sources_content = InputFilesContent((
            FileContent(path="main.py", content=b'print("from main")'),
            FileContent(path="subdir/sub.py", content=b'print("from sub")'),
        ))

        sources = self.request_single_product(Digest, sources_content)
        pex_output = self.create_pex_and_get_all_data(entry_point="main",
                                                      sources=sources)

        pex_files = pex_output["files"]
        assert "pex" not in pex_files
        assert "main.py" in pex_files
        assert "subdir/sub.py" in pex_files

        init_subsystem(PythonSetup)
        python_setup = PythonSetup.global_instance()
        env = {
            "PATH": create_path_env_var(python_setup.interpreter_search_paths)
        }

        process = Process(
            argv=("python", "test.pex"),
            env=env,
            input_files=pex_output["pex"].directory_digest,
            description="Run the pex and make sure it works",
        )
        result = self.request_single_product(ProcessResult, process)
        assert result.stdout == b"from main\n"
Exemplo n.º 2
0
    def test_write_file(self):
        request = Process(
            argv=("/bin/bash", "-c", "echo -n 'European Burmese' > roland"),
            description="echo roland",
            output_files=("roland", ),
            input_files=EMPTY_DIRECTORY_DIGEST,
        )

        process_result = self.request_single_product(ProcessResult, request)

        self.assertEqual(
            process_result.output_directory_digest,
            Digest(
                fingerprint=
                "63949aa823baf765eff07b946050d76ec0033144c785a94d3ebd82baa931cd16",
                serialized_bytes_length=80,
            ),
        )

        files_content_result = self.request_single_product(
            FilesContent,
            process_result.output_directory_digest,
        )

        self.assertEqual(files_content_result.dependencies,
                         (FileContent("roland", b"European Burmese", False), ))
Exemplo n.º 3
0
 def test_create_from_snapshot_with_env(self):
     req = Process(
         argv=("foo", ),
         description="Some process",
         env={"VAR": "VAL"},
         input_files=EMPTY_DIRECTORY_DIGEST,
     )
     self.assertEqual(req.env, ("VAR", "VAL"))
Exemplo n.º 4
0
    def test_fallible_failing_command_returns_exited_result(self):
        request = Process(
            argv=("/bin/bash", "-c", "exit 1"),
            description="one-cat",
            input_files=EMPTY_DIRECTORY_DIGEST,
        )

        result = self.request_single_product(FallibleProcessResult, request)

        self.assertEqual(result.exit_code, 1)
Exemplo n.º 5
0
    def test_non_fallible_failing_command_raises(self):
        request = Process(
            argv=("/bin/bash", "-c", "exit 1"),
            description="one-cat",
            input_files=EMPTY_DIRECTORY_DIGEST,
        )

        with self.assertRaises(ExecutionError) as cm:
            self.request_single_product(ProcessResult, request)
        self.assertIn("process 'one-cat' failed with exit code 1.",
                      str(cm.exception))
Exemplo n.º 6
0
async def cat_files_process_result_concatted(
        cat_exe_req: CatExecutionRequest) -> Concatted:
    cat_bin = cat_exe_req.shell_cat
    cat_files_snapshot = await Get[Snapshot](PathGlobs, cat_exe_req.path_globs)
    process = Process(
        argv=cat_bin.argv_from_snapshot(cat_files_snapshot),
        input_files=cat_files_snapshot.directory_digest,
        description="cat some files",
    )
    cat_process_result = await Get[ProcessResult](Process, process)
    return Concatted(cat_process_result.stdout.decode())
Exemplo n.º 7
0
 def test_jdk(self):
     with temporary_dir() as temp_dir:
         with open(os.path.join(temp_dir, "roland"), "w") as f:
             f.write("European Burmese")
         request = Process(
             argv=("/bin/cat", ".jdk/roland"),
             input_files=EMPTY_DIRECTORY_DIGEST,
             description="cat JDK roland",
             jdk_home=temp_dir,
         )
         result = self.request_single_product(ProcessResult, request)
         self.assertEqual(result.stdout, b"European Burmese")
Exemplo n.º 8
0
 def test_timeout(self):
     request = Process(
         argv=("/bin/bash", "-c",
               "/bin/sleep 0.2; /bin/echo -n 'European Burmese'"),
         timeout_seconds=0.1,
         description="sleepy-cat",
         input_files=EMPTY_DIRECTORY_DIGEST,
     )
     result = self.request_single_product(FallibleProcessResult, request)
     self.assertNotEqual(result.exit_code, 0)
     self.assertIn(b"Exceeded timeout", result.stdout)
     self.assertIn(b"sleepy-cat", result.stdout)
Exemplo n.º 9
0
    def test_platform_on_local_epr_result(self) -> None:

        this_platform = Platform.current

        req = Process(
            argv=("/bin/echo", "test"),
            input_files=EMPTY_DIRECTORY_DIGEST,
            description="Run some program that will exit cleanly.",
        )
        result = self.request_single_product(FallibleProcessResultWithPlatform, req)
        assert result.exit_code == 0
        assert result.platform == this_platform
Exemplo n.º 10
0
async def get_javac_version_output(
    javac_version_command: JavacVersionExecutionRequest,
) -> JavacVersionOutput:
    javac_version_proc_req = Process(
        argv=javac_version_command.gen_argv(),
        description=javac_version_command.description,
        input_files=EMPTY_DIRECTORY_DIGEST,
    )
    javac_version_proc_result = await Get[ProcessResult](
        Process,
        javac_version_proc_req,
    )

    return JavacVersionOutput(javac_version_proc_result.stderr.decode())
Exemplo n.º 11
0
    def create_execute_request(
            self,
            python_setup: PythonSetup,
            subprocess_encoding_environment: SubprocessEncodingEnvironment,
            *,
            pex_path: str,
            pex_args: Iterable[str],
            description: str,
            input_files: Digest,
            env: Optional[Mapping[str, str]] = None,
            **kwargs: Any) -> Process:
        """Creates an Process that will run a PEX hermetically.

        :param python_setup: The parameters for selecting python interpreters to use when invoking
                             the PEX.
        :param subprocess_encoding_environment: The locale settings to use for the PEX invocation.
        :param pex_path: The path within `input_files` of the PEX file (or directory if a loose
                         pex).
        :param pex_args: The arguments to pass to the PEX executable.
        :param description: A description of the process execution to be performed.
        :param input_files: The files that contain the pex itself and any input files it needs to
                            run against.
        :param env: The environment to run the PEX in.
        :param **kwargs: Any additional :class:`Process` kwargs to pass through.
        """

        # NB: we use the hardcoded and generic bin name `python`, rather than something dynamic like
        # `sys.executable`, to ensure that the interpreter may be discovered both locally and in remote
        # execution (so long as `env` is populated with a `PATH` env var and `python` is discoverable
        # somewhere on that PATH). This is only used to run the downloaded PEX tool; it is not
        # necessarily the interpreter that PEX will use to execute the generated .pex file.
        # TODO(#7735): Set --python-setup-interpreter-search-paths differently for the host and target
        # platforms, when we introduce platforms in https://github.com/pantsbuild/pants/issues/7735.
        argv = ("python", pex_path, *pex_args)

        hermetic_env = dict(
            PATH=create_path_env_var(python_setup.interpreter_search_paths),
            PEX_ROOT="./pex_root",
            PEX_INHERIT_PATH="false",
            PEX_IGNORE_RCFILES="true",
            **subprocess_encoding_environment.invocation_environment_dict)
        if env:
            hermetic_env.update(env)

        return Process(argv=argv,
                       input_files=input_files,
                       description=description,
                       env=hermetic_env,
                       **kwargs)
Exemplo n.º 12
0
    def test_multiple_file_creation(self):
        input_files_content = InputFilesContent((
            FileContent(path="a.txt", content=b"hello"),
            FileContent(path="b.txt", content=b"goodbye"),
        ))

        digest = self.request_single_product(Digest, input_files_content)

        req = Process(
            argv=("/bin/cat", "a.txt", "b.txt"),
            input_files=digest,
            description="cat the contents of this file",
        )

        result = self.request_single_product(ProcessResult, req)
        self.assertEqual(result.stdout, b"hellogoodbye")
Exemplo n.º 13
0
    def test_input_file_creation(self):
        file_name = "some.filename"
        file_contents = b"some file contents"

        input_file = InputFilesContent((FileContent(path=file_name,
                                                    content=file_contents), ))
        digest = self.request_single_product(Digest, input_file)

        req = Process(
            argv=("/bin/cat", file_name),
            input_files=digest,
            description="cat the contents of this file",
        )

        result = self.request_single_product(ProcessResult, req)
        self.assertEqual(result.stdout, file_contents)
Exemplo n.º 14
0
    def test_not_executable(self):
        file_name = "echo.sh"
        file_contents = b'#!/bin/bash -eu\necho "Hello"\n'

        input_file = InputFilesContent((FileContent(path=file_name,
                                                    content=file_contents), ))
        digest = self.request_single_product(Digest, input_file)

        req = Process(
            argv=("./echo.sh", ),
            input_files=digest,
            description="cat the contents of this file",
        )

        with self.assertRaisesWithMessageContaining(ExecutionError,
                                                    "Permission"):
            self.request_single_product(ProcessResult, req)
Exemplo n.º 15
0
    def test_file_in_directory_creation(self):
        path = "somedir/filename"
        content = b"file contents"

        input_file = InputFilesContent((FileContent(path=path,
                                                    content=content), ))
        digest = self.request_single_product(Digest, input_file)

        req = Process(
            argv=("/bin/cat", "somedir/filename"),
            input_files=digest,
            description=
            "Cat a file in a directory to make sure that doesn't break",
        )

        result = self.request_single_product(ProcessResult, req)
        self.assertEqual(result.stdout, content)
Exemplo n.º 16
0
    def test_executable(self):
        file_name = "echo.sh"
        file_contents = b'#!/bin/bash -eu\necho "Hello"\n'

        input_file = InputFilesContent((FileContent(path=file_name,
                                                    content=file_contents,
                                                    is_executable=True), ))
        digest = self.request_single_product(Digest, input_file)

        req = Process(
            argv=("./echo.sh", ),
            input_files=digest,
            description="cat the contents of this file",
        )

        result = self.request_single_product(ProcessResult, req)
        self.assertEqual(result.stdout, b"Hello\n")
Exemplo n.º 17
0
    def _run_bootstrapper(self, bridge_jar, context):
        bootstrapper_entry = self._zinc_factory._compiler_bootstrapper(self._products)
        bootstrapper = self._relative_to_buildroot(bootstrapper_entry.path)

        # CLI args and their associated ClasspathEntry objects.
        bootstrap_cp_entries = (
            ("--compiler-interface", self._compiler_interface),
            ("--compiler-bridge-src", self._compiler_bridge),
            ("--scala-compiler", self.scala_compiler),
            ("--scala-library", self.scala_library),
            ("--scala-reflect", self.scala_reflect),
        )

        bootstrapper_args = [
            "--out",
            self._relative_to_buildroot(bridge_jar),
        ]
        for arg, cp_entry in bootstrap_cp_entries:
            bootstrapper_args.append(arg)
            bootstrapper_args.append(self._relative_to_buildroot(cp_entry.path))

        inputs_digest = context._scheduler.merge_directories(
            [bootstrapper_entry.directory_digest]
            + [entry.directory_digest for _, entry in bootstrap_cp_entries]
        )
        argv = tuple(
            [".jdk/bin/java"]
            + ["-cp", bootstrapper, Zinc.ZINC_BOOTSTRAPER_MAIN]
            + bootstrapper_args
        )
        req = Process(
            argv=argv,
            input_files=inputs_digest,
            output_files=(self._relative_to_buildroot(bridge_jar),),
            description="bootstrap compiler bridge.",
            # Since this is always hermetic, we need to use `underlying_dist`
            jdk_home=self.underlying_dist.home,
        )
        return context.execute_process_synchronously_or_raise(
            req, "zinc-subsystem", [WorkUnitLabel.COMPILER]
        )
Exemplo n.º 18
0
    def _execute_hermetic_compile(self, cmd, ctx):
        # For now, executing a compile remotely only works for targets that
        # do not have any dependencies or inner classes

        input_snapshot = ctx.target.sources_snapshot(
            scheduler=self.context._scheduler)
        output_files = tuple(
            # Assume no extra .class files to grab. We'll fix up that case soon.
            # Drop the source_root from the file path.
            # Assumes `-d .` has been put in the command.
            os.path.relpath(f.replace(".java", ".class"),
                            ctx.target.target_base)
            for f in input_snapshot.files if f.endswith(".java"))

        process = Process(
            argv=tuple(cmd),
            input_files=input_snapshot.directory_digest,
            output_files=output_files,
            description=f"Compiling {ctx.target.address.spec} with javac",
        )
        exec_result = self.context.execute_process_synchronously_without_raising(
            process,
            "javac",
            (WorkUnitLabel.TASK, WorkUnitLabel.JVM),
        )

        # Dump the output to the .pants.d directory where it's expected by downstream tasks.
        merged_directories = self.context._scheduler.merge_directories([
            exec_result.output_directory_digest,
            self.post_compile_extra_resources_digest(
                ctx, prepend_post_merge_relative_path=False),
        ])
        classes_directory = Path(ctx.classes_dir.path).relative_to(
            get_buildroot())
        self.context._scheduler.materialize_directory(
            DirectoryToMaterialize(merged_directories,
                                   path_prefix=str(classes_directory)), )
Exemplo n.º 19
0
async def javac_compile_process_result(
    javac_compile_req: JavacCompileRequest, ) -> JavacCompileResult:
    java_files = javac_compile_req.javac_sources.java_files
    for java_file in java_files:
        if not java_file.endswith(".java"):
            raise ValueError(
                f"Can only compile .java files but got {java_file}")
    sources_snapshot = await Get[Snapshot](PathGlobs, PathGlobs(java_files))
    output_dirs = tuple(
        {os.path.dirname(java_file)
         for java_file in java_files})
    process = Process(
        argv=javac_compile_req.argv_from_source_snapshot(sources_snapshot),
        input_files=sources_snapshot.directory_digest,
        output_directories=output_dirs,
        description="javac compilation",
    )
    javac_proc_result = await Get[ProcessResult](Process, process)

    return JavacCompileResult(
        javac_proc_result.stdout.decode(),
        javac_proc_result.stderr.decode(),
        javac_proc_result.output_directory_digest,
    )
Exemplo n.º 20
0
    def _compile_hermetic(
        self,
        jvm_options,
        ctx,
        classes_dir,
        jar_file,
        compiler_bridge_classpath_entry,
        dependency_classpath,
        scalac_classpath_entries,
    ):
        zinc_relpath = fast_relpath(self._zinc.zinc.path, get_buildroot())

        snapshots = [
            ctx.target.sources_snapshot(self.context._scheduler),
        ]

        # scala_library() targets with java_sources have circular dependencies on those java source
        # files, and we provide them to the same zinc command line that compiles the scala, so we need
        # to make sure those source files are available in the hermetic execution sandbox.
        java_sources_targets = getattr(ctx.target, "java_sources", [])
        java_sources_snapshots = [
            tgt.sources_snapshot(self.context._scheduler)
            for tgt in java_sources_targets
        ]
        snapshots.extend(java_sources_snapshots)

        # Ensure the dependencies and compiler bridge jars are available in the execution sandbox.
        relevant_classpath_entries = dependency_classpath + [
            compiler_bridge_classpath_entry,
            self._nailgun_server_classpath_entry(
            ),  # We include nailgun-server, to use it to start servers when needed from the hermetic execution case.
        ]
        directory_digests = [
            entry.directory_digest for entry in relevant_classpath_entries
            if entry.directory_digest
        ]
        if len(directory_digests) != len(relevant_classpath_entries):
            for dep in relevant_classpath_entries:
                if not dep.directory_digest:
                    raise AssertionError(
                        "ClasspathEntry {} didn't have a Digest, so won't be present for hermetic "
                        "execution of zinc".format(dep))
        directory_digests.extend(
            classpath_entry.directory_digest
            for classpath_entry in scalac_classpath_entries)

        if self._zinc.use_native_image:
            if jvm_options:
                raise ValueError(
                    "`{}` got non-empty jvm_options when running with a graal native-image, but this is "
                    "unsupported. jvm_options received: {}".format(
                        self.options_scope, safe_shlex_join(jvm_options)))
            native_image_path, native_image_snapshot = self._zinc.native_image(
                self.context)
            native_image_snapshots = [
                native_image_snapshot.directory_digest,
            ]
            scala_boot_classpath = [
                classpath_entry.path
                for classpath_entry in scalac_classpath_entries
            ] + [
                # We include rt.jar on the scala boot classpath because the compiler usually gets its
                # contents from the VM it is executing in, but not in the case of a native image. This
                # resolves a `object java.lang.Object in compiler mirror not found.` error.
                ".jdk/jre/lib/rt.jar",
                # The same goes for the jce.jar, which provides javax.crypto.
                ".jdk/jre/lib/jce.jar",
            ]
            image_specific_argv = [
                native_image_path,
                "-java-home",
                ".jdk",
                f"-Dscala.boot.class.path={os.pathsep.join(scala_boot_classpath)}",
                "-Dscala.usejavacp=true",
            ]
        else:
            native_image_snapshots = []
            # TODO: Lean on distribution for the bin/java appending here
            image_specific_argv = (
                [".jdk/bin/java"] + jvm_options +
                ["-cp", zinc_relpath, Zinc.ZINC_COMPILE_MAIN])

        (argfile_snapshot, ) = self.context._scheduler.capture_snapshots([
            PathGlobsAndRoot(
                PathGlobs([fast_relpath(ctx.args_file, get_buildroot())]),
                get_buildroot(),
            ),
        ])

        relpath_to_analysis = fast_relpath(ctx.analysis_file, get_buildroot())
        merged_local_only_scratch_inputs = self._compute_local_only_inputs(
            classes_dir, relpath_to_analysis, jar_file)

        # TODO: Extract something common from Executor._create_command to make the command line
        argv = image_specific_argv + [f"@{argfile_snapshot.files[0]}"]

        merged_input_digest = self.context._scheduler.merge_directories(
            [self._zinc.zinc.directory_digest] +
            [s.directory_digest for s in snapshots] + directory_digests +
            native_image_snapshots + [
                self.post_compile_extra_resources_digest(ctx),
                argfile_snapshot.directory_digest
            ])

        # NB: We always capture the output jar, but if classpath jars are not used, we additionally
        # capture loose classes from the workspace. This is because we need to both:
        #   1) allow loose classes as an input to dependent compiles
        #   2) allow jars to be materialized at the end of the run.
        output_directories = () if self.get_options().use_classpath_jars else (
            classes_dir, )

        req = Process(
            argv=tuple(argv),
            input_files=merged_input_digest,
            output_files=(jar_file, relpath_to_analysis),
            output_directories=output_directories,
            description=f"zinc compile for {ctx.target.address.spec}",
            unsafe_local_only_files_because_we_favor_speed_over_correctness_for_this_rule
            =merged_local_only_scratch_inputs,
            jdk_home=self._zinc.underlying_dist.home,
            is_nailgunnable=True,
        )
        res = self.context.execute_process_synchronously_or_raise(
            req, self.name(), [WorkUnitLabel.COMPILER])

        # TODO: Materialize as a batch in do_compile or somewhere
        self.context._scheduler.materialize_directory(
            DirectoryToMaterialize(res.output_directory_digest))

        # TODO: This should probably return a ClasspathEntry rather than a Digest
        return res.output_directory_digest
Exemplo n.º 21
0
    def console_output(self, targets):
        input_snapshots = tuple(
            target.sources_snapshot(scheduler=self.context._scheduler)
            for target in targets)
        input_files = {
            f
            for snapshot in input_snapshots for f in snapshot.files
        }

        # TODO: Work out a nice library-like utility for writing an argfile, as this will be common.
        with temporary_dir() as tmpdir:
            list_file = os.path.join(tmpdir, "input_files_list")
            with open(list_file, "w") as list_file_out:
                for input_file in sorted(input_files):
                    list_file_out.write(input_file)
                    list_file_out.write("\n")
            list_file_snapshot = self.context._scheduler.capture_snapshots(
                (PathGlobsAndRoot(
                    PathGlobs(("input_files_list", )),
                    tmpdir,
                ), ))[0]

        cloc_path, cloc_snapshot = ClocBinary.global_instance(
        ).hackily_snapshot(self.context)

        directory_digest = self.context._scheduler.merge_directories(
            tuple(s.directory_digest for s in input_snapshots + (
                cloc_snapshot,
                list_file_snapshot,
            )))

        cmd = (
            "/usr/bin/perl",
            cloc_path,
            "--skip-uniqueness",
            "--ignored=ignored",
            "--list-file=input_files_list",
            "--report-file=report",
        )

        # The cloc script reaches into $PATH to look up perl. Let's assume it's in /usr/bin.
        req = Process(
            argv=cmd,
            input_files=directory_digest,
            output_files=("ignored", "report"),
            description="cloc",
        )
        exec_result = self.context.execute_process_synchronously_or_raise(
            req, "cloc", (WorkUnitLabel.TOOL, ))

        files_content_tuple = self.context._scheduler.product_request(
            FilesContent,
            [exec_result.output_directory_digest])[0].dependencies

        files_content = {
            fc.path: fc.content.decode()
            for fc in files_content_tuple
        }
        for line in files_content["report"].split("\n"):
            yield line

        if self.get_options().ignored:
            yield "Ignored the following files:"
            for line in files_content["ignored"].split("\n"):
                yield line
Exemplo n.º 22
0
    def _runtool_hermetic(self, main, tool_name, distribution, input_digest,
                          ctx):
        use_youtline = tool_name == "scalac-outliner"

        tool_classpath_abs = self._scalac_classpath if use_youtline else self._rsc_classpath
        tool_classpath = fast_relpath_collection(tool_classpath_abs)

        rsc_jvm_options = Rsc.global_instance().get_options().jvm_options

        if not use_youtline and self._rsc.use_native_image:
            if rsc_jvm_options:
                raise ValueError(
                    "`{}` got non-empty jvm_options when running with a graal native-image, but this is "
                    "unsupported. jvm_options received: {}".format(
                        self.options_scope, safe_shlex_join(rsc_jvm_options)))
            native_image_path, native_image_snapshot = self._rsc.native_image(
                self.context)
            additional_snapshots = [native_image_snapshot]
            initial_args = [native_image_path]
        else:
            additional_snapshots = []
            initial_args = (
                [distribution.java] + rsc_jvm_options +
                ["-cp", os.pathsep.join(tool_classpath), main])

        (argfile_snapshot, ) = self.context._scheduler.capture_snapshots([
            PathGlobsAndRoot(
                PathGlobs([fast_relpath(ctx.args_file, get_buildroot())]),
                get_buildroot(),
            ),
        ])

        cmd = initial_args + [f"@{argfile_snapshot.files[0]}"]

        pathglobs = list(tool_classpath)

        if pathglobs:
            root = PathGlobsAndRoot(PathGlobs(tuple(pathglobs)),
                                    get_buildroot())
            # dont capture snapshot, if pathglobs is empty
            path_globs_input_digest = self.context._scheduler.capture_snapshots(
                (root, ))[0].directory_digest

        epr_input_files = self.context._scheduler.merge_directories(
            ((path_globs_input_digest, ) if path_globs_input_digest else ()) +
            ((input_digest, ) if input_digest else ()) +
            tuple(s.directory_digest for s in additional_snapshots) +
            (argfile_snapshot.directory_digest, ))

        epr = Process(
            argv=tuple(cmd),
            input_files=epr_input_files,
            output_files=(fast_relpath(ctx.rsc_jar_file.path,
                                       get_buildroot()), ),
            output_directories=tuple(),
            timeout_seconds=15 * 60,
            description=f"run {tool_name} for {ctx.target}",
            # TODO: These should always be unicodes
            # Since this is always hermetic, we need to use `underlying.home` because
            # Process requires an existing, local jdk location.
            jdk_home=distribution.underlying_home,
            is_nailgunnable=True,
        )
        res = self.context.execute_process_synchronously_without_raising(
            epr, self.name(), [WorkUnitLabel.COMPILER])

        if res.exit_code != 0:
            raise TaskError(res.stderr, exit_code=res.exit_code)

        # TODO: parse the output of -Xprint:timings for rsc and write it to self._record_target_stats()!

        res.output_directory_digest.dump(ctx.rsc_jar_file.path)
        self.context._scheduler.materialize_directory(
            DirectoryToMaterialize(res.output_directory_digest), )
        ctx.rsc_jar_file.hydrate_missing_directory_digest(
            res.output_directory_digest)

        return res