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