Beispiel #1
0
async def package_go_binary(field_set: GoBinaryFieldSet) -> BuiltPackage:
    main_pkg = await Get(GoBinaryMainPackage, GoBinaryMainPackageRequest(field_set.main))
    built_package = await Get(
        BuiltGoPackage, BuildGoPackageTargetRequest(main_pkg.address, is_main=True)
    )
    main_pkg_a_file_path = built_package.import_paths_to_pkg_a_files["main"]
    import_config = await Get(
        ImportConfig, ImportConfigRequest(built_package.import_paths_to_pkg_a_files)
    )
    input_digest = await Get(Digest, MergeDigests([built_package.digest, import_config.digest]))

    output_filename = PurePath(field_set.output_path.value_or_default(file_ending=None))
    binary = await Get(
        LinkedGoBinary,
        LinkGoBinaryRequest(
            input_digest=input_digest,
            archives=(main_pkg_a_file_path,),
            import_config_path=import_config.CONFIG_PATH,
            output_filename=f"./{output_filename.name}",
            description=f"Link Go binary for {field_set.address}",
        ),
    )

    renamed_output_digest = await Get(Digest, AddPrefix(binary.digest, str(output_filename.parent)))

    artifact = BuiltPackageArtifact(relpath=str(output_filename))
    return BuiltPackage(renamed_output_digest, (artifact,))
Beispiel #2
0
 def create_config(stdlib: bool) -> str:
     config = rule_runner.request(
         ImportConfig, [ImportConfigRequest(mapping, include_stdlib=stdlib)]
     )
     digest_contents = rule_runner.request(DigestContents, [config.digest])
     assert len(digest_contents) == 1
     file_content = digest_contents[0]
     assert file_content.path == os.path.normpath(ImportConfig.CONFIG_PATH)
     return file_content.content.decode()
Beispiel #3
0
async def setup_analyzer() -> PackageAnalyzerSetup:
    def get_file(filename: str) -> bytes:
        content = pkgutil.get_data("pants.backend.go.util_rules", filename)
        if not content:
            raise AssertionError(f"Unable to find resource for `{filename}`.")
        return content

    analyer_sources_content = [
        FileContent(filename, get_file(filename))
        for filename in ("analyze_package.go", "read.go")
    ]

    source_digest, import_config = await MultiGet(
        Get(Digest, CreateDigest(analyer_sources_content)),
        Get(ImportConfig, ImportConfigRequest,
            ImportConfigRequest.stdlib_only()),
    )

    built_analyzer_pkg = await Get(
        BuiltGoPackage,
        BuildGoPackageRequest(
            import_path="main",
            subpath="",
            digest=source_digest,
            go_file_names=tuple(fc.path for fc in analyer_sources_content),
            s_file_names=(),
            direct_dependencies=(),
            minimum_go_version=None,
        ),
    )
    main_pkg_a_file_path = built_analyzer_pkg.import_paths_to_pkg_a_files[
        "main"]
    input_digest = await Get(
        Digest, MergeDigests([built_analyzer_pkg.digest,
                              import_config.digest]))

    analyzer = await Get(
        LinkedGoBinary,
        LinkGoBinaryRequest(
            input_digest=input_digest,
            archives=(main_pkg_a_file_path, ),
            import_config_path=import_config.CONFIG_PATH,
            output_filename=PackageAnalyzerSetup.PATH,
            description="Link Go package analyzer",
        ),
    )

    return PackageAnalyzerSetup(analyzer.digest)
