1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
26 // A Context specifies the supporting context for a build.
28 GOARCH string // target architecture
29 GOOS string // target operating system
30 CgoEnabled bool // whether cgo can be used
31 BuildTags []string // additional tags to recognize in +build lines
33 // By default, ScanDir uses the operating system's
34 // file system calls to read directories and files.
35 // Callers can override those calls to provide other
36 // ways to read data by setting ReadDir and ReadFile.
37 // ScanDir does not make any assumptions about the
38 // format of the strings dir and file: they can be
39 // slash-separated, backslash-separated, even URLs.
41 // ReadDir returns a slice of os.FileInfo, sorted by Name,
42 // describing the content of the named directory.
43 // The dir argument is the argument to ScanDir.
44 // If ReadDir is nil, ScanDir uses io.ReadDir.
45 ReadDir func(dir string) (fi []os.FileInfo, err error)
47 // ReadFile returns the content of the file named file
48 // in the directory named dir. The dir argument is the
49 // argument to ScanDir, and the file argument is the
50 // Name field from an os.FileInfo returned by ReadDir.
51 // The returned path is the full name of the file, to be
52 // used in error messages.
54 // If ReadFile is nil, ScanDir uses filepath.Join(dir, file)
55 // as the path and ioutil.ReadFile to read the data.
56 ReadFile func(dir, file string) (path string, content []byte, err error)
59 func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) {
60 if f := ctxt.ReadDir; f != nil {
63 return ioutil.ReadDir(dir)
66 func (ctxt *Context) readFile(dir, file string) (string, []byte, error) {
67 if f := ctxt.ReadFile; f != nil {
70 p := filepath.Join(dir, file)
71 content, err := ioutil.ReadFile(p)
72 return p, content, err
75 // The DefaultContext is the default Context for builds.
76 // It uses the GOARCH and GOOS environment variables
77 // if set, or else the compiled code's GOARCH and GOOS.
78 var DefaultContext Context = defaultContext()
80 var cgoEnabled = map[string]bool{
86 "freebsd/amd64": true,
88 "windows/amd64": true,
91 func defaultContext() Context {
94 c.GOARCH = envOr("GOARCH", runtime.GOARCH)
95 c.GOOS = envOr("GOOS", runtime.GOOS)
97 s := os.Getenv("CGO_ENABLED")
104 c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH]
110 func envOr(name, def string) string {
118 type DirInfo struct {
119 Package string // Name of package in dir
120 PackageComment *ast.CommentGroup // Package comments from GoFiles
121 ImportPath string // Import path of package in dir
122 Imports []string // All packages imported by GoFiles
123 ImportPos map[string][]token.Position // Source code location of imports
126 GoFiles []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles)
127 HFiles []string // .h files in dir
128 CFiles []string // .c files in dir
129 SFiles []string // .s (and, when using cgo, .S files in dir)
130 CgoFiles []string // .go files that import "C"
133 CgoPkgConfig []string // Cgo pkg-config directives
134 CgoCFLAGS []string // Cgo CFLAGS directives
135 CgoLDFLAGS []string // Cgo LDFLAGS directives
138 TestGoFiles []string // _test.go files in package
139 XTestGoFiles []string // _test.go files outside package
140 TestImports []string // All packages imported by (X)TestGoFiles
141 TestImportPos map[string][]token.Position
144 func (d *DirInfo) IsCommand() bool {
145 // TODO(rsc): This is at least a little bogus.
146 return d.Package == "main"
149 // ScanDir calls DefaultContext.ScanDir.
150 func ScanDir(dir string) (info *DirInfo, err error) {
151 return DefaultContext.ScanDir(dir)
154 // TODO(rsc): Move this comment to a more appropriate place.
156 // ScanDir returns a structure with details about the Go package
157 // found in the given directory.
159 // Most .go, .c, .h, and .s files in the directory are considered part
160 // of the package. The exceptions are:
162 // - .go files in package main (unless no other package is found)
163 // - .go files in package documentation
164 // - files starting with _ or .
165 // - files with build constraints not satisfied by the context
169 // A build constraint is a line comment beginning with the directive +build
170 // that lists the conditions under which a file should be included in the package.
171 // Constraints may appear in any kind of source file (not just Go), but
172 // they must be appear near the top of the file, preceded
173 // only by blank lines and other line comments.
175 // A build constraint is evaluated as the OR of space-separated options;
176 // each option evaluates as the AND of its comma-separated terms;
177 // and each term is an alphanumeric word or, preceded by !, its negation.
178 // That is, the build constraint:
180 // // +build linux,386 darwin,!cgo
182 // corresponds to the boolean formula:
184 // (linux AND 386) OR (darwin AND (NOT cgo))
186 // During a particular build, the following words are satisfied:
188 // - the target operating system, as spelled by runtime.GOOS
189 // - the target architecture, as spelled by runtime.GOARCH
190 // - "cgo", if ctxt.CgoEnabled is true
191 // - any additional words listed in ctxt.BuildTags
193 // If a file's name, after stripping the extension and a possible _test suffix,
194 // matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating
195 // system and architecture values, then the file is considered to have an implicit
196 // build constraint requiring those terms.
200 // To keep a file from being considered for the build:
204 // (any other unsatisfied word will work as well, but ``ignore'' is conventional.)
206 // To build a file only when using cgo, and only on Linux and OS X:
208 // // +build linux,cgo darwin,cgo
210 // Such a file is usually paired with another file implementing the
211 // default functionality for other systems, which in this case would
212 // carry the constraint:
214 // // +build !linux !darwin !cgo
216 // Naming a file dns_windows.go will cause it to be included only when
217 // building the package for Windows; similarly, math_386.s will be included
218 // only when building the package for 32-bit x86.
220 func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
221 dirs, err := ctxt.readDir(dir)
226 var Sfiles []string // files with ".S" (capital S)
228 imported := make(map[string][]token.Position)
229 testImported := make(map[string][]token.Position)
230 fset := token.NewFileSet()
231 for _, d := range dirs {
236 if strings.HasPrefix(name, "_") ||
237 strings.HasPrefix(name, ".") {
240 if !ctxt.goodOSArchFile(name) {
244 ext := path.Ext(name)
246 case ".go", ".c", ".s", ".h", ".S":
253 // Look for +build comments to accept or reject the file.
254 filename, data, err := ctxt.readFile(dir, name)
258 if !ctxt.shouldBuild(data) {
262 // Going to save the file. For non-Go files, can stop here.
265 di.CFiles = append(di.CFiles, name)
268 di.HFiles = append(di.HFiles, name)
271 di.SFiles = append(di.SFiles, name)
274 Sfiles = append(Sfiles, name)
278 pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
283 pkg := string(pf.Name.Name)
284 if pkg == "main" && di.Package != "" && di.Package != "main" {
287 if pkg == "documentation" {
291 isTest := strings.HasSuffix(name, "_test.go")
292 if isTest && strings.HasSuffix(pkg, "_test") {
293 pkg = pkg[:len(pkg)-len("_test")]
296 if pkg != di.Package && di.Package == "main" {
297 // Found non-main package but was recording
298 // information about package main. Reset.
301 if di.Package == "" {
303 } else if pkg != di.Package {
304 return nil, fmt.Errorf("%s: found packages %s and %s", dir, pkg, di.Package)
307 if di.PackageComment != nil {
308 di.PackageComment.List = append(di.PackageComment.List, pf.Doc.List...)
310 di.PackageComment = pf.Doc
314 // Record imports and information about cgo.
316 for _, decl := range pf.Decls {
317 d, ok := decl.(*ast.GenDecl)
321 for _, dspec := range d.Specs {
322 spec, ok := dspec.(*ast.ImportSpec)
326 quoted := string(spec.Path.Value)
327 path, err := strconv.Unquote(quoted)
329 log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
332 testImported[path] = append(testImported[path], fset.Position(spec.Pos()))
334 imported[path] = append(imported[path], fset.Position(spec.Pos()))
338 return nil, fmt.Errorf("%s: use of cgo in test not supported", filename)
341 if cg == nil && len(d.Specs) == 1 {
345 if err := ctxt.saveCgo(filename, &di, cg); err != nil {
355 di.CgoFiles = append(di.CgoFiles, name)
358 if pkg == string(pf.Name.Name) {
359 di.TestGoFiles = append(di.TestGoFiles, name)
361 di.XTestGoFiles = append(di.XTestGoFiles, name)
364 di.GoFiles = append(di.GoFiles, name)
367 if di.Package == "" {
368 return nil, fmt.Errorf("%s: no Go source files", dir)
370 di.Imports = make([]string, len(imported))
371 di.ImportPos = imported
373 for p := range imported {
377 di.TestImports = make([]string, len(testImported))
378 di.TestImportPos = testImported
380 for p := range testImported {
381 di.TestImports[i] = p
385 // add the .S files only if we are using cgo
386 // (which means gcc will compile them).
387 // The standard assemblers expect .s files.
388 if len(di.CgoFiles) > 0 {
389 di.SFiles = append(di.SFiles, Sfiles...)
390 sort.Strings(di.SFiles)
393 // File name lists are sorted because ReadDir sorts.
394 sort.Strings(di.Imports)
395 sort.Strings(di.TestImports)
399 var slashslash = []byte("//")
401 // shouldBuild reports whether it is okay to use this file,
402 // The rule is that in the file's leading run of // comments
403 // and blank lines, which must be followed by a blank line
404 // (to avoid including a Go package clause doc comment),
405 // lines beginning with '// +build' are taken as build directives.
407 // The file is accepted only if each such line lists something
408 // matching the file. For example:
410 // // +build windows linux
412 // marks the file as applicable only on Windows and Linux.
414 func (ctxt *Context) shouldBuild(content []byte) bool {
415 // Pass 1. Identify leading run of // comments and blank lines,
416 // which must be followed by a blank line.
421 if i := bytes.IndexByte(line, '\n'); i >= 0 {
422 line, p = line[:i], p[i+1:]
426 line = bytes.TrimSpace(line)
427 if len(line) == 0 { // Blank line
428 end = cap(content) - cap(line) // &line[0] - &content[0]
431 if !bytes.HasPrefix(line, slashslash) { // Not comment line
435 content = content[:end]
437 // Pass 2. Process each line in the run.
441 if i := bytes.IndexByte(line, '\n'); i >= 0 {
442 line, p = line[:i], p[i+1:]
446 line = bytes.TrimSpace(line)
447 if bytes.HasPrefix(line, slashslash) {
448 line = bytes.TrimSpace(line[len(slashslash):])
449 if len(line) > 0 && line[0] == '+' {
450 // Looks like a comment +line.
451 f := strings.Fields(string(line))
452 if f[0] == "+build" {
454 for _, tok := range f[1:] {
461 return false // this one doesn't match
467 return true // everything matches
470 // saveCgo saves the information from the #cgo lines in the import "C" comment.
471 // These lines set CFLAGS and LDFLAGS and pkg-config directives that affect
472 // the way cgo's C code is built.
474 // TODO(rsc): This duplicates code in cgo.
475 // Once the dust settles, remove this code from cgo.
476 func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) error {
478 for _, line := range strings.Split(text, "\n") {
482 // #cgo [GOOS/GOARCH...] LDFLAGS: stuff
484 line = strings.TrimSpace(line)
485 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
490 line = strings.TrimSpace(line[4:])
491 i := strings.Index(line, ":")
493 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
495 line, argstr := line[:i], line[i+1:]
497 // Parse GOOS/GOARCH stuff.
498 f := strings.Fields(line)
500 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
503 cond, verb := f[:len(f)-1], f[len(f)-1]
506 for _, c := range cond {
517 args, err := splitQuoted(argstr)
519 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
521 for _, arg := range args {
523 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
529 di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
531 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
533 di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
535 return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
541 var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:")
543 func safeName(s string) bool {
547 for i := 0; i < len(s); i++ {
548 if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 {
555 // splitQuoted splits the string s around each instance of one or more consecutive
556 // white space characters while taking into account quotes and escaping, and
557 // returns an array of substrings of s or an empty list if s contains only white space.
558 // Single quotes and double quotes are recognized to prevent splitting within the
559 // quoted region, and are removed from the resulting substrings. If a quote in s
560 // isn't closed err will be set and r will have the unclosed argument as the
561 // last element. The backslash is used for escaping.
563 // For example, the following string:
565 // a b:"c d" 'e''f' "g\""
567 // Would be parsed as:
569 // []string{"a", "b:c d", "ef", `g"`}
571 func splitQuoted(s string) (r []string, err error) {
573 arg := make([]rune, len(s))
578 for _, rune := range s {
585 case quote != '\x00':
590 case rune == '"' || rune == '\'':
594 case unicode.IsSpace(rune):
597 args = append(args, string(arg[:i]))
606 args = append(args, string(arg[:i]))
609 err = errors.New("unclosed quote")
611 err = errors.New("unfinished escaping")
616 // match returns true if the name is one of:
620 // cgo (if cgo is enabled)
621 // !cgo (if cgo is disabled)
622 // tag (if tag is listed in ctxt.BuildTags)
623 // !tag (if tag is not listed in ctxt.BuildTags)
624 // a slash-separated list of any of these
626 func (ctxt *Context) match(name string) bool {
630 if i := strings.Index(name, ","); i >= 0 {
631 // comma-separated list
632 return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
634 if strings.HasPrefix(name, "!!") { // bad syntax, reject always
637 if strings.HasPrefix(name, "!") { // negation
638 return !ctxt.match(name[1:])
641 // Tags must be letters, digits, underscores.
642 // Unlike in Go identifiers, all digits is fine (e.g., "386").
643 for _, c := range name {
644 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' {
650 if ctxt.CgoEnabled && name == "cgo" {
653 if name == ctxt.GOOS || name == ctxt.GOARCH {
658 for _, tag := range ctxt.BuildTags {
667 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
668 // suffix which does not match the current system.
669 // The recognized name formats are:
673 // name_$(GOOS)_$(GOARCH).*
674 // name_$(GOOS)_test.*
675 // name_$(GOARCH)_test.*
676 // name_$(GOOS)_$(GOARCH)_test.*
678 func (ctxt *Context) goodOSArchFile(name string) bool {
679 if dot := strings.Index(name, "."); dot != -1 {
682 l := strings.Split(name, "_")
683 if n := len(l); n > 0 && l[n-1] == "test" {
687 if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
688 return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
690 if n >= 1 && knownOS[l[n-1]] {
691 return l[n-1] == ctxt.GOOS
693 if n >= 1 && knownArch[l[n-1]] {
694 return l[n-1] == ctxt.GOARCH
699 var knownOS = make(map[string]bool)
700 var knownArch = make(map[string]bool)
703 for _, v := range strings.Fields(goosList) {
706 for _, v := range strings.Fields(goarchList) {