| package packages |
| |
| import ( |
| "go/parser" |
| "go/token" |
| "path/filepath" |
| "strconv" |
| "strings" |
| ) |
| |
| // 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: |
| // - test files |
| // - adding test and non-test files to test variants of packages |
| // - determining the correct package to add given a new import path |
| // - creating packages that don't exist |
| func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs, needPkgs []string, err error) { |
| havePkgs := make(map[string]string) // importPath -> non-test package ID |
| needPkgsSet := make(map[string]bool) |
| modifiedPkgsSet := make(map[string]bool) |
| |
| for _, pkg := range response.Packages { |
| // This is an approximation of import path to id. This can be |
| // wrong for tests, vendored packages, and a number of other cases. |
| havePkgs[pkg.PkgPath] = pkg.ID |
| } |
| |
| outer: |
| for path, contents := range cfg.Overlay { |
| base := filepath.Base(path) |
| if strings.HasSuffix(path, "_test.go") { |
| // Overlays don't support adding new test files yet. |
| // TODO(matloob): support adding new test files. |
| continue |
| } |
| dir := filepath.Dir(path) |
| for _, pkg := range response.Packages { |
| var dirContains, fileExists bool |
| for _, f := range pkg.GoFiles { |
| if sameFile(filepath.Dir(f), dir) { |
| dirContains = true |
| } |
| if filepath.Base(f) == base { |
| fileExists = true |
| } |
| } |
| // The overlay could have included an entirely new package. |
| isNewPackage := extractPackage(pkg, path, contents) |
| if dirContains || isNewPackage { |
| if !fileExists { |
| pkg.GoFiles = append(pkg.GoFiles, path) // TODO(matloob): should the file just be added to GoFiles? |
| pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, path) |
| modifiedPkgsSet[pkg.ID] = true |
| } |
| imports, err := extractImports(path, contents) |
| if err != nil { |
| // Let the parser or type checker report errors later. |
| continue outer |
| } |
| for _, imp := range imports { |
| _, found := pkg.Imports[imp] |
| if !found { |
| needPkgsSet[imp] = true |
| // TODO(matloob): Handle cases when the following block isn't correct. |
| // These include imports of test variants, imports of vendored packages, etc. |
| id, ok := havePkgs[imp] |
| if !ok { |
| id = imp |
| } |
| pkg.Imports[imp] = &Package{ID: id} |
| } |
| } |
| continue outer |
| } |
| } |
| } |
| |
| needPkgs = make([]string, 0, len(needPkgsSet)) |
| for pkg := range needPkgsSet { |
| needPkgs = append(needPkgs, pkg) |
| } |
| modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) |
| for pkg := range modifiedPkgsSet { |
| modifiedPkgs = append(modifiedPkgs, pkg) |
| } |
| return modifiedPkgs, needPkgs, err |
| } |
| |
| func extractImports(filename string, contents []byte) ([]string, error) { |
| f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? |
| if err != nil { |
| return nil, err |
| } |
| var res []string |
| for _, imp := range f.Imports { |
| quotedPath := imp.Path.Value |
| path, err := strconv.Unquote(quotedPath) |
| if err != nil { |
| return nil, err |
| } |
| res = append(res, path) |
| } |
| return res, nil |
| } |
| |
| // extractPackage attempts to extract a package defined 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 extractPackage(pkg *Package, filename string, contents []byte) bool { |
| // TODO(rstambler): Check the message of the actual error? |
| // It differs between $GOPATH and module mode. |
| if len(pkg.Errors) != 1 { |
| return false |
| } |
| if pkg.Name != "" || pkg.ExportFile != "" { |
| return false |
| } |
| if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 { |
| return false |
| } |
| if len(pkg.Imports) > 0 { |
| return false |
| } |
| f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? |
| if err != nil { |
| return false |
| } |
| // TODO(rstambler): This doesn't work for main packages. |
| if filepath.Base(pkg.PkgPath) != f.Name.Name { |
| return false |
| } |
| pkg.Name = f.Name.Name |
| pkg.Errors = nil |
| return true |
| } |