Skip to content

Commit b2aa62b

Browse files
adonovangopherbot
authored andcommitted
internal/stdlib: provide API for import graph of std library
This CL adds two functions for accessing the direct and transitive imports of the packages of the standard library: func Imports(pkgs ...string) iter.Seq[string] func Dependencies(pkgs ...string) iter.Seq[string] These are needed by modernizers so that they can avoid offering fixes that add an import of, say, "slices" while analyzing a package that is itself a dependency of "slices". The compressed graph is generated from the current toolchain; this may not exactly match the source code being analyzed by the application, but we expect drift to be small. Updates golang/go#70815 Change-Id: I2d7180bcff1d1c72ce61b8436a346b8921c02ba9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/653356 Commit-Queue: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 608d370 commit b2aa62b

File tree

8 files changed

+834
-4
lines changed

8 files changed

+834
-4
lines changed

internal/stdlib/deps.go

+359
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/stdlib/deps_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 stdlib_test
6+
7+
import (
8+
"iter"
9+
"os"
10+
"slices"
11+
"sort"
12+
"strings"
13+
"testing"
14+
15+
"github.com/google/go-cmp/cmp"
16+
"golang.org/x/tools/internal/stdlib"
17+
)
18+
19+
func TestImports(t *testing.T) { testDepsFunc(t, "testdata/nethttp.imports", stdlib.Imports) }
20+
func TestDeps(t *testing.T) { testDepsFunc(t, "testdata/nethttp.deps", stdlib.Dependencies) }
21+
22+
// testDepsFunc checks that the specified dependency function applied
23+
// to net/http returns the set of dependencies in the named file.
24+
func testDepsFunc(t *testing.T, filename string, depsFunc func(pkgs ...string) iter.Seq[string]) {
25+
data, err := os.ReadFile(filename)
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
want := strings.Split(strings.TrimSpace(string(data)), "\n")
30+
got := slices.Collect(depsFunc("net/http"))
31+
sort.Strings(want)
32+
sort.Strings(got)
33+
if diff := cmp.Diff(got, want); diff != "" {
34+
t.Fatalf("Deps mismatch (-want +got):\n%s", diff)
35+
}
36+
}

internal/stdlib/generate.go

+123-2
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,26 @@
77
// The generate command reads all the GOROOT/api/go1.*.txt files and
88
// generates a single combined manifest.go file containing the Go
99
// standard library API symbols along with versions.
10+
//
11+
// It also runs "go list -deps std" and records the import graph. This
12+
// information may be used, for example, to ensure that tools don't
13+
// suggest fixes that import package P when analyzing one of P's
14+
// dependencies.
1015
package main
1116