Beispiel #4
0
async def setup_analyzer() -> AnalyzerSetup:
    source_entry_content = pkgutil.get_data("pants.backend.go.util_rules", "generate_testmain.go")
    if not source_entry_content:
        raise AssertionError("Unable to find resource for `generate_testmain.go`.")

    source_entry = FileContent("generate_testmain.go", source_entry_content)

    source_digest, import_config = await MultiGet(
        Get(Digest, CreateDigest([source_entry])),
        Get(ImportConfig, ImportConfigRequest, ImportConfigRequest.stdlib_only()),
    )

    built_analyzer_pkg = await Get(
        BuiltGoPackage,
        BuildGoPackageRequest(
            import_path="main",
            subpath="",
            digest=source_digest,
            go_file_names=(source_entry.path,),
            s_file_names=(),
            direct_dependencies=(),
            minimum_go_version=None,
        ),
    )
    main_pkg_a_file_path = built_analyzer_pkg.import_paths_to_pkg_a_files["main"]
    input_digest = await Get(
        Digest, MergeDigests([built_analyzer_pkg.digest, import_config.digest])
    )

    analyzer = await Get(
        LinkedGoBinary,
        LinkGoBinaryRequest(
            input_digest=input_digest,
            archives=(main_pkg_a_file_path,),
            import_config_path=import_config.CONFIG_PATH,
            output_filename=AnalyzerSetup.PATH,
            description="Link Go test sources analyzer",
        ),
    )

    return AnalyzerSetup(analyzer.digest)
Beispiel #5
0
async def setup_go_binary(request: LoadedGoBinaryRequest) -> LoadedGoBinary:
    file_contents = setup_files(request.dir_name, request.file_names)

    source_digest, import_config = await MultiGet(
        Get(Digest, CreateDigest(file_contents)),
        Get(ImportConfig, ImportConfigRequest,
            ImportConfigRequest.stdlib_only()),
    )

    built_pkg = await Get(
        BuiltGoPackage,
        BuildGoPackageRequest(
            import_path="main",
            dir_path="",
            digest=source_digest,
            go_file_names=tuple(fc.path for fc in file_contents),
            s_file_names=(),
            direct_dependencies=(),
            minimum_go_version=None,
        ),
    )
    main_pkg_a_file_path = built_pkg.import_paths_to_pkg_a_files["main"]
    input_digest = await Get(
        Digest, MergeDigests([built_pkg.digest, import_config.digest]))

    binary = await Get(
        LinkedGoBinary,
        LinkGoBinaryRequest(
            input_digest=input_digest,
            archives=(main_pkg_a_file_path, ),
            import_config_path=import_config.CONFIG_PATH,
            output_filename=request.output_name,
            description="Link Go package analyzer",
        ),
    )
    return LoadedGoBinary(binary.digest)
