def test_cgo_not_supported(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "BUILD": "go_mod(name='mod')\ngo_package(name='pkg')",
            "go.mod": dedent(
                """\
                module go.example.com/foo
                go 1.17
                """
            ),
            "hello.go": dedent(
                """\
                package main

                // int fortytwo()
                // {
                //	    return 42;
                // }
                import "C"
                import "fmt"

                func main() {
                    f := C.intFunc(C.fortytwo)
                    fmt.Println(C.intFunc(C.fortytwo))
                }
                """
            ),
        }
    )
    with engine_error(NotImplementedError):
        rule_runner.request(
            FallibleFirstPartyPkgAnalysis,
            [FirstPartyPkgAnalysisRequest(Address("", target_name="pkg"))],
        )
Beispiel #2
0
async def infer_go_dependencies(
    request: InferGoProtobufDependenciesRequest,
    go_protobuf_mapping: GoProtobufImportPathMapping,
) -> InferredDependencies:
    address = request.sources_field.address
    maybe_pkg_analysis = await Get(FallibleFirstPartyPkgAnalysis,
                                   FirstPartyPkgAnalysisRequest(address))
    if maybe_pkg_analysis.analysis is None:
        _logger.error(
            softwrap(f"""
                Failed to analyze {maybe_pkg_analysis.import_path} for dependency inference:

                {maybe_pkg_analysis.stderr}
                """))
        return InferredDependencies([])
    pkg_analysis = maybe_pkg_analysis.analysis

    inferred_dependencies: list[Address] = []
    for import_path in (
            *pkg_analysis.imports,
            *pkg_analysis.test_imports,
            *pkg_analysis.xtest_imports,
    ):
        candidate_addresses = go_protobuf_mapping.mapping.get(import_path, ())
        inferred_dependencies.extend(candidate_addresses)

    return InferredDependencies(inferred_dependencies)
Beispiel #3
0
async def infer_go_dependencies(
    request: InferGoPackageDependenciesRequest,
    std_lib_imports: GoStdLibImports,
    package_mapping: ImportPathToPackages,
) -> InferredDependencies:
    addr = request.sources_field.address
    maybe_pkg_analysis = await Get(
        FallibleFirstPartyPkgAnalysis, FirstPartyPkgAnalysisRequest(addr)
    )
    if maybe_pkg_analysis.analysis is None:
        logger.error(
            f"Failed to analyze {maybe_pkg_analysis.import_path} for dependency inference:\n"
            f"{maybe_pkg_analysis.stderr}"
        )
        return InferredDependencies([])
    pkg_analysis = maybe_pkg_analysis.analysis

    inferred_dependencies = []
    for import_path in (
        *pkg_analysis.imports,
        *pkg_analysis.test_imports,
        *pkg_analysis.xtest_imports,
    ):
        if import_path in std_lib_imports:
            continue
        # Avoid a dependency cycle caused by external test imports of this package (i.e., "xtest").
        if import_path == pkg_analysis.import_path:
            continue
        candidate_packages = package_mapping.mapping.get(import_path, ())
        if len(candidate_packages) > 1:
            # TODO(#12761): Use ExplicitlyProvidedDependencies for disambiguation.
            logger.warning(
                f"Ambiguous mapping for import path {import_path} on packages at addresses: {candidate_packages}"
            )
        elif len(candidate_packages) == 1:
            inferred_dependencies.append(candidate_packages[0])
        else:
            logger.debug(
                f"Unable to infer dependency for import path '{import_path}' "
                f"in go_package at address '{addr}'."
            )

    return InferredDependencies(inferred_dependencies)
    def assert_analysis(
        dir_path: str,
        *,
        imports: list[str],
        test_imports: list[str],
        xtest_imports: list[str],
        go_files: list[str],
        test_files: list[str],
        xtest_files: list[str],
    ) -> None:
        addr = Address(os.path.join("foo", dir_path))
        maybe_analysis = rule_runner.request(
            FallibleFirstPartyPkgAnalysis, [FirstPartyPkgAnalysisRequest(addr)]
        )
        assert maybe_analysis.analysis is not None
        analysis = maybe_analysis.analysis

        assert analysis.imports == tuple(imports)
        assert analysis.test_imports == tuple(test_imports)
        assert analysis.xtest_imports == tuple(xtest_imports)
        assert analysis.go_files == tuple(go_files)
        assert analysis.test_go_files == tuple(test_files)
        assert analysis.xtest_go_files == tuple(xtest_files)
        assert not analysis.s_files

        assert analysis.minimum_go_version == "1.16"

        assert analysis.embed_patterns == ()
        assert analysis.test_embed_patterns == ()
        assert analysis.xtest_embed_patterns == ()

        maybe_digest = rule_runner.request(
            FallibleFirstPartyPkgDigest, [FirstPartyPkgDigestRequest(addr)]
        )
        assert maybe_digest.pkg_digest is not None
        pkg_digest = maybe_digest.pkg_digest
        actual_snapshot = rule_runner.request(Snapshot, [pkg_digest.digest])
        expected_snapshot = rule_runner.request(Snapshot, [PathGlobs([f"foo/{dir_path}/*.go"])])
        assert actual_snapshot == expected_snapshot
        assert pkg_digest.embed_config is None
        assert pkg_digest.xtest_embed_config is None
        assert pkg_digest.xtest_embed_config is None
def test_invalid_package(rule_runner) -> None:
    rule_runner.write_files(
        {
            "BUILD": "go_mod(name='mod')\ngo_package(name='pkg')",
            "go.mod": dedent(
                """\
                module go.example.com/foo
                go 1.17
                """
            ),
            "bad.go": "invalid!!!",
        }
    )
    maybe_analysis = rule_runner.request(
        FallibleFirstPartyPkgAnalysis,
        [FirstPartyPkgAnalysisRequest(Address("", target_name="pkg"))],
    )
    assert maybe_analysis.analysis is None
    assert maybe_analysis.exit_code == 1
    assert "bad.go:1:1: expected 'package', found invalid\n" in maybe_analysis.stderr
Beispiel #6
0
async def setup_build_go_package_target_request(
        request: BuildGoPackageTargetRequest,
        union_membership: UnionMembership) -> FallibleBuildGoPackageRequest:
    wrapped_target = await Get(WrappedTarget, Address, request.address)
    target = wrapped_target.target

    codegen_request = maybe_get_codegen_request_type(target, union_membership)
    if codegen_request:
        codegen_result = await Get(FallibleBuildGoPackageRequest,
                                   GoCodegenBuildRequest, codegen_request)
        return codegen_result

    embed_config: EmbedConfig | None = None
    if target.has_field(GoPackageSourcesField):
        _maybe_first_party_pkg_analysis, _maybe_first_party_pkg_digest = await MultiGet(
            Get(FallibleFirstPartyPkgAnalysis,
                FirstPartyPkgAnalysisRequest(target.address)),
            Get(FallibleFirstPartyPkgDigest,
                FirstPartyPkgDigestRequest(target.address)),
        )
        if _maybe_first_party_pkg_analysis.analysis is None:
            return FallibleBuildGoPackageRequest(
                None,
                _maybe_first_party_pkg_analysis.import_path,
                exit_code=_maybe_first_party_pkg_analysis.exit_code,
                stderr=_maybe_first_party_pkg_analysis.stderr,
            )
        if _maybe_first_party_pkg_digest.pkg_digest is None:
            return FallibleBuildGoPackageRequest(
                None,
                _maybe_first_party_pkg_analysis.import_path,
                exit_code=_maybe_first_party_pkg_digest.exit_code,
                stderr=_maybe_first_party_pkg_digest.stderr,
            )
        _first_party_pkg_analysis = _maybe_first_party_pkg_analysis.analysis
        _first_party_pkg_digest = _maybe_first_party_pkg_digest.pkg_digest

        digest = _first_party_pkg_digest.digest
        import_path = _first_party_pkg_analysis.import_path
        dir_path = _first_party_pkg_analysis.dir_path
        minimum_go_version = _first_party_pkg_analysis.minimum_go_version

        go_file_names = _first_party_pkg_analysis.go_files
        embed_config = _first_party_pkg_digest.embed_config
        if request.for_tests:
            # TODO: Build the test sources separately and link the two object files into the
            #  package archive?
            # TODO: The `go` tool changes the displayed import path for the package when it has
            #  test files. Do we need to do something similar?
            go_file_names += _first_party_pkg_analysis.test_go_files
            if _first_party_pkg_digest.test_embed_config:
                if embed_config:
                    embed_config = embed_config.merge(
                        _first_party_pkg_digest.test_embed_config)
                else:
                    embed_config = _first_party_pkg_digest.test_embed_config
        s_file_names = _first_party_pkg_analysis.s_files

    elif target.has_field(GoThirdPartyPackageDependenciesField):
        import_path = target[GoImportPathField].value

        _go_mod_address = target.address.maybe_convert_to_target_generator()
        _go_mod_info = await Get(GoModInfo, GoModInfoRequest(_go_mod_address))
        _third_party_pkg_info = await Get(
            ThirdPartyPkgAnalysis,
            ThirdPartyPkgAnalysisRequest(import_path, _go_mod_info.digest,
                                         _go_mod_info.mod_path),
        )

        # We error if trying to _build_ a package with issues (vs. only generating the target and
        # using in project introspection).
        if _third_party_pkg_info.error:
            raise _third_party_pkg_info.error

        dir_path = _third_party_pkg_info.dir_path
        digest = _third_party_pkg_info.digest
        minimum_go_version = _third_party_pkg_info.minimum_go_version
        go_file_names = _third_party_pkg_info.go_files
        s_file_names = _third_party_pkg_info.s_files

    else:
        raise AssertionError(
            f"Unknown how to build `{target.alias}` target at address {request.address} with Go. "
            "Please open a bug at https://github.com/pantsbuild/pants/issues/new/choose with this "
            "message!")

    all_deps = await Get(Targets, DependenciesRequest(target[Dependencies]))
    maybe_direct_dependencies = await MultiGet(
        Get(FallibleBuildGoPackageRequest,
            BuildGoPackageTargetRequest(tgt.address)) for tgt in all_deps
        if (tgt.has_field(GoPackageSourcesField)
            or tgt.has_field(GoThirdPartyPackageDependenciesField)
            or bool(maybe_get_codegen_request_type(tgt, union_membership))))
    direct_dependencies = []
    for maybe_dep in maybe_direct_dependencies:
        if maybe_dep.request is None:
            return dataclasses.replace(
                maybe_dep,
                import_path="main" if request.is_main else import_path,
                dependency_failed=True,
            )
        direct_dependencies.append(maybe_dep.request)

    result = BuildGoPackageRequest(
        digest=digest,
        import_path="main" if request.is_main else import_path,
        dir_path=dir_path,
        go_file_names=go_file_names,
        s_file_names=s_file_names,
        minimum_go_version=minimum_go_version,
        direct_dependencies=tuple(direct_dependencies),
        for_tests=request.for_tests,
        embed_config=embed_config,
    )
    return FallibleBuildGoPackageRequest(result, import_path)
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)
def test_embeds_supported(rule_runner: RuleRunner) -> None:
    go_sources = {
        "foo.go": dedent(
            """\
            package foo
            import _ "embed"
            //go:embed grok.txt
            var message
            """
        ),
        "foo_test.go": dedent(
            """\
            package foo
            import _ "embed"
            //go:embed test_grok.txt
            var testMessage
            """
        ),
        "bar_test.go": dedent(
            """\
            package foo_test
            import _ "embed"
            //go:embed xtest_grok.txt
            var testMessage
            """
        ),
    }
    resources = {
        "grok.txt": "This will be embedded in a Go binary.",
        "test_grok.txt": "This will be embedded in a Go binary.",
        "xtest_grok.txt": "This will be embedded in a Go binary.",
    }
    rule_runner.write_files(
        {
            "BUILD": dedent(
                """
                go_mod(name='mod')
                go_package(name='pkg', dependencies=[":resources"])
                resources(
                  name="resources",
                  sources=["*.txt"],
                )
                """
            ),
            "go.mod": dedent(
                """\
                module go.example.com/foo
                go 1.17
                """
            ),
            **resources,  # type: ignore[arg-type]
            **go_sources,  # type: ignore[arg-type]
        }
    )
    maybe_analysis = rule_runner.request(
        FallibleFirstPartyPkgAnalysis,
        [FirstPartyPkgAnalysisRequest(Address("", target_name="pkg"))],
    )
    assert maybe_analysis.analysis is not None
    analysis = maybe_analysis.analysis
    assert analysis.embed_patterns == ("grok.txt",)
    assert analysis.test_embed_patterns == ("test_grok.txt",)
    assert analysis.xtest_embed_patterns == ("xtest_grok.txt",)

    maybe_digest = rule_runner.request(
        FallibleFirstPartyPkgDigest,
        [FirstPartyPkgDigestRequest(Address("", target_name="pkg"))],
    )
    assert maybe_digest.pkg_digest is not None
    pkg_digest = maybe_digest.pkg_digest
    actual_snapshot = rule_runner.request(Snapshot, [pkg_digest.digest])
    expected_snapshot = rule_runner.make_snapshot(
        {
            **go_sources,
            **{os.path.join("__resources__", f): content for f, content in resources.items()},
        }
    )
    assert actual_snapshot == expected_snapshot

    assert pkg_digest.embed_config == EmbedConfig(
        {"grok.txt": ["grok.txt"]}, {"grok.txt": "__resources__/grok.txt"}
    )
    assert pkg_digest.test_embed_config == EmbedConfig(
        {"grok.txt": ["grok.txt"], "test_grok.txt": ["test_grok.txt"]},
        {"grok.txt": "__resources__/grok.txt", "test_grok.txt": "__resources__/test_grok.txt"},
    )
    assert pkg_digest.xtest_embed_config == EmbedConfig(
        {"xtest_grok.txt": ["xtest_grok.txt"]}, {"xtest_grok.txt": "__resources__/xtest_grok.txt"}
    )