summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/imports
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/imports')
-rw-r--r--src/cmd/go/internal/imports/build.go251
-rw-r--r--src/cmd/go/internal/imports/read.go249
-rw-r--r--src/cmd/go/internal/imports/read_test.go228
-rw-r--r--src/cmd/go/internal/imports/scan.go107
-rw-r--r--src/cmd/go/internal/imports/scan_test.go94
-rw-r--r--src/cmd/go/internal/imports/tags.go58
-rw-r--r--src/cmd/go/internal/imports/testdata/android/.h.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/a_android.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/b_android_arm64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/c_linux.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/e.go5
-rw-r--r--src/cmd/go/internal/imports/testdata/android/f.go5
-rw-r--r--src/cmd/go/internal/imports/testdata/android/g.go5
-rw-r--r--src/cmd/go/internal/imports/testdata/android/tags.txt1
-rw-r--r--src/cmd/go/internal/imports/testdata/android/want.txt6
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/.h.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/a_illumos.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/c_solaris.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/e.go5
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/f.go5
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/g.go5
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/tags.txt1
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/want.txt6
-rw-r--r--src/cmd/go/internal/imports/testdata/star/tags.txt1
-rw-r--r--src/cmd/go/internal/imports/testdata/star/want.txt4
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x1.go9
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x_darwin.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x_windows.go3
32 files changed, 1084 insertions, 0 deletions
diff --git a/src/cmd/go/internal/imports/build.go b/src/cmd/go/internal/imports/build.go
new file mode 100644
index 0000000..50aeabc
--- /dev/null
+++ b/src/cmd/go/internal/imports/build.go
@@ -0,0 +1,251 @@
+// 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.
+
+// Copied from Go distribution src/go/build/build.go, syslist.go
+
+package imports
+
+import (
+ "bytes"
+ "strings"
+ "unicode"
+)
+
+var slashslash = []byte("//")
+
+// ShouldBuild reports whether it is okay to use this file,
+// The rule is that in the file's leading run of // comments
+// and blank lines, which must be followed by a blank line
+// (to avoid including a Go package clause doc comment),
+// lines beginning with '// +build' are taken as build directives.
+//
+// The file is accepted only if each such line lists something
+// matching the file. For example:
+//
+// // +build windows linux
+//
+// marks the file as applicable only on Windows and Linux.
+//
+// If tags["*"] is true, then ShouldBuild will consider every
+// build tag except "ignore" to be both true and false for
+// the purpose of satisfying build tags, in order to estimate
+// (conservatively) whether a file could ever possibly be used
+// in any build.
+//
+func ShouldBuild(content []byte, tags map[string]bool) bool {
+ // 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
+ }
+ }
+ content = content[:end]
+
+ // Pass 2. Process each line in the run.
+ p = content
+ allok := true
+ 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 !bytes.HasPrefix(line, slashslash) {
+ continue
+ }
+ line = bytes.TrimSpace(line[len(slashslash):])
+ if len(line) > 0 && line[0] == '+' {
+ // Looks like a comment +line.
+ f := strings.Fields(string(line))
+ if f[0] == "+build" {
+ ok := false
+ for _, tok := range f[1:] {
+ if matchTags(tok, tags) {
+ ok = true
+ }
+ }
+ if !ok {
+ allok = false
+ }
+ }
+ }
+ }
+
+ return allok
+}
+
+// matchTags reports whether the name is one of:
+//
+// tag (if tags[tag] is true)
+// !tag (if tags[tag] is false)
+// a comma-separated list of any of these
+//
+func matchTags(name string, tags map[string]bool) bool {
+ if name == "" {
+ return false
+ }
+ if i := strings.Index(name, ","); i >= 0 {
+ // comma-separated list
+ ok1 := matchTags(name[:i], tags)
+ ok2 := matchTags(name[i+1:], tags)
+ return ok1 && ok2
+ }
+ if strings.HasPrefix(name, "!!") { // bad syntax, reject always
+ return false
+ }
+ if strings.HasPrefix(name, "!") { // negation
+ return len(name) > 1 && matchTag(name[1:], tags, false)
+ }
+ return matchTag(name, tags, true)
+}
+
+// matchTag reports whether the tag name is valid and satisfied by tags[name]==want.
+func matchTag(name string, tags map[string]bool, want bool) bool {
+ // Tags must be letters, digits, underscores or dots.
+ // Unlike in Go identifiers, all digits are fine (e.g., "386").
+ for _, c := range name {
+ if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
+ return false
+ }
+ }
+
+ if tags["*"] && name != "" && name != "ignore" {
+ // Special case for gathering all possible imports:
+ // if we put * in the tags map then all tags
+ // except "ignore" are considered both present and not
+ // (so we return true no matter how 'want' is set).
+ return true
+ }
+
+ have := tags[name]
+ if name == "linux" {
+ have = have || tags["android"]
+ }
+ if name == "solaris" {
+ have = have || tags["illumos"]
+ }
+ if name == "darwin" {
+ have = have || tags["ios"]
+ }
+ return have == want
+}
+
+// MatchFile returns false if the name contains a $GOOS or $GOARCH
+// suffix which does not match the current system.
+// The recognized name formats are:
+//
+// name_$(GOOS).*
+// name_$(GOARCH).*
+// name_$(GOOS)_$(GOARCH).*
+// name_$(GOOS)_test.*
+// name_$(GOARCH)_test.*
+// name_$(GOOS)_$(GOARCH)_test.*
+//
+// Exceptions:
+// if GOOS=android, then files with GOOS=linux are also matched.
+// if GOOS=illumos, then files with GOOS=solaris are also matched.
+// if GOOS=ios, then files with GOOS=darwin are also matched.
+//
+// If tags["*"] is true, then MatchFile will consider all possible
+// GOOS and GOARCH to be available and will consequently
+// always return true.
+func MatchFile(name string, tags map[string]bool) bool {
+ if tags["*"] {
+ return true
+ }
+ if dot := strings.Index(name, "."); dot != -1 {
+ name = name[:dot]
+ }
+
+ // Before Go 1.4, a file called "linux.go" would be equivalent to having a
+ // build tag "linux" in that file. For Go 1.4 and beyond, we require this
+ // auto-tagging to apply only to files with a non-empty prefix, so
+ // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
+ // systems, such as android, to arrive without breaking existing code with
+ // innocuous source code in "android.go". The easiest fix: cut everything
+ // in the name before the initial _.
+ i := strings.Index(name, "_")
+ if i < 0 {
+ return true
+ }
+ name = name[i:] // ignore everything before first _
+
+ l := strings.Split(name, "_")
+ if n := len(l); n > 0 && l[n-1] == "test" {
+ l = l[:n-1]
+ }
+ n := len(l)
+ if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
+ return matchTag(l[n-2], tags, true) && matchTag(l[n-1], tags, true)
+ }
+ if n >= 1 && KnownOS[l[n-1]] {
+ return matchTag(l[n-1], tags, true)
+ }
+ if n >= 1 && KnownArch[l[n-1]] {
+ return matchTag(l[n-1], tags, true)
+ }
+ return true
+}
+
+var KnownOS = map[string]bool{
+ "aix": true,
+ "android": true,
+ "darwin": true,
+ "dragonfly": true,
+ "freebsd": true,
+ "hurd": true,
+ "illumos": true,
+ "ios": true,
+ "js": true,
+ "linux": true,
+ "nacl": true, // legacy; don't remove
+ "netbsd": true,
+ "openbsd": true,
+ "plan9": true,
+ "solaris": true,
+ "windows": true,
+ "zos": true,
+}
+
+var KnownArch = map[string]bool{
+ "386": true,
+ "amd64": true,
+ "amd64p32": true, // legacy; don't remove
+ "arm": true,
+ "armbe": true,
+ "arm64": true,
+ "arm64be": true,
+ "ppc64": true,
+ "ppc64le": true,
+ "mips": true,
+ "mipsle": true,
+ "mips64": true,
+ "mips64le": true,
+ "mips64p32": true,
+ "mips64p32le": true,
+ "ppc": true,
+ "riscv": true,
+ "riscv64": true,
+ "s390": true,
+ "s390x": true,
+ "sparc": true,
+ "sparc64": true,
+ "wasm": true,
+}
diff --git a/src/cmd/go/internal/imports/read.go b/src/cmd/go/internal/imports/read.go
new file mode 100644
index 0000000..5e27078
--- /dev/null
+++ b/src/cmd/go/internal/imports/read.go
@@ -0,0 +1,249 @@
+// Copyright 2012 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.
+
+// Copied from Go distribution src/go/build/read.go.
+
+package imports
+
+import (
+ "bufio"
+ "errors"
+ "io"
+ "unicode/utf8"
+)
+
+type importReader struct {
+ b *bufio.Reader
+ buf []byte
+ peek byte
+ err error
+ eof bool
+ nerr int
+}
+
+func isIdent(c byte) bool {
+ return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
+}
+
+var (
+ errSyntax = errors.New("syntax error")
+ errNUL = errors.New("unexpected NUL in input")
+)
+
+// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
+func (r *importReader) syntaxError() {
+ if r.err == nil {
+ r.err = errSyntax
+ }
+}
+
+// readByte reads the next byte from the input, saves it in buf, and returns it.
+// If an error occurs, readByte records the error in r.err and returns 0.
+func (r *importReader) readByte() byte {
+ c, err := r.b.ReadByte()
+ if err == nil {
+ r.buf = append(r.buf, c)
+ if 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 {
+ if r.err != nil {
+ if r.nerr++; r.nerr > 10000 {
+ panic("go/build: import reader looping")
+ }
+ return 0
+ }
+
+ // Use r.peek as first input byte.
+ // Don't just return r.peek here: it might have been left by peekByte(false)
+ // and this might be peekByte(true).
+ c := r.peek
+ if c == 0 {
+ c = r.readByte()
+ }
+ for r.err == nil && !r.eof {
+ if skipSpace {
+ // For the purposes of this reader, semicolons are never necessary to
+ // understand the input and are treated as spaces.
+ switch c {
+ case ' ', '\f', '\t', '\r', '\n', ';':
+ c = r.readByte()
+ continue
+
+ case '/':
+ c = r.readByte()
+ if c == '/' {
+ for c != '\n' && r.err == nil && !r.eof {
+ c = r.readByte()
+ }
+ } else if c == '*' {
+ var c1 byte
+ for (c != '*' || c1 != '/') && r.err == nil {
+ if r.eof {
+ r.syntaxError()
+ }
+ c, c1 = c1, r.readByte()
+ }
+ } else {
+ r.syntaxError()
+ }
+ c = r.readByte()
+ continue
+ }
+ }
+ break
+ }
+ r.peek = c
+ return r.peek
+}
+
+// nextByte is like peekByte but advances beyond the returned byte.
+func (r *importReader) nextByte(skipSpace bool) byte {
+ c := r.peekByte(skipSpace)
+ r.peek = 0
+ return c
+}
+
+// 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) {
+ r.peekByte(true)
+ for i := 0; i < len(kw); i++ {
+ if r.nextByte(false) != kw[i] {
+ r.syntaxError()
+ return
+ }
+ }
+ if isIdent(r.peekByte(false)) {
+ r.syntaxError()
+ }
+}
+
+// readIdent reads an identifier from the input.
+// If an identifier is not present, readIdent records a syntax error.
+func (r *importReader) readIdent() {
+ c := r.peekByte(true)
+ if !isIdent(c) {
+ r.syntaxError()
+ return
+ }
+ for isIdent(r.peekByte(false)) {
+ r.peek = 0
+ }
+}
+
+// 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) {
+ 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 {
+ r.syntaxError()
+ }
+ }
+ 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' {
+ r.syntaxError()
+ }
+ if c == '\\' {
+ r.nextByte(false)
+ }
+ }
+ default:
+ r.syntaxError()
+ }
+}
+
+// readImport reads an import clause - optional identifier followed by quoted string -
+// from the input.
+func (r *importReader) readImport(imports *[]string) {
+ c := r.peekByte(true)
+ if c == '.' {
+ r.peek = 0
+ } else if isIdent(c) {
+ r.readIdent()
+ }
+ r.readString(imports)
+}
+
+// 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)}
+ r.peekByte(true)
+ if r.err == nil && !r.eof {
+ // Didn't reach EOF, so must have found a non-space byte. Remove it.
+ r.buf = r.buf[:len(r.buf)-1]
+ }
+ return r.buf, r.err
+}
+
+// ReadImports is like io.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) {
+ r := &importReader{b: bufio.NewReader(f)}
+
+ r.readKeyword("package")
+ r.readIdent()
+ for r.peekByte(true) == 'i' {
+ r.readKeyword("import")
+ if r.peekByte(true) == '(' {
+ r.nextByte(false)
+ for r.peekByte(true) != ')' && r.err == nil {
+ r.readImport(imports)
+ }
+ r.nextByte(false)
+ } else {
+ r.readImport(imports)
+ }
+ }
+
+ // 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
+ }
+
+ // 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 {
+ r.err = nil
+ for r.err == nil && !r.eof {
+ r.readByte()
+ }
+ }
+
+ return r.buf, r.err
+}
diff --git a/src/cmd/go/internal/imports/read_test.go b/src/cmd/go/internal/imports/read_test.go
new file mode 100644
index 0000000..6ea356f
--- /dev/null
+++ b/src/cmd/go/internal/imports/read_test.go
@@ -0,0 +1,228 @@
+// Copyright 2012 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.
+
+// Copied from Go distribution src/go/build/read.go.
+
+package imports
+
+import (
+ "io"
+ "strings"
+ "testing"
+)
+
+const quote = "`"
+
+type readTest struct {
+ // Test input contains ℙ where readImports should stop.
+ in string
+ err string
+}
+
+var readImportsTests = []readTest{
+ {
+ `package p`,
+ "",
+ },
+ {
+ `package p; import "x"`,
+ "",
+ },
+ {
+ `package p; import . "x"`,
+ "",
+ },
+ {
+ `package p; import "x";ℙvar x = 1`,
+ "",
+ },
+ {
+ `package p
+
+ // comment
+
+ import "x"
+ import _ "x"
+ import a "x"
+
+ /* comment */
+
+ import (
+ "x" /* comment */
+ _ "x"
+ a "x" // comment
+ ` + quote + `x` + quote + `
+ _ /*comment*/ ` + quote + `x` + quote + `
+ a ` + quote + `x` + quote + `
+ )
+ import (
+ )
+ import ()
+ import()import()import()
+ import();import();import()
+
+ ℙvar x = 1
+ `,
+ "",
+ },
+}
+
+var readCommentsTests = []readTest{
+ {
+ `ℙpackage p`,
+ "",
+ },
+ {
+ `ℙpackage p; import "x"`,
+ "",
+ },
+ {
+ `ℙpackage p; import . "x"`,
+ "",
+ },
+ {
+ `// foo
+
+ /* bar */
+
+ /* quux */ // baz
+
+ /*/ zot */
+
+ // asdf
+ ℙHello, world`,
+ "",
+ },
+}
+
+func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) {
+ for i, tt := range tests {
+ var in, testOut string
+ j := strings.Index(tt.in, "ℙ")
+ if j < 0 {
+ in = tt.in
+ testOut = tt.in
+ } else {
+ in = tt.in[:j] + tt.in[j+len("ℙ"):]
+ testOut = tt.in[:j]
+ }
+ r := strings.NewReader(in)
+ buf, err := read(r)
+ if err != nil {
+ if tt.err == "" {
+ t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf))
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.err) {
+ t.Errorf("#%d: err=%q, expected %q", i, err, tt.err)
+ continue
+ }
+ continue
+ }
+ if err == nil && tt.err != "" {
+ t.Errorf("#%d: success, expected %q", i, tt.err)
+ continue
+ }
+
+ out := string(buf)
+ if out != testOut {
+ t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut)
+ }
+ }
+}
+
+func TestReadImports(t *testing.T) {
+ testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return ReadImports(r, true, nil) })
+}
+
+func TestReadComments(t *testing.T) {
+ testRead(t, readCommentsTests, ReadComments)
+}
+
+var readFailuresTests = []readTest{
+ {
+ `package`,
+ "syntax error",
+ },
+ {
+ "package p\n\x00\nimport `math`\n",
+ "unexpected NUL in input",
+ },
+ {
+ `package p; import`,
+ "syntax error",
+ },
+ {
+ `package p; import "`,
+ "syntax error",
+ },
+ {
+ "package p; import ` \n\n",
+ "syntax error",
+ },
+ {
+ `package p; import "x`,
+ "syntax error",
+ },
+ {
+ `package p; import _`,
+ "syntax error",
+ },
+ {
+ `package p; import _ "`,
+ "syntax error",
+ },
+ {
+ `package p; import _ "x`,
+ "syntax error",
+ },
+ {
+ `package p; import .`,
+ "syntax error",
+ },
+ {
+ `package p; import . "`,
+ "syntax error",
+ },
+ {
+ `package p; import . "x`,
+ "syntax error",
+ },
+ {
+ `package p; import (`,
+ "syntax error",
+ },
+ {
+ `package p; import ("`,
+ "syntax error",
+ },
+ {
+ `package p; import ("x`,
+ "syntax error",
+ },
+ {
+ `package p; import ("x"`,
+ "syntax error",
+ },
+}
+
+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.
+ // Convert tests not to return syntax errors.
+ tests := make([]readTest, len(readFailuresTests))
+ copy(tests, readFailuresTests)
+ for i := range tests {
+ tt := &tests[i]
+ if !strings.Contains(tt.err, "NUL") {
+ tt.err = ""
+ }
+ }
+ testRead(t, tests, func(r io.Reader) ([]byte, error) { return ReadImports(r, false, nil) })
+}
diff --git a/src/cmd/go/internal/imports/scan.go b/src/cmd/go/internal/imports/scan.go
new file mode 100644
index 0000000..ee11a87
--- /dev/null
+++ b/src/cmd/go/internal/imports/scan.go
@@ -0,0 +1,107 @@
+// 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 imports
+
+import (
+ "fmt"
+ "io/fs"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+
+ "cmd/go/internal/fsys"
+)
+
+func ScanDir(dir string, tags map[string]bool) ([]string, []string, error) {
+ infos, err := fsys.ReadDir(dir)
+ if err != nil {
+ return nil, nil, err
+ }
+ var files []string
+ for _, info := range infos {
+ name := info.Name()
+
+ // If the directory entry is a symlink, stat it to obtain the info for the
+ // link target instead of the link itself.
+ if info.Mode()&fs.ModeSymlink != 0 {
+ info, err = fsys.Stat(filepath.Join(dir, name))
+ if err != nil {
+ continue // Ignore broken symlinks.
+ }
+ }
+
+ if info.Mode().IsRegular() && !strings.HasPrefix(name, "_") && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && MatchFile(name, tags) {
+ files = append(files, filepath.Join(dir, name))
+ }
+ }
+ return scanFiles(files, tags, false)
+}
+
+func ScanFiles(files []string, tags map[string]bool) ([]string, []string, error) {
+ return scanFiles(files, tags, true)
+}
+
+func scanFiles(files []string, tags map[string]bool, explicitFiles bool) ([]string, []string, error) {
+ imports := make(map[string]bool)
+ testImports := make(map[string]bool)
+ numFiles := 0
+Files:
+ for _, name := range files {
+ r, err := fsys.Open(name)
+ if err != nil {
+ return nil, nil, err
+ }
+ var list []string
+ data, err := ReadImports(r, false, &list)
+ r.Close()
+ if err != nil {
+ return nil, nil, fmt.Errorf("reading %s: %v", name, err)
+ }
+
+ // import "C" is implicit requirement of cgo tag.
+ // When listing files on the command line (explicitFiles=true)
+ // we do not apply build tag filtering but we still do apply
+ // cgo filtering, so no explicitFiles check here.
+ // Why? Because we always have, and it's not worth breaking
+ // that behavior now.
+ for _, path := range list {
+ if path == `"C"` && !tags["cgo"] && !tags["*"] {
+ continue Files
+ }
+ }
+
+ if !explicitFiles && !ShouldBuild(data, tags) {
+ continue
+ }
+ numFiles++
+ m := imports
+ if strings.HasSuffix(name, "_test.go") {
+ m = testImports
+ }
+ for _, p := range list {
+ q, err := strconv.Unquote(p)
+ if err != nil {
+ continue
+ }
+ m[q] = true
+ }
+ }
+ if numFiles == 0 {
+ return nil, nil, ErrNoGo
+ }
+ return keys(imports), keys(testImports), nil
+}
+
+var ErrNoGo = fmt.Errorf("no Go source files")
+
+func keys(m map[string]bool) []string {
+ var list []string
+ for k := range m {
+ list = append(list, k)
+ }
+ sort.Strings(list)
+ return list
+}
diff --git a/src/cmd/go/internal/imports/scan_test.go b/src/cmd/go/internal/imports/scan_test.go
new file mode 100644
index 0000000..2d245ee
--- /dev/null
+++ b/src/cmd/go/internal/imports/scan_test.go
@@ -0,0 +1,94 @@
+// 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 imports
+
+import (
+ "bytes"
+ "internal/testenv"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+func TestScan(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+
+ imports, testImports, err := ScanDir(filepath.Join(runtime.GOROOT(), "src/encoding/json"), Tags())
+ if err != nil {
+ t.Fatal(err)
+ }
+ foundBase64 := false
+ for _, p := range imports {
+ if p == "encoding/base64" {
+ foundBase64 = true
+ }
+ if p == "encoding/binary" {
+ // A dependency but not an import
+ t.Errorf("json reported as importing encoding/binary but does not")
+ }
+ if p == "net/http" {
+ // A test import but not an import
+ t.Errorf("json reported as importing encoding/binary but does not")
+ }
+ }
+ if !foundBase64 {
+ t.Errorf("json missing import encoding/base64 (%q)", imports)
+ }
+
+ foundHTTP := false
+ for _, p := range testImports {
+ if p == "net/http" {
+ foundHTTP = true
+ }
+ if p == "unicode/utf16" {
+ // A package import but not a test import
+ t.Errorf("json reported as test-importing unicode/utf16 but does not")
+ }
+ }
+ if !foundHTTP {
+ t.Errorf("json missing test import net/http (%q)", testImports)
+ }
+}
+func TestScanDir(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+
+ dirs, err := os.ReadDir("testdata")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, dir := range dirs {
+ if !dir.IsDir() || strings.HasPrefix(dir.Name(), ".") {
+ continue
+ }
+ t.Run(dir.Name(), func(t *testing.T) {
+ tagsData, err := os.ReadFile(filepath.Join("testdata", dir.Name(), "tags.txt"))
+ if err != nil {
+ t.Fatalf("error reading tags: %v", err)
+ }
+ tags := make(map[string]bool)
+ for _, t := range strings.Fields(string(tagsData)) {
+ tags[t] = true
+ }
+
+ wantData, err := os.ReadFile(filepath.Join("testdata", dir.Name(), "want.txt"))
+ if err != nil {
+ t.Fatalf("error reading want: %v", err)
+ }
+ want := string(bytes.TrimSpace(wantData))
+
+ imports, _, err := ScanDir(path.Join("testdata", dir.Name()), tags)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := strings.Join(imports, "\n")
+ if got != want {
+ t.Errorf("ScanDir: got imports:\n%s\n\nwant:\n%s", got, want)
+ }
+ })
+ }
+}
diff --git a/src/cmd/go/internal/imports/tags.go b/src/cmd/go/internal/imports/tags.go
new file mode 100644
index 0000000..01b448b
--- /dev/null
+++ b/src/cmd/go/internal/imports/tags.go
@@ -0,0 +1,58 @@
+// 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 imports
+
+import (
+ "cmd/go/internal/cfg"
+ "sync"
+)
+
+var (
+ tags map[string]bool
+ tagsOnce sync.Once
+)
+
+// Tags returns a set of build tags that are true for the target platform.
+// It includes GOOS, GOARCH, the compiler, possibly "cgo",
+// release tags like "go1.13", and user-specified build tags.
+func Tags() map[string]bool {
+ tagsOnce.Do(func() {
+ tags = loadTags()
+ })
+ return tags
+}
+
+func loadTags() map[string]bool {
+ tags := map[string]bool{
+ cfg.BuildContext.GOOS: true,
+ cfg.BuildContext.GOARCH: true,
+ cfg.BuildContext.Compiler: true,
+ }
+ if cfg.BuildContext.CgoEnabled {
+ tags["cgo"] = true
+ }
+ for _, tag := range cfg.BuildContext.BuildTags {
+ tags[tag] = true
+ }
+ for _, tag := range cfg.BuildContext.ReleaseTags {
+ tags[tag] = true
+ }
+ return tags
+}
+
+var (
+ anyTags map[string]bool
+ anyTagsOnce sync.Once
+)
+
+// AnyTags returns a special set of build tags that satisfy nearly all
+// build tag expressions. Only "ignore" and malformed build tag requirements
+// are considered false.
+func AnyTags() map[string]bool {
+ anyTagsOnce.Do(func() {
+ anyTags = map[string]bool{"*": true}
+ })
+ return anyTags
+}
diff --git a/src/cmd/go/internal/imports/testdata/android/.h.go b/src/cmd/go/internal/imports/testdata/android/.h.go
new file mode 100644
index 0000000..53c529e
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/.h.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "h"
diff --git a/src/cmd/go/internal/imports/testdata/android/a_android.go b/src/cmd/go/internal/imports/testdata/android/a_android.go
new file mode 100644
index 0000000..2ed972e
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/a_android.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "a"
diff --git a/src/cmd/go/internal/imports/testdata/android/b_android_arm64.go b/src/cmd/go/internal/imports/testdata/android/b_android_arm64.go
new file mode 100644
index 0000000..ee9c312
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/b_android_arm64.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "b"
diff --git a/src/cmd/go/internal/imports/testdata/android/c_linux.go b/src/cmd/go/internal/imports/testdata/android/c_linux.go
new file mode 100644
index 0000000..91624ce
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/c_linux.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "c"
diff --git a/src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go b/src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go
new file mode 100644
index 0000000..34e07df
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "d"
diff --git a/src/cmd/go/internal/imports/testdata/android/e.go b/src/cmd/go/internal/imports/testdata/android/e.go
new file mode 100644
index 0000000..d9b2db7
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/e.go
@@ -0,0 +1,5 @@
+// +build android
+
+package android
+
+import _ "e"
diff --git a/src/cmd/go/internal/imports/testdata/android/f.go b/src/cmd/go/internal/imports/testdata/android/f.go
new file mode 100644
index 0000000..281e4dd
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/f.go
@@ -0,0 +1,5 @@
+// +build linux
+
+package android
+
+import _ "f"
diff --git a/src/cmd/go/internal/imports/testdata/android/g.go b/src/cmd/go/internal/imports/testdata/android/g.go
new file mode 100644
index 0000000..66a789c
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/g.go
@@ -0,0 +1,5 @@
+// +build !android
+
+package android
+
+import _ "g"
diff --git a/src/cmd/go/internal/imports/testdata/android/tags.txt b/src/cmd/go/internal/imports/testdata/android/tags.txt
new file mode 100644
index 0000000..aaf5a6b
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/tags.txt
@@ -0,0 +1 @@
+android arm64 \ No newline at end of file
diff --git a/src/cmd/go/internal/imports/testdata/android/want.txt b/src/cmd/go/internal/imports/testdata/android/want.txt
new file mode 100644
index 0000000..0fdf397
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/want.txt
@@ -0,0 +1,6 @@
+a
+b
+c
+d
+e
+f
diff --git a/src/cmd/go/internal/imports/testdata/illumos/.h.go b/src/cmd/go/internal/imports/testdata/illumos/.h.go
new file mode 100644
index 0000000..53c529e
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/.h.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "h"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/a_illumos.go b/src/cmd/go/internal/imports/testdata/illumos/a_illumos.go
new file mode 100644
index 0000000..2e6cb50
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/a_illumos.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "a"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go b/src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go
new file mode 100644
index 0000000..2834d80
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "b"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/c_solaris.go b/src/cmd/go/internal/imports/testdata/illumos/c_solaris.go
new file mode 100644
index 0000000..d7f9462
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/c_solaris.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "c"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go b/src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go
new file mode 100644
index 0000000..0f52c2b
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "d"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/e.go b/src/cmd/go/internal/imports/testdata/illumos/e.go
new file mode 100644
index 0000000..5e1ed3c
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/e.go
@@ -0,0 +1,5 @@
+// +build illumos
+
+package illumos
+
+import _ "e"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/f.go b/src/cmd/go/internal/imports/testdata/illumos/f.go
new file mode 100644
index 0000000..f3e3f72
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/f.go
@@ -0,0 +1,5 @@
+// +build solaris
+
+package illumos
+
+import _ "f"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/g.go b/src/cmd/go/internal/imports/testdata/illumos/g.go
new file mode 100644
index 0000000..b30f1eb
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/g.go
@@ -0,0 +1,5 @@
+// +build !illumos
+
+package illumos
+
+import _ "g"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/tags.txt b/src/cmd/go/internal/imports/testdata/illumos/tags.txt
new file mode 100644
index 0000000..b6386a3
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/tags.txt
@@ -0,0 +1 @@
+illumos amd64
diff --git a/src/cmd/go/internal/imports/testdata/illumos/want.txt b/src/cmd/go/internal/imports/testdata/illumos/want.txt
new file mode 100644
index 0000000..0fdf397
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/want.txt
@@ -0,0 +1,6 @@
+a
+b
+c
+d
+e
+f
diff --git a/src/cmd/go/internal/imports/testdata/star/tags.txt b/src/cmd/go/internal/imports/testdata/star/tags.txt
new file mode 100644
index 0000000..f59ec20
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/tags.txt
@@ -0,0 +1 @@
+* \ No newline at end of file
diff --git a/src/cmd/go/internal/imports/testdata/star/want.txt b/src/cmd/go/internal/imports/testdata/star/want.txt
new file mode 100644
index 0000000..139f5f4
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/want.txt
@@ -0,0 +1,4 @@
+import1
+import2
+import3
+import4
diff --git a/src/cmd/go/internal/imports/testdata/star/x.go b/src/cmd/go/internal/imports/testdata/star/x.go
new file mode 100644
index 0000000..98f9191
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x.go
@@ -0,0 +1,3 @@
+package x
+
+import "import1"
diff --git a/src/cmd/go/internal/imports/testdata/star/x1.go b/src/cmd/go/internal/imports/testdata/star/x1.go
new file mode 100644
index 0000000..6a9594a
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x1.go
@@ -0,0 +1,9 @@
+// +build blahblh
+// +build linux
+// +build !linux
+// +build windows
+// +build darwin
+
+package x
+
+import "import4"
diff --git a/src/cmd/go/internal/imports/testdata/star/x_darwin.go b/src/cmd/go/internal/imports/testdata/star/x_darwin.go
new file mode 100644
index 0000000..a0c3fdd
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x_darwin.go
@@ -0,0 +1,3 @@
+package xxxx
+
+import "import3"
diff --git a/src/cmd/go/internal/imports/testdata/star/x_windows.go b/src/cmd/go/internal/imports/testdata/star/x_windows.go
new file mode 100644
index 0000000..63c5082
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x_windows.go
@@ -0,0 +1,3 @@
+package x
+
+import "import2"