Skip to content

Commit 0784336

Browse files
committed
cmd/go/internal/tool: include dynamically buildable tools
`go tool` only listed binaries already built into `pkg/tool`, so users couldn't discover builtin tools that can be built on demand. Extend the listing to include cmd/ tools that can be built with the current toolchain while avoiding duplicates and keeping the output sorted. For #75960 Change-Id: I32b0f5ae6cd2c67cf3af465ffb5b4ac3d446ded4
1 parent 48127f6 commit 0784336

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

src/cmd/go/internal/tool/tool.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"os/exec"
2020
"os/signal"
2121
"path"
22+
"path/filepath"
2223
"slices"
2324
"sort"
2425
"strings"
@@ -148,6 +149,16 @@ func listTools(loaderstate *modload.State, ctx context.Context) {
148149
return
149150
}
150151

152+
toolSet := make(map[string]bool)
153+
var toolNames []string
154+
addTool := func(name string) {
155+
if toolSet[name] {
156+
return
157+
}
158+
toolSet[name] = true
159+
toolNames = append(toolNames, name)
160+
}
161+
151162
sort.Strings(names)
152163
for _, name := range names {
153164
// Unify presentation by going to lower case.
@@ -159,7 +170,37 @@ func listTools(loaderstate *modload.State, ctx context.Context) {
159170
if cfg.BuildToolchainName == "gccgo" && !isGccgoTool(name) {
160171
continue
161172
}
162-
fmt.Println(name)
173+
addTool(name)
174+
}
175+
176+
// Also list builtin tools that can be built on demand.
177+
// These are packages in cmd/ that would be installed to the tool directory.
178+
cmdDir := filepath.Join(cfg.GOROOT, "src", "cmd")
179+
entries, err := os.ReadDir(cmdDir)
180+
if err == nil {
181+
for _, entry := range entries {
182+
if !entry.IsDir() {
183+
continue
184+
}
185+
toolName := entry.Name()
186+
// Skip packages that are not tools.
187+
if toolName == "internal" || toolName == "vendor" {
188+
continue
189+
}
190+
// Check if this tool is already in the tool directory.
191+
if toolSet[toolName] {
192+
continue
193+
}
194+
// Check if it's a valid builtin tool; add if so to keep deduped set.
195+
if tool := loadBuiltinTool(toolName); tool != "" {
196+
addTool(toolName)
197+
}
198+
}
199+
}
200+
201+
sort.Strings(toolNames)
202+
for _, tool := range toolNames {
203+
fmt.Println(tool)
163204
}
164205

165206
loaderstate.InitWorkfile()
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tool
6+
7+
import (
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
)
12+
13+
func TestListToolsBuiltinDiscovery(t *testing.T) {
14+
// Test the directory scanning logic that was added to listTools
15+
// This tests that we correctly identify directories and skip non-directories
16+
17+
// Create a temporary directory structure to simulate cmd/ directory
18+
tempDir := t.TempDir()
19+
cmdDir := filepath.Join(tempDir, "cmd")
20+
if err := os.MkdirAll(cmdDir, 0755); err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
// Create some tool directories
25+
tools := []string{"vet", "cgo", "cover", "fix", "godoc"}
26+
for _, tool := range tools {
27+
toolDir := filepath.Join(cmdDir, tool)
28+
if err := os.MkdirAll(toolDir, 0755); err != nil {
29+
t.Fatal(err)
30+
}
31+
}
32+
33+
// Create some non-tool directories that should be skipped
34+
nonTools := []string{"internal", "vendor"}
35+
for _, nonTool := range nonTools {
36+
nonToolDir := filepath.Join(cmdDir, nonTool)
37+
if err := os.MkdirAll(nonToolDir, 0755); err != nil {
38+
t.Fatal(err)
39+
}
40+
}
41+
42+
// Create a regular file (should be skipped)
43+
filePath := filepath.Join(cmdDir, "not-a-directory.txt")
44+
if err := os.WriteFile(filePath, []byte("test"), 0644); err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
// Test directory reading logic (simulating the logic from listTools)
49+
entries, err := os.ReadDir(cmdDir)
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
54+
var foundTools []string
55+
for _, entry := range entries {
56+
// Skip non-directories (this is the logic we added)
57+
if !entry.IsDir() {
58+
continue
59+
}
60+
61+
toolName := entry.Name()
62+
// Skip packages that are not tools (this is the logic we added)
63+
if toolName == "internal" || toolName == "vendor" {
64+
continue
65+
}
66+
67+
foundTools = append(foundTools, toolName)
68+
}
69+
70+
// Sort for consistent comparison
71+
// (In the real code, this happens via the toolSet map and final output)
72+
for i := 0; i < len(foundTools)-1; i++ {
73+
for j := i + 1; j < len(foundTools); j++ {
74+
if foundTools[i] > foundTools[j] {
75+
foundTools[i], foundTools[j] = foundTools[j], foundTools[i]
76+
}
77+
}
78+
}
79+
80+
// Verify we found the expected tools
81+
expectedTools := []string{"cgo", "cover", "fix", "godoc", "vet"}
82+
if len(foundTools) != len(expectedTools) {
83+
t.Errorf("Found %d tools, expected %d: %v", len(foundTools), len(expectedTools), foundTools)
84+
}
85+
86+
for i, expected := range expectedTools {
87+
if i >= len(foundTools) || foundTools[i] != expected {
88+
t.Errorf("Expected tool %q at position %d, got %q", expected, i, foundTools[i])
89+
}
90+
}
91+
}
92+
93+
func TestToolSetTracking(t *testing.T) {
94+
// Test the toolSet map logic that prevents duplicates
95+
// This tests part of the new functionality in listTools
96+
97+
// Simulate the toolSet map logic
98+
toolSet := make(map[string]bool)
99+
100+
// Add some tools to the set (simulating tools found in tool directory)
101+
existingTools := []string{"vet", "cgo"}
102+
for _, tool := range existingTools {
103+
toolSet[tool] = true
104+
}
105+
106+
// Test that existing tools are marked as present
107+
for _, tool := range existingTools {
108+
if !toolSet[tool] {
109+
t.Errorf("Expected tool %q to be in toolSet", tool)
110+
}
111+
}
112+
113+
// Test that new tools can be added and checked
114+
newTools := []string{"cover", "fix"}
115+
for _, tool := range newTools {
116+
if toolSet[tool] {
117+
t.Errorf("Expected new tool %q to not be in toolSet initially", tool)
118+
}
119+
toolSet[tool] = true
120+
if !toolSet[tool] {
121+
t.Errorf("Expected tool %q to be in toolSet after adding", tool)
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)