xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/stdliblist.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1// Copyright 2021 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"bytes"
19	"encoding/json"
20	"flag"
21	"fmt"
22	"go/build"
23	"os"
24	"path/filepath"
25	"strings"
26)
27
28// Copy and pasted from golang.org/x/tools/go/packages
29type flatPackagesError struct {
30	Pos  string // "file:line:col" or "file:line" or "" or "-"
31	Msg  string
32	Kind flatPackagesErrorKind
33}
34
35type flatPackagesErrorKind int
36
37const (
38	UnknownError flatPackagesErrorKind = iota
39	ListError
40	ParseError
41	TypeError
42)
43
44func (err flatPackagesError) Error() string {
45	pos := err.Pos
46	if pos == "" {
47		pos = "-" // like token.Position{}.String()
48	}
49	return pos + ": " + err.Msg
50}
51
52// flatPackage is the JSON form of Package
53// It drops all the type and syntax fields, and transforms the Imports
54type flatPackage struct {
55	ID              string
56	Name            string              `json:",omitempty"`
57	PkgPath         string              `json:",omitempty"`
58	Standard        bool                `json:",omitempty"`
59	Errors          []flatPackagesError `json:",omitempty"`
60	GoFiles         []string            `json:",omitempty"`
61	CompiledGoFiles []string            `json:",omitempty"`
62	OtherFiles      []string            `json:",omitempty"`
63	ExportFile      string              `json:",omitempty"`
64	Imports         map[string]string   `json:",omitempty"`
65}
66
67type goListPackage struct {
68	Dir        string // directory containing package sources
69	ImportPath string // import path of package in dir
70	Name       string // package name
71	Target     string // install path
72	Goroot     bool   // is this package in the Go root?
73	Standard   bool   // is this package part of the standard Go library?
74	Root       string // Go root or Go path dir containing this package
75	Export     string // file containing export data (when using -export)
76	// Source files
77	GoFiles           []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
78	CgoFiles          []string // .go source files that import "C"
79	CompiledGoFiles   []string // .go files presented to compiler (when using -compiled)
80	IgnoredGoFiles    []string // .go source files ignored due to build constraints
81	IgnoredOtherFiles []string // non-.go source files ignored due to build constraints
82	CFiles            []string // .c source files
83	CXXFiles          []string // .cc, .cxx and .cpp source files
84	MFiles            []string // .m source files
85	HFiles            []string // .h, .hh, .hpp and .hxx source files
86	FFiles            []string // .f, .F, .for and .f90 Fortran source files
87	SFiles            []string // .s source files
88	SwigFiles         []string // .swig files
89	SwigCXXFiles      []string // .swigcxx files
90	SysoFiles         []string // .syso object files to add to archive
91	TestGoFiles       []string // _test.go files in package
92	XTestGoFiles      []string // _test.go files outside package
93	// Embedded files
94	EmbedPatterns      []string // //go:embed patterns
95	EmbedFiles         []string // files matched by EmbedPatterns
96	TestEmbedPatterns  []string // //go:embed patterns in TestGoFiles
97	TestEmbedFiles     []string // files matched by TestEmbedPatterns
98	XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles
99	XTestEmbedFiles    []string // files matched by XTestEmbedPatterns
100	// Dependency information
101	Imports   []string          // import paths used by this package
102	ImportMap map[string]string // map from source import to ImportPath (identity entries omitted)
103	// Error information
104	Incomplete bool                 // this package or a dependency has an error
105	Error      *flatPackagesError   // error loading package
106	DepsErrors []*flatPackagesError // errors loading dependencies
107}
108
109func stdlibPackageID(importPath string) string {
110	return "@io_bazel_rules_go//stdlib:" + importPath
111}
112
113// outputBasePath replace the cloneBase with output base label
114func outputBasePath(cloneBase, p string) string {
115	dir, _ := filepath.Rel(cloneBase, p)
116	return filepath.Join("__BAZEL_OUTPUT_BASE__", dir)
117}
118
119// absoluteSourcesPaths replace cloneBase of the absolution
120// paths with the label for all source files in a package
121func absoluteSourcesPaths(cloneBase, pkgDir string, srcs []string) []string {
122	ret := make([]string, 0, len(srcs))
123	pkgDir = outputBasePath(cloneBase, pkgDir)
124	for _, src := range srcs {
125		absPath := src
126
127		// Generated files will already have an absolute path. These come from
128		// the compiler's cache.
129		if !filepath.IsAbs(src) {
130			absPath = filepath.Join(pkgDir, src)
131		}
132
133		ret = append(ret, absPath)
134	}
135	return ret
136}
137
138// filterGoFiles keeps only files either ending in .go or those without an
139// extension (which are from the cache). This is a work around for
140// https://golang.org/issue/28749: cmd/go puts assembly, C, and C++ files in
141// CompiledGoFiles.
142func filterGoFiles(srcs []string) []string {
143	ret := make([]string, 0, len(srcs))
144	for _, f := range srcs {
145		if ext := filepath.Ext(f); ext == ".go" || ext == "" {
146			ret = append(ret, f)
147		}
148	}
149
150	return ret
151}
152
153func flatPackageForStd(cloneBase string, pkg *goListPackage) *flatPackage {
154	goFiles := absoluteSourcesPaths(cloneBase, pkg.Dir, pkg.GoFiles)
155	compiledGoFiles := absoluteSourcesPaths(cloneBase, pkg.Dir, pkg.CompiledGoFiles)
156
157	newPkg := &flatPackage{
158		ID:              stdlibPackageID(pkg.ImportPath),
159		Name:            pkg.Name,
160		PkgPath:         pkg.ImportPath,
161		ExportFile:      outputBasePath(cloneBase, pkg.Target),
162		Imports:         map[string]string{},
163		Standard:        pkg.Standard,
164		GoFiles:         goFiles,
165		CompiledGoFiles: filterGoFiles(compiledGoFiles),
166	}
167
168	// imports
169	//
170	// Imports contains the IDs of all imported packages.
171	// ImportsMap records (path, ID) only where they differ.
172	ids := make(map[string]struct{})
173	for _, id := range pkg.Imports {
174		ids[id] = struct{}{}
175	}
176
177	for path, id := range pkg.ImportMap {
178		newPkg.Imports[path] = stdlibPackageID(id)
179		delete(ids, id)
180	}
181
182	for id := range ids {
183		if id != "C" {
184			newPkg.Imports[id] = stdlibPackageID(id)
185		}
186	}
187
188	return newPkg
189}
190
191// stdliblist runs `go list -json` on the standard library and saves it to a file.
192func stdliblist(args []string) error {
193	// process the args
194	flags := flag.NewFlagSet("stdliblist", flag.ExitOnError)
195	goenv := envFlags(flags)
196	out := flags.String("out", "", "Path to output go list json")
197	if err := flags.Parse(args); err != nil {
198		return err
199	}
200	if err := goenv.checkFlags(); err != nil {
201		return err
202	}
203
204	if filepath.IsAbs(goenv.sdk) {
205		return fmt.Errorf("-sdk needs to be a relative path, but got %s", goenv.sdk)
206	}
207
208	// In Go 1.18, the standard library started using go:embed directives.
209	// When Bazel runs this action, it does so inside a sandbox where GOROOT points
210	// to an external/go_sdk directory that contains a symlink farm of all files in
211	// the Go SDK.
212	// If we run "go list" with that GOROOT, this action will fail because those
213	// go:embed directives will refuse to include the symlinks in the sandbox.
214	//
215	// To work around this, cloneGoRoot creates a copy of a subset of external/go_sdk
216	// that is sufficient to call "go list" into a new cloneBase directory, e.g.
217	// "go list" needs to call "compile", which needs "pkg/tool".
218	// We also need to retain the same relative path to the root directory, e.g.
219	// "$OUTPUT_BASE/external/go_sdk" becomes
220	// {cloneBase}/external/go_sdk", which will be set at GOROOT later. This ensures
221	// that file paths in the generated JSON are still valid.
222	//
223	// Here we replicate goRoot(absolute path of goenv.sdk) to newGoRoot.
224	cloneBase, cleanup, err := goenv.workDir()
225	if err != nil {
226		return err
227	}
228	defer func() { cleanup() }()
229
230	newGoRoot := filepath.Join(cloneBase, goenv.sdk)
231	if err := replicate(abs(goenv.sdk), abs(newGoRoot), replicatePaths("src", "pkg/tool", "pkg/include")); err != nil {
232		return err
233	}
234
235	// Ensure paths are absolute.
236	absPaths := []string{}
237	for _, path := range filepath.SplitList(os.Getenv("PATH")) {
238		absPaths = append(absPaths, abs(path))
239	}
240	os.Setenv("PATH", strings.Join(absPaths, string(os.PathListSeparator)))
241	os.Setenv("GOROOT", newGoRoot)
242
243	cgoEnabled := os.Getenv("CGO_ENABLED") == "1"
244	// Make sure we have an absolute path to the C compiler.
245	// TODO(#1357): also take absolute paths of includes and other paths in flags.
246	ccEnv, ok := os.LookupEnv("CC")
247	if cgoEnabled && !ok {
248		return fmt.Errorf("CC must be set")
249	}
250	os.Setenv("CC", quotePathIfNeeded(abs(ccEnv)))
251
252	// We want to keep the cache around so that the processed files can be used by other tools.
253	cachePath := abs(*out + ".gocache")
254	os.Setenv("GOCACHE", cachePath)
255	os.Setenv("GOMODCACHE", cachePath)
256	os.Setenv("GOPATH", cachePath)
257
258	listArgs := goenv.goCmd("list")
259	if len(build.Default.BuildTags) > 0 {
260		listArgs = append(listArgs, "-tags", strings.Join(build.Default.BuildTags, ","))
261	}
262
263	if cgoEnabled {
264		listArgs = append(listArgs, "-compiled=true")
265	}
266
267	listArgs = append(listArgs, "-json", "builtin", "std", "runtime/cgo")
268
269	jsonFile, err := os.Create(*out)
270	if err != nil {
271		return err
272	}
273	defer jsonFile.Close()
274
275	jsonData := &bytes.Buffer{}
276	if err := goenv.runCommandToFile(jsonData, os.Stderr, listArgs); err != nil {
277		return err
278	}
279
280	encoder := json.NewEncoder(jsonFile)
281	decoder := json.NewDecoder(jsonData)
282	for decoder.More() {
283		var pkg *goListPackage
284		if err := decoder.Decode(&pkg); err != nil {
285			return err
286		}
287		if err := encoder.Encode(flatPackageForStd(cloneBase, pkg)); err != nil {
288			return err
289		}
290	}
291
292	return nil
293}
294