Beispiel #6
0
async def run_go_tests(
    field_set: GoTestFieldSet, test_subsystem: TestSubsystem, go_test_subsystem: GoTestSubsystem
) -> TestResult:
    maybe_pkg_info, wrapped_target = await MultiGet(
        Get(FallibleFirstPartyPkgInfo, FirstPartyPkgInfoRequest(field_set.address)),
        Get(WrappedTarget, Address, field_set.address),
    )

    if maybe_pkg_info.info is None:
        assert maybe_pkg_info.stderr is not None
        return TestResult(
            exit_code=maybe_pkg_info.exit_code,
            stdout="",
            stderr=maybe_pkg_info.stderr,
            stdout_digest=EMPTY_FILE_DIGEST,
            stderr_digest=EMPTY_FILE_DIGEST,
            address=field_set.address,
            output_setting=test_subsystem.output,
        )
    pkg_info = maybe_pkg_info.info

    target = wrapped_target.target
    import_path = target[GoImportPathField].value

    testmain = await Get(
        GeneratedTestMain,
        GenerateTestMainRequest(
            pkg_info.digest,
            FrozenOrderedSet(
                os.path.join(".", pkg_info.subpath, name) for name in pkg_info.test_files
            ),
            FrozenOrderedSet(
                os.path.join(".", pkg_info.subpath, name) for name in pkg_info.xtest_files
            ),
            import_path=import_path,
        ),
    )

    if not testmain.has_tests and not testmain.has_xtests:
        # Nothing to do so return an empty result.
        # TODO: There should really be a "skipped entirely" mechanism for `TestResult`.
        return TestResult(
            exit_code=0,
            stdout="",
            stderr="",
            stdout_digest=EMPTY_FILE_DIGEST,
            stderr_digest=EMPTY_FILE_DIGEST,
            address=field_set.address,
            output_setting=test_subsystem.output,
        )

    # Construct the build request for the package under test.
    maybe_test_pkg_build_request = await Get(
        FallibleBuildGoPackageRequest,
        BuildGoPackageTargetRequest(field_set.address, for_tests=True),
    )
    if maybe_test_pkg_build_request.request is None:
        assert maybe_test_pkg_build_request.stderr is not None
        return TestResult(
            exit_code=maybe_test_pkg_build_request.exit_code,
            stdout="",
            stderr=maybe_test_pkg_build_request.stderr,
            stdout_digest=EMPTY_FILE_DIGEST,
            stderr_digest=EMPTY_FILE_DIGEST,
            address=field_set.address,
            output_setting=test_subsystem.output,
        )
    test_pkg_build_request = maybe_test_pkg_build_request.request
    main_direct_deps = [test_pkg_build_request]

    if testmain.has_xtests:
        # Build a synthetic package for xtests where the import path is the same as the package under test
        # but with "_test" appended.
        #
        # Subset the direct dependencies to only the dependencies used by the xtest code. (Dependency
        # inference will have included all of the regular, test, and xtest dependencies of the package in
        # the build graph.) Moreover, ensure that any import of the package under test is on the _test_
        # version of the package that was just built.
        dep_by_import_path = {
            dep.import_path: dep for dep in test_pkg_build_request.direct_dependencies
        }
        direct_dependencies: OrderedSet[BuildGoPackageRequest] = OrderedSet()
        for xtest_import in pkg_info.xtest_imports:
            if xtest_import == pkg_info.import_path:
                direct_dependencies.add(test_pkg_build_request)
            elif xtest_import in dep_by_import_path:
                direct_dependencies.add(dep_by_import_path[xtest_import])

        xtest_pkg_build_request = BuildGoPackageRequest(
            import_path=f"{import_path}_test",
            digest=pkg_info.digest,
            subpath=pkg_info.subpath,
            go_file_names=pkg_info.xtest_files,
            s_file_names=(),  # TODO: Are there .s files for xtest?
            direct_dependencies=tuple(direct_dependencies),
            minimum_go_version=pkg_info.minimum_go_version,
        )
        main_direct_deps.append(xtest_pkg_build_request)

    # Generate the synthetic main package which imports the test and/or xtest packages.
    maybe_built_main_pkg = await Get(
        FallibleBuiltGoPackage,
        BuildGoPackageRequest(
            import_path="main",
            digest=testmain.digest,
            subpath="",
            go_file_names=(GeneratedTestMain.TEST_MAIN_FILE,),
            s_file_names=(),
            direct_dependencies=tuple(main_direct_deps),
            minimum_go_version=pkg_info.minimum_go_version,
        ),
    )
    if maybe_built_main_pkg.output is None:
        assert maybe_built_main_pkg.stderr is not None
        return TestResult(
            exit_code=maybe_built_main_pkg.exit_code,
            stdout="",
            stderr=maybe_built_main_pkg.stderr,
            stdout_digest=EMPTY_FILE_DIGEST,
            stderr_digest=EMPTY_FILE_DIGEST,
            address=field_set.address,
            output_setting=test_subsystem.output,
        )
    built_main_pkg = maybe_built_main_pkg.output

    main_pkg_a_file_path = built_main_pkg.import_paths_to_pkg_a_files["main"]
    import_config = await Get(
        ImportConfig, ImportConfigRequest(built_main_pkg.import_paths_to_pkg_a_files)
    )
    input_digest = await Get(Digest, MergeDigests([built_main_pkg.digest, import_config.digest]))

    binary = await Get(
        LinkedGoBinary,
        LinkGoBinaryRequest(
            input_digest=input_digest,
            archives=(main_pkg_a_file_path,),
            import_config_path=import_config.CONFIG_PATH,
            output_filename="./test_runner",  # TODO: Name test binary the way that `go` does?
            description=f"Link Go test binary for {field_set.address}",
        ),
    )

    cache_scope = (
        ProcessCacheScope.PER_SESSION if test_subsystem.force else ProcessCacheScope.SUCCESSFUL
    )

    result = await Get(
        FallibleProcessResult,
        Process(
            ["./test_runner", *transform_test_args(go_test_subsystem.args)],
            input_digest=binary.digest,
            description=f"Run Go tests: {field_set.address}",
            cache_scope=cache_scope,
            level=LogLevel.INFO,
        ),
    )
    return TestResult.from_fallible_process_result(result, field_set.address, test_subsystem.output)
