diff options
author | Cherry Zhang <cherryyz@google.com> | 2020-10-28 09:12:20 -0400 |
---|---|---|
committer | Cherry Zhang <cherryyz@google.com> | 2020-10-28 09:12:20 -0400 |
commit | a16e30d162c1c7408db7821e7b9513cefa09c6ca (patch) | |
tree | af752ba9ba44c547df39bb0af9bff79f610ba9d5 /src/go/build/build.go | |
parent | 91e4d2d57bc341dd82c98247117114c851380aef (diff) | |
parent | cf6cfba4d5358404dd890f6025e573a4b2156543 (diff) | |
download | go-git-dev.link.tar.gz |
[dev.link] all: merge branch 'master' into dev.linkdev.link
Clean merge.
Change-Id: Ia7b2808bc649790198d34c226a61d9e569084dc5
Diffstat (limited to 'src/go/build/build.go')
-rw-r--r-- | src/go/build/build.go | 437 |
1 files changed, 277 insertions, 160 deletions
diff --git a/src/go/build/build.go b/src/go/build/build.go index 5c3d876130..80e9b9c739 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -10,11 +10,11 @@ import ( "fmt" "go/ast" "go/doc" - "go/parser" "go/token" "internal/goroot" "internal/goversion" "io" + "io/fs" "io/ioutil" "os" "os/exec" @@ -98,10 +98,10 @@ type Context struct { // filepath.EvalSymlinks. HasSubdir func(root, dir string) (rel string, ok bool) - // ReadDir returns a slice of os.FileInfo, sorted by Name, + // ReadDir returns a slice of fs.FileInfo, sorted by Name, // describing the content of the named directory. // If ReadDir is nil, Import uses ioutil.ReadDir. - ReadDir func(dir string) ([]os.FileInfo, error) + ReadDir func(dir string) ([]fs.FileInfo, error) // OpenFile opens a file (not a directory) for reading. // If OpenFile is nil, Import uses os.Open. @@ -183,7 +183,7 @@ func hasSubdir(root, dir string) (rel string, ok bool) { } // readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir. -func (ctxt *Context) readDir(path string) ([]os.FileInfo, error) { +func (ctxt *Context) readDir(path string) ([]fs.FileInfo, error) { if f := ctxt.ReadDir; f != nil { return f(path) } @@ -409,19 +409,20 @@ type Package struct { BinaryOnly bool // cannot be rebuilt from source (has //go:binary-only-package comment) // Source files - GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) - CgoFiles []string // .go source files that import "C" - IgnoredGoFiles []string // .go source files ignored for this build - InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on) - CFiles []string // .c source files - CXXFiles []string // .cc, .cpp and .cxx source files - MFiles []string // .m (Objective-C) 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 system object files to add to archive + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) + CgoFiles []string // .go source files that import "C" + IgnoredGoFiles []string // .go source files ignored for this build (including ignored _test.go files) + InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on) + IgnoredOtherFiles []string // non-.go source files ignored for this build + CFiles []string // .c source files + CXXFiles []string // .cc, .cpp and .cxx source files + MFiles []string // .m (Objective-C) 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 system object files to add to archive // Cgo directives CgoCFLAGS []string // Cgo CFLAGS directives @@ -431,17 +432,26 @@ type Package struct { CgoLDFLAGS []string // Cgo LDFLAGS directives CgoPkgConfig []string // Cgo pkg-config directives - // Dependency information - Imports []string // import paths from GoFiles, CgoFiles - ImportPos map[string][]token.Position // line information for Imports - // Test information - TestGoFiles []string // _test.go files in package + TestGoFiles []string // _test.go files in package + XTestGoFiles []string // _test.go files outside package + + // Dependency information + Imports []string // import paths from GoFiles, CgoFiles + ImportPos map[string][]token.Position // line information for Imports TestImports []string // import paths from TestGoFiles TestImportPos map[string][]token.Position // line information for TestImports - XTestGoFiles []string // _test.go files outside package XTestImports []string // import paths from XTestGoFiles XTestImportPos map[string][]token.Position // line information for XTestImports + + // //go:embed patterns found in Go source files + // For example, if a source file says + // //go:embed a* b.c + // then the list will contain those two strings as separate entries. + // (See package embed for more details about //go:embed.) + EmbedPatterns []string // patterns from GoFiles, CgoFiles + TestEmbedPatterns []string // patterns from TestGoFiles + XTestEmbedPatterns []string // patterns from XTestGoFiles } // IsCommand reports whether the package is considered a @@ -784,6 +794,7 @@ Found: var badGoError error var Sfiles []string // files with ".S"(capital S)/.sx(capital s equivalent for case insensitive filesystems) var firstFile, firstCommentFile string + var embeds, testEmbeds, xTestEmbeds []string imported := make(map[string][]token.Position) testImported := make(map[string][]token.Position) xTestImported := make(map[string][]token.Position) @@ -793,7 +804,7 @@ Found: if d.IsDir() { continue } - if (d.Mode() & os.ModeSymlink) != 0 { + if d.Mode()&fs.ModeSymlink != 0 { if fi, err := os.Stat(filepath.Join(p.Dir, d.Name())); err == nil && fi.IsDir() { // Symlinks to directories are not source files. continue @@ -810,60 +821,43 @@ Found: p.InvalidGoFiles = append(p.InvalidGoFiles, name) } - match, data, filename, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly) + info, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly, fset) if err != nil { badFile(err) continue } - if !match { - if ext == ".go" { + if info == nil { + if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") { + // not due to build constraints - don't report + } else if ext == ".go" { p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) + } else if fileListForExt(p, ext) != nil { + p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, name) } continue } + data, filename := info.header, info.name // Going to save the file. For non-Go files, can stop here. switch ext { - case ".c": - p.CFiles = append(p.CFiles, name) - continue - case ".cc", ".cpp", ".cxx": - p.CXXFiles = append(p.CXXFiles, name) - continue - case ".m": - p.MFiles = append(p.MFiles, name) - continue - case ".h", ".hh", ".hpp", ".hxx": - p.HFiles = append(p.HFiles, name) - continue - case ".f", ".F", ".for", ".f90": - p.FFiles = append(p.FFiles, name) - continue - case ".s": - p.SFiles = append(p.SFiles, name) - continue + case ".go": + // keep going case ".S", ".sx": + // special case for cgo, handled at end Sfiles = append(Sfiles, name) continue - case ".swig": - p.SwigFiles = append(p.SwigFiles, name) - continue - case ".swigcxx": - p.SwigCXXFiles = append(p.SwigCXXFiles, name) - continue - case ".syso": - // binary objects to add to package archive - // Likely of the form foo_windows.syso, but - // the name was vetted above with goodOSArchFile. - p.SysoFiles = append(p.SysoFiles, name) + default: + if list := fileListForExt(p, ext); list != nil { + *list = append(*list, name) + } continue } - pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments) - if err != nil { - badFile(err) + if info.parseErr != nil { + badFile(info.parseErr) continue } + pf := info.parsed pkg := pf.Name.Name if pkg == "documentation" { @@ -910,48 +904,23 @@ Found: } // Record imports and information about cgo. - type importPos struct { - path string - pos token.Pos - } - var fileImports []importPos isCgo := false - for _, decl := range pf.Decls { - d, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - for _, dspec := range d.Specs { - spec, ok := dspec.(*ast.ImportSpec) - if !ok { + for _, imp := range info.imports { + if imp.path == "C" { + if isTest { + badFile(fmt.Errorf("use of cgo in test %s not supported", filename)) continue } - quoted := spec.Path.Value - path, err := strconv.Unquote(quoted) - if err != nil { - panic(fmt.Sprintf("%s: parser returned invalid quoted string: <%s>", filename, quoted)) - } - fileImports = append(fileImports, importPos{path, spec.Pos()}) - if path == "C" { - if isTest { - badFile(fmt.Errorf("use of cgo in test %s not supported", filename)) - } else { - cg := spec.Doc - if cg == nil && len(d.Specs) == 1 { - cg = d.Doc - } - if cg != nil { - if err := ctxt.saveCgo(filename, p, cg); err != nil { - badFile(err) - } - } - isCgo = true + isCgo = true + if imp.doc != nil { + if err := ctxt.saveCgo(filename, p, imp.doc); err != nil { + badFile(err) } } } } - var fileList *[]string + var fileList, embedList *[]string var importMap map[string][]token.Position switch { case isCgo: @@ -959,6 +928,7 @@ Found: if ctxt.CgoEnabled { fileList = &p.CgoFiles importMap = imported + embedList = &embeds } else { // Ignore imports from cgo files if cgo is disabled. fileList = &p.IgnoredGoFiles @@ -966,19 +936,25 @@ Found: case isXTest: fileList = &p.XTestGoFiles importMap = xTestImported + embedList = &xTestEmbeds case isTest: fileList = &p.TestGoFiles importMap = testImported + embedList = &testEmbeds default: fileList = &p.GoFiles importMap = imported + embedList = &embeds } *fileList = append(*fileList, name) if importMap != nil { - for _, imp := range fileImports { + for _, imp := range info.imports { importMap[imp.path] = append(importMap[imp.path], fset.Position(imp.pos)) } } + if embedList != nil { + *embedList = append(*embedList, info.embeds...) + } } for tag := range allTags { @@ -986,6 +962,10 @@ Found: } sort.Strings(p.AllTags) + p.EmbedPatterns = uniq(embeds) + p.TestEmbedPatterns = uniq(testEmbeds) + p.XTestEmbedPatterns = uniq(xTestEmbeds) + p.Imports, p.ImportPos = cleanImports(imported) p.TestImports, p.TestImportPos = cleanImports(testImported) p.XTestImports, p.XTestImportPos = cleanImports(xTestImported) @@ -996,6 +976,9 @@ Found: if len(p.CgoFiles) > 0 { p.SFiles = append(p.SFiles, Sfiles...) sort.Strings(p.SFiles) + } else { + p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, Sfiles...) + sort.Strings(p.IgnoredOtherFiles) } if badGoError != nil { @@ -1007,6 +990,46 @@ Found: return p, pkgerr } +func fileListForExt(p *Package, ext string) *[]string { + switch ext { + case ".c": + return &p.CFiles + case ".cc", ".cpp", ".cxx": + return &p.CXXFiles + case ".m": + return &p.MFiles + case ".h", ".hh", ".hpp", ".hxx": + return &p.HFiles + case ".f", ".F", ".for", ".f90": + return &p.FFiles + case ".s", ".S", ".sx": + return &p.SFiles + case ".swig": + return &p.SwigFiles + case ".swigcxx": + return &p.SwigCXXFiles + case ".syso": + return &p.SysoFiles + } + return nil +} + +func uniq(list []string) []string { + if list == nil { + return nil + } + out := make([]string, len(list)) + copy(out, list) + sort.Strings(out) + uniq := out[:0] + for _, x := range out { + if len(uniq) == 0 || uniq[len(uniq)-1] != x { + uniq = append(uniq, x) + } + } + return uniq +} + var errNoModules = errors.New("not using modules") // importGo checks whether it can use the go command to find the directory for path. @@ -1298,22 +1321,46 @@ func parseWord(data []byte) (word, rest []byte) { // MatchFile considers the name of the file and may use ctxt.OpenFile to // read some or all of the file's content. func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) { - match, _, _, err = ctxt.matchFile(dir, name, nil, nil) - return + info, err := ctxt.matchFile(dir, name, nil, nil, nil) + return info != nil, err +} + +var dummyPkg Package + +// fileInfo records information learned about a file included in a build. +type fileInfo struct { + name string // full name including dir + header []byte + fset *token.FileSet + parsed *ast.File + parseErr error + imports []fileImport + embeds []string + embedErr error +} + +type fileImport struct { + path string + pos token.Pos + doc *ast.CommentGroup } // matchFile determines whether the file with the given name in the given directory // should be included in the package being constructed. -// It returns the data read from the file. +// If the file should be included, matchFile returns a non-nil *fileInfo (and a nil error). +// Non-nil errors are reserved for unexpected problems. +// // If name denotes a Go program, matchFile reads until the end of the -// imports (and returns that data) even though it only considers text -// until the first non-comment. +// imports and returns that section of the file in the fileInfo's header field, +// even though it only considers text until the first non-comment +// for +build lines. +// // If allTags is non-nil, matchFile records any encountered build tag // by setting allTags[tag] = true. -func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binaryOnly *bool) (match bool, data []byte, filename string, err error) { +func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binaryOnly *bool, fset *token.FileSet) (*fileInfo, error) { if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") { - return + return nil, nil } i := strings.LastIndex(name, ".") @@ -1323,53 +1370,53 @@ func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binary ext := name[i:] if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { - return + return nil, nil } - switch ext { - case ".go", ".c", ".cc", ".cxx", ".cpp", ".m", ".s", ".h", ".hh", ".hpp", ".hxx", ".f", ".F", ".f90", ".S", ".sx", ".swig", ".swigcxx": - // tentatively okay - read to make sure - case ".syso": - // binary, no reading - match = true - return - default: + if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil { // skip - return + return nil, nil + } + + info := &fileInfo{name: ctxt.joinPath(dir, name), fset: fset} + if ext == ".syso" { + // binary, no reading + return info, nil } - filename = ctxt.joinPath(dir, name) - f, err := ctxt.openFile(filename) + f, err := ctxt.openFile(info.name) if err != nil { - return + return nil, err } - if strings.HasSuffix(filename, ".go") { - data, err = readImports(f, false, nil) - if strings.HasSuffix(filename, "_test.go") { + if strings.HasSuffix(name, ".go") { + err = readGoInfo(f, info) + if strings.HasSuffix(name, "_test.go") { binaryOnly = nil // ignore //go:binary-only-package comments in _test.go files } } else { binaryOnly = nil // ignore //go:binary-only-package comments in non-Go sources - data, err = readComments(f) + info.header, err = readComments(f) } f.Close() if err != nil { - err = fmt.Errorf("read %s: %v", filename, err) - return + return nil, fmt.Errorf("read %s: %v", info.name, err) } // Look for +build comments to accept or reject the file. - var sawBinaryOnly bool - if !ctxt.shouldBuild(data, allTags, &sawBinaryOnly) && !ctxt.UseAllFiles { - return + ok, sawBinaryOnly, err := ctxt.shouldBuild(info.header, allTags) + if err != nil { + return nil, err + } + if !ok && !ctxt.UseAllFiles { + return nil, nil } if binaryOnly != nil && sawBinaryOnly { *binaryOnly = true } - match = true - return + + return info, nil } func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) { @@ -1391,7 +1438,25 @@ func ImportDir(dir string, mode ImportMode) (*Package, error) { return Default.ImportDir(dir, mode) } -var slashslash = []byte("//") +var ( + bSlashSlash = []byte(slashSlash) + bStarSlash = []byte(starSlash) + bSlashStar = []byte(slashStar) + + goBuildComment = []byte("//go:build") + + errGoBuildWithoutBuild = errors.New("//go:build comment without // +build comment") + errMultipleGoBuild = errors.New("multiple //go:build comments") // unused in Go 1.(N-1) +) + +func isGoBuildComment(line []byte) bool { + if !bytes.HasPrefix(line, goBuildComment) { + return false + } + line = bytes.TrimSpace(line) + rest := line[len(goBuildComment):] + return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest) +} // Special comment denoting a binary-only package. // See https://golang.org/design/2775-binary-only-packages @@ -1411,37 +1476,24 @@ var binaryOnlyComment = []byte("//go:binary-only-package") // // marks the file as applicable only on Windows and Linux. // -// If shouldBuild finds a //go:binary-only-package comment in the file, -// it sets *binaryOnly to true. Otherwise it does not change *binaryOnly. +// For each build tag it consults, shouldBuild sets allTags[tag] = true. // -func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binaryOnly *bool) bool { - sawBinaryOnly := false +// shouldBuild reports whether the file should be built +// and whether a //go:binary-only-package comment was found. +func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) (shouldBuild, binaryOnly bool, err error) { // Pass 1. Identify leading run of // comments and blank lines, // which must be followed by a blank line. - end := 0 - p := content - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if len(line) == 0 { // Blank line - end = len(content) - len(p) - continue - } - if !bytes.HasPrefix(line, slashslash) { // Not comment line - break - } + // Also identify any //go:build comments. + content, goBuild, sawBinaryOnly, err := parseFileHeader(content) + if err != nil { + return false, false, err } - content = content[:end] - // Pass 2. Process each line in the run. - p = content - allok := true + // Pass 2. Process each +build line in the run. + p := content + shouldBuild = true + sawBuild := false for len(p) > 0 { line := p if i := bytes.IndexByte(line, '\n'); i >= 0 { @@ -1450,17 +1502,15 @@ func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binary p = p[len(p):] } line = bytes.TrimSpace(line) - if !bytes.HasPrefix(line, slashslash) { + if !bytes.HasPrefix(line, bSlashSlash) { continue } - if bytes.Equal(line, binaryOnlyComment) { - sawBinaryOnly = true - } - line = bytes.TrimSpace(line[len(slashslash):]) + line = bytes.TrimSpace(line[len(bSlashSlash):]) if len(line) > 0 && line[0] == '+' { // Looks like a comment +line. f := strings.Fields(string(line)) if f[0] == "+build" { + sawBuild = true ok := false for _, tok := range f[1:] { if ctxt.match(tok, allTags) { @@ -1468,17 +1518,84 @@ func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binary } } if !ok { - allok = false + shouldBuild = false } } } } - if binaryOnly != nil && sawBinaryOnly { - *binaryOnly = true + if goBuild != nil && !sawBuild { + return false, false, errGoBuildWithoutBuild + } + + return shouldBuild, sawBinaryOnly, nil +} + +func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) { + end := 0 + p := content + ended := false // found non-blank, non-// line, so stopped accepting // +build lines + inSlashStar := false // in /* */ comment + +Lines: + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if len(line) == 0 && !ended { // Blank line + // Remember position of most recent blank line. + // When we find the first non-blank, non-// line, + // this "end" position marks the latest file position + // where a // +build line can appear. + // (It must appear _before_ a blank line before the non-blank, non-// line. + // Yes, that's confusing, which is part of why we moved to //go:build lines.) + // Note that ended==false here means that inSlashStar==false, + // since seeing a /* would have set ended==true. + end = len(content) - len(p) + continue Lines + } + if !bytes.HasPrefix(line, slashSlash) { // Not comment line + ended = true + } + + if !inSlashStar && isGoBuildComment(line) { + if false && goBuild != nil { // enabled in Go 1.N + return nil, nil, false, errMultipleGoBuild + } + goBuild = line + } + if !inSlashStar && bytes.Equal(line, binaryOnlyComment) { + sawBinaryOnly = true + } + + Comments: + for len(line) > 0 { + if inSlashStar { + if i := bytes.Index(line, starSlash); i >= 0 { + inSlashStar = false + line = bytes.TrimSpace(line[i+len(starSlash):]) + continue Comments + } + continue Lines + } + if bytes.HasPrefix(line, bSlashSlash) { + continue Lines + } + if bytes.HasPrefix(line, bSlashStar) { + inSlashStar = true + line = bytes.TrimSpace(line[len(bSlashStar):]) + continue Comments + } + // Found non-comment text. + break Lines + } } - return allok + return content[:end], goBuild, sawBinaryOnly, nil } // saveCgo saves the information from the #cgo lines in the import "C" comment. |