1217
import (
1318
"bytes"
1419
"cmp"
20+
"encoding/binary"
21+
"encoding/json"
1522
"errors"
1623
"fmt"
1724
"go/format"
1825
"go/types"
1926
"io/fs"
2027
"log"
2128
"os"
29+
"os/exec"
2230
"path/filepath"
2331
"regexp"
2432
"runtime"
@@ -29,6 +37,13 @@ import (
2937
)
3038

3139
func main() {
40+
manifest()
41+
deps()
42+
}
43+
44+
// -- generate std manifest --
45+
46+
func manifest() {
3247
pkgs := make(map[string]map[string]symInfo) // package -> symbol -> info
3348
symRE := regexp.MustCompile(`^pkg (\S+).*?, (var|func|type|const|method \([^)]*\)) ([\pL\p{Nd}_]+)(.*)`)
3449

@@ -131,7 +146,7 @@ func main() {
131146

132147
// Write the combined manifest.
133148
var buf bytes.Buffer
134-
buf.WriteString(`// Copyright 2024 The Go Authors. All rights reserved.
149+
buf.WriteString(`// Copyright 2025 The Go Authors. All rights reserved.
135150
// Use of this source code is governed by a BSD-style
136151
// license that can be found in the LICENSE file.
137152
@@ -157,7 +172,7 @@ var PackageSymbols = map[string][]Symbol{
157172
if err != nil {
158173
log.Fatal(err)
159174
}
160-
if err := os.WriteFile("manifest.go", fmtbuf, 0666); err != nil {
175+
if err := os.WriteFile("manifest.go", fmtbuf, 0o666); err != nil {
161176
log.Fatal(err)
162177
}
163178
}
@@ -223,3 +238,109 @@ func removeTypeParam(s string) string {
223238
}
224239
return s
225240
}
241+
242+
// -- generate dependency graph --
243+
244+
func deps() {
245+
stdout := new(bytes.Buffer)
246+
cmd := exec.Command("go", "list", "-deps", "-json", "std")
247+
cmd.Stdout = stdout
248+
cmd.Stderr = os.Stderr
249+
if err := cmd.Run(); err != nil {
250+
log.Fatal(err)
251+
}
252+
253+
type Package struct {
254+
// go list JSON output
255+
ImportPath string // import path of package in dir
256+
Imports []string // import paths used by this package
257+
258+
// encoding
259+
index int
260+
deps []int // indices of direct imports, sorted
261+
}
262+
pkgs := make(map[string]*Package)
263+
var keys []string
264+
for dec := json.NewDecoder(stdout); dec.More(); {
265+
var pkg Package
266+
if err := dec.Decode(&pkg); err != nil {
267+
log.Fatal(err)
268+
}
269+
pkgs[pkg.ImportPath] = &pkg
270+
keys = append(keys, pkg.ImportPath)
271+
}
272+
273+
// Sort and number the packages.
274+
// There are 344 as of Mar 2025.
275+
slices.Sort(keys)
276+
for i, name := range keys {
277+
pkgs[name].index = i
278+
}
279+
280+
// Encode the dependencies.
281+
for _, pkg := range pkgs {
282+
for _, imp := range pkg.Imports {
283+
if imp == "C" {
284+
continue
285+
}
286+
pkg.deps = append(pkg.deps, pkgs[imp].index)
287+
}
288+
slices.Sort(pkg.deps)
289+
}
290+
291+
// Emit the table.
292+
var buf bytes.Buffer
293+
buf.WriteString(`// Copyright 2025 The Go Authors. All rights reserved.
294+
// Use of this source code is governed by a BSD-style
295+
// license that can be found in the LICENSE file.
296+
297+
// Code generated by generate.go. DO NOT EDIT.
298+
299+
package stdlib
300+
301+
type pkginfo struct {
302+
name string
303+
deps string // list of indices of dependencies, as varint-encoded deltas
304+
}
305+
var deps = [...]pkginfo{
306+
`)
307+
for _, name := range keys {
308+
prev := 0
309+
var deps []int
310+
for _, v := range pkgs[name].deps {
311+
deps = append(deps, v-prev) // delta
312+
prev = v
313+
}
314+
var data []byte
315+
for _, v := range deps {
316+
data = binary.AppendUvarint(data, uint64(v))
317+
}
318+
fmt.Fprintf(&buf, "\t{%q, %q},\n", name, data)
319+
}
320+
fmt.Fprintln(&buf, "}")
321+
322+
fmtbuf, err := format.Source(buf.Bytes())
323+
if err != nil {
324+
log.Fatal(err)
325+
}
326+
if err := os.WriteFile("deps.go", fmtbuf, 0o666); err != nil {
327+
log.Fatal(err)
328+
}
329+
330+
// Also generate the data for the test.
331+
for _, t := range [...]struct{ flag, filename string }{
332+
{"-deps=true", "testdata/nethttp.deps"},
333+
{`-f={{join .Imports "\n"}}`, "testdata/nethttp.imports"},
334+
} {
335+
stdout := new(bytes.Buffer)
336+
cmd := exec.Command("go", "list", t.flag, "net/http")
337+
cmd.Stdout = stdout
338+
cmd.Stderr = os.Stderr
339+
if err := cmd.Run(); err != nil {
340+
log.Fatal(err)
341+
}
342+
if err := os.WriteFile(t.filename, stdout.Bytes(), 0666); err != nil {
343+
log.Fatal(err)
344+
}
345+
}
346+
}

internal/stdlib/import.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 stdlib
6+
7+
// This file provides the API for the import graph of the standard library.
8+
//
9+
// Be aware that the compiler-generated code for every package
10+
// implicitly depends on package "runtime" and a handful of others
11+
// (see runtimePkgs in GOROOT/src/cmd/internal/objabi/pkgspecial.go).
12+
13+
import (
14+
"encoding/binary"
15+
"iter"
16+
"slices"
17+
"strings"
18+
)
19+
20+
// Imports returns the sequence of packages directly imported by the
21+
// named standard packages, in name order.
22+
// The imports of an unknown package are the empty set.
23+
//
24+
// The graph is built into the application and may differ from the
25+
// graph in the Go source tree being analyzed by the application.
26+
func Imports(pkgs ...string) iter.Seq[string] {
27+
return func(yield func(string) bool) {
28+
for _, pkg := range pkgs {
29+
if i, ok := find(pkg); ok {
30+
var depIndex uint64
31+
for data := []byte(deps[i].deps); len(data) > 0; {
32+
delta, n := binary.Uvarint(data)
33+
depIndex += delta
34+
if !yield(deps[depIndex].name) {
35+
return
36+
}
37+
data = data[n:]
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
// Dependencies returns the set of all dependencies of the named
45+
// standard packages, including the initial package,
46+
// in a deterministic topological order.
47+
// The dependencies of an unknown package are the empty set.
48+
//
49+
// The graph is built into the application and may differ from the
50+
// graph in the Go source tree being analyzed by the application.
51+
func Dependencies(pkgs ...string) iter.Seq[string] {
52+
return func(yield func(string) bool) {
53+
for _, pkg := range pkgs {
54+
if i, ok := find(pkg); ok {
55+
var seen [1 + len(deps)/8]byte // bit set of seen packages
56+
var visit func(i int) bool
57+
visit = func(i int) bool {
58+
bit := byte(1) << (i % 8)
59+
if seen[i/8]&bit == 0 {
60+
seen[i/8] |= bit
61+
var depIndex uint64
62+
for data := []byte(deps[i].deps); len(data) > 0; {
63+
delta, n := binary.Uvarint(data)
64+
depIndex += delta
65+
if !visit(int(depIndex)) {
66+
return false
67+
}
68+
data = data[n:]
69+
}
70+
if !yield(deps[i].name) {
71+
return false
72+
}
73+
}
74+
return true
75+
}
76+
if !visit(i) {
77+
return
78+
}
79+
}
80+
}
81+
}
82+
}
83+
84+
// find returns the index of pkg in the deps table.
85+
func find(pkg string) (int, bool) {
86+
return slices.BinarySearchFunc(deps[:], pkg, func(p pkginfo, n string) int {
87+
return strings.Compare(p.name, n)
88+
})
89+
}

internal/stdlib/manifest.go

+8-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/stdlib/stdlib.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
// Package stdlib provides a table of all exported symbols in the
88
// standard library, along with the version at which they first
9-
// appeared.
9+
// appeared. It also provides the import graph of std packages.
1010
package stdlib
1111

1212
import (

0 commit comments

Comments
 (0)