Beispiel #7
0
async def run_go_tests(field_set: GoTestFieldSet,
                       test_subsystem: TestSubsystem,
                       go_test_subsystem: GoTestSubsystem) -> TestResult:
    maybe_pkg_analysis, maybe_pkg_digest, dependencies = await MultiGet(
        Get(FallibleFirstPartyPkgAnalysis,
            FirstPartyPkgAnalysisRequest(field_set.address)),
        Get(FallibleFirstPartyPkgDigest,
            FirstPartyPkgDigestRequest(field_set.address)),
        Get(Targets, DependenciesRequest(field_set.dependencies)),
    )

    def compilation_failure(exit_code: int, stdout: str | None,
                            stderr: str | None) -> TestResult:
        return TestResult(
            exit_code=exit_code,
            stdout=stdout or "",
            stderr=stderr or "",
            stdout_digest=EMPTY_FILE_DIGEST,
            stderr_digest=EMPTY_FILE_DIGEST,
            address=field_set.address,
            output_setting=test_subsystem.output,
            result_metadata=None,
        )

    if maybe_pkg_analysis.analysis is None:
        assert maybe_pkg_analysis.stderr is not None
        return compilation_failure(maybe_pkg_analysis.exit_code, None,
                                   maybe_pkg_analysis.stderr)
    if maybe_pkg_digest.pkg_digest is None:
        assert maybe_pkg_digest.stderr is not None
        return compilation_failure(maybe_pkg_digest.exit_code, None,
                                   maybe_pkg_digest.stderr)

    pkg_analysis = maybe_pkg_analysis.analysis
    pkg_digest = maybe_pkg_digest.pkg_digest
    import_path = pkg_analysis.import_path

    testmain = await Get(
        GeneratedTestMain,
        GenerateTestMainRequest(
            pkg_digest.digest,
            FrozenOrderedSet(
                os.path.join(".", pkg_analysis.dir_path, name)
                for name in pkg_analysis.test_go_files),
            FrozenOrderedSet(
                os.path.join(".", pkg_analysis.dir_path, name)
                for name in pkg_analysis.xtest_go_files),
            import_path,
            field_set.address,
        ),
    )

    if testmain.failed_exit_code_and_stderr is not None:
        _exit_code, _stderr = testmain.failed_exit_code_and_stderr
        return compilation_failure(_exit_code, None, _stderr)

    if not testmain.has_tests and not testmain.has_xtests:
        return TestResult.skip(field_set.address,
                               output_setting=test_subsystem.output)

    # Construct the build request for the package under test.
    maybe_test_pkg_build_request = await Get(
        FallibleBuildGoPackageRequest,
        BuildGoPackageTargetRequest(field_set.address, for_tests=True),
    )
    if maybe_test_pkg_build_request.request is None:
        assert maybe_test_pkg_build_request.stderr is not None
        return compilation_failure(maybe_test_pkg_build_request.exit_code,
                                   None, maybe_test_pkg_build_request.stderr)
    test_pkg_build_request = maybe_test_pkg_build_request.request

    main_direct_deps = [test_pkg_build_request]

    if testmain.has_xtests:
        # Build a synthetic package for xtests where the import path is the same as the package under test
        # but with "_test" appended.
        #
        # Subset the direct dependencies to only the dependencies used by the xtest code. (Dependency
        # inference will have included all of the regular, test, and xtest dependencies of the package in
        # the build graph.) Moreover, ensure that any import of the package under test is on the _test_
        # version of the package that was just built.
        dep_by_import_path = {
            dep.import_path: dep
            for dep in test_pkg_build_request.direct_dependencies
        }
        direct_dependencies: OrderedSet[BuildGoPackageRequest] = OrderedSet()
        for xtest_import in pkg_analysis.xtest_imports:
            if xtest_import == pkg_analysis.import_path:
                direct_dependencies.add(test_pkg_build_request)
            elif xtest_import in dep_by_import_path:
                direct_dependencies.add(dep_by_import_path[xtest_import])

        xtest_pkg_build_request = BuildGoPackageRequest(
            import_path=f"{import_path}_test",
            digest=pkg_digest.digest,
            dir_path=pkg_analysis.dir_path,
            go_file_names=pkg_analysis.xtest_go_files,
            s_file_names=(),  # TODO: Are there .s files for xtest?
            direct_dependencies=tuple(direct_dependencies),
            minimum_go_version=pkg_analysis.minimum_go_version,
            embed_config=pkg_digest.xtest_embed_config,
        )
        main_direct_deps.append(xtest_pkg_build_request)

    # Generate the synthetic main package which imports the test and/or xtest packages.
    maybe_built_main_pkg = await Get(
        FallibleBuiltGoPackage,
        BuildGoPackageRequest(
            import_path="main",
            digest=testmain.digest,
            dir_path="",
            go_file_names=(GeneratedTestMain.TEST_MAIN_FILE, ),
            s_file_names=(),
            direct_dependencies=tuple(main_direct_deps),
            minimum_go_version=pkg_analysis.minimum_go_version,
        ),
    )
    if maybe_built_main_pkg.output is None:
        assert maybe_built_main_pkg.stderr is not None
        return compilation_failure(maybe_built_main_pkg.exit_code,
                                   maybe_built_main_pkg.stdout,
                                   maybe_built_main_pkg.stderr)
    built_main_pkg = maybe_built_main_pkg.output

    main_pkg_a_file_path = built_main_pkg.import_paths_to_pkg_a_files["main"]
    import_config = await Get(
        ImportConfig,
        ImportConfigRequest(built_main_pkg.import_paths_to_pkg_a_files))
    linker_input_digest = await Get(
        Digest, MergeDigests([built_main_pkg.digest, import_config.digest]))
    binary = await Get(
        LinkedGoBinary,
        LinkGoBinaryRequest(
            input_digest=linker_input_digest,
            archives=(main_pkg_a_file_path, ),
            import_config_path=import_config.CONFIG_PATH,
            output_filename=
            "./test_runner",  # TODO: Name test binary the way that `go` does?
            description=f"Link Go test binary for {field_set.address}",
        ),
    )

    # To emulate Go's test runner, we set the working directory to the path of the `go_package`.
    # This allows tests to open dependencies on `file` targets regardless of where they are
    # located. See https://dave.cheney.net/2016/05/10/test-fixtures-in-go.
    working_dir = field_set.address.spec_path
    binary_with_prefix, files_sources = await MultiGet(
        Get(Digest, AddPrefix(binary.digest, working_dir)),
        Get(
            SourceFiles,
            SourceFilesRequest(
                (dep.get(SourcesField) for dep in dependencies),
                for_sources_types=(FileSourceField, ),
                enable_codegen=True,
            ),
        ),
    )
    test_input_digest = await Get(
        Digest,
        MergeDigests((binary_with_prefix, files_sources.snapshot.digest)))

    cache_scope = (ProcessCacheScope.PER_SESSION
                   if test_subsystem.force else ProcessCacheScope.SUCCESSFUL)

    result = await Get(
        FallibleProcessResult,
        Process(
            [
                "./test_runner",
                *transform_test_args(go_test_subsystem.args,
                                     field_set.timeout.value),
            ],
            input_digest=test_input_digest,
            description=f"Run Go tests: {field_set.address}",
            cache_scope=cache_scope,
            working_directory=working_dir,
            level=LogLevel.DEBUG,
        ),
    )
    return TestResult.from_fallible_process_result(result, field_set.address,
                                                   test_subsystem.output)
Beispiel #8
0
async def build_go_package(request: BuildGoPackageRequest) -> FallibleBuiltGoPackage:
    maybe_built_deps = await MultiGet(
        Get(FallibleBuiltGoPackage, BuildGoPackageRequest, build_request)
        for build_request in request.direct_dependencies
    )

    import_paths_to_pkg_a_files: dict[str, str] = {}
    dep_digests = []
    for maybe_dep in maybe_built_deps:
        if maybe_dep.output is None:
            return dataclasses.replace(
                maybe_dep, import_path=request.import_path, dependency_failed=True
            )
        dep = maybe_dep.output
        import_paths_to_pkg_a_files.update(dep.import_paths_to_pkg_a_files)
        dep_digests.append(dep.digest)

    merged_deps_digest, import_config, embedcfg = await MultiGet(
        Get(Digest, MergeDigests(dep_digests)),
        Get(ImportConfig, ImportConfigRequest(FrozenDict(import_paths_to_pkg_a_files))),
        Get(RenderedEmbedConfig, RenderEmbedConfigRequest(request.embed_config)),
    )

    input_digest = await Get(
        Digest,
        MergeDigests([merged_deps_digest, import_config.digest, embedcfg.digest, request.digest]),
    )

    assembly_digests = None
    symabis_path = None
    if request.s_file_names:
        assembly_setup = await Get(
            FallibleAssemblyPreCompilation,
            AssemblyPreCompilationRequest(input_digest, request.s_file_names, request.subpath),
        )
        if assembly_setup.result is None:
            return FallibleBuiltGoPackage(
                None,
                request.import_path,
                assembly_setup.exit_code,
                stdout=assembly_setup.stdout,
                stderr=assembly_setup.stderr,
            )
        input_digest = assembly_setup.result.merged_compilation_input_digest
        assembly_digests = assembly_setup.result.assembly_digests
        symabis_path = "./symabis"

    compile_args = [
        "tool",
        "compile",
        "-o",
        "__pkg__.a",
        "-pack",
        "-p",
        request.import_path,
        "-importcfg",
        import_config.CONFIG_PATH,
        # See https://github.com/golang/go/blob/f229e7031a6efb2f23241b5da000c3b3203081d6/src/cmd/go/internal/work/gc.go#L79-L100
        # for why Go sets the default to 1.16.
        f"-lang=go{request.minimum_go_version or '1.16'}",
    ]

    if symabis_path:
        compile_args.extend(["-symabis", symabis_path])

    if embedcfg.digest != EMPTY_DIGEST:
        compile_args.extend(["-embedcfg", RenderedEmbedConfig.PATH])

    if not request.s_file_names:
        # If there are no non-Go sources, then pass -complete flag which tells the compiler that the provided
        # Go files are the entire package.
        compile_args.append("-complete")

    relativized_sources = (
        f"./{request.subpath}/{name}" if request.subpath else f"./{name}"
        for name in request.go_file_names
    )
    compile_args.extend(["--", *relativized_sources])
    compile_result = await Get(
        FallibleProcessResult,
        GoSdkProcess(
            input_digest=input_digest,
            command=tuple(compile_args),
            description=f"Compile Go package: {request.import_path}",
            output_files=("__pkg__.a",),
        ),
    )
    if compile_result.exit_code != 0:
        return FallibleBuiltGoPackage(
            None,
            request.import_path,
            compile_result.exit_code,
            stdout=compile_result.stdout.decode("utf-8"),
            stderr=compile_result.stderr.decode("utf-8"),
        )

    compilation_digest = compile_result.output_digest
    if assembly_digests:
        assembly_result = await Get(
            AssemblyPostCompilation,
            AssemblyPostCompilationRequest(
                compilation_digest,
                assembly_digests,
                request.s_file_names,
                request.subpath,
            ),
        )
        if assembly_result.result.exit_code != 0:
            return FallibleBuiltGoPackage(
                None,
                request.import_path,
                assembly_result.result.exit_code,
                stdout=assembly_result.result.stdout.decode("utf-8"),
                stderr=assembly_result.result.stderr.decode("utf-8"),
            )
        assert assembly_result.merged_output_digest
        compilation_digest = assembly_result.merged_output_digest

    path_prefix = os.path.join("__pkgs__", path_safe(request.import_path))
    import_paths_to_pkg_a_files[request.import_path] = os.path.join(path_prefix, "__pkg__.a")
    output_digest = await Get(Digest, AddPrefix(compilation_digest, path_prefix))
    merged_result_digest = await Get(Digest, MergeDigests([*dep_digests, output_digest]))

    output = BuiltGoPackage(merged_result_digest, FrozenDict(import_paths_to_pkg_a_files))
    return FallibleBuiltGoPackage(output, request.import_path)