| 1 | // Copyright 2018 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 packages |
| 6 | |
| 7 | import ( |
| 8 | "bytes" |
| 9 | "context" |
| 10 | "encoding/json" |
| 11 | "fmt" |
| 12 | "go/types" |
| 13 | "io/ioutil" |
| 14 | "log" |
| 15 | "os" |
| 16 | "path" |
| 17 | "path/filepath" |
| 18 | "reflect" |
| 19 | "sort" |
| 20 | "strconv" |
| 21 | "strings" |
| 22 | "sync" |
| 23 | "unicode" |
| 24 | |
| 25 | exec "golang.org/x/sys/execabs" |
| 26 | "golang.org/x/tools/go/internal/packagesdriver" |
| 27 | "golang.org/x/tools/internal/gocommand" |
| 28 | "golang.org/x/tools/internal/packagesinternal" |
| 29 | ) |
| 30 | |
| 31 | // debug controls verbose logging. |
| 32 | var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG")) |
| 33 | |
| 34 | // A goTooOldError reports that the go command |
| 35 | // found by exec.LookPath is too old to use the new go list behavior. |
| 36 | type goTooOldError struct { |
| 37 | error |
| 38 | } |
| 39 | |
| 40 | // responseDeduper wraps a driverResponse, deduplicating its contents. |
| 41 | type responseDeduper struct { |
| 42 | seenRoots map[string]bool |
| 43 | seenPackages map[string]*Package |
| 44 | dr *driverResponse |
| 45 | } |
| 46 | |
| 47 | func newDeduper() *responseDeduper { |
| 48 | return &responseDeduper{ |
| 49 | dr: &driverResponse{}, |
| 50 | seenRoots: map[string]bool{}, |
| 51 | seenPackages: map[string]*Package{}, |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | // addAll fills in r with a driverResponse. |
| 56 | func (r *responseDeduper) addAll(dr *driverResponse) { |
| 57 | for _, pkg := range dr.Packages { |
| 58 | r.addPackage(pkg) |
| 59 | } |
| 60 | for _, root := range dr.Roots { |
| 61 | r.addRoot(root) |
| 62 | } |
| 63 | r.dr.GoVersion = dr.GoVersion |
| 64 | } |
| 65 | |
| 66 | func (r *responseDeduper) addPackage(p *Package) { |
| 67 | if r.seenPackages[p.ID] != nil { |
| 68 | return |
| 69 | } |
| 70 | r.seenPackages[p.ID] = p |
| 71 | r.dr.Packages = append(r.dr.Packages, p) |
| 72 | } |
| 73 | |
| 74 | func (r *responseDeduper) addRoot(id string) { |
| 75 | if r.seenRoots[id] { |
| 76 | return |
| 77 | } |
| 78 | r.seenRoots[id] = true |
| 79 | r.dr.Roots = append(r.dr.Roots, id) |
| 80 | } |
| 81 | |
| 82 | type golistState struct { |
| 83 | cfg *Config |
| 84 | ctx context.Context |
| 85 | |
| 86 | envOnce sync.Once |
| 87 | goEnvError error |
| 88 | goEnv map[string]string |
| 89 | |
| 90 | rootsOnce sync.Once |
| 91 | rootDirsError error |
| 92 | rootDirs map[string]string |
| 93 | |
| 94 | goVersionOnce sync.Once |
| 95 | goVersionError error |
| 96 | goVersion int // The X in Go 1.X. |
| 97 | |
| 98 | // vendorDirs caches the (non)existence of vendor directories. |
| 99 | vendorDirs map[string]bool |
| 100 | } |
| 101 | |
| 102 | // getEnv returns Go environment variables. Only specific variables are |
| 103 | // populated -- computing all of them is slow. |
| 104 | func (state *golistState) getEnv() (map[string]string, error) { |
| 105 | state.envOnce.Do(func() { |
| 106 | var b *bytes.Buffer |
| 107 | b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH") |
| 108 | if state.goEnvError != nil { |
| 109 | return |
| 110 | } |
| 111 | |
| 112 | state.goEnv = make(map[string]string) |
| 113 | decoder := json.NewDecoder(b) |
| 114 | if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil { |
| 115 | return |
| 116 | } |
| 117 | }) |
| 118 | return state.goEnv, state.goEnvError |
| 119 | } |
| 120 | |
| 121 | // mustGetEnv is a convenience function that can be used if getEnv has already succeeded. |
| 122 | func (state *golistState) mustGetEnv() map[string]string { |
| 123 | env, err := state.getEnv() |
| 124 | if err != nil { |
| 125 | panic(fmt.Sprintf("mustGetEnv: %v", err)) |
| 126 | } |
| 127 | return env |
| 128 | } |
| 129 | |
| 130 | // goListDriver uses the go list command to interpret the patterns and produce |
| 131 | // the build system package structure. |
| 132 | // See driver for more details. |
| 133 | func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { |
| 134 | // Make sure that any asynchronous go commands are killed when we return. |
| 135 | parentCtx := cfg.Context |
| 136 | if parentCtx == nil { |
| 137 | parentCtx = context.Background() |
| 138 | } |
| 139 | ctx, cancel := context.WithCancel(parentCtx) |
| 140 | defer cancel() |
| 141 | |
| 142 | response := newDeduper() |
| 143 | |
| 144 | state := &golistState{ |
| 145 | cfg: cfg, |
| 146 | ctx: ctx, |
| 147 | vendorDirs: map[string]bool{}, |
| 148 | } |
| 149 | |
| 150 | // Fill in response.Sizes asynchronously if necessary. |
| 151 | var sizeserr error |
| 152 | var sizeswg sync.WaitGroup |
| 153 | if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { |
| 154 | sizeswg.Add(1) |
| 155 | go func() { |
| 156 | var sizes types.Sizes |
| 157 | sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) |
| 158 | // types.SizesFor always returns nil or a *types.StdSizes. |
| 159 | response.dr.Sizes, _ = sizes.(*types.StdSizes) |
| 160 | sizeswg.Done() |
| 161 | }() |
| 162 | } |
| 163 | |
| 164 | // Determine files requested in contains patterns |
| 165 | var containFiles []string |
| 166 | restPatterns := make([]string, 0, len(patterns)) |
| 167 | // Extract file= and other [querytype]= patterns. Report an error if querytype |
| 168 | // doesn't exist. |
| 169 | extractQueries: |
| 170 | for _, pattern := range patterns { |
| 171 | eqidx := strings.Index(pattern, "=") |
| 172 | if eqidx < 0 { |
| 173 | restPatterns = append(restPatterns, pattern) |
| 174 | } else { |
| 175 | query, value := pattern[:eqidx], pattern[eqidx+len("="):] |
| 176 | switch query { |
| 177 | case "file": |
| 178 | containFiles = append(containFiles, value) |
| 179 | case "pattern": |
| 180 | restPatterns = append(restPatterns, value) |
| 181 | case "": // not a reserved query |
| 182 | restPatterns = append(restPatterns, pattern) |
| 183 | default: |
| 184 | for _, rune := range query { |
| 185 | if rune < 'a' || rune > 'z' { // not a reserved query |
| 186 | restPatterns = append(restPatterns, pattern) |
| 187 | continue extractQueries |
| 188 | } |
| 189 | } |
| 190 | // Reject all other patterns containing "=" |
| 191 | return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern) |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // See if we have any patterns to pass through to go list. Zero initial |
| 197 | // patterns also requires a go list call, since it's the equivalent of |
| 198 | // ".". |
| 199 | if len(restPatterns) > 0 || len(patterns) == 0 { |
| 200 | dr, err := state.createDriverResponse(restPatterns...) |
| 201 | if err != nil { |
| 202 | return nil, err |
| 203 | } |
| 204 | response.addAll(dr) |
| 205 | } |
| 206 | |
| 207 | if len(containFiles) != 0 { |
| 208 | if err := state.runContainsQueries(response, containFiles); err != nil { |
| 209 | return nil, err |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // Only use go/packages' overlay processing if we're using a Go version |
| 214 | // below 1.16. Otherwise, go list handles it. |
| 215 | if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 { |
| 216 | modifiedPkgs, needPkgs, err := state.processGolistOverlay(response) |
| 217 | if err != nil { |
| 218 | return nil, err |
| 219 | } |
| 220 | |
| 221 | var containsCandidates []string |
| 222 | if len(containFiles) > 0 { |
| 223 | containsCandidates = append(containsCandidates, modifiedPkgs...) |
| 224 | containsCandidates = append(containsCandidates, needPkgs...) |
| 225 | } |
| 226 | if err := state.addNeededOverlayPackages(response, needPkgs); err != nil { |
| 227 | return nil, err |
| 228 | } |
| 229 | // Check candidate packages for containFiles. |
| 230 | if len(containFiles) > 0 { |
| 231 | for _, id := range containsCandidates { |
| 232 | pkg, ok := response.seenPackages[id] |
| 233 | if !ok { |
| 234 | response.addPackage(&Package{ |
| 235 | ID: id, |
| 236 | Errors: []Error{{ |
| 237 | Kind: ListError, |
| 238 | Msg: fmt.Sprintf("package %s expected but not seen", id), |
| 239 | }}, |
| 240 | }) |
| 241 | continue |
| 242 | } |
| 243 | for _, f := range containFiles { |
| 244 | for _, g := range pkg.GoFiles { |
| 245 | if sameFile(f, g) { |
| 246 | response.addRoot(id) |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | // Add root for any package that matches a pattern. This applies only to |
| 253 | // packages that are modified by overlays, since they are not added as |
| 254 | // roots automatically. |
| 255 | for _, pattern := range restPatterns { |
| 256 | match := matchPattern(pattern) |
| 257 | for _, pkgID := range modifiedPkgs { |
| 258 | pkg, ok := response.seenPackages[pkgID] |
| 259 | if !ok { |
| 260 | continue |
| 261 | } |
| 262 | if match(pkg.PkgPath) { |
| 263 | response.addRoot(pkg.ID) |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | sizeswg.Wait() |
| 270 | if sizeserr != nil { |
| 271 | return nil, sizeserr |
| 272 | } |
| 273 | return response.dr, nil |
| 274 | } |
| 275 | |
| 276 | func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error { |
| 277 | if len(pkgs) == 0 { |
| 278 | return nil |
| 279 | } |
| 280 | dr, err := state.createDriverResponse(pkgs...) |
| 281 | if err != nil { |
| 282 | return err |
| 283 | } |
| 284 | for _, pkg := range dr.Packages { |
| 285 | response.addPackage(pkg) |
| 286 | } |
| 287 | _, needPkgs, err := state.processGolistOverlay(response) |
| 288 | if err != nil { |
| 289 | return err |
| 290 | } |
| 291 | return state.addNeededOverlayPackages(response, needPkgs) |
| 292 | } |
| 293 | |
| 294 | func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error { |
| 295 | for _, query := range queries { |
| 296 | // TODO(matloob): Do only one query per directory. |
| 297 | fdir := filepath.Dir(query) |
| 298 | // Pass absolute path of directory to go list so that it knows to treat it as a directory, |
| 299 | // not a package path. |
| 300 | pattern, err := filepath.Abs(fdir) |
| 301 | if err != nil { |
| 302 | return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) |
| 303 | } |
| 304 | dirResponse, err := state.createDriverResponse(pattern) |
| 305 | |
| 306 | // If there was an error loading the package, or no packages are returned, |
| 307 | // or the package is returned with errors, try to load the file as an |
| 308 | // ad-hoc package. |
| 309 | // Usually the error will appear in a returned package, but may not if we're |
| 310 | // in module mode and the ad-hoc is located outside a module. |
| 311 | if err != nil || len(dirResponse.Packages) == 0 || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && |
| 312 | len(dirResponse.Packages[0].Errors) == 1 { |
| 313 | var queryErr error |
| 314 | if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil { |
| 315 | return err // return the original error |
| 316 | } |
| 317 | } |
| 318 | isRoot := make(map[string]bool, len(dirResponse.Roots)) |
| 319 | for _, root := range dirResponse.Roots { |
| 320 | isRoot[root] = true |
| 321 | } |
| 322 | for _, pkg := range dirResponse.Packages { |
| 323 | // Add any new packages to the main set |
| 324 | // We don't bother to filter packages that will be dropped by the changes of roots, |
| 325 | // that will happen anyway during graph construction outside this function. |
| 326 | // Over-reporting packages is not a problem. |
| 327 | response.addPackage(pkg) |
| 328 | // if the package was not a root one, it cannot have the file |
| 329 | if !isRoot[pkg.ID] { |
| 330 | continue |
| 331 | } |
| 332 | for _, pkgFile := range pkg.GoFiles { |
| 333 | if filepath.Base(query) == filepath.Base(pkgFile) { |
| 334 | response.addRoot(pkg.ID) |
| 335 | break |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | } |
| 340 | return nil |
| 341 | } |
| 342 | |
| 343 | // adhocPackage attempts to load or construct an ad-hoc package for a given |
| 344 | // query, if the original call to the driver produced inadequate results. |
| 345 | func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) { |
| 346 | response, err := state.createDriverResponse(query) |
| 347 | if err != nil { |
| 348 | return nil, err |
| 349 | } |
| 350 | // If we get nothing back from `go list`, |
| 351 | // try to make this file into its own ad-hoc package. |
| 352 | // TODO(rstambler): Should this check against the original response? |
| 353 | if len(response.Packages) == 0 { |
| 354 | response.Packages = append(response.Packages, &Package{ |
| 355 | ID: "command-line-arguments", |
| 356 | PkgPath: query, |
| 357 | GoFiles: []string{query}, |
| 358 | CompiledGoFiles: []string{query}, |
| 359 | Imports: make(map[string]*Package), |
| 360 | }) |
| 361 | response.Roots = append(response.Roots, "command-line-arguments") |
| 362 | } |
| 363 | // Handle special cases. |
| 364 | if len(response.Packages) == 1 { |
| 365 | // golang/go#33482: If this is a file= query for ad-hoc packages where |
| 366 | // the file only exists on an overlay, and exists outside of a module, |
| 367 | // add the file to the package and remove the errors. |
| 368 | if response.Packages[0].ID == "command-line-arguments" || |
| 369 | filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) { |
| 370 | if len(response.Packages[0].GoFiles) == 0 { |
| 371 | filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath |
| 372 | // TODO(matloob): check if the file is outside of a root dir? |
| 373 | for path := range state.cfg.Overlay { |
| 374 | if path == filename { |
| 375 | response.Packages[0].Errors = nil |
| 376 | response.Packages[0].GoFiles = []string{path} |
| 377 | response.Packages[0].CompiledGoFiles = []string{path} |
| 378 | } |
| 379 | } |
| 380 | } |
| 381 | } |
| 382 | } |
| 383 | return response, nil |
| 384 | } |
| 385 | |
| 386 | // Fields must match go list; |
| 387 | // see $GOROOT/src/cmd/go/internal/load/pkg.go. |
| 388 | type jsonPackage struct { |
| 389 | ImportPath string |
| 390 | Dir string |
| 391 | Name string |
| 392 | Export string |
| 393 | GoFiles []string |
| 394 | CompiledGoFiles []string |
| 395 | IgnoredGoFiles []string |
| 396 | IgnoredOtherFiles []string |
| 397 | EmbedPatterns []string |
| 398 | EmbedFiles []string |
| 399 | CFiles []string |
| 400 | CgoFiles []string |
| 401 | CXXFiles []string |
| 402 | MFiles []string |
| 403 | HFiles []string |
| 404 | FFiles []string |
| 405 | SFiles []string |
| 406 | SwigFiles []string |
| 407 | SwigCXXFiles []string |
| 408 | SysoFiles []string |
| 409 | Imports []string |
| 410 | ImportMap map[string]string |
| 411 | Deps []string |
| 412 | Module *Module |
| 413 | TestGoFiles []string |
| 414 | TestImports []string |
| 415 | XTestGoFiles []string |
| 416 | XTestImports []string |
| 417 | ForTest string // q in a "p [q.test]" package, else "" |
| 418 | DepOnly bool |
| 419 | |
| 420 | Error *packagesinternal.PackageError |
| 421 | DepsErrors []*packagesinternal.PackageError |
| 422 | } |
| 423 | |
| 424 | type jsonPackageError struct { |
| 425 | ImportStack []string |
| 426 | Pos string |
| 427 | Err string |
| 428 | } |
| 429 | |
| 430 | func otherFiles(p *jsonPackage) [][]string { |
| 431 | return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} |
| 432 | } |
| 433 | |
| 434 | // createDriverResponse uses the "go list" command to expand the pattern |
| 435 | // words and return a response for the specified packages. |
| 436 | func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { |
| 437 | // go list uses the following identifiers in ImportPath and Imports: |
| 438 | // |
| 439 | // "p" -- importable package or main (command) |
| 440 | // "q.test" -- q's test executable |
| 441 | // "p [q.test]" -- variant of p as built for q's test executable |
| 442 | // "q_test [q.test]" -- q's external test package |
| 443 | // |
| 444 | // The packages p that are built differently for a test q.test |
| 445 | // are q itself, plus any helpers used by the external test q_test, |
| 446 | // typically including "testing" and all its dependencies. |
| 447 | |
| 448 | // Run "go list" for complete |
| 449 | // information on the specified packages. |
| 450 | goVersion, err := state.getGoVersion() |
| 451 | if err != nil { |
| 452 | return nil, err |
| 453 | } |
| 454 | buf, err := state.invokeGo("list", golistargs(state.cfg, words, goVersion)...) |
| 455 | if err != nil { |
| 456 | return nil, err |
| 457 | } |
| 458 | |
| 459 | seen := make(map[string]*jsonPackage) |
| 460 | pkgs := make(map[string]*Package) |
| 461 | additionalErrors := make(map[string][]Error) |
| 462 | // Decode the JSON and convert it to Package form. |
| 463 | response := &driverResponse{ |
| 464 | GoVersion: goVersion, |
| 465 | } |
| 466 | for dec := json.NewDecoder(buf); dec.More(); { |
| 467 | p := new(jsonPackage) |
| 468 | if err := dec.Decode(p); err != nil { |
| 469 | return nil, fmt.Errorf("JSON decoding failed: %v", err) |
| 470 | } |
| 471 | |
| 472 | if p.ImportPath == "" { |
| 473 | // The documentation for go list says that “[e]rroneous packages will have |
| 474 | // a non-empty ImportPath”. If for some reason it comes back empty, we |
| 475 | // prefer to error out rather than silently discarding data or handing |
| 476 | // back a package without any way to refer to it. |
| 477 | if p.Error != nil { |
| 478 | return nil, Error{ |
| 479 | Pos: p.Error.Pos, |
| 480 | Msg: p.Error.Err, |
| 481 | } |
| 482 | } |
| 483 | return nil, fmt.Errorf("package missing import path: %+v", p) |
| 484 | } |
| 485 | |
| 486 | // Work around https://golang.org/issue/33157: |
| 487 | // go list -e, when given an absolute path, will find the package contained at |
| 488 | // that directory. But when no package exists there, it will return a fake package |
| 489 | // with an error and the ImportPath set to the absolute path provided to go list. |
| 490 | // Try to convert that absolute path to what its package path would be if it's |
| 491 | // contained in a known module or GOPATH entry. This will allow the package to be |
| 492 | // properly "reclaimed" when overlays are processed. |
| 493 | if filepath.IsAbs(p.ImportPath) && p.Error != nil { |
| 494 | pkgPath, ok, err := state.getPkgPath(p.ImportPath) |
| 495 | if err != nil { |
| 496 | return nil, err |
| 497 | } |
| 498 | if ok { |
| 499 | p.ImportPath = pkgPath |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | if old, found := seen[p.ImportPath]; found { |
| 504 | // If one version of the package has an error, and the other doesn't, assume |
| 505 | // that this is a case where go list is reporting a fake dependency variant |
| 506 | // of the imported package: When a package tries to invalidly import another |
| 507 | // package, go list emits a variant of the imported package (with the same |
| 508 | // import path, but with an error on it, and the package will have a |
| 509 | // DepError set on it). An example of when this can happen is for imports of |
| 510 | // main packages: main packages can not be imported, but they may be |
| 511 | // separately matched and listed by another pattern. |
| 512 | // See golang.org/issue/36188 for more details. |
| 513 | |
| 514 | // The plan is that eventually, hopefully in Go 1.15, the error will be |
| 515 | // reported on the importing package rather than the duplicate "fake" |
| 516 | // version of the imported package. Once all supported versions of Go |
| 517 | // have the new behavior this logic can be deleted. |
| 518 | // TODO(matloob): delete the workaround logic once all supported versions of |
| 519 | // Go return the errors on the proper package. |
| 520 | |
| 521 | // There should be exactly one version of a package that doesn't have an |
| 522 | // error. |
| 523 | if old.Error == nil && p.Error == nil { |
| 524 | if !reflect.DeepEqual(p, old) { |
| 525 | return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) |
| 526 | } |
| 527 | continue |
| 528 | } |
| 529 | |
| 530 | // Determine if this package's error needs to be bubbled up. |
| 531 | // This is a hack, and we expect for go list to eventually set the error |
| 532 | // on the package. |
| 533 | if old.Error != nil { |
| 534 | var errkind string |
| 535 | if strings.Contains(old.Error.Err, "not an importable package") { |
| 536 | errkind = "not an importable package" |
| 537 | } else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") { |
| 538 | errkind = "use of internal package not allowed" |
| 539 | } |
| 540 | if errkind != "" { |
| 541 | if len(old.Error.ImportStack) < 1 { |
| 542 | return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind) |
| 543 | } |
| 544 | importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1] |
| 545 | if importingPkg == old.ImportPath { |
| 546 | // Using an older version of Go which put this package itself on top of import |
| 547 | // stack, instead of the importer. Look for importer in second from top |
| 548 | // position. |
| 549 | if len(old.Error.ImportStack) < 2 { |
| 550 | return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind) |
| 551 | } |
| 552 | importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2] |
| 553 | } |
| 554 | additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ |
| 555 | Pos: old.Error.Pos, |
| 556 | Msg: old.Error.Err, |
| 557 | Kind: ListError, |
| 558 | }) |
| 559 | } |
| 560 | } |
| 561 | |
| 562 | // Make sure that if there's a version of the package without an error, |
| 563 | // that's the one reported to the user. |
| 564 | if old.Error == nil { |
| 565 | continue |
| 566 | } |
| 567 | |
| 568 | // This package will replace the old one at the end of the loop. |
| 569 | } |
| 570 | seen[p.ImportPath] = p |
| 571 | |
| 572 | pkg := &Package{ |
| 573 | Name: p.Name, |
| 574 | ID: p.ImportPath, |
| 575 | GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), |
| 576 | CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), |
| 577 | OtherFiles: absJoin(p.Dir, otherFiles(p)...), |
| 578 | EmbedFiles: absJoin(p.Dir, p.EmbedFiles), |
| 579 | EmbedPatterns: absJoin(p.Dir, p.EmbedPatterns), |
| 580 | IgnoredFiles: absJoin(p.Dir, p.IgnoredGoFiles, p.IgnoredOtherFiles), |
| 581 | forTest: p.ForTest, |
| 582 | depsErrors: p.DepsErrors, |
| 583 | Module: p.Module, |
| 584 | } |
| 585 | |
| 586 | if (state.cfg.Mode&typecheckCgo) != 0 && len(p.CgoFiles) != 0 { |
| 587 | if len(p.CompiledGoFiles) > len(p.GoFiles) { |
| 588 | // We need the cgo definitions, which are in the first |
| 589 | // CompiledGoFile after the non-cgo ones. This is a hack but there |
| 590 | // isn't currently a better way to find it. We also need the pure |
| 591 | // Go files and unprocessed cgo files, all of which are already |
| 592 | // in pkg.GoFiles. |
| 593 | cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] |
| 594 | pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) |
| 595 | } else { |
| 596 | // golang/go#38990: go list silently fails to do cgo processing |
| 597 | pkg.CompiledGoFiles = nil |
| 598 | pkg.Errors = append(pkg.Errors, Error{ |
| 599 | Msg: "go list failed to return CompiledGoFiles. This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.", |
| 600 | Kind: ListError, |
| 601 | }) |
| 602 | } |
| 603 | } |
| 604 | |
| 605 | // Work around https://golang.org/issue/28749: |
| 606 | // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. |
| 607 | // Remove files from CompiledGoFiles that are non-go files |
| 608 | // (or are not files that look like they are from the cache). |
| 609 | if len(pkg.CompiledGoFiles) > 0 { |
| 610 | out := pkg.CompiledGoFiles[:0] |
| 611 | for _, f := range pkg.CompiledGoFiles { |
| 612 | if ext := filepath.Ext(f); ext != ".go" && ext != "" { // ext == "" means the file is from the cache, so probably cgo-processed file |
| 613 | continue |
| 614 | } |
| 615 | out = append(out, f) |
| 616 | } |
| 617 | pkg.CompiledGoFiles = out |
| 618 | } |
| 619 | |
| 620 | // Extract the PkgPath from the package's ID. |
| 621 | if i := strings.IndexByte(pkg.ID, ' '); i >= 0 { |
| 622 | pkg.PkgPath = pkg.ID[:i] |
| 623 | } else { |
| 624 | pkg.PkgPath = pkg.ID |
| 625 | } |
| 626 | |
| 627 | if pkg.PkgPath == "unsafe" { |
| 628 | pkg.GoFiles = nil // ignore fake unsafe.go file |
| 629 | } |
| 630 | |
| 631 | // Assume go list emits only absolute paths for Dir. |
| 632 | if p.Dir != "" && !filepath.IsAbs(p.Dir) { |
| 633 | log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir) |
| 634 | } |
| 635 | |
| 636 | if p.Export != "" && !filepath.IsAbs(p.Export) { |
| 637 | pkg.ExportFile = filepath.Join(p.Dir, p.Export) |
| 638 | } else { |
| 639 | pkg.ExportFile = p.Export |
| 640 | } |
| 641 | |
| 642 | // imports |
| 643 | // |
| 644 | // Imports contains the IDs of all imported packages. |
| 645 | // ImportsMap records (path, ID) only where they differ. |
| 646 | ids := make(map[string]bool) |
| 647 | for _, id := range p.Imports { |
| 648 | ids[id] = true |
| 649 | } |
| 650 | pkg.Imports = make(map[string]*Package) |
| 651 | for path, id := range p.ImportMap { |
| 652 | pkg.Imports[path] = &Package{ID: id} // non-identity import |
| 653 | delete(ids, id) |
| 654 | } |
| 655 | for id := range ids { |
| 656 | if id == "C" { |
| 657 | continue |
| 658 | } |
| 659 | |
| 660 | pkg.Imports[id] = &Package{ID: id} // identity import |
| 661 | } |
| 662 | if !p.DepOnly { |
| 663 | response.Roots = append(response.Roots, pkg.ID) |
| 664 | } |
| 665 | |
| 666 | // Work around for pre-go.1.11 versions of go list. |
| 667 | // TODO(matloob): they should be handled by the fallback. |
| 668 | // Can we delete this? |
| 669 | if len(pkg.CompiledGoFiles) == 0 { |
| 670 | pkg.CompiledGoFiles = pkg.GoFiles |
| 671 | } |
| 672 | |
| 673 | // Temporary work-around for golang/go#39986. Parse filenames out of |
| 674 | // error messages. This happens if there are unrecoverable syntax |
| 675 | // errors in the source, so we can't match on a specific error message. |
| 676 | if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) { |
| 677 | addFilenameFromPos := func(pos string) bool { |
| 678 | split := strings.Split(pos, ":") |
| 679 | if len(split) < 1 { |
| 680 | return false |
| 681 | } |
| 682 | filename := strings.TrimSpace(split[0]) |
| 683 | if filename == "" { |
| 684 | return false |
| 685 | } |
| 686 | if !filepath.IsAbs(filename) { |
| 687 | filename = filepath.Join(state.cfg.Dir, filename) |
| 688 | } |
| 689 | info, _ := os.Stat(filename) |
| 690 | if info == nil { |
| 691 | return false |
| 692 | } |
| 693 | pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) |
| 694 | pkg.GoFiles = append(pkg.GoFiles, filename) |
| 695 | return true |
| 696 | } |
| 697 | found := addFilenameFromPos(err.Pos) |
| 698 | // In some cases, go list only reports the error position in the |
| 699 | // error text, not the error position. One such case is when the |
| 700 | // file's package name is a keyword (see golang.org/issue/39763). |
| 701 | if !found { |
| 702 | addFilenameFromPos(err.Err) |
| 703 | } |
| 704 | } |
| 705 | |
| 706 | if p.Error != nil { |
| 707 | msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. |
| 708 | // Address golang.org/issue/35964 by appending import stack to error message. |
| 709 | if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 { |
| 710 | msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack) |
| 711 | } |
| 712 | pkg.Errors = append(pkg.Errors, Error{ |
| 713 | Pos: p.Error.Pos, |
| 714 | Msg: msg, |
| 715 | Kind: ListError, |
| 716 | }) |
| 717 | } |
| 718 | |
| 719 | pkgs[pkg.ID] = pkg |
| 720 | } |
| 721 | |
| 722 | for id, errs := range additionalErrors { |
| 723 | if p, ok := pkgs[id]; ok { |
| 724 | p.Errors = append(p.Errors, errs...) |
| 725 | } |
| 726 | } |
| 727 | for _, pkg := range pkgs { |
| 728 | response.Packages = append(response.Packages, pkg) |
| 729 | } |
| 730 | sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID }) |
| 731 | |
| 732 | return response, nil |
| 733 | } |
| 734 | |
| 735 | func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool { |
| 736 | if len(p.GoFiles) > 0 || len(p.CompiledGoFiles) > 0 { |
| 737 | return false |
| 738 | } |
| 739 | |
| 740 | goV, err := state.getGoVersion() |
| 741 | if err != nil { |
| 742 | return false |
| 743 | } |
| 744 | |
| 745 | // On Go 1.14 and earlier, only add filenames from errors if the import stack is empty. |
| 746 | // The import stack behaves differently for these versions than newer Go versions. |
| 747 | if goV < 15 { |
| 748 | return len(p.Error.ImportStack) == 0 |
| 749 | } |
| 750 | |
| 751 | // On Go 1.15 and later, only parse filenames out of error if there's no import stack, |
| 752 | // or the current package is at the top of the import stack. This is not guaranteed |
| 753 | // to work perfectly, but should avoid some cases where files in errors don't belong to this |
| 754 | // package. |
| 755 | return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath |
| 756 | } |
| 757 | |
| 758 | // getGoVersion returns the effective minor version of the go command. |
| 759 | func (state *golistState) getGoVersion() (int, error) { |
| 760 | state.goVersionOnce.Do(func() { |
| 761 | state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.cfg.gocmdRunner) |
| 762 | }) |
| 763 | return state.goVersion, state.goVersionError |
| 764 | } |
| 765 | |
| 766 | // getPkgPath finds the package path of a directory if it's relative to a root |
| 767 | // directory. |
| 768 | func (state *golistState) getPkgPath(dir string) (string, bool, error) { |
| 769 | absDir, err := filepath.Abs(dir) |
| 770 | if err != nil { |
| 771 | return "", false, err |
| 772 | } |
| 773 | roots, err := state.determineRootDirs() |
| 774 | if err != nil { |
| 775 | return "", false, err |
| 776 | } |
| 777 | |
| 778 | for rdir, rpath := range roots { |
| 779 | // Make sure that the directory is in the module, |
| 780 | // to avoid creating a path relative to another module. |
| 781 | if !strings.HasPrefix(absDir, rdir) { |
| 782 | continue |
| 783 | } |
| 784 | // TODO(matloob): This doesn't properly handle symlinks. |
| 785 | r, err := filepath.Rel(rdir, dir) |
| 786 | if err != nil { |
| 787 | continue |
| 788 | } |
| 789 | if rpath != "" { |
| 790 | // We choose only one root even though the directory even it can belong in multiple modules |
| 791 | // or GOPATH entries. This is okay because we only need to work with absolute dirs when a |
| 792 | // file is missing from disk, for instance when gopls calls go/packages in an overlay. |
| 793 | // Once the file is saved, gopls, or the next invocation of the tool will get the correct |
| 794 | // result straight from golist. |
| 795 | // TODO(matloob): Implement module tiebreaking? |
| 796 | return path.Join(rpath, filepath.ToSlash(r)), true, nil |
| 797 | } |
| 798 | return filepath.ToSlash(r), true, nil |
| 799 | } |
| 800 | return "", false, nil |
| 801 | } |
| 802 | |
| 803 | // absJoin absolutizes and flattens the lists of files. |
| 804 | func absJoin(dir string, fileses ...[]string) (res []string) { |
| 805 | for _, files := range fileses { |
| 806 | for _, file := range files { |
| 807 | if !filepath.IsAbs(file) { |
| 808 | file = filepath.Join(dir, file) |
| 809 | } |
| 810 | res = append(res, file) |
| 811 | } |
| 812 | } |
| 813 | return res |
| 814 | } |
| 815 | |
| 816 | func jsonFlag(cfg *Config, goVersion int) string { |
| 817 | if goVersion < 19 { |
| 818 | return "-json" |
| 819 | } |
| 820 | var fields []string |
| 821 | added := make(map[string]bool) |
| 822 | addFields := func(fs ...string) { |
| 823 | for _, f := range fs { |
| 824 | if !added[f] { |
| 825 | added[f] = true |
| 826 | fields = append(fields, f) |
| 827 | } |
| 828 | } |
| 829 | } |
| 830 | addFields("Name", "ImportPath", "Error") // These fields are always needed |
| 831 | if cfg.Mode&NeedFiles != 0 || cfg.Mode&NeedTypes != 0 { |
| 832 | addFields("Dir", "GoFiles", "IgnoredGoFiles", "IgnoredOtherFiles", "CFiles", |
| 833 | "CgoFiles", "CXXFiles", "MFiles", "HFiles", "FFiles", "SFiles", |
| 834 | "SwigFiles", "SwigCXXFiles", "SysoFiles") |
| 835 | if cfg.Tests { |
| 836 | addFields("TestGoFiles", "XTestGoFiles") |
| 837 | } |
| 838 | } |
| 839 | if cfg.Mode&NeedTypes != 0 { |
| 840 | // CompiledGoFiles seems to be required for the test case TestCgoNoSyntax, |
| 841 | // even when -compiled isn't passed in. |
| 842 | // TODO(#52435): Should we make the test ask for -compiled, or automatically |
| 843 | // request CompiledGoFiles in certain circumstances? |
| 844 | addFields("Dir", "CompiledGoFiles") |
| 845 | } |
| 846 | if cfg.Mode&NeedCompiledGoFiles != 0 { |
| 847 | addFields("Dir", "CompiledGoFiles", "Export") |
| 848 | } |
| 849 | if cfg.Mode&NeedImports != 0 { |
| 850 | // When imports are requested, DepOnly is used to distinguish between packages |
| 851 | // explicitly requested and transitive imports of those packages. |
| 852 | addFields("DepOnly", "Imports", "ImportMap") |
| 853 | if cfg.Tests { |
| 854 | addFields("TestImports", "XTestImports") |
| 855 | } |
| 856 | } |
| 857 | if cfg.Mode&NeedDeps != 0 { |
| 858 | addFields("DepOnly") |
| 859 | } |
| 860 | if usesExportData(cfg) { |
| 861 | // Request Dir in the unlikely case Export is not absolute. |
| 862 | addFields("Dir", "Export") |
| 863 | } |
| 864 | if cfg.Mode&needInternalForTest != 0 { |
| 865 | addFields("ForTest") |
| 866 | } |
| 867 | if cfg.Mode&needInternalDepsErrors != 0 { |
| 868 | addFields("DepsErrors") |
| 869 | } |
| 870 | if cfg.Mode&NeedModule != 0 { |
| 871 | addFields("Module") |
| 872 | } |
| 873 | if cfg.Mode&NeedEmbedFiles != 0 { |
| 874 | addFields("EmbedFiles") |
| 875 | } |
| 876 | if cfg.Mode&NeedEmbedPatterns != 0 { |
| 877 | addFields("EmbedPatterns") |
| 878 | } |
| 879 | return "-json=" + strings.Join(fields, ",") |
| 880 | } |
| 881 | |
| 882 | func golistargs(cfg *Config, words []string, goVersion int) []string { |
| 883 | const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo |
| 884 | fullargs := []string{ |
| 885 | "-e", jsonFlag(cfg, goVersion), |
| 886 | fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0), |
| 887 | fmt.Sprintf("-test=%t", cfg.Tests), |
| 888 | fmt.Sprintf("-export=%t", usesExportData(cfg)), |
| 889 | fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), |
| 890 | // go list doesn't let you pass -test and -find together, |
| 891 | // probably because you'd just get the TestMain. |
| 892 | fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)), |
| 893 | } |
| 894 | fullargs = append(fullargs, cfg.BuildFlags...) |
| 895 | fullargs = append(fullargs, "--") |
| 896 | fullargs = append(fullargs, words...) |
| 897 | return fullargs |
| 898 | } |
| 899 | |
| 900 | // cfgInvocation returns an Invocation that reflects cfg's settings. |
| 901 | func (state *golistState) cfgInvocation() gocommand.Invocation { |
| 902 | cfg := state.cfg |
| 903 | return gocommand.Invocation{ |
| 904 | BuildFlags: cfg.BuildFlags, |
| 905 | ModFile: cfg.modFile, |
| 906 | ModFlag: cfg.modFlag, |
| 907 | CleanEnv: cfg.Env != nil, |
| 908 | Env: cfg.Env, |
| 909 | Logf: cfg.Logf, |
| 910 | WorkingDir: cfg.Dir, |
| 911 | } |
| 912 | } |
| 913 | |
| 914 | // invokeGo returns the stdout of a go command invocation. |
| 915 | func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { |
| 916 | cfg := state.cfg |
| 917 | |
| 918 | inv := state.cfgInvocation() |
| 919 | |
| 920 | // For Go versions 1.16 and above, `go list` accepts overlays directly via |
| 921 | // the -overlay flag. Set it, if it's available. |
| 922 | // |
| 923 | // The check for "list" is not necessarily required, but we should avoid |
| 924 | // getting the go version if possible. |
| 925 | if verb == "list" { |
| 926 | goVersion, err := state.getGoVersion() |
| 927 | if err != nil { |
| 928 | return nil, err |
| 929 | } |
| 930 | if goVersion >= 16 { |
| 931 | filename, cleanup, err := state.writeOverlays() |
| 932 | if err != nil { |
| 933 | return nil, err |
| 934 | } |
| 935 | defer cleanup() |
| 936 | inv.Overlay = filename |
| 937 | } |
| 938 | } |
| 939 | inv.Verb = verb |
| 940 | inv.Args = args |
| 941 | gocmdRunner := cfg.gocmdRunner |
| 942 | if gocmdRunner == nil { |
| 943 | gocmdRunner = &gocommand.Runner{} |
| 944 | } |
| 945 | stdout, stderr, friendlyErr, err := gocmdRunner.RunRaw(cfg.Context, inv) |
| 946 | if err != nil { |
| 947 | // Check for 'go' executable not being found. |
| 948 | if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { |
| 949 | return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) |
| 950 | } |
| 951 | |
| 952 | exitErr, ok := err.(*exec.ExitError) |
| 953 | if !ok { |
| 954 | // Catastrophic error: |
| 955 | // - context cancellation |
| 956 | return nil, fmt.Errorf("couldn't run 'go': %w", err) |
| 957 | } |
| 958 | |
| 959 | // Old go version? |
| 960 | if strings.Contains(stderr.String(), "flag provided but not defined") { |
| 961 | return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} |
| 962 | } |
| 963 | |
| 964 | // Related to #24854 |
| 965 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") { |
| 966 | return nil, friendlyErr |
| 967 | } |
| 968 | |
| 969 | // Is there an error running the C compiler in cgo? This will be reported in the "Error" field |
| 970 | // and should be suppressed by go list -e. |
| 971 | // |
| 972 | // This condition is not perfect yet because the error message can include other error messages than runtime/cgo. |
| 973 | isPkgPathRune := func(r rune) bool { |
| 974 | // From https://golang.org/ref/spec#Import_declarations: |
| 975 | // Implementation restriction: A compiler may restrict ImportPaths to non-empty strings |
| 976 | // using only characters belonging to Unicode's L, M, N, P, and S general categories |
| 977 | // (the Graphic characters without spaces) and may also exclude the |
| 978 | // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. |
| 979 | return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && |
| 980 | !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) |
| 981 | } |
| 982 | // golang/go#36770: Handle case where cmd/go prints module download messages before the error. |
| 983 | msg := stderr.String() |
| 984 | for strings.HasPrefix(msg, "go: downloading") { |
| 985 | msg = msg[strings.IndexRune(msg, '\n')+1:] |
| 986 | } |
| 987 | if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { |
| 988 | msg := msg[len("# "):] |
| 989 | if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") { |
| 990 | return stdout, nil |
| 991 | } |
| 992 | // Treat pkg-config errors as a special case (golang.org/issue/36770). |
| 993 | if strings.HasPrefix(msg, "pkg-config") { |
| 994 | return stdout, nil |
| 995 | } |
| 996 | } |
| 997 | |
| 998 | // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show |
| 999 | // the error in the Err section of stdout in case -e option is provided. |
| 1000 | // This fix is provided for backwards compatibility. |
| 1001 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") { |
| 1002 | output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1003 | strings.Trim(stderr.String(), "\n")) |
| 1004 | return bytes.NewBufferString(output), nil |
| 1005 | } |
| 1006 | |
| 1007 | // Similar to the previous error, but currently lacks a fix in Go. |
| 1008 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") { |
| 1009 | output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1010 | strings.Trim(stderr.String(), "\n")) |
| 1011 | return bytes.NewBufferString(output), nil |
| 1012 | } |
| 1013 | |
| 1014 | // Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath. |
| 1015 | // If the package doesn't exist, put the absolute path of the directory into the error message, |
| 1016 | // as Go 1.13 list does. |
| 1017 | const noSuchDirectory = "no such directory" |
| 1018 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) { |
| 1019 | errstr := stderr.String() |
| 1020 | abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):]) |
| 1021 | output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1022 | abspath, strings.Trim(stderr.String(), "\n")) |
| 1023 | return bytes.NewBufferString(output), nil |
| 1024 | } |
| 1025 | |
| 1026 | // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist. |
| 1027 | // Note that the error message we look for in this case is different that the one looked for above. |
| 1028 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") { |
| 1029 | output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1030 | strings.Trim(stderr.String(), "\n")) |
| 1031 | return bytes.NewBufferString(output), nil |
| 1032 | } |
| 1033 | |
| 1034 | // Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a |
| 1035 | // directory outside any module. |
| 1036 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") { |
| 1037 | output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1038 | // TODO(matloob): command-line-arguments isn't correct here. |
| 1039 | "command-line-arguments", strings.Trim(stderr.String(), "\n")) |
| 1040 | return bytes.NewBufferString(output), nil |
| 1041 | } |
| 1042 | |
| 1043 | // Another variation of the previous error |
| 1044 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") { |
| 1045 | output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1046 | // TODO(matloob): command-line-arguments isn't correct here. |
| 1047 | "command-line-arguments", strings.Trim(stderr.String(), "\n")) |
| 1048 | return bytes.NewBufferString(output), nil |
| 1049 | } |
| 1050 | |
| 1051 | // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit |
| 1052 | // status if there's a dependency on a package that doesn't exist. But it should return |
| 1053 | // a zero exit status and set an error on that package. |
| 1054 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { |
| 1055 | // Don't clobber stdout if `go list` actually returned something. |
| 1056 | if len(stdout.String()) > 0 { |
| 1057 | return stdout, nil |
| 1058 | } |
| 1059 | // try to extract package name from string |
| 1060 | stderrStr := stderr.String() |
| 1061 | var importPath string |
| 1062 | colon := strings.Index(stderrStr, ":") |
| 1063 | if colon > 0 && strings.HasPrefix(stderrStr, "go build ") { |
| 1064 | importPath = stderrStr[len("go build "):colon] |
| 1065 | } |
| 1066 | output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, |
| 1067 | importPath, strings.Trim(stderrStr, "\n")) |
| 1068 | return bytes.NewBufferString(output), nil |
| 1069 | } |
| 1070 | |
| 1071 | // Export mode entails a build. |
| 1072 | // If that build fails, errors appear on stderr |
| 1073 | // (despite the -e flag) and the Export field is blank. |
| 1074 | // Do not fail in that case. |
| 1075 | // The same is true if an ad-hoc package given to go list doesn't exist. |
| 1076 | // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when |
| 1077 | // packages don't exist or a build fails. |
| 1078 | if !usesExportData(cfg) && !containsGoFile(args) { |
| 1079 | return nil, friendlyErr |
| 1080 | } |
| 1081 | } |
| 1082 | return stdout, nil |
| 1083 | } |
| 1084 | |
| 1085 | // OverlayJSON is the format overlay files are expected to be in. |
| 1086 | // The Replace map maps from overlaid paths to replacement paths: |
| 1087 | // the Go command will forward all reads trying to open |
| 1088 | // each overlaid path to its replacement path, or consider the overlaid |
| 1089 | // path not to exist if the replacement path is empty. |
| 1090 | // |
| 1091 | // From golang/go#39958. |
| 1092 | type OverlayJSON struct { |
| 1093 | Replace map[string]string `json:"replace,omitempty"` |
| 1094 | } |
| 1095 | |
| 1096 | // writeOverlays writes out files for go list's -overlay flag, as described |
| 1097 | // above. |
| 1098 | func (state *golistState) writeOverlays() (filename string, cleanup func(), err error) { |
| 1099 | // Do nothing if there are no overlays in the config. |
| 1100 | if len(state.cfg.Overlay) == 0 { |
| 1101 | return "", func() {}, nil |
| 1102 | } |
| 1103 | dir, err := ioutil.TempDir("", "gopackages-*") |
| 1104 | if err != nil { |
| 1105 | return "", nil, err |
| 1106 | } |
| 1107 | // The caller must clean up this directory, unless this function returns an |
| 1108 | // error. |
| 1109 | cleanup = func() { |
| 1110 | os.RemoveAll(dir) |
| 1111 | } |
| 1112 | defer func() { |
| 1113 | if err != nil { |
| 1114 | cleanup() |
| 1115 | } |
| 1116 | }() |
| 1117 | overlays := map[string]string{} |
| 1118 | for k, v := range state.cfg.Overlay { |
| 1119 | // Create a unique filename for the overlaid files, to avoid |
| 1120 | // creating nested directories. |
| 1121 | noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "") |
| 1122 | f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator)) |
| 1123 | if err != nil { |
| 1124 | return "", func() {}, err |
| 1125 | } |
| 1126 | if _, err := f.Write(v); err != nil { |
| 1127 | return "", func() {}, err |
| 1128 | } |
| 1129 | if err := f.Close(); err != nil { |
| 1130 | return "", func() {}, err |
| 1131 | } |
| 1132 | overlays[k] = f.Name() |
| 1133 | } |
| 1134 | b, err := json.Marshal(OverlayJSON{Replace: overlays}) |
| 1135 | if err != nil { |
| 1136 | return "", func() {}, err |
| 1137 | } |
| 1138 | // Write out the overlay file that contains the filepath mappings. |
| 1139 | filename = filepath.Join(dir, "overlay.json") |
| 1140 | if err := ioutil.WriteFile(filename, b, 0665); err != nil { |
| 1141 | return "", func() {}, err |
| 1142 | } |
| 1143 | return filename, cleanup, nil |
| 1144 | } |
| 1145 | |
| 1146 | func containsGoFile(s []string) bool { |
| 1147 | for _, f := range s { |
| 1148 | if strings.HasSuffix(f, ".go") { |
| 1149 | return true |
| 1150 | } |
| 1151 | } |
| 1152 | return false |
| 1153 | } |
| 1154 | |
| 1155 | func cmdDebugStr(cmd *exec.Cmd) string { |
| 1156 | env := make(map[string]string) |
| 1157 | for _, kv := range cmd.Env { |
| 1158 | split := strings.SplitN(kv, "=", 2) |
| 1159 | k, v := split[0], split[1] |
| 1160 | env[k] = v |
| 1161 | } |
| 1162 | |
| 1163 | var args []string |
| 1164 | for _, arg := range cmd.Args { |
| 1165 | quoted := strconv.Quote(arg) |
| 1166 | if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") { |
| 1167 | args = append(args, quoted) |
| 1168 | } else { |
| 1169 | args = append(args, arg) |
| 1170 | } |
| 1171 | } |
| 1172 | return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], strings.Join(args, " ")) |
| 1173 | } |
| 1174 |
Members