// Copyright 2018 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package packagesimport ()// processGolistOverlay provides rudimentary support for adding// files that don't exist on disk to an overlay. The results can be// sometimes incorrect.// TODO(matloob): Handle unsupported cases, including the following:// - determining the correct package to add given a new import pathfunc ( *golistState) ( *responseDeduper) (, []string, error) { := make(map[string]string) // importPath -> non-test package ID := make(map[string]bool) := make(map[string]bool) := make(map[string][]*Package)for , := range .dr.Packages {// This is an approximation of import path to id. This can be // wrong for tests, vendored packages, and a number of other cases. [.PkgPath] = .ID , := commonDir(.GoFiles)if != nil {returnnil, nil, }if != "" { [] = append([], ) } }// If no new imports are added, it is safe to avoid loading any needPkgs. // Otherwise, it's hard to tell which package is actually being loaded // (due to vendoring) and whether any modified package will show up // in the transitive set of dependencies (because new imports are added, // potentially modifying the transitive set of dependencies).varbool// If both a package and its test package are created by the overlay, we // need the real package first. Process all non-test files before test // files, and make the whole process deterministic while we're at it.var []stringfor := range .cfg.Overlay { = append(, ) }sort.Slice(, func(, int) bool { := strings.HasSuffix([], "_test.go") := strings.HasSuffix([], "_test.go")if != {return ! // non-tests are before tests. }return [] < [] })for , := range { := .cfg.Overlay[] := filepath.Base() := filepath.Dir()var *Package// if opath belongs to both a package and its test variant, this will be the test variantvar *Package// if opath is a test file, this is the package it is testingvarbool := strings.HasSuffix(, "_test.go") , := extractPackageName(, )if ! {// Don't bother adding a file that doesn't even have a parsable package statement // to the overlay.continue }// If all the overlay files belong to a different package, change the // package name to that package.maybeFixPackageName(, , []) :for , := range .dr.Packages {if != .Name && .ID != "command-line-arguments" {continue }for , := range .GoFiles {if !sameFile(filepath.Dir(), ) {continue }// Make sure to capture information on the package's test variant, if needed.if && !hasTestFiles() {// TODO(matloob): Are there packages other than the 'production' variant // of a package that this can match? This shouldn't match the test main package // because the file is generated in another directory. = continue } elseif ! && hasTestFiles() {// We're examining a test variant, but the overlaid file is // a non-test file. Because the overlay implementation // (currently) only adds a file to one package, skip this // package, so that we can add the file to the production // variant of the package. (https://golang.org/issue/36857 // tracks handling overlays on both the production and test // variant of a package).continue }if != nil && != && .PkgPath == .PkgPath {// We have already seen the production version of the // for which p is a test variant.ifhasTestFiles() { = } } = iffilepath.Base() == { = true } } }// The overlay could have included an entirely new package or an // ad-hoc package. An ad-hoc package is one that we have manually // constructed from inadequate `go list` results for a file= query. // It will have the ID command-line-arguments.if == nil || .ID == "command-line-arguments" {// Try to find the module or gopath dir the file is contained in. // Then for modules, add the module opath to the beginning. , , := .getPkgPath()if != nil {returnnil, nil, }if ! {break }varstring// only set for x tests := strings.HasSuffix(, "_test")if { = += "_test" } := if {if { = fmt.Sprintf("%s [%s.test]", , ) } else { = fmt.Sprintf("%s [%s.test]", , ) } }if != nil {// TODO(rstambler): We should change the package's path and ID // here. The only issue is that this messes with the roots. } else {// Try to reclaim a package with the same ID, if it exists in the response.for , := range .dr.Packages {ifreclaimPackage(, , , ) { = break } }// Otherwise, create a new package.if == nil { = &Package{PkgPath: ,ID: ,Name: ,Imports: make(map[string]*Package), } .addPackage() [.PkgPath] = // Add the production package's sources for a test variant.if && ! && != nil { .GoFiles = append(.GoFiles, .GoFiles...) .CompiledGoFiles = append(.CompiledGoFiles, .CompiledGoFiles...)// Add the package under test and its imports to the test variant. .forTest = .PkgPathfor , := range .Imports { .Imports[] = &Package{ID: .ID} } }if { .forTest = } } } }if ! { .GoFiles = append(.GoFiles, )// TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior // if the file will be ignored due to its build tags. .CompiledGoFiles = append(.CompiledGoFiles, ) [.ID] = true } , := extractImports(, )if != nil {// Let the parser or type checker report errors later.continue }for , := range {// TODO(rstambler): If the package is an x test and the import has // a test variant, make sure to replace it.if , := .Imports[]; {continue } = true , := []if ! {varerror , = .resolveImport(, )if != nil {returnnil, nil, } } .Imports[] = &Package{ID: }// Add dependencies to the non-test variant version of this package as well.if != nil { .Imports[] = &Package{ID: } } } }// toPkgPath guesses the package path given the id. := func(, string) (string, error) {if := strings.IndexByte(, ' '); >= 0 {return .resolveImport(, [:]) }return .resolveImport(, ) }// Now that new packages have been created, do another pass to determine // the new set of missing packages.for , := range .dr.Packages {for , := range .Imports {iflen(.GoFiles) == 0 {returnnil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", .PkgPath) } , := (filepath.Dir(.GoFiles[0]), .ID)if != nil {returnnil, nil, }if , := []; ! { [] = true } } }if { = make([]string, 0, len())for := range { = append(, ) } } = make([]string, 0, len())for := range { = append(, ) }return , , }// resolveImport finds the ID of a package given its import path.// In particular, it will find the right vendored copy when in GOPATH mode.func ( *golistState) (, string) (string, error) { , := .getEnv()if != nil {return"", }if ["GOMOD"] != "" {return , nil } := for { := filepath.Join(, "vendor") , := .vendorDirs[]if ! { , := os.Stat() = == nil && .IsDir() .vendorDirs[] = }if { := filepath.Join(, )if , := os.Stat(); == nil && .IsDir() {// We should probably check for .go files here, but shame on anyone who fools us. , , := .getPkgPath()if != nil {return"", }if {return , nil } } }// We know we've hit the top of the filesystem when we Dir / and get /, // or C:\ and get C:\, etc. := filepath.Dir()if == {break } = }return , nil}func ( *Package) bool {for , := range .GoFiles {ifstrings.HasSuffix(, "_test.go") {returntrue } }returnfalse}// determineRootDirs returns a mapping from absolute directories that could// contain code to their corresponding import path prefixes.func ( *golistState) () (map[string]string, error) { , := .getEnv()if != nil {returnnil, }if ["GOMOD"] != "" { .rootsOnce.Do(func() { .rootDirs, .rootDirsError = .determineRootDirsModules() }) } else { .rootsOnce.Do(func() { .rootDirs, .rootDirsError = .determineRootDirsGOPATH() }) }return .rootDirs, .rootDirsError}func ( *golistState) () (map[string]string, error) {// List all of the modules--the first will be the directory for the main // module. Any replaced modules will also need to be treated as roots. // Editing files in the module cache isn't a great idea, so we don't // plan to ever support that. , := .invokeGo("list", "-m", "-json", "all")if != nil {// 'go list all' will fail if we're outside of a module and // GO111MODULE=on. Try falling back without 'all'.varerror , = .invokeGo("list", "-m", "-json")if != nil {returnnil, } } := map[string]string{} := map[string]string{}varintfor := json.NewDecoder(); .More(); { := new(gocommand.ModuleJSON)if := .Decode(); != nil {returnnil, }if .Dir != "" && .Path != "" {// This is a valid module; add it to the map. , := filepath.Abs(.Dir)if != nil {returnnil, } [] = .Path// The first result is the main module.if == 0 || .Replace != nil && .Replace.Path != "" { [] = .Path } } ++ }return , nil}func ( *golistState) () (map[string]string, error) { := map[string]string{}for , := rangefilepath.SplitList(.mustGetEnv()["GOPATH"]) { , := filepath.Abs()if != nil {returnnil, } [filepath.Join(, "src")] = "" }return , nil}func ( string, []byte) ([]string, error) { , := parser.ParseFile(token.NewFileSet(), , , parser.ImportsOnly) // TODO(matloob): reuse fileset?if != nil {returnnil, }var []stringfor , := range .Imports { := .Path.Value , := strconv.Unquote()if != nil {returnnil, } = append(, ) }return , nil}// reclaimPackage attempts to reuse a package that failed to load in an overlay.//// If the package has errors and has no Name, GoFiles, or Imports,// then it's possible that it doesn't yet exist on disk.func ( *Package, string, string, []byte) bool {// TODO(rstambler): Check the message of the actual error? // It differs between $GOPATH and module mode.if .ID != {returnfalse }iflen(.Errors) != 1 {returnfalse }if .Name != "" || .ExportFile != "" {returnfalse }iflen(.GoFiles) > 0 || len(.CompiledGoFiles) > 0 || len(.OtherFiles) > 0 {returnfalse }iflen(.Imports) > 0 {returnfalse } , := extractPackageName(, )if ! {returnfalse } .Name = .Errors = nilreturntrue}func ( string, []byte) (string, bool) {// TODO(rstambler): Check the message of the actual error? // It differs between $GOPATH and module mode. , := parser.ParseFile(token.NewFileSet(), , , parser.PackageClauseOnly) // TODO(matloob): reuse fileset?if != nil {return"", false }return .Name.Name, true}// commonDir returns the directory that all files are in, "" if files is empty,// or an error if they aren't in the same directory.func ( []string) (string, error) { := make(map[string]bool)for , := range { [filepath.Dir()] = true }iflen() > 1 {return"", fmt.Errorf("files (%v) are in more than one directory: %v", , ) }for := range {// seen has only one element; return it.return , nil }return"", nil// no files}// It is possible that the files in the disk directory dir have a different package// name from newName, which is deduced from the overlays. If they all have a different// package name, and they all have the same package name, then that name becomes// the package name.// It returns true if it changes the package name, false otherwise.func ( string, bool, []*Package) { := make(map[string]int)for , := range { [.Name]++ }iflen() != 1 {// some files are in different packagesreturn }varstringfor := range { = }if == {return }// We might have a case where all of the package names in the directory are // the same, but the overlay file is for an x test, which belongs to its // own package. If the x test does not yet exist on disk, we may not yet // have its package name on disk, but we should not rename the packages. // // We use a heuristic to determine if this file belongs to an x test: // The test file should have a package name whose package name has a _test // suffix or looks like "newName_test". := strings.HasPrefix(+"_test", ) || strings.HasSuffix(, "_test")if && {return }for , := range { .Name = }}// This function is copy-pasted from// https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360.// It should be deleted when we remove support for overlays from go/packages.//// NOTE: This does not handle any ./... or ./ style queries, as this function// doesn't know the working directory.//// matchPattern(pattern)(name) reports whether// name matches pattern. Pattern is a limited glob// pattern in which '...' means 'any string' and there// is no other special syntax.// Unfortunately, there are two special cases. Quoting "go help packages"://// First, /... at the end of the pattern can match an empty string,// so that net/... matches both net and packages in its subdirectories, like net/http.// Second, any slash-separated pattern element containing a wildcard never// participates in a match of the "vendor" element in the path of a vendored// package, so that ./... does not match packages in subdirectories of// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.// Note, however, that a directory named vendor that itself contains code// is not a vendored package: cmd/vendor would be a command named vendor,// and the pattern cmd/... matches it.func ( string) func( string) bool {// Convert pattern to regular expression. // The strategy for the trailing /... is to nest it in an explicit ? expression. // The strategy for the vendor exclusion is to change the unmatchable // vendor strings to a disallowed code point (vendorChar) and to use // "(anything but that codepoint)*" as the implementation of the ... wildcard. // This is a bit complicated but the obvious alternative, // namely a hand-written search like in most shell glob matchers, // is too easy to make accidentally exponential. // Using package regexp guarantees linear-time matching.const = "\x00"ifstrings.Contains(, ) {returnfunc( string) bool { returnfalse } } := regexp.QuoteMeta() = replaceVendor(, )switch {casestrings.HasSuffix(, `/`++`/\.\.\.`): = strings.TrimSuffix(, `/`++`/\.\.\.`) + `(/vendor|/` + + `/\.\.\.)`case == +`/\.\.\.`: = `(/vendor|/` + + `/\.\.\.)`casestrings.HasSuffix(, `/\.\.\.`): = strings.TrimSuffix(, `/\.\.\.`) + `(/\.\.\.)?` } = strings.ReplaceAll(, `\.\.\.`, `[^`++`]*`) := regexp.MustCompile(`^` + + `$`)returnfunc( string) bool {ifstrings.Contains(, ) {returnfalse }return .MatchString(replaceVendor(, )) }}// replaceVendor returns the result of replacing// non-trailing vendor path elements in x with repl.func (, string) string {if !strings.Contains(, "vendor") {return } := strings.Split(, "/")for := 0; < len()-1; ++ {if [] == "vendor" { [] = } }returnstrings.Join(, "/")}
The pages are generated with Goldsv0.4.9. (GOOS=linux GOARCH=amd64)