Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go: add tailor support for go_package, go_module, and go_external_module targets #12406

Merged
merged 6 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/python/pants/backend/experimental/go/register.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.backend.go import build, distribution, import_analysis, module, target_type_rules
from pants.backend.go import build, distribution, import_analysis, module, tailor, target_type_rules
from pants.backend.go import target_types as go_target_types
from pants.backend.go.target_types import GoBinary, GoExternalModule, GoModule, GoPackage

Expand All @@ -14,8 +14,9 @@ def rules():
return [
*build.rules(),
*distribution.rules(),
*import_analysis.rules(),
*go_target_types.rules(),
*target_type_rules.rules(),
*import_analysis.rules(),
*module.rules(),
*tailor.rules(),
*target_type_rules.rules(),
]
2 changes: 2 additions & 0 deletions src/python/pants/backend/go/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ModuleDescriptor:

@dataclass(frozen=True)
class ResolvedGoModule:
address: Address
import_path: str
minimum_go_version: Optional[str]
modules: FrozenOrderedSet[ModuleDescriptor]
Expand Down Expand Up @@ -165,6 +166,7 @@ async def resolve_go_module(
raise ValueError("No `module` directive found in go.mod.")

return ResolvedGoModule(
address=request.address,
import_path=module_path,
minimum_go_version=minimum_go_version,
modules=FrozenOrderedSet(parse_module_descriptors(result.stdout)),
Expand Down
149 changes: 149 additions & 0 deletions src/python/pants/backend/go/tailor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import itertools
import os
from dataclasses import dataclass

from pants.backend.go.module import ResolvedGoModule, ResolveGoModuleRequest
from pants.backend.go.target_types import GoExternalModule, GoModule, GoModuleSources, GoPackage
from pants.base.specs import AddressSpecs, MaybeEmptyDescendantAddresses, MaybeEmptySiblingAddresses
from pants.core.goals.tailor import (
AllOwnedSources,
PutativeTarget,
PutativeTargets,
PutativeTargetsRequest,
group_by_dir,
)
from pants.engine.fs import PathGlobs, Paths
from pants.engine.internals.selectors import Get, MultiGet
from pants.engine.rules import collect_rules, rule
from pants.engine.target import UnexpandedTargets
from pants.engine.unions import UnionRule
from pants.util.logging import LogLevel


@dataclass(frozen=True)
class PutativeGoPackageTargetsRequest(PutativeTargetsRequest):
pass


@rule(level=LogLevel.DEBUG, desc="Determine candidate Go `go_package` targets to create")
async def find_putative_go_package_targets(
request: PutativeGoPackageTargetsRequest, all_owned_sources: AllOwnedSources
) -> PutativeTargets:
all_go_files = await Get(Paths, PathGlobs, request.search_paths.path_globs("*.go"))
unowned_go_files = set(all_go_files.files) - set(all_owned_sources)

putative_targets = []
for dirname, filenames in group_by_dir(unowned_go_files).items():
putative_targets.append(
PutativeTarget.for_target_type(
GoPackage,
dirname,
os.path.basename(dirname),
sorted(filenames),
)
)

return PutativeTargets(putative_targets)


@dataclass(frozen=True)
class PutativeGoModuleTargetsRequest(PutativeTargetsRequest):
pass


@rule(level=LogLevel.DEBUG, desc="Determine candidate Go `go_module` targets to create")
async def find_putative_go_module_targets(
request: PutativeGoModuleTargetsRequest, all_owned_sources: AllOwnedSources
) -> PutativeTargets:
all_go_mod_files = await Get(Paths, PathGlobs, request.search_paths.path_globs("go.mod"))
unowned_go_mod_files = set(all_go_mod_files.files) - set(all_owned_sources)

putative_targets = []
for dirname, filenames in group_by_dir(unowned_go_mod_files).items():
putative_targets.append(
PutativeTarget.for_target_type(
GoModule,
dirname,
os.path.basename(dirname),
sorted(filenames),
)
)

return PutativeTargets(putative_targets)


def compute_go_external_module_target_name(name: str, version: str) -> str:
return f"{name.replace('/', '_')}_{version}"


@dataclass(frozen=True)
class PutativeGoExternalModuleTargetsRequest(PutativeTargetsRequest):
pass


@rule(level=LogLevel.DEBUG, desc="Determine candidate Go `go_external_module` targets to create")
async def find_putative_go_external_module_targets(
request: PutativeGoExternalModuleTargetsRequest, _all_owned_sources: AllOwnedSources
) -> PutativeTargets:
# Unlike ordinary tailor invocations, this rule looks at existing `go_module` targets and not at actual
# source files because it infers `go_external_module` targets based on go.mod contents. (This may require
# invoking `tailor` first to create `go_module` targets and then again to create `go_external_module`
# targets.)
#
# TODO: This might better work as a BUILD macro if https://github.com/pantsbuild/pants/issues/7022 is
# resolved and macros are able to invoke the engine or processes.

addresses = itertools.chain.from_iterable(
[
[MaybeEmptySiblingAddresses(search_path), MaybeEmptyDescendantAddresses(search_path)]
for search_path in request.search_paths.dirs
]
)
candidate_targets = await Get(UnexpandedTargets, AddressSpecs(addresses))
go_module_targets = [
tgt
for tgt in candidate_targets
if tgt.has_field(GoModuleSources) and not tgt.address.is_file_target
]

putative_targets = []

resolved_go_modules = await MultiGet(
Get(ResolvedGoModule, ResolveGoModuleRequest(go_module_target.address))
for go_module_target in go_module_targets
)

for resolved_go_module in resolved_go_modules:
for module_descriptor in resolved_go_module.modules:
putative_targets.append(
PutativeTarget.for_target_type(
GoExternalModule,
resolved_go_module.address.spec_path,
compute_go_external_module_target_name(
module_descriptor.module_path, module_descriptor.module_version
),
[],
kwargs={
"path": module_descriptor.module_path,
"version": module_descriptor.module_version,
"import_path": module_descriptor.import_path,
},
build_file_name="BUILD.godeps",
comments=(
"Auto-generated by `./pants tailor`. Re-run `./pants tailor` if go.mod changes.",
),
)
)

return PutativeTargets(putative_targets)


def rules():
return [
*collect_rules(),
UnionRule(PutativeTargetsRequest, PutativeGoPackageTargetsRequest),
UnionRule(PutativeTargetsRequest, PutativeGoModuleTargetsRequest),
UnionRule(PutativeTargetsRequest, PutativeGoExternalModuleTargetsRequest),
]
161 changes: 161 additions & 0 deletions src/python/pants/backend/go/tailor_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import textwrap

import pytest

from pants.backend.go import module, target_type_rules
from pants.backend.go.tailor import (
PutativeGoExternalModuleTargetsRequest,
PutativeGoModuleTargetsRequest,
PutativeGoPackageTargetsRequest,
)
from pants.backend.go.tailor import rules as go_tailor_rules
from pants.backend.go.target_types import GoExternalModule, GoModule, GoPackage
from pants.core.goals.tailor import (
AllOwnedSources,
PutativeTarget,
PutativeTargets,
PutativeTargetsSearchPaths,
)
from pants.core.goals.tailor import rules as core_tailor_rules
from pants.core.util_rules import external_tool, source_files
from pants.engine.rules import QueryRule
from pants.testutil.rule_runner import RuleRunner


@pytest.fixture
def rule_runner() -> RuleRunner:
rule_runner = RuleRunner(
rules=[
*core_tailor_rules(),
*go_tailor_rules(),
*external_tool.rules(),
*source_files.rules(),
*module.rules(),
*target_type_rules.rules(),
QueryRule(PutativeTargets, [PutativeGoPackageTargetsRequest, AllOwnedSources]),
QueryRule(PutativeTargets, [PutativeGoModuleTargetsRequest, AllOwnedSources]),
QueryRule(PutativeTargets, [PutativeGoExternalModuleTargetsRequest, AllOwnedSources]),
],
target_types=[
GoPackage,
GoModule,
GoExternalModule,
],
)
rule_runner.set_options(["--backend-packages=pants.backend.experimental.go"])
return rule_runner


def test_find_putative_go_package_targets(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"src/go/owned/BUILD": "go_package()\n",
"src/go/owned/src.go": "package owned\n",
"src/go/unowned/src.go": "package unowned\n",
}
)
putative_targets = rule_runner.request(
PutativeTargets,
[
PutativeGoPackageTargetsRequest(PutativeTargetsSearchPaths(("src/",))),
AllOwnedSources(
[
"src/go/owned/src.go",
]
),
],
)
assert putative_targets == PutativeTargets(
[
PutativeTarget.for_target_type(
GoPackage,
"src/go/unowned",
"unowned",
[
"src.go",
],
),
]
)


def test_find_putative_go_module_targets(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"src/go/owned/BUILD": "go_module()\n",
"src/go/owned/go.mod": "module example.com/src/go/owned\n",
"src/go/unowned/go.mod": "module example.com/src/go/unowned\n",
}
)
putative_targets = rule_runner.request(
PutativeTargets,
[
PutativeGoModuleTargetsRequest(PutativeTargetsSearchPaths(("src/",))),
AllOwnedSources(
[
"src/go/owned/go.mod",
]
),
],
)
assert putative_targets == PutativeTargets(
[
PutativeTarget.for_target_type(
GoModule,
"src/go/unowned",
"unowned",
[
"go.mod",
],
),
]
)


def test_find_putative_go_external_module_targets(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"src/go/BUILD": "go_module()\n",
"src/go/go.mod": textwrap.dedent(
"""\
module example.com/src/go
go 1.16
require (
github.com/google/uuid v1.2.0
)
"""
),
}
)
putative_targets = rule_runner.request(
PutativeTargets,
[
PutativeGoExternalModuleTargetsRequest(PutativeTargetsSearchPaths(("src/",))),
AllOwnedSources(
[
"src/go/go.mod",
]
),
],
)
assert putative_targets == PutativeTargets(
[
PutativeTarget.for_target_type(
GoExternalModule,
"src/go",
"github.com_google_uuid_v1.2.0",
[],
kwargs={
"path": "github.com/google/uuid",
"version": "v1.2.0",
"import_path": "github.com/google/uuid",
},
build_file_name="BUILD.godeps",
comments=(
"Auto-generated by `./pants tailor`. Re-run `./pants tailor` if go.mod changes.",
),
),
]
)