Skip to content

Treat go.mod tool directives as import roots - #23670

Open
aegis-cipherowl wants to merge 3 commits into
pantsbuild:mainfrom
aegis-cipherowl:henry-yang/upstream/go-mod-tool-directives
Open

aegis-cipherowl wants to merge 3 commits into
pantsbuild:mainfrom
aegis-cipherowl:henry-yang/upstream/go-mod-tool-directives

Conversation

@aegis-cipherowl

@aegis-cipherowl aegis-cipherowl commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Background

Split out of #23665 at @tdyas's request. Stacked on #23665 — review that one first; this diff is a pure addition on top of it.

A tool directive (Go 1.24+) names an executable package a module depends on without any .go file importing it. It is the official replacement for the tools.go pattern, which [golang].third_party_target_generation = "imported" already handles via the constraint-free scan.

Without this, a repository that has migrated to tool directives loses those targets under imported and fails to build, while one still using tools.go does not — so the two idioms behave inconsistently. #23665's option help and release note were amended to stop promising tool support; this PR restores those sentences.

Parses both the single-line and block forms. Comments are stripped first, and matching is on the tool keyword rather than a prefix, so module example.com/toolkit and require example.com/toolbox are not mistaken for directives — there is a test for exactly that.

LLM assistance notice

This PR was written primarily by Claude Code (Claude Opus 5). I directed the scope and reviewed the result.

🤖 Generated with Claude Code

@tdyas

tdyas commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Please set the PR's base branch to be the branch of the other PR on which it is stacked. That will isolate the diff to just this PR's actual changes.

@aegis-cipherowl

Copy link
Copy Markdown
Contributor Author

I would like to, but GitHub requires a PR's base branch to exist in the base repository, and I do not have push access to pantsbuild/pants — the branch only exists on the aegis-cipherowl fork. Retargeting fails with:

GraphQL: Proposed base branch 'henry-yang/upstream/import-scoped-target-generation' was not found (updatePullRequest)

Two ways to get the isolated diff you are after:

  1. Land Add [golang].third_party_target_generation = imported (closes #20340) #23665 first. Then I rebase this onto main and the diff is naturally just this change. No special access needed, and Add [golang].third_party_target_generation = imported (closes #20340) #23665 is the one under active review anyway — this is my suggestion.
  2. If you would rather review them in parallel, push henry-yang/upstream/import-scoped-target-generation (or any name) to pantsbuild/pants and I will retarget this PR to it immediately.

In the meantime, b2d71dfde8 is exactly this PR's change — 27 lines of parser, its call site, and its tests. Everything else in the diff is #23665.

benjyw pushed a commit that referenced this pull request Oct 9, 2026
… (#23665)

## Background

Closes #20340. Implements the design @rdeknijf proposed and @benjyw
approved there; the design is theirs, this is our implementation of it,
opened with their go-ahead.

Adds `[golang].third_party_target_generation` = `build-list` (default,
today's behavior) | `imported`. Under `imported`,
`go_third_party_package` targets are generated only for third-party
packages reachable through the import graph from first-party code, so
target generation stops downloading and analyzing modules nothing
imports.

#23499's `third_party_target_granularity = module` does not cover this:
`generate_targets_from_go_mod` calls
`download_and_analyze_third_party_packages` unconditionally, and the
granularity branch selects from the already-resolved result. The two
compose — import scoping decides *which* modules are resolved,
granularity decides how many targets each produces.

Four parts: constraint-free `AllImports`/`AllTestImports` on the package
analyzer (so build-tag-gated, other-platform and `tools.go` imports
still resolve); a first-party root scan; a fixpoint walk downloading
only reached modules; and a codegen roots union with Protobuf
registered, so generated `.pb.go` imports still get targets.

The root scan reads `.go` files from the filesystem rather than
resolving targets. That is forced: its only caller is a target
generator, so touching the target graph deadlocks the engine with `The
dependency graph contained a cycle`. It is easy to miss, because
injecting `AllTargets` there leaves `pants list` green in this repo —
which has no `go_mod` targets — and only fails once a real one exists.

## Measured

32-`go_mod` production repo, `check src/go/config::` (an 8-module leaf),
fresh local store:

| scenario | build-list | imported |
| -- | -- | -- |
| remote cache off | 420s | 165s |
| remote cache on, warm | 446s | **96s** |

| | build-list | imported |
| -- | -- | -- |
| distinct modules resolved | 1102 | **266** |
| GOSUMDB fallbacks | 1060 | **0** |
| targets, repo-wide | 107,973 | **6,610** |
| **packages compiled** | **104** | **104** |

The identical compile count is the correctness check. GOSUMDB fallbacks
go to zero structurally: Go 1.17+ prunes `go.sum` to exactly the
imported closure, which is what the walk resolves, so the fallback
branch is unreachable.

## Notes

`tool` directive support is split out to #23670, stacked on this.

## LLM assistance notice

This PR was written primarily by Claude Code (Claude Opus 5). I directed
the scope and reviewed the result.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@benjyw

benjyw commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@aegis-cipherowl can you rebase and resolve the merge conflicts, now that the base is merged into pantsbuild/main?

@benjyw

benjyw commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Then cc me and I will review.

A `tool` directive (Go 1.24+) names an executable package a module depends on
without any `.go` file importing it. It is the official replacement for the
`tools.go` pattern, which `[golang].third_party_target_generation = "imported"`
already handles via the constraint-free scan.

Without this, a repository that has migrated to `tool` directives loses those
targets under `imported` and fails to build, while one still using `tools.go`
does not -- an inconsistency between the old idiom and its successor.

Parses both the single-line form (`tool example.com/cmd/foo`) and the block
form. Comments are stripped first, and matching is on the `tool` keyword rather
than a prefix, so `module example.com/toolkit` and `require example.com/toolbox`
are not mistaken for directives.

Split out of pantsbuild#23665 at review request.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBwx1ZTZJ5oGxSAadmtucS
@aegis-cipherowl
aegis-cipherowl force-pushed the henry-yang/upstream/go-mod-tool-directives branch from bd28b3e to 69cf0bc Compare October 23, 2026 09:22
@benjyw

benjyw commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution. We've just branched for 2.34.x, so merging this pull request now will come out in 2.35.x, please move the release notes updates to docs/notes/2.35.x.md if that's appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants