aboutsummaryrefslogtreecommitdiff
path: root/src/go/build
diff options
context:
space:
mode:
Diffstat (limited to 'src/go/build')
-rw-r--r--src/go/build/build.go437
-rw-r--r--src/go/build/build_test.go227
-rw-r--r--src/go/build/deps_test.go58
-rw-r--r--src/go/build/read.go268
-rw-r--r--src/go/build/read_test.go91
5 files changed, 820 insertions, 261 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.
diff --git a/src/go/build/build_test.go b/src/go/build/build_test.go
index 22c62ce87d..5a4a2d62f5 100644
--- a/src/go/build/build_test.go
+++ b/src/go/build/build_test.go
@@ -120,7 +120,7 @@ func TestMultiplePackageImport(t *testing.T) {
}
func TestLocalDirectory(t *testing.T) {
- if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" {
+ if runtime.GOOS == "ios" {
t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH)
}
@@ -138,48 +138,178 @@ func TestLocalDirectory(t *testing.T) {
}
}
-func TestShouldBuild(t *testing.T) {
- const file1 = "// +build tag1\n\n" +
- "package main\n"
- want1 := map[string]bool{"tag1": true}
-
- const file2 = "// +build cgo\n\n" +
- "// This package implements parsing of tags like\n" +
- "// +build tag1\n" +
- "package build"
- want2 := map[string]bool{"cgo": true}
-
- const file3 = "// Copyright The Go Authors.\n\n" +
- "package build\n\n" +
- "// shouldBuild checks tags given by lines of the form\n" +
- "// +build tag\n" +
- "func shouldBuild(content []byte)\n"
- want3 := map[string]bool{}
-
- ctx := &Context{BuildTags: []string{"tag1"}}
- m := map[string]bool{}
- if !ctx.shouldBuild([]byte(file1), m, nil) {
- t.Errorf("shouldBuild(file1) = false, want true")
- }
- if !reflect.DeepEqual(m, want1) {
- t.Errorf("shouldBuild(file1) tags = %v, want %v", m, want1)
- }
-
- m = map[string]bool{}
- if ctx.shouldBuild([]byte(file2), m, nil) {
- t.Errorf("shouldBuild(file2) = true, want false")
- }
- if !reflect.DeepEqual(m, want2) {
- t.Errorf("shouldBuild(file2) tags = %v, want %v", m, want2)
- }
+var shouldBuildTests = []struct {
+ name string
+ content string
+ tags map[string]bool
+ binaryOnly bool
+ shouldBuild bool
+ err error
+}{
+ {
+ name: "Yes",
+ content: "// +build yes\n\n" +
+ "package main\n",
+ tags: map[string]bool{"yes": true},
+ shouldBuild: true,
+ },
+ {
+ name: "Or",
+ content: "// +build no yes\n\n" +
+ "package main\n",
+ tags: map[string]bool{"yes": true, "no": true},
+ shouldBuild: true,
+ },
+ {
+ name: "And",
+ content: "// +build no,yes\n\n" +
+ "package main\n",
+ tags: map[string]bool{"yes": true, "no": true},
+ shouldBuild: false,
+ },
+ {
+ name: "Cgo",
+ content: "// +build cgo\n\n" +
+ "// Copyright The Go Authors.\n\n" +
+ "// This package implements parsing of tags like\n" +
+ "// +build tag1\n" +
+ "package build",
+ tags: map[string]bool{"cgo": true},
+ shouldBuild: false,
+ },
+ {
+ name: "AfterPackage",
+ content: "// Copyright The Go Authors.\n\n" +
+ "package build\n\n" +
+ "// shouldBuild checks tags given by lines of the form\n" +
+ "// +build tag\n" +
+ "func shouldBuild(content []byte)\n",
+ tags: map[string]bool{},
+ shouldBuild: true,
+ },
+ {
+ name: "TooClose",
+ content: "// +build yes\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: true,
+ },
+ {
+ name: "TooCloseNo",
+ content: "// +build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: true,
+ },
+ {
+ name: "BinaryOnly",
+ content: "//go:binary-only-package\n" +
+ "// +build yes\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ binaryOnly: true,
+ shouldBuild: true,
+ },
+ {
+ name: "ValidGoBuild",
+ content: "// +build yes\n\n" +
+ "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{"yes": true},
+ shouldBuild: true,
+ },
+ {
+ name: "MissingBuild",
+ content: "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: false,
+ err: errGoBuildWithoutBuild,
+ },
+ {
+ name: "MissingBuild2",
+ content: "/* */\n" +
+ "// +build yes\n\n" +
+ "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: false,
+ err: errGoBuildWithoutBuild,
+ },
+ {
+ name: "MissingBuild2",
+ content: "/*\n" +
+ "// +build yes\n\n" +
+ "*/\n" +
+ "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: false,
+ err: errGoBuildWithoutBuild,
+ },
+ {
+ name: "Comment1",
+ content: "/*\n" +
+ "//go:build no\n" +
+ "*/\n\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: true,
+ },
+ {
+ name: "Comment2",
+ content: "/*\n" +
+ "text\n" +
+ "*/\n\n" +
+ "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: false,
+ err: errGoBuildWithoutBuild,
+ },
+ {
+ name: "Comment3",
+ content: "/*/*/ /* hi *//* \n" +
+ "text\n" +
+ "*/\n\n" +
+ "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: false,
+ err: errGoBuildWithoutBuild,
+ },
+ {
+ name: "Comment4",
+ content: "/**///go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: true,
+ },
+ {
+ name: "Comment5",
+ content: "/**/\n" +
+ "//go:build no\n" +
+ "package main\n",
+ tags: map[string]bool{},
+ shouldBuild: false,
+ err: errGoBuildWithoutBuild,
+ },
+}
- m = map[string]bool{}
- ctx = &Context{BuildTags: nil}
- if !ctx.shouldBuild([]byte(file3), m, nil) {
- t.Errorf("shouldBuild(file3) = false, want true")
- }
- if !reflect.DeepEqual(m, want3) {
- t.Errorf("shouldBuild(file3) tags = %v, want %v", m, want3)
+func TestShouldBuild(t *testing.T) {
+ for _, tt := range shouldBuildTests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := &Context{BuildTags: []string{"yes"}}
+ tags := map[string]bool{}
+ shouldBuild, binaryOnly, err := ctx.shouldBuild([]byte(tt.content), tags)
+ if shouldBuild != tt.shouldBuild || binaryOnly != tt.binaryOnly || !reflect.DeepEqual(tags, tt.tags) || err != tt.err {
+ t.Errorf("mismatch:\n"+
+ "have shouldBuild=%v, binaryOnly=%v, tags=%v, err=%v\n"+
+ "want shouldBuild=%v, binaryOnly=%v, tags=%v, err=%v",
+ shouldBuild, binaryOnly, tags, err,
+ tt.shouldBuild, tt.binaryOnly, tt.tags, tt.err)
+ }
+ })
}
}
@@ -250,7 +380,7 @@ func TestMatchFile(t *testing.T) {
}
func TestImportCmd(t *testing.T) {
- if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" {
+ if runtime.GOOS == "ios" {
t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH)
}
@@ -482,11 +612,13 @@ func TestImportPackageOutsideModule(t *testing.T) {
ctxt.GOPATH = gopath
ctxt.Dir = filepath.Join(gopath, "src/example.com/p")
- want := "cannot find module providing package"
+ want := "working directory is not part of a module"
if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil {
t.Fatal("importing package when no go.mod is present succeeded unexpectedly")
} else if errStr := err.Error(); !strings.Contains(errStr, want) {
t.Fatalf("error when importing package when no go.mod is present: got %q; want %q", errStr, want)
+ } else {
+ t.Logf(`ctxt.Import("example.com/p", _, FindOnly): %v`, err)
}
}
@@ -547,9 +679,16 @@ func TestMissingImportErrorRepetition(t *testing.T) {
if err == nil {
t.Fatal("unexpected success")
}
+
// Don't count the package path with a URL like https://...?go-get=1.
// See golang.org/issue/35986.
errStr := strings.ReplaceAll(err.Error(), "://"+pkgPath+"?go-get=1", "://...?go-get=1")
+
+ // Also don't count instances in suggested "go get" or similar commands
+ // (see https://golang.org/issue/41576). The suggested command typically
+ // follows a semicolon.
+ errStr = strings.SplitN(errStr, ";", 2)[0]
+
if n := strings.Count(errStr, pkgPath); n != 1 {
t.Fatalf("package path %q appears in error %d times; should appear once\nerror: %v", pkgPath, n, err)
}
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index fa8ecf10f4..b26b2bd199 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -11,12 +11,12 @@ import (
"bytes"
"fmt"
"internal/testenv"
+ "io/fs"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
- "strconv"
"strings"
"testing"
)
@@ -99,10 +99,16 @@ var depsRules = `
RUNTIME
< io;
+ syscall !< io;
reflect !< sort;
+ RUNTIME, unicode/utf8
+ < path;
+
+ unicode !< path;
+
# SYSCALL is RUNTIME plus the packages necessary for basic system calls.
- RUNTIME, unicode/utf8, unicode/utf16, io
+ RUNTIME, unicode/utf8, unicode/utf16
< internal/syscall/windows/sysdll, syscall/js
< syscall
< internal/syscall/unix, internal/syscall/windows, internal/syscall/windows/registry
@@ -116,6 +122,9 @@ var depsRules = `
< context
< TIME;
+ TIME, io, path, sort
+ < io/fs;
+
# MATH is RUNTIME plus the basic math packages.
RUNTIME
< math
@@ -129,6 +138,9 @@ var depsRules = `
MATH
< math/rand;
+ MATH
+ < runtime/metrics;
+
MATH, unicode/utf8
< strconv;
@@ -137,7 +149,7 @@ var depsRules = `
# STR is basic string and buffer manipulation.
RUNTIME, io, unicode/utf8, unicode/utf16, unicode
< bytes, strings
- < bufio, path;
+ < bufio;
bufio, path, strconv
< STR;
@@ -145,7 +157,7 @@ var depsRules = `
# OS is basic OS access, including helpers (path/filepath, os/exec, etc).
# OS includes string routines, but those must be layered above package os.
# OS does not include reflection.
- TIME, io, sort
+ io/fs
< internal/testlog
< internal/poll
< os
@@ -155,7 +167,9 @@ var depsRules = `
os/signal, STR
< path/filepath
- < io/ioutil, os/exec
+ < io/ioutil, os/exec;
+
+ io/ioutil, os/exec, os/signal
< OS;
reflect !< OS;
@@ -318,7 +332,6 @@ var depsRules = `
# so large dependencies must be kept out.
# This is a long-looking list but most of these
# are small with few dependencies.
- # math/rand should probably be removed at some point.
CGO,
golang.org/x/net/dns/dnsmessage,
golang.org/x/net/lif,
@@ -327,11 +340,11 @@ var depsRules = `
internal/poll,
internal/singleflight,
internal/race,
- math/rand,
os
< net;
fmt, unicode !< net;
+ math/rand !< net; # net uses runtime instead
# NET is net plus net-helper packages.
FMT, net
@@ -449,7 +462,7 @@ var depsRules = `
OS, compress/gzip, regexp
< internal/profile;
- html/template, internal/profile, net/http, runtime/pprof, runtime/trace
+ html, internal/profile, net/http, runtime/pprof, runtime/trace
< net/http/pprof;
# RPC
@@ -457,14 +470,19 @@ var depsRules = `
< net/rpc
< net/rpc/jsonrpc;
+ # System Information
+ internal/cpu, sync
+ < internal/sysinfo;
+
# Test-only
log
- < testing/iotest;
+ < testing/iotest
+ < testing/fstest;
FMT, flag, math/rand
< testing/quick;
- FMT, flag, runtime/debug, runtime/trace
+ FMT, flag, runtime/debug, runtime/trace, internal/sysinfo
< testing;
internal/testlog, runtime/pprof, regexp
@@ -479,7 +497,7 @@ var depsRules = `
CGO, OS, fmt
< os/signal/internal/pty;
- NET, testing
+ NET, testing, math/rand
< golang.org/x/net/nettest;
FMT, container/heap, math/rand
@@ -492,7 +510,7 @@ func listStdPkgs(goroot string) ([]string, error) {
var pkgs []string
src := filepath.Join(goroot, "src") + string(filepath.Separator)
- walkFn := func(path string, fi os.FileInfo, err error) error {
+ walkFn := func(path string, fi fs.FileInfo, err error) error {
if err != nil || !fi.IsDir() || path == src {
return nil
}
@@ -594,24 +612,22 @@ func findImports(pkg string) ([]string, error) {
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
- f, err := os.Open(filepath.Join(dir, name))
+ var info fileInfo
+ info.name = filepath.Join(dir, name)
+ f, err := os.Open(info.name)
if err != nil {
return nil, err
}
- var imp []string
- data, err := readImports(f, false, &imp)
+ err = readGoInfo(f, &info)
f.Close()
if err != nil {
return nil, fmt.Errorf("reading %v: %v", name, err)
}
- if bytes.Contains(data, buildIgnore) {
+ if bytes.Contains(info.header, buildIgnore) {
continue
}
- for _, quoted := range imp {
- path, err := strconv.Unquote(quoted)
- if err != nil {
- continue
- }
+ for _, imp := range info.imports {
+ path := imp.path
if !haveImport[path] {
haveImport[path] = true
imports = append(imports, path)
diff --git a/src/go/build/read.go b/src/go/build/read.go
index 29b8cdc786..6806a51c24 100644
--- a/src/go/build/read.go
+++ b/src/go/build/read.go
@@ -7,7 +7,13 @@ package build
import (
"bufio"
"errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
"io"
+ "strconv"
+ "strings"
+ "unicode"
"unicode/utf8"
)
@@ -57,6 +63,29 @@ func (r *importReader) readByte() byte {
return c
}
+// readByteNoBuf is like readByte but doesn't buffer the byte.
+// It exhausts r.buf before reading from r.b.
+func (r *importReader) readByteNoBuf() byte {
+ if len(r.buf) > 0 {
+ c := r.buf[0]
+ r.buf = r.buf[1:]
+ return c
+ }
+ c, err := r.b.ReadByte()
+ if err == nil && c == 0 {
+ err = errNUL
+ }
+ if err != nil {
+ if err == io.EOF {
+ r.eof = true
+ } else if r.err == nil {
+ r.err = err
+ }
+ c = 0
+ }
+ return c
+}
+
// peekByte returns the next byte from the input reader but does not advance beyond it.
// If skipSpace is set, peekByte skips leading spaces and comments.
func (r *importReader) peekByte(skipSpace bool) byte {
@@ -117,6 +146,74 @@ func (r *importReader) nextByte(skipSpace bool) byte {
return c
}
+var goEmbed = []byte("go:embed")
+
+// findEmbed advances the input reader to the next //go:embed comment.
+// It reports whether it found a comment.
+// (Otherwise it found an error or EOF.)
+func (r *importReader) findEmbed(first bool) bool {
+ // The import block scan stopped after a non-space character,
+ // so the reader is not at the start of a line on the first call.
+ // After that, each //go:embed extraction leaves the reader
+ // at the end of a line.
+ startLine := !first
+ var c byte
+ for r.err == nil && !r.eof {
+ c = r.readByteNoBuf()
+ Reswitch:
+ switch c {
+ default:
+ startLine = false
+
+ case '\n':
+ startLine = true
+
+ case ' ', '\t':
+ // leave startLine alone
+
+ case '/':
+ c = r.readByteNoBuf()
+ switch c {
+ default:
+ startLine = false
+ goto Reswitch
+
+ case '*':
+ var c1 byte
+ for (c != '*' || c1 != '/') && r.err == nil {
+ if r.eof {
+ r.syntaxError()
+ }
+ c, c1 = c1, r.readByteNoBuf()
+ }
+ startLine = false
+
+ case '/':
+ if startLine {
+ // Try to read this as a //go:embed comment.
+ for i := range goEmbed {
+ c = r.readByteNoBuf()
+ if c != goEmbed[i] {
+ goto SkipSlashSlash
+ }
+ }
+ c = r.readByteNoBuf()
+ if c == ' ' || c == '\t' {
+ // Found one!
+ return true
+ }
+ }
+ SkipSlashSlash:
+ for c != '\n' && r.err == nil && !r.eof {
+ c = r.readByteNoBuf()
+ }
+ startLine = true
+ }
+ }
+ }
+ return false
+}
+
// readKeyword reads the given keyword from the input.
// If the keyword is not present, readKeyword records a syntax error.
func (r *importReader) readKeyword(kw string) {
@@ -147,15 +244,11 @@ func (r *importReader) readIdent() {
// readString reads a quoted string literal from the input.
// If an identifier is not present, readString records a syntax error.
-func (r *importReader) readString(save *[]string) {
+func (r *importReader) readString() {
switch r.nextByte(true) {
case '`':
- start := len(r.buf) - 1
for r.err == nil {
if r.nextByte(false) == '`' {
- if save != nil {
- *save = append(*save, string(r.buf[start:]))
- }
break
}
if r.eof {
@@ -163,13 +256,9 @@ func (r *importReader) readString(save *[]string) {
}
}
case '"':
- start := len(r.buf) - 1
for r.err == nil {
c := r.nextByte(false)
if c == '"' {
- if save != nil {
- *save = append(*save, string(r.buf[start:]))
- }
break
}
if r.eof || c == '\n' {
@@ -186,17 +275,17 @@ func (r *importReader) readString(save *[]string) {
// readImport reads an import clause - optional identifier followed by quoted string -
// from the input.
-func (r *importReader) readImport(imports *[]string) {
+func (r *importReader) readImport() {
c := r.peekByte(true)
if c == '.' {
r.peek = 0
} else if isIdent(c) {
r.readIdent()
}
- r.readString(imports)
+ r.readString()
}
-// readComments is like ioutil.ReadAll, except that it only reads the leading
+// readComments is like io.ReadAll, except that it only reads the leading
// block of comments in the file.
func readComments(f io.Reader) ([]byte, error) {
r := &importReader{b: bufio.NewReader(f)}
@@ -208,9 +297,14 @@ func readComments(f io.Reader) ([]byte, error) {
return r.buf, r.err
}
-// readImports is like ioutil.ReadAll, except that it expects a Go file as input
-// and stops reading the input once the imports have completed.
-func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) {
+// readGoInfo expects a Go file as input and reads the file up to and including the import section.
+// It records what it learned in *info.
+// If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr,
+// info.imports, info.embeds, and info.embedErr.
+//
+// It only returns an error if there are problems reading the file,
+// not for syntax errors in the file itself.
+func readGoInfo(f io.Reader, info *fileInfo) error {
r := &importReader{b: bufio.NewReader(f)}
r.readKeyword("package")
@@ -220,28 +314,162 @@ func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte
if r.peekByte(true) == '(' {
r.nextByte(false)
for r.peekByte(true) != ')' && r.err == nil {
- r.readImport(imports)
+ r.readImport()
}
r.nextByte(false)
} else {
- r.readImport(imports)
+ r.readImport()
}
}
+ info.header = r.buf
+
// If we stopped successfully before EOF, we read a byte that told us we were done.
// Return all but that last byte, which would cause a syntax error if we let it through.
if r.err == nil && !r.eof {
- return r.buf[:len(r.buf)-1], nil
+ info.header = r.buf[:len(r.buf)-1]
}
// If we stopped for a syntax error, consume the whole file so that
// we are sure we don't change the errors that go/parser returns.
- if r.err == errSyntax && !reportSyntaxError {
+ if r.err == errSyntax {
r.err = nil
for r.err == nil && !r.eof {
r.readByte()
}
+ info.header = r.buf
+ }
+ if r.err != nil {
+ return r.err
+ }
+
+ if info.fset == nil {
+ return nil
+ }
+
+ // Parse file header & record imports.
+ info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments)
+ if info.parseErr != nil {
+ return nil
+ }
+
+ hasEmbed := false
+ for _, decl := range info.parsed.Decls {
+ d, ok := decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ for _, dspec := range d.Specs {
+ spec, ok := dspec.(*ast.ImportSpec)
+ if !ok {
+ continue
+ }
+ quoted := spec.Path.Value
+ path, err := strconv.Unquote(quoted)
+ if err != nil {
+ return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted)
+ }
+ if path == "embed" {
+ hasEmbed = true
+ }
+
+ doc := spec.Doc
+ if doc == nil && len(d.Specs) == 1 {
+ doc = d.Doc
+ }
+ info.imports = append(info.imports, fileImport{path, spec.Pos(), doc})
+ }
}
- return r.buf, r.err
+ // If the file imports "embed",
+ // we have to look for //go:embed comments
+ // in the remainder of the file.
+ // The compiler will enforce the mapping of comments to
+ // declared variables. We just need to know the patterns.
+ // If there were //go:embed comments earlier in the file
+ // (near the package statement or imports), the compiler
+ // will reject them. They can be (and have already been) ignored.
+ if hasEmbed {
+ var line []byte
+ for first := true; r.findEmbed(first); first = false {
+ line = line[:0]
+ for {
+ c := r.readByteNoBuf()
+ if c == '\n' || r.err != nil || r.eof {
+ break
+ }
+ line = append(line, c)
+ }
+ // Add args if line is well-formed.
+ // Ignore badly-formed lines - the compiler will report them when it finds them,
+ // and we can pretend they are not there to help go list succeed with what it knows.
+ args, err := parseGoEmbed(string(line))
+ if err == nil {
+ info.embeds = append(info.embeds, args...)
+ }
+ }
+ }
+
+ return nil
+}
+
+// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
+// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
+// There is a copy of this code in cmd/compile/internal/gc/noder.go as well.
+func parseGoEmbed(args string) ([]string, error) {
+ var list []string
+ for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
+ var path string
+ Switch:
+ switch args[0] {
+ default:
+ i := len(args)
+ for j, c := range args {
+ if unicode.IsSpace(c) {
+ i = j
+ break
+ }
+ }
+ path = args[:i]
+ args = args[i:]
+
+ case '`':
+ i := strings.Index(args[1:], "`")
+ if i < 0 {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
+ }
+ path = args[1 : 1+i]
+ args = args[1+i+1:]
+
+ case '"':
+ i := 1
+ for ; i < len(args); i++ {
+ if args[i] == '\\' {
+ i++
+ continue
+ }
+ if args[i] == '"' {
+ q, err := strconv.Unquote(args[:i+1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
+ }
+ path = q
+ args = args[i+1:]
+ break Switch
+ }
+ }
+ if i >= len(args) {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
+ }
+ }
+
+ if args != "" {
+ r, _ := utf8.DecodeRuneInString(args)
+ if !unicode.IsSpace(r) {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
+ }
+ }
+ list = append(list, path)
+ }
+ return list, nil
}
diff --git a/src/go/build/read_test.go b/src/go/build/read_test.go
index 8636533f69..9264d2606f 100644
--- a/src/go/build/read_test.go
+++ b/src/go/build/read_test.go
@@ -5,7 +5,9 @@
package build
import (
+ "go/token"
"io"
+ "reflect"
"strings"
"testing"
)
@@ -13,12 +15,12 @@ import (
const quote = "`"
type readTest struct {
- // Test input contains ℙ where readImports should stop.
+ // Test input contains ℙ where readGoInfo should stop.
in string
err string
}
-var readImportsTests = []readTest{
+var readGoInfoTests = []readTest{
{
`package p`,
"",
@@ -37,15 +39,15 @@ var readImportsTests = []readTest{
},
{
`package p
-
+
// comment
-
+
import "x"
import _ "x"
import a "x"
-
+
/* comment */
-
+
import (
"x" /* comment */
_ "x"
@@ -59,7 +61,7 @@ var readImportsTests = []readTest{
import ()
import()import()import()
import();import();import()
-
+
ℙvar x = 1
`,
"",
@@ -85,7 +87,7 @@ var readCommentsTests = []readTest{
/* bar */
/* quux */ // baz
-
+
/*/ zot */
// asdf
@@ -127,8 +129,12 @@ func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, erro
}
}
-func TestReadImports(t *testing.T) {
- testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) })
+func TestReadGoInfo(t *testing.T) {
+ testRead(t, readGoInfoTests, func(r io.Reader) ([]byte, error) {
+ var info fileInfo
+ err := readGoInfo(r, &info)
+ return info.header, err
+ })
}
func TestReadComments(t *testing.T) {
@@ -202,11 +208,6 @@ var readFailuresTests = []readTest{
},
}
-func TestReadFailures(t *testing.T) {
- // Errors should be reported (true arg to readImports).
- testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) })
-}
-
func TestReadFailuresIgnored(t *testing.T) {
// Syntax errors should not be reported (false arg to readImports).
// Instead, entire file should be the output and no error.
@@ -219,5 +220,63 @@ func TestReadFailuresIgnored(t *testing.T) {
tt.err = ""
}
}
- testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false, nil) })
+ testRead(t, tests, func(r io.Reader) ([]byte, error) {
+ var info fileInfo
+ err := readGoInfo(r, &info)
+ return info.header, err
+ })
+}
+
+var readEmbedTests = []struct {
+ in string
+ out []string
+}{
+ {
+ "package p\n",
+ nil,
+ },
+ {
+ "package p\nimport \"embed\"\nvar i int\n//go:embed x y z\nvar files embed.FS",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\nvar i int\n//go:embed x \"\\x79\" `z`\nvar files embed.FS",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\nvar i int\n//go:embed x y\n//go:embed z\nvar files embed.FS",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\nvar i int\n\t //go:embed x y\n\t //go:embed z\n\t var files embed.FS",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\n//go:embed x y z\nvar files embed.FS",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\n//go:embed x y z\n", // no import, no scan
+ nil,
+ },
+ {
+ "package p\n//go:embed x y z\nvar files embed.FS", // no import, no scan
+ nil,
+ },
+}
+
+func TestReadEmbed(t *testing.T) {
+ fset := token.NewFileSet()
+ for i, tt := range readEmbedTests {
+ var info fileInfo
+ info.fset = fset
+ err := readGoInfo(strings.NewReader(tt.in), &info)
+ if err != nil {
+ t.Errorf("#%d: %v", i, err)
+ continue
+ }
+ if !reflect.DeepEqual(info.embeds, tt.out) {
+ t.Errorf("#%d: embeds=%v, want %v", i, info.embeds, tt.out)
+ }
+ }
}