diff options
Diffstat (limited to 'go/tools/builders/stdliblist.go')
-rw-r--r-- | go/tools/builders/stdliblist.go | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/go/tools/builders/stdliblist.go b/go/tools/builders/stdliblist.go new file mode 100644 index 00000000..f6a61442 --- /dev/null +++ b/go/tools/builders/stdliblist.go @@ -0,0 +1,293 @@ +// Copyright 2021 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "go/build" + "os" + "path/filepath" + "strings" +) + +// Copy and pasted from golang.org/x/tools/go/packages +type flatPackagesError struct { + Pos string // "file:line:col" or "file:line" or "" or "-" + Msg string + Kind flatPackagesErrorKind +} + +type flatPackagesErrorKind int + +const ( + UnknownError flatPackagesErrorKind = iota + ListError + ParseError + TypeError +) + +func (err flatPackagesError) Error() string { + pos := err.Pos + if pos == "" { + pos = "-" // like token.Position{}.String() + } + return pos + ": " + err.Msg +} + +// flatPackage is the JSON form of Package +// It drops all the type and syntax fields, and transforms the Imports +type flatPackage struct { + ID string + Name string `json:",omitempty"` + PkgPath string `json:",omitempty"` + Standard bool `json:",omitempty"` + Errors []flatPackagesError `json:",omitempty"` + GoFiles []string `json:",omitempty"` + CompiledGoFiles []string `json:",omitempty"` + OtherFiles []string `json:",omitempty"` + ExportFile string `json:",omitempty"` + Imports map[string]string `json:",omitempty"` +} + +type goListPackage struct { + Dir string // directory containing package sources + ImportPath string // import path of package in dir + Name string // package name + Target string // install path + Goroot bool // is this package in the Go root? + Standard bool // is this package part of the standard Go library? + Root string // Go root or Go path dir containing this package + Export string // file containing export data (when using -export) + // Source files + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) + CgoFiles []string // .go source files that import "C" + CompiledGoFiles []string // .go files presented to compiler (when using -compiled) + IgnoredGoFiles []string // .go source files ignored due to build constraints + IgnoredOtherFiles []string // non-.go source files ignored due to build constraints + CFiles []string // .c source files + CXXFiles []string // .cc, .cxx and .cpp source files + MFiles []string // .m source files + HFiles []string // .h, .hh, .hpp and .hxx source files + FFiles []string // .f, .F, .for and .f90 Fortran source files + SFiles []string // .s source files + SwigFiles []string // .swig files + SwigCXXFiles []string // .swigcxx files + SysoFiles []string // .syso object files to add to archive + TestGoFiles []string // _test.go files in package + XTestGoFiles []string // _test.go files outside package + // Embedded files + EmbedPatterns []string // //go:embed patterns + EmbedFiles []string // files matched by EmbedPatterns + TestEmbedPatterns []string // //go:embed patterns in TestGoFiles + TestEmbedFiles []string // files matched by TestEmbedPatterns + XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles + XTestEmbedFiles []string // files matched by XTestEmbedPatterns + // Dependency information + Imports []string // import paths used by this package + ImportMap map[string]string // map from source import to ImportPath (identity entries omitted) + // Error information + Incomplete bool // this package or a dependency has an error + Error *flatPackagesError // error loading package + DepsErrors []*flatPackagesError // errors loading dependencies +} + +func stdlibPackageID(importPath string) string { + return "@io_bazel_rules_go//stdlib:" + importPath +} + +// outputBasePath replace the cloneBase with output base label +func outputBasePath(cloneBase, p string) string { + dir, _ := filepath.Rel(cloneBase, p) + return filepath.Join("__BAZEL_OUTPUT_BASE__", dir) +} + +// absoluteSourcesPaths replace cloneBase of the absolution +// paths with the label for all source files in a package +func absoluteSourcesPaths(cloneBase, pkgDir string, srcs []string) []string { + ret := make([]string, 0, len(srcs)) + pkgDir = outputBasePath(cloneBase, pkgDir) + for _, src := range srcs { + absPath := src + + // Generated files will already have an absolute path. These come from + // the compiler's cache. + if !filepath.IsAbs(src) { + absPath = filepath.Join(pkgDir, src) + } + + ret = append(ret, absPath) + } + return ret +} + +// filterGoFiles keeps only files either ending in .go or those without an +// extension (which are from the cache). This is a work around for +// https://golang.org/issue/28749: cmd/go puts assembly, C, and C++ files in +// CompiledGoFiles. +func filterGoFiles(srcs []string) []string { + ret := make([]string, 0, len(srcs)) + for _, f := range srcs { + if ext := filepath.Ext(f); ext == ".go" || ext == "" { + ret = append(ret, f) + } + } + + return ret +} + +func flatPackageForStd(cloneBase string, pkg *goListPackage) *flatPackage { + goFiles := absoluteSourcesPaths(cloneBase, pkg.Dir, pkg.GoFiles) + compiledGoFiles := absoluteSourcesPaths(cloneBase, pkg.Dir, pkg.CompiledGoFiles) + + newPkg := &flatPackage{ + ID: stdlibPackageID(pkg.ImportPath), + Name: pkg.Name, + PkgPath: pkg.ImportPath, + ExportFile: outputBasePath(cloneBase, pkg.Target), + Imports: map[string]string{}, + Standard: pkg.Standard, + GoFiles: goFiles, + CompiledGoFiles: filterGoFiles(compiledGoFiles), + } + + // imports + // + // Imports contains the IDs of all imported packages. + // ImportsMap records (path, ID) only where they differ. + ids := make(map[string]struct{}) + for _, id := range pkg.Imports { + ids[id] = struct{}{} + } + + for path, id := range pkg.ImportMap { + newPkg.Imports[path] = stdlibPackageID(id) + delete(ids, id) + } + + for id := range ids { + if id != "C" { + newPkg.Imports[id] = stdlibPackageID(id) + } + } + + return newPkg +} + +// stdliblist runs `go list -json` on the standard library and saves it to a file. +func stdliblist(args []string) error { + // process the args + flags := flag.NewFlagSet("stdliblist", flag.ExitOnError) + goenv := envFlags(flags) + out := flags.String("out", "", "Path to output go list json") + if err := flags.Parse(args); err != nil { + return err + } + if err := goenv.checkFlags(); err != nil { + return err + } + + if filepath.IsAbs(goenv.sdk) { + return fmt.Errorf("-sdk needs to be a relative path, but got %s", goenv.sdk) + } + + // In Go 1.18, the standard library started using go:embed directives. + // When Bazel runs this action, it does so inside a sandbox where GOROOT points + // to an external/go_sdk directory that contains a symlink farm of all files in + // the Go SDK. + // If we run "go list" with that GOROOT, this action will fail because those + // go:embed directives will refuse to include the symlinks in the sandbox. + // + // To work around this, cloneGoRoot creates a copy of a subset of external/go_sdk + // that is sufficient to call "go list" into a new cloneBase directory, e.g. + // "go list" needs to call "compile", which needs "pkg/tool". + // We also need to retain the same relative path to the root directory, e.g. + // "$OUTPUT_BASE/external/go_sdk" becomes + // {cloneBase}/external/go_sdk", which will be set at GOROOT later. This ensures + // that file paths in the generated JSON are still valid. + // + // Here we replicate goRoot(absolute path of goenv.sdk) to newGoRoot. + cloneBase, cleanup, err := goenv.workDir() + if err != nil { + return err + } + defer func() { cleanup() }() + + newGoRoot := filepath.Join(cloneBase, goenv.sdk) + if err := replicate(abs(goenv.sdk), abs(newGoRoot), replicatePaths("src", "pkg/tool", "pkg/include")); err != nil { + return err + } + + // Ensure paths are absolute. + absPaths := []string{} + for _, path := range filepath.SplitList(os.Getenv("PATH")) { + absPaths = append(absPaths, abs(path)) + } + os.Setenv("PATH", strings.Join(absPaths, string(os.PathListSeparator))) + os.Setenv("GOROOT", newGoRoot) + + cgoEnabled := os.Getenv("CGO_ENABLED") == "1" + // Make sure we have an absolute path to the C compiler. + // TODO(#1357): also take absolute paths of includes and other paths in flags. + ccEnv, ok := os.LookupEnv("CC") + if cgoEnabled && !ok { + return fmt.Errorf("CC must be set") + } + os.Setenv("CC", quotePathIfNeeded(abs(ccEnv))) + + // We want to keep the cache around so that the processed files can be used by other tools. + cachePath := abs(*out + ".gocache") + os.Setenv("GOCACHE", cachePath) + os.Setenv("GOMODCACHE", cachePath) + os.Setenv("GOPATH", cachePath) + + listArgs := goenv.goCmd("list") + if len(build.Default.BuildTags) > 0 { + listArgs = append(listArgs, "-tags", strings.Join(build.Default.BuildTags, ",")) + } + + if cgoEnabled { + listArgs = append(listArgs, "-compiled=true") + } + + listArgs = append(listArgs, "-json", "builtin", "std", "runtime/cgo") + + jsonFile, err := os.Create(*out) + if err != nil { + return err + } + defer jsonFile.Close() + + jsonData := &bytes.Buffer{} + if err := goenv.runCommandToFile(jsonData, os.Stderr, listArgs); err != nil { + return err + } + + encoder := json.NewEncoder(jsonFile) + decoder := json.NewDecoder(jsonData) + for decoder.More() { + var pkg *goListPackage + if err := decoder.Decode(&pkg); err != nil { + return err + } + if err := encoder.Encode(flatPackageForStd(cloneBase, pkg)); err != nil { + return err + } + } + + return nil +} |