diff --git a/src/python/pants/backend/experimental/go/register.py b/src/python/pants/backend/experimental/go/register.py index a1d5b968309..65fc0965008 100644 --- a/src/python/pants/backend/experimental/go/register.py +++ b/src/python/pants/backend/experimental/go/register.py @@ -8,7 +8,7 @@ from pants.backend.go.lint.gofmt import skip_field as gofmt_skip_field from pants.backend.go.lint.gofmt.rules import rules as gofmt_rules from pants.backend.go.subsystems import golang -from pants.backend.go.target_types import GoBinary, GoExternalPackageTarget, GoModule, GoPackage +from pants.backend.go.target_types import GoBinary, GoExternalPackageTarget, GoModTarget, GoPackage from pants.backend.go.util_rules import ( assembly, build_go_pkg, @@ -23,7 +23,7 @@ def target_types(): - return [GoBinary, GoPackage, GoModule, GoExternalPackageTarget] + return [GoBinary, GoPackage, GoModTarget, GoExternalPackageTarget] def rules(): diff --git a/src/python/pants/backend/go/goals/custom_goals.py b/src/python/pants/backend/go/goals/custom_goals.py index 881babb890c..b3656567aa9 100644 --- a/src/python/pants/backend/go/goals/custom_goals.py +++ b/src/python/pants/backend/go/goals/custom_goals.py @@ -5,10 +5,10 @@ import logging -from pants.backend.go.target_types import GoModuleSources +from pants.backend.go.target_types import GoModSourcesField from pants.backend.go.util_rules.build_go_pkg import BuildGoPackageRequest, BuiltGoPackage from pants.backend.go.util_rules.external_module import ResolveExternalGoPackageRequest -from pants.backend.go.util_rules.go_mod import ResolvedGoModule, ResolveGoModuleRequest +from pants.backend.go.util_rules.go_mod import GoModInfo, GoModInfoRequest from pants.backend.go.util_rules.go_pkg import ( ResolvedGoPackage, ResolveGoPackageRequest, @@ -16,7 +16,7 @@ is_third_party_package_target, ) from pants.engine.console import Console -from pants.engine.fs import Workspace +from pants.engine.fs import MergeDigests, Snapshot, Workspace from pants.engine.goal import Goal, GoalSubsystem from pants.engine.internals.selectors import Get, MultiGet from pants.engine.rules import collect_rules, goal_rule @@ -38,16 +38,17 @@ class GoResolveGoal(Goal): @goal_rule async def run_go_resolve(targets: UnexpandedTargets, workspace: Workspace) -> GoResolveGoal: - # TODO: Use MultiGet to resolve the go_module targets. - # TODO: Combine all of the go.sum's into a single Digest to write. - for target in targets: - if target.has_field(GoModuleSources): - resolved_go_module = await Get(ResolvedGoModule, ResolveGoModuleRequest(target.address)) - # TODO: Only update the files if they actually changed. - workspace.write_digest(resolved_go_module.digest, path_prefix=target.address.spec_path) - logger.info(f"{target.address}: Updated go.mod and go.sum.\n") - else: - logger.info(f"{target.address}: Skipping because target is not a `go_module`.\n") + all_go_mod_info = await MultiGet( + Get(GoModInfo, GoModInfoRequest(target.address)) + for target in targets + if target.has_field(GoModSourcesField) + ) + result = await Get( + Snapshot, MergeDigests(go_mod_info.digest for go_mod_info in all_go_mod_info) + ) + logger.info(f"Updating these files: {list(result.files)}") + # TODO: Only update the files if they actually changed. + workspace.write_digest(result.digest) return GoResolveGoal(exit_code=0) diff --git a/src/python/pants/backend/go/goals/package_binary_integration_test.py b/src/python/pants/backend/go/goals/package_binary_integration_test.py index e3ad95b3a4c..4acc03c8e01 100644 --- a/src/python/pants/backend/go/goals/package_binary_integration_test.py +++ b/src/python/pants/backend/go/goals/package_binary_integration_test.py @@ -10,7 +10,7 @@ from pants.backend.go import target_type_rules from pants.backend.go.goals import package_binary from pants.backend.go.goals.package_binary import GoBinaryFieldSet -from pants.backend.go.target_types import GoBinary, GoModule, GoPackage +from pants.backend.go.target_types import GoBinary, GoModTarget, GoPackage from pants.backend.go.util_rules import ( assembly, build_go_pkg, @@ -33,7 +33,7 @@ @pytest.fixture() def rule_runner() -> RuleRunner: rule_runner = RuleRunner( - target_types=[GoBinary, GoPackage, GoModule], + target_types=[GoBinary, GoPackage, GoModTarget], rules=[ *assembly.rules(), *compile.rules(), diff --git a/src/python/pants/backend/go/goals/tailor.py b/src/python/pants/backend/go/goals/tailor.py index 83315831215..7fa86462e27 100644 --- a/src/python/pants/backend/go/goals/tailor.py +++ b/src/python/pants/backend/go/goals/tailor.py @@ -4,7 +4,7 @@ import os from dataclasses import dataclass -from pants.backend.go.target_types import GoModule, GoPackage +from pants.backend.go.target_types import GoModTarget, GoPackage from pants.core.goals.tailor import ( AllOwnedSources, PutativeTarget, @@ -61,7 +61,7 @@ async def find_putative_go_module_targets( for dirname, filenames in group_by_dir(unowned_go_mod_files).items(): putative_targets.append( PutativeTarget.for_target_type( - GoModule, + GoModTarget, dirname, os.path.basename(dirname), sorted(filenames), diff --git a/src/python/pants/backend/go/goals/tailor_test.py b/src/python/pants/backend/go/goals/tailor_test.py index 751f4c53cf0..1a0b310294f 100644 --- a/src/python/pants/backend/go/goals/tailor_test.py +++ b/src/python/pants/backend/go/goals/tailor_test.py @@ -9,7 +9,7 @@ PutativeGoPackageTargetsRequest, ) from pants.backend.go.goals.tailor import rules as go_tailor_rules -from pants.backend.go.target_types import GoModule, GoPackage +from pants.backend.go.target_types import GoModTarget, GoPackage from pants.backend.go.util_rules import external_module, go_mod, sdk from pants.core.goals.tailor import ( AllOwnedSources, @@ -38,7 +38,7 @@ def rule_runner() -> RuleRunner: QueryRule(PutativeTargets, [PutativeGoPackageTargetsRequest, AllOwnedSources]), QueryRule(PutativeTargets, [PutativeGoModuleTargetsRequest, AllOwnedSources]), ], - target_types=[GoPackage, GoModule], + target_types=[GoPackage, GoModTarget], ) rule_runner.set_options(["--backend-packages=pants.backend.experimental.go"]) return rule_runner @@ -80,5 +80,5 @@ def test_find_putative_go_module_targets(rule_runner: RuleRunner) -> None: ], ) assert putative_targets == PutativeTargets( - [PutativeTarget.for_target_type(GoModule, "src/go/unowned", "unowned", ["go.mod"])] + [PutativeTarget.for_target_type(GoModTarget, "src/go/unowned", "unowned", ["go.mod"])] ) diff --git a/src/python/pants/backend/go/target_type_rules.py b/src/python/pants/backend/go/target_type_rules.py index 56e7d5a7cb4..3c71a2f816b 100644 --- a/src/python/pants/backend/go/target_type_rules.py +++ b/src/python/pants/backend/go/target_type_rules.py @@ -14,7 +14,7 @@ GoExternalPackageImportPathField, GoExternalPackageTarget, GoImportPath, - GoModule, + GoModTarget, GoPackageDependencies, GoPackageSources, ) @@ -25,10 +25,10 @@ ResolveExternalGoPackageRequest, ) from pants.backend.go.util_rules.go_mod import ( - FindNearestGoModuleRequest, - ResolvedGoModule, - ResolvedOwningGoModule, - ResolveGoModuleRequest, + GoModInfo, + GoModInfoRequest, + OwningGoMod, + OwningGoModRequest, ) from pants.backend.go.util_rules.go_pkg import ResolvedGoPackage, ResolveGoPackageRequest from pants.backend.go.util_rules.import_analysis import GoStdLibImports @@ -66,14 +66,14 @@ class InjectGoPackageDependenciesRequest(InjectDependenciesRequest): async def inject_go_package_dependencies( request: InjectGoPackageDependenciesRequest, ) -> InjectedDependencies: - owning_go_module_result = await Get( - ResolvedOwningGoModule, - FindNearestGoModuleRequest(request.dependencies_field.address.spec_path), + owning_go_mod = await Get( + OwningGoMod, OwningGoModRequest(request.dependencies_field.address.spec_path) + ) + return ( + InjectedDependencies([owning_go_mod.address]) + if owning_go_mod.address + else InjectedDependencies() ) - if owning_go_module_result.module_address: - return InjectedDependencies([owning_go_module_result.module_address]) - else: - return InjectedDependencies() # TODO: Figure out how to merge (or not) this with ResolvedImportPaths as a base class. @@ -232,7 +232,7 @@ async def inject_go_external_package_dependencies( class GenerateGoExternalPackageTargetsRequest(GenerateTargetsRequest): - generate_from = GoModule + generate_from = GoModTarget @rule(desc="Generate targets for each external package in `go.mod`", level=LogLevel.DEBUG) @@ -240,17 +240,17 @@ async def generate_go_external_package_targets( request: GenerateGoExternalPackageTargetsRequest, ) -> GeneratedTargets: generator_addr = request.generator.address - resolved_module = await Get(ResolvedGoModule, ResolveGoModuleRequest(generator_addr)) + go_mod_info = await Get(GoModInfo, GoModInfoRequest(generator_addr)) all_resolved_packages = await MultiGet( Get( ResolveExternalGoModuleToPackagesResult, ResolveExternalGoModuleToPackagesRequest( path=module_descriptor.path, version=module_descriptor.version, - go_sum_digest=resolved_module.digest, + go_sum_digest=go_mod_info.go_sum_stripped_digest, ), ) - for module_descriptor in resolved_module.modules + for module_descriptor in go_mod_info.modules ) def create_tgt(pkg: ResolvedGoPackage) -> GoExternalPackageTarget: diff --git a/src/python/pants/backend/go/target_type_rules_test.py b/src/python/pants/backend/go/target_type_rules_test.py index 411a56aeec6..37a08ce634b 100644 --- a/src/python/pants/backend/go/target_type_rules_test.py +++ b/src/python/pants/backend/go/target_type_rules_test.py @@ -14,8 +14,8 @@ GoExternalModuleVersionField, GoExternalPackageImportPathField, GoExternalPackageTarget, - GoModule, - GoModuleSources, + GoModSourcesField, + GoModTarget, GoPackage, GoPackageSources, ) @@ -52,7 +52,7 @@ def rule_runner() -> RuleRunner: QueryRule(InferredDependencies, [InferGoPackageDependenciesRequest]), QueryRule(GeneratedTargets, [GenerateGoExternalPackageTargetsRequest]), ], - target_types=[GoPackage, GoModule, GoExternalPackageTarget], + target_types=[GoPackage, GoModTarget, GoExternalPackageTarget], ) rule_runner.set_options([], env_inherit={"PATH"}) return rule_runner @@ -61,7 +61,7 @@ def rule_runner() -> RuleRunner: def assert_go_module_address(rule_runner: RuleRunner, target: Target, expected_address: Address): addresses = rule_runner.request(Addresses, [DependenciesRequest(target[Dependencies])]) targets = rule_runner.request(Targets, [addresses]) - go_module_targets = [tgt for tgt in targets if tgt.has_field(GoModuleSources)] + go_module_targets = [tgt for tgt in targets if tgt.has_field(GoModSourcesField)] assert len(go_module_targets) == 1 assert go_module_targets[0].address == expected_address diff --git a/src/python/pants/backend/go/target_types.py b/src/python/pants/backend/go/target_types.py index b751444dab9..97e4dd9d09a 100644 --- a/src/python/pants/backend/go/target_types.py +++ b/src/python/pants/backend/go/target_types.py @@ -48,23 +48,46 @@ class GoPackage(Target): # ----------------------------------------------------------------------------------------------- -class GoModuleSources(Sources): +class GoModSourcesField(Sources): alias = "_sources" default = ("go.mod", "go.sum") - expected_num_files = range(1, 3) + expected_num_files = range(1, 3) # i.e. 1 or 2. - def validate_resolved_files(self, files: Sequence[str]) -> None: - super().validate_resolved_files(files) - if "go.mod" not in [os.path.basename(f) for f in files]: - raise InvalidFieldException(f"""No go.mod file was found for target {self.address}.""") + @property + def go_mod_path(self) -> str: + return os.path.join(self.address.spec_path, "go.mod") + @property + def go_sum_path(self) -> str: + return os.path.join(self.address.spec_path, "go.sum") -class GoModule(Target): + def validate_resolved_files(self, files: Sequence[str]) -> None: + super().validate_resolved_files(files) + if self.go_mod_path not in files: + raise InvalidFieldException( + f"The {repr(self.alias)} field in target {self.address} must include " + f"{self.go_mod_path}, but only had: {list(files)}\n\n" + f"Make sure that you're declaring the `{GoModTarget.alias}` target in the same " + "directory as your `go.mod` file." + ) + invalid_files = set(files) - {self.go_mod_path, self.go_sum_path} + if invalid_files: + raise InvalidFieldException( + f"The {repr(self.alias)} field in target {self.address} must only include " + f"`{self.go_mod_path}` and optionally {self.go_sum_path}, but had: " + f"{sorted(invalid_files)}\n\n" + f"Make sure that you're declaring the `{GoModTarget.alias}` target in the same " + f"directory as your `go.mod` file and that you don't override the `{self.alias}` " + "field." + ) + + +class GoModTarget(Target): alias = "go_module" core_fields = ( *COMMON_TARGET_FIELDS, Dependencies, - GoModuleSources, + GoModSourcesField, ) help = "First-party Go module." diff --git a/src/python/pants/backend/go/util_rules/assembly_integration_test.py b/src/python/pants/backend/go/util_rules/assembly_integration_test.py index e78c30650ae..9f2330f159c 100644 --- a/src/python/pants/backend/go/util_rules/assembly_integration_test.py +++ b/src/python/pants/backend/go/util_rules/assembly_integration_test.py @@ -6,7 +6,7 @@ import pytest from pants.backend.go import target_type_rules -from pants.backend.go.target_types import GoExternalPackageTarget, GoModule, GoPackage +from pants.backend.go.target_types import GoExternalPackageTarget, GoModTarget, GoPackage from pants.backend.go.util_rules import ( assembly, build_go_pkg, @@ -40,7 +40,7 @@ def rule_runner() -> RuleRunner: *target_type_rules.rules(), QueryRule(BuiltGoPackage, [BuildGoPackageRequest]), ], - target_types=[GoPackage, GoModule, GoExternalPackageTarget], + target_types=[GoPackage, GoModTarget, GoExternalPackageTarget], ) rule_runner.set_options([], env_inherit={"PATH"}) return rule_runner diff --git a/src/python/pants/backend/go/util_rules/compile_integration_test.py b/src/python/pants/backend/go/util_rules/compile_integration_test.py index 27fceb7a226..3956b72d2c3 100644 --- a/src/python/pants/backend/go/util_rules/compile_integration_test.py +++ b/src/python/pants/backend/go/util_rules/compile_integration_test.py @@ -5,7 +5,7 @@ import pytest -from pants.backend.go.target_types import GoModule, GoPackage +from pants.backend.go.target_types import GoModTarget, GoPackage from pants.backend.go.util_rules import compile, sdk from pants.backend.go.util_rules.compile import CompiledGoSources, CompileGoSourcesRequest from pants.engine.fs import CreateDigest, Digest, FileContent, Snapshot @@ -36,7 +36,7 @@ def rule_runner() -> RuleRunner: QueryRule(Snapshot, [Digest]), QueryRule(CompiledGoSources, [CompileGoSourcesRequest]), ], - target_types=[GoPackage, GoModule], + target_types=[GoPackage, GoModTarget], ) rule_runner.set_options([], env_inherit={"PATH"}) return rule_runner diff --git a/src/python/pants/backend/go/util_rules/external_module.py b/src/python/pants/backend/go/util_rules/external_module.py index 1c7591ab852..0333325e3e3 100644 --- a/src/python/pants/backend/go/util_rules/external_module.py +++ b/src/python/pants/backend/go/util_rules/external_module.py @@ -228,10 +228,7 @@ async def resolve_external_module_to_go_packages( left_go_sum_contents = fc.content break - go_sum_only_digest = await Get( - Digest, DigestSubset(request.go_sum_digest, PathGlobs(["go.sum"])) - ) - go_sum_prefixed_digest = await Get(Digest, AddPrefix(go_sum_only_digest, "__sources__")) + go_sum_prefixed_digest = await Get(Digest, AddPrefix(request.go_sum_digest, "__sources__")) right_digest_contents = await Get(DigestContents, Digest, go_sum_prefixed_digest) right_go_sum_contents = b"" for fc in right_digest_contents: diff --git a/src/python/pants/backend/go/util_rules/external_module_integration_test.py b/src/python/pants/backend/go/util_rules/external_module_integration_test.py index 8dbedefaae9..32a288aa0c5 100644 --- a/src/python/pants/backend/go/util_rules/external_module_integration_test.py +++ b/src/python/pants/backend/go/util_rules/external_module_integration_test.py @@ -3,7 +3,7 @@ import pytest -from pants.backend.go.target_types import GoModule, GoPackage +from pants.backend.go.target_types import GoModTarget, GoPackage from pants.backend.go.util_rules import external_module, sdk from pants.backend.go.util_rules.external_module import ( DownloadedExternalModule, @@ -33,7 +33,7 @@ def rule_runner() -> RuleRunner: ), QueryRule(DigestContents, [Digest]), ], - target_types=[GoPackage, GoModule], + target_types=[GoPackage, GoModTarget], ) rule_runner.set_options([], env_inherit={"PATH"}) return rule_runner diff --git a/src/python/pants/backend/go/util_rules/go_mod.py b/src/python/pants/backend/go/util_rules/go_mod.py index 2d68b70711c..2143942317a 100644 --- a/src/python/pants/backend/go/util_rules/go_mod.py +++ b/src/python/pants/backend/go/util_rules/go_mod.py @@ -5,20 +5,22 @@ import json import logging from dataclasses import dataclass -from typing import List, Optional import ijson -from pants.backend.go.target_types import GoModuleSources +from pants.backend.go.target_types import GoModSourcesField from pants.backend.go.util_rules.sdk import GoSdkProcess from pants.base.specs import AddressSpecs, AscendantAddresses, MaybeEmptySiblingAddresses from pants.build_graph.address import Address -from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest -from pants.engine.fs import Digest, RemovePrefix, Snapshot -from pants.engine.internals.selectors import Get +from pants.engine.fs import Digest, DigestSubset, PathGlobs, RemovePrefix from pants.engine.process import ProcessResult -from pants.engine.rules import collect_rules, rule -from pants.engine.target import Target, UnexpandedTargets, WrappedTarget +from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.engine.target import ( + HydratedSources, + HydrateSourcesRequest, + UnexpandedTargets, + WrappedTarget, +) from pants.util.ordered_set import FrozenOrderedSet logger = logging.getLogger(__name__) @@ -30,39 +32,33 @@ class ModuleDescriptor: version: str -# TODO: Add class docstring with info on the fields. @dataclass(frozen=True) -class ResolvedGoModule: - # The go_module target. - target: Target - - # Import path of the Go module. Inferred from the import path in the go.mod file. +class GoModInfo: + # Import path of the Go module, based on the `module` in `go.mod`. import_path: str - # Minimum Go version of the module from `go` statement in go.mod. - minimum_go_version: Optional[str] - # Modules referenced by this go.mod with resolved versions. modules: FrozenOrderedSet[ModuleDescriptor] - # Digest containing go.mod and updated go.sum. + # Digest containing the full paths to `go.mod` and `go.sum`. digest: Digest + # Digest containing only the `go.sum` with no leading directory prefix. + go_sum_stripped_digest: Digest + @dataclass(frozen=True) -class ResolveGoModuleRequest: +class GoModInfoRequest: address: Address -# Parse the output of `go mod download` into a list of module descriptors. -def parse_module_descriptors(raw_json: bytes) -> List[ModuleDescriptor]: - # `ijson` cannot handle empty input so short-circuit if there is no data. - if len(raw_json) == 0: +def parse_module_descriptors(raw_json: bytes) -> list[ModuleDescriptor]: + """Parse the JSON output of `go list -m`.""" + if not raw_json: return [] module_descriptors = [] for raw_module_descriptor in ijson.items(raw_json, "", multiple_values=True): - # Skip listing the main module. if raw_module_descriptor.get("Main", False): continue @@ -76,69 +72,67 @@ def parse_module_descriptors(raw_json: bytes) -> List[ModuleDescriptor]: @rule async def resolve_go_module( - request: ResolveGoModuleRequest, -) -> ResolvedGoModule: + request: GoModInfoRequest, +) -> GoModInfo: wrapped_target = await Get(WrappedTarget, Address, request.address) - target = wrapped_target.target + sources_field = wrapped_target.target[GoModSourcesField] - sources = await Get(SourceFiles, SourceFilesRequest([target.get(GoModuleSources)])) - flattened_sources_snapshot = await Get( - Snapshot, RemovePrefix(sources.snapshot.digest, request.address.spec_path) + # Get the `go.mod` (and `go.sum`) and strip so the file has no directory prefix. + hydrated_sources = await Get(HydratedSources, HydrateSourcesRequest(sources_field)) + sources_without_prefix = await Get( + Digest, RemovePrefix(hydrated_sources.snapshot.digest, request.address.spec_path) ) + go_sum_digest_get = Get(Digest, DigestSubset(sources_without_prefix, PathGlobs(["go.sum"]))) - # Parse the go.mod for the module path and minimum Go version. - parse_result = await Get( + mod_json_get = Get( ProcessResult, GoSdkProcess( - input_digest=flattened_sources_snapshot.digest, command=("mod", "edit", "-json"), - description=f"Parse go.mod for {request.address}.", + input_digest=sources_without_prefix, + description=f"Parse {sources_field.go_mod_path}", ), ) - module_metadata = json.loads(parse_result.stdout) - module_path = module_metadata["Module"]["Path"] - minimum_go_version = module_metadata.get( - "Go", "1.16" - ) # TODO: Figure out better default if missing. Use the SDKs version versus this hard-code. - - # Resolve the dependencies in the go.mod. - list_modules_result = await Get( + list_modules_get = Get( ProcessResult, GoSdkProcess( - input_digest=flattened_sources_snapshot.digest, + input_digest=sources_without_prefix, command=("list", "-m", "-json", "all"), - description=f"List modules in build of {request.address}.", + description=f"List modules in {sources_field.go_mod_path}", ), ) - modules = parse_module_descriptors(list_modules_result.stdout) - return ResolvedGoModule( - target=target, - import_path=module_path, - minimum_go_version=minimum_go_version, + mod_json, list_modules, go_sum_digest = await MultiGet( + mod_json_get, list_modules_get, go_sum_digest_get + ) + + module_metadata = json.loads(mod_json.stdout) + modules = parse_module_descriptors(list_modules.stdout) + return GoModInfo( + import_path=module_metadata["Module"]["Path"], modules=FrozenOrderedSet(modules), - digest=flattened_sources_snapshot.digest, # TODO: Is this a resolved version? Need to update for go-resolve goal? + digest=hydrated_sources.snapshot.digest, + go_sum_stripped_digest=go_sum_digest, ) @dataclass(frozen=True) -class FindNearestGoModuleRequest: +class OwningGoModRequest: spec_path: str @dataclass(frozen=True) -class ResolvedOwningGoModule: - module_address: Optional[Address] +class OwningGoMod: + address: Address | None @rule -async def find_nearest_go_module(request: FindNearestGoModuleRequest) -> ResolvedOwningGoModule: +async def find_nearest_go_module(request: OwningGoModRequest) -> OwningGoMod: spec_path = request.spec_path candidate_targets = await Get( UnexpandedTargets, AddressSpecs([AscendantAddresses(spec_path), MaybeEmptySiblingAddresses(spec_path)]), ) - go_module_targets = [tgt for tgt in candidate_targets if tgt.has_field(GoModuleSources)] + go_module_targets = [tgt for tgt in candidate_targets if tgt.has_field(GoModSourcesField)] # Sort by address.spec_path in descending order so the nearest go_module target is sorted first. sorted_go_module_targets = sorted( @@ -146,10 +140,9 @@ async def find_nearest_go_module(request: FindNearestGoModuleRequest) -> Resolve ) if sorted_go_module_targets: nearest_go_module_target = sorted_go_module_targets[0] - return ResolvedOwningGoModule(module_address=nearest_go_module_target.address) - else: - # TODO: Consider eventually requiring all go_package's to associate with a go_module. - return ResolvedOwningGoModule(module_address=None) + return OwningGoMod(nearest_go_module_target.address) + # TODO: Consider eventually requiring all go_package's to associate with a go_module. + return OwningGoMod(None) def rules(): diff --git a/src/python/pants/backend/go/util_rules/go_mod_integration_test.py b/src/python/pants/backend/go/util_rules/go_mod_integration_test.py index c2a9d865441..3f899c3129f 100644 --- a/src/python/pants/backend/go/util_rules/go_mod_integration_test.py +++ b/src/python/pants/backend/go/util_rules/go_mod_integration_test.py @@ -7,13 +7,10 @@ import pytest -from pants.backend.go.target_types import GoModule, GoPackage +from pants.backend.go.target_types import GoModTarget, GoPackage from pants.backend.go.util_rules import go_mod, sdk -from pants.backend.go.util_rules.go_mod import ResolvedGoModule, ResolveGoModuleRequest +from pants.backend.go.util_rules.go_mod import GoModInfo, GoModInfoRequest from pants.build_graph.address import Address -from pants.core.util_rules import external_tool, source_files -from pants.engine import fs -from pants.engine.fs import Digest, DigestContents from pants.engine.rules import QueryRule from pants.testutil.rule_runner import RuleRunner @@ -22,21 +19,17 @@ def rule_runner() -> RuleRunner: rule_runner = RuleRunner( rules=[ - *external_tool.rules(), - *source_files.rules(), - *fs.rules(), *sdk.rules(), *go_mod.rules(), - QueryRule(ResolvedGoModule, [ResolveGoModuleRequest]), - QueryRule(DigestContents, [Digest]), + QueryRule(GoModInfo, [GoModInfoRequest]), ], - target_types=[GoPackage, GoModule], + target_types=[GoPackage, GoModTarget], ) rule_runner.set_options([], env_inherit={"PATH"}) return rule_runner -def test_resolve_go_module(rule_runner: RuleRunner) -> None: +def test_go_mod_info(rule_runner: RuleRunner) -> None: rule_runner.write_files( { "foo/pkg/foo.go": "package pkg\n", @@ -70,17 +63,20 @@ def test_resolve_go_module(rule_runner: RuleRunner) -> None: """ ), "foo/main.go": "package main\nfunc main() { }\n", - "foo/BUILD": "go_module(name='mod')\ngo_package(name='pkg')\n", + "foo/BUILD": dedent( + """\ + go_module(name='mod') + go_package(name='pkg') + """ + ), } ) resolved_go_module = rule_runner.request( - ResolvedGoModule, [ResolveGoModuleRequest(Address("foo", target_name="mod"))] + GoModInfo, [GoModInfoRequest(Address("foo", target_name="mod"))] ) assert resolved_go_module.import_path == "go.example.com/foo" - assert resolved_go_module.minimum_go_version == "1.17" - assert len(resolved_go_module.modules) > 0 - found_protobuf_module = False - for module_descriptor in resolved_go_module.modules: - if module_descriptor.path == "github.com/golang/protobuf": - found_protobuf_module = True - assert found_protobuf_module + assert resolved_go_module.modules + assert any( + module_descriptor.path == "github.com/golang/protobuf" + for module_descriptor in resolved_go_module.modules + ) diff --git a/src/python/pants/backend/go/util_rules/go_pkg.py b/src/python/pants/backend/go/util_rules/go_pkg.py index 19c7f607e1e..5707f14e375 100644 --- a/src/python/pants/backend/go/util_rules/go_pkg.py +++ b/src/python/pants/backend/go/util_rules/go_pkg.py @@ -9,22 +9,20 @@ from pants.backend.go.target_types import ( GoExternalPackageDependencies, GoImportPath, - GoModuleSources, GoPackageSources, ) from pants.backend.go.util_rules.go_mod import ( - FindNearestGoModuleRequest, - ResolvedGoModule, - ResolvedOwningGoModule, - ResolveGoModuleRequest, + GoModInfo, + GoModInfoRequest, + OwningGoMod, + OwningGoModRequest, ) from pants.backend.go.util_rules.sdk import GoSdkProcess from pants.build_graph.address import Address -from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest -from pants.engine.internals.selectors import Get, MultiGet +from pants.engine.fs import Digest, MergeDigests from pants.engine.process import ProcessResult -from pants.engine.rules import collect_rules, rule -from pants.engine.target import Target, WrappedTarget +from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.engine.target import HydratedSources, HydrateSourcesRequest, Target, WrappedTarget logger = logging.getLogger(__name__) @@ -192,20 +190,26 @@ def is_third_party_package_target(tgt: Target) -> bool: async def resolve_go_package( request: ResolveGoPackageRequest, ) -> ResolvedGoPackage: - wrapped_target, owning_go_module_result = await MultiGet( + wrapped_target, owning_go_mod = await MultiGet( Get(WrappedTarget, Address, request.address), - Get(ResolvedOwningGoModule, FindNearestGoModuleRequest(request.address.spec_path)), + Get(OwningGoMod, OwningGoModRequest(request.address.spec_path)), ) target = wrapped_target.target - if not owning_go_module_result.module_address: + if not owning_go_mod.address: raise ValueError(f"The go_package at address {request.address} has no owning go_module.") - resolved_go_module = await Get( - ResolvedGoModule, ResolveGoModuleRequest(owning_go_module_result.module_address) + + go_mod_spec_path = owning_go_mod.address.spec_path + assert request.address.spec_path.startswith(go_mod_spec_path) + spec_subpath = request.address.spec_path[len(go_mod_spec_path) :] + + go_mod_info, pkg_sources = await MultiGet( + Get(GoModInfo, GoModInfoRequest(owning_go_mod.address)), + Get(HydratedSources, HydrateSourcesRequest(target[GoPackageSources])), + ) + input_digest = await Get( + Digest, MergeDigests([pkg_sources.snapshot.digest, go_mod_info.digest]) ) - go_module_spec_path = resolved_go_module.target.address.spec_path - assert request.address.spec_path.startswith(go_module_spec_path) - spec_subpath = request.address.spec_path[len(go_module_spec_path) :] # Compute the import_path for this go_package. import_path_field = target.get(GoImportPath) @@ -213,37 +217,22 @@ async def resolve_go_package( # Use any explicit import path set on the `go_package` target. import_path = import_path_field.value else: - # Otherwise infer the import path from the owning `go_module` target. The inferred import path will be the - # module's import path plus any subdirectories in the spec_path between the go_module and go_package target. - if not resolved_go_module.import_path: - raise ValueError( - f"Unable to infer import path for the `go_package` at address {request.address} " - f"because the owning go_module at address {resolved_go_module.target.address} " - "does not have an import path defined nor could one be inferred." - ) - import_path = f"{resolved_go_module.import_path}/" + # Otherwise infer the import path from the owning `go_module` target. The inferred import + # path will be the module's import path plus any subdirectories in the spec_path + # between the go_module and go_package target. + import_path = f"{go_mod_info.import_path}/" if spec_subpath.startswith("/"): import_path += spec_subpath[1:] else: import_path += spec_subpath - sources = await Get( - SourceFiles, - SourceFilesRequest( - [ - target.get(GoPackageSources), - resolved_go_module.target.get(GoModuleSources), - ] - ), - ) - result = await Get( ProcessResult, GoSdkProcess( - input_digest=sources.snapshot.digest, + input_digest=input_digest, command=("list", "-json", f"./{spec_subpath}"), description="Resolve go_package metadata.", - working_dir=resolved_go_module.target.address.spec_path, + working_dir=go_mod_spec_path, ), ) @@ -252,7 +241,7 @@ async def resolve_go_package( metadata, import_path=import_path, address=request.address, - module_address=owning_go_module_result.module_address, + module_address=owning_go_mod.address, ) diff --git a/src/python/pants/backend/go/util_rules/go_pkg_integration_test.py b/src/python/pants/backend/go/util_rules/go_pkg_integration_test.py index f1544cc9850..c7c2458f8e3 100644 --- a/src/python/pants/backend/go/util_rules/go_pkg_integration_test.py +++ b/src/python/pants/backend/go/util_rules/go_pkg_integration_test.py @@ -4,7 +4,7 @@ import pytest -from pants.backend.go.target_types import GoModule, GoPackage +from pants.backend.go.target_types import GoModTarget, GoPackage from pants.backend.go.util_rules import go_mod, go_pkg, sdk from pants.backend.go.util_rules.go_pkg import ResolvedGoPackage, ResolveGoPackageRequest from pants.build_graph.address import Address @@ -23,7 +23,7 @@ def rule_runner() -> RuleRunner: *sdk.rules(), QueryRule(ResolvedGoPackage, [ResolveGoPackageRequest]), ], - target_types=[GoPackage, GoModule], + target_types=[GoPackage, GoModTarget], ) rule_runner.set_options([], env_inherit={"PATH"}) return rule_runner