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
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)
}
}
- var fileList *[]string
+ var fileList, embedList *[]string
var importMap map[string][]token.Position
switch {
case isCgo:
if ctxt.CgoEnabled {
fileList = &p.CgoFiles
importMap = imported
+ embedList = &embeds
} else {
// Ignore imports from cgo files if cgo is disabled.
fileList = &p.IgnoredGoFiles
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 {
importMap[imp.path] = append(importMap[imp.path], fset.Position(imp.pos))
}
}
+ if embedList != nil {
+ *embedList = append(*embedList, info.embeds...)
+ }
}
for tag := range allTags {
}
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)
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.
parsed *ast.File
parseErr error
imports []fileImport
+ embeds []string
+ embedErr error
}
type fileImport struct {
"go/parser"
"io"
"strconv"
+ "strings"
+ "unicode"
"unicode/utf8"
)
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 {
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) {
// 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,
-// and info.imports.
+// 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.
return nil
}
+ hasEmbed := false
for _, decl := range info.parsed.Decls {
d, ok := decl.(*ast.GenDecl)
if !ok {
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 {
}
}
+ // 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
+}
package build
import (
+ "go/token"
"io"
+ "reflect"
"strings"
"testing"
)
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.Files",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\nvar i int\n//go:embed x \"\\x79\" `z`\nvar files embed.Files",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\nvar i int\n//go:embed x y\n//go:embed z\nvar files embed.Files",
+ []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.Files",
+ []string{"x", "y", "z"},
+ },
+ {
+ "package p\nimport \"embed\"\n//go:embed x y z\nvar files embed.Files",
+ []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.Files", // 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)
+ }
+ }
+}