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.
28 // A Context specifies the supporting context for a build.
30 GOARCH string // target architecture
31 GOOS string // target operating system
32 GOROOT string // Go root
33 GOPATH string // Go path
34 CgoEnabled bool // whether cgo can be used
35 BuildTags []string // additional tags to recognize in +build lines
36 UseAllFiles bool // use files regardless of +build lines, file names
37 Compiler string // compiler to assume when computing target paths
39 // By default, Import uses the operating system's file system calls
40 // to read directories and files. To read from other sources,
41 // callers can set the following functions. They all have default
42 // behaviors that use the local file system, so clients need only set
43 // the functions whose behaviors they wish to change.
45 // JoinPath joins the sequence of path fragments into a single path.
46 // If JoinPath is nil, Import uses filepath.Join.
47 JoinPath func(elem ...string) string
49 // SplitPathList splits the path list into a slice of individual paths.
50 // If SplitPathList is nil, Import uses filepath.SplitList.
51 SplitPathList func(list string) []string
53 // IsAbsPath reports whether path is an absolute path.
54 // If IsAbsPath is nil, Import uses filepath.IsAbs.
55 IsAbsPath func(path string) bool
57 // IsDir reports whether the path names a directory.
58 // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
59 IsDir func(path string) bool
61 // HasSubdir reports whether dir is a subdirectory of
62 // (perhaps multiple levels below) root.
63 // If so, HasSubdir sets rel to a slash-separated path that
64 // can be joined to root to produce a path equivalent to dir.
65 // If HasSubdir is nil, Import uses an implementation built on
66 // filepath.EvalSymlinks.
67 HasSubdir func(root, dir string) (rel string, ok bool)
69 // ReadDir returns a slice of os.FileInfo, sorted by Name,
70 // describing the content of the named directory.
71 // If ReadDir is nil, Import uses ioutil.ReadDir.
72 ReadDir func(dir string) (fi []os.FileInfo, err error)
74 // OpenFile opens a file (not a directory) for reading.
75 // If OpenFile is nil, Import uses os.Open.
76 OpenFile func(path string) (r io.ReadCloser, err error)
79 // joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join.
80 func (ctxt *Context) joinPath(elem ...string) string {
81 if f := ctxt.JoinPath; f != nil {
84 return filepath.Join(elem...)
87 // splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList.
88 func (ctxt *Context) splitPathList(s string) []string {
89 if f := ctxt.SplitPathList; f != nil {
92 return filepath.SplitList(s)
95 // isAbsPath calls ctxt.IsAbsSPath (if not nil) or else filepath.IsAbs.
96 func (ctxt *Context) isAbsPath(path string) bool {
97 if f := ctxt.IsAbsPath; f != nil {
100 return filepath.IsAbs(path)
103 // isDir calls ctxt.IsDir (if not nil) or else uses os.Stat.
104 func (ctxt *Context) isDir(path string) bool {
105 if f := ctxt.IsDir; f != nil {
108 fi, err := os.Stat(path)
109 return err == nil && fi.IsDir()
112 // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
113 // the local file system to answer the question.
114 func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) {
115 if f := ctxt.HasSubdir; f != nil {
119 if p, err := filepath.EvalSymlinks(root); err == nil {
122 if p, err := filepath.EvalSymlinks(dir); err == nil {
125 const sep = string(filepath.Separator)
126 root = filepath.Clean(root)
127 if !strings.HasSuffix(root, sep) {
130 dir = filepath.Clean(dir)
131 if !strings.HasPrefix(dir, root) {
134 return filepath.ToSlash(dir[len(root):]), true
137 // readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir.
138 func (ctxt *Context) readDir(path string) ([]os.FileInfo, error) {
139 if f := ctxt.ReadDir; f != nil {
142 return ioutil.ReadDir(path)
145 // openFile calls ctxt.OpenFile (if not nil) or else os.Open.
146 func (ctxt *Context) openFile(path string) (io.ReadCloser, error) {
147 if fn := ctxt.OpenFile; fn != nil {
151 f, err := os.Open(path)
153 return nil, err // nil interface
158 // isFile determines whether path is a file by trying to open it.
159 // It reuses openFile instead of adding another function to the
161 func (ctxt *Context) isFile(path string) bool {
162 f, err := ctxt.openFile(path)
170 // gopath returns the list of Go path directories.
171 func (ctxt *Context) gopath() []string {
173 for _, p := range ctxt.splitPathList(ctxt.GOPATH) {
174 if p == "" || p == ctxt.GOROOT {
175 // Empty paths are uninteresting.
176 // If the path is the GOROOT, ignore it.
177 // People sometimes set GOPATH=$GOROOT, which is useless
178 // but would cause us to find packages with import paths
180 // Do not get confused by this common mistake.
188 // SrcDirs returns a list of package source root directories.
189 // It draws from the current Go root and Go path but omits directories
190 // that do not exist.
191 func (ctxt *Context) SrcDirs() []string {
193 if ctxt.GOROOT != "" {
194 dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg")
196 all = append(all, dir)
199 for _, p := range ctxt.gopath() {
200 dir := ctxt.joinPath(p, "src")
202 all = append(all, dir)
208 // Default is the default Context for builds.
209 // It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables
210 // if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
211 var Default Context = defaultContext()
213 var cgoEnabled = map[string]bool{
215 "darwin/amd64": true,
219 "freebsd/amd64": true,
221 "windows/amd64": true,
224 func defaultContext() Context {
227 c.GOARCH = envOr("GOARCH", runtime.GOARCH)
228 c.GOOS = envOr("GOOS", runtime.GOOS)
229 c.GOROOT = runtime.GOROOT()
230 c.GOPATH = envOr("GOPATH", "")
231 c.Compiler = runtime.Compiler
233 switch os.Getenv("CGO_ENABLED") {
239 c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH]
245 func envOr(name, def string) string {
253 // An ImportMode controls the behavior of the Import method.
257 // If FindOnly is set, Import stops after locating the directory
258 // that should contain the sources for a package. It does not
259 // read any files in the directory.
260 FindOnly ImportMode = 1 << iota
262 // If AllowBinary is set, Import can be satisfied by a compiled
263 // package object without corresponding sources.
267 // A Package describes the Go package found in a directory.
268 type Package struct {
269 Dir string // directory containing package sources
270 Name string // package name
271 Doc string // documentation synopsis
272 ImportPath string // import path of package ("" if unknown)
273 Root string // root of Go tree where this package lives
274 SrcRoot string // package source root directory ("" if unknown)
275 PkgRoot string // package install root directory ("" if unknown)
276 BinDir string // command install directory ("" if unknown)
277 Goroot bool // package found in Go root
278 PkgObj string // installed .a file
281 GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
282 CgoFiles []string // .go source files that import "C"
283 CFiles []string // .c source files
284 HFiles []string // .h source files
285 SFiles []string // .s source files
286 SysoFiles []string // .syso system object files to add to archive
289 CgoPkgConfig []string // Cgo pkg-config directives
290 CgoCFLAGS []string // Cgo CFLAGS directives
291 CgoLDFLAGS []string // Cgo LDFLAGS directives
293 // Dependency information
294 Imports []string // imports from GoFiles, CgoFiles
295 ImportPos map[string][]token.Position // line information for Imports
298 TestGoFiles []string // _test.go files in package
299 TestImports []string // imports from TestGoFiles
300 TestImportPos map[string][]token.Position // line information for TestImports
301 XTestGoFiles []string // _test.go files outside package
302 XTestImports []string // imports from XTestGoFiles
303 XTestImportPos map[string][]token.Position // line information for XTestImports
306 // IsCommand reports whether the package is considered a
307 // command to be installed (not just a library).
308 // Packages named "main" are treated as commands.
309 func (p *Package) IsCommand() bool {
310 return p.Name == "main"
313 // ImportDir is like Import but processes the Go package found in
314 // the named directory.
315 func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) {
316 return ctxt.Import(".", dir, mode)
319 // NoGoError is the error used by Import to describe a directory
320 // containing no Go source files.
321 type NoGoError struct {
325 func (e *NoGoError) Error() string {
326 return "no Go source files in " + e.Dir
329 // Import returns details about the Go package named by the import path,
330 // interpreting local import paths relative to the srcDir directory.
331 // If the path is a local import path naming a package that can be imported
332 // using a standard import path, the returned package will set p.ImportPath
335 // In the directory containing the package, .go, .c, .h, and .s files are
336 // considered part of the package except for:
338 // - .go files in package documentation
339 // - files starting with _ or . (likely editor temporary files)
340 // - files with build constraints not satisfied by the context
342 // If an error occurs, Import returns a non-nil error and a non-nil
343 // *Package containing partial information.
345 func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) {
352 switch ctxt.Compiler {
354 dir, elem := pathpkg.Split(p.ImportPath)
355 pkga = "pkg/gccgo/" + dir + "lib" + elem + ".a"
357 pkga = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + "/" + p.ImportPath + ".a"
359 // Save error for end of function.
360 pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler)
364 if IsLocalImport(path) {
365 pkga = "" // local imports have no installed path
367 return p, fmt.Errorf("import %q: import relative to unknown directory", path)
369 if !ctxt.isAbsPath(path) {
370 p.Dir = ctxt.joinPath(srcDir, path)
372 // Determine canonical import path, if any.
373 if ctxt.GOROOT != "" {
374 root := ctxt.joinPath(ctxt.GOROOT, "src", "pkg")
375 if sub, ok := ctxt.hasSubdir(root, p.Dir); ok {
383 for i, root := range all {
384 rootsrc := ctxt.joinPath(root, "src")
385 if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok {
386 // We found a potential import path for dir,
387 // but check that using it wouldn't find something
389 if ctxt.GOROOT != "" {
390 if dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", sub); ctxt.isDir(dir) {
394 for _, earlyRoot := range all[:i] {
395 if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) {
400 // sub would not name some other directory instead of this one.
407 // It's okay that we didn't find a root containing dir.
408 // Keep going with the information we have.
410 if strings.HasPrefix(path, "/") {
411 return p, fmt.Errorf("import %q: cannot import absolute path", path)
413 // Determine directory from import path.
414 if ctxt.GOROOT != "" {
415 dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", path)
416 isDir := ctxt.isDir(dir)
417 binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga))
418 if isDir || binaryOnly {
425 for _, root := range ctxt.gopath() {
426 dir := ctxt.joinPath(root, "src", path)
427 isDir := ctxt.isDir(dir)
428 binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga))
429 if isDir || binaryOnly {
435 return p, fmt.Errorf("import %q: cannot find package", path)
441 p.SrcRoot = ctxt.joinPath(p.Root, "src", "pkg")
443 p.SrcRoot = ctxt.joinPath(p.Root, "src")
445 p.PkgRoot = ctxt.joinPath(p.Root, "pkg")
446 p.BinDir = ctxt.joinPath(p.Root, "bin")
448 p.PkgObj = ctxt.joinPath(p.Root, pkga)
452 if mode&FindOnly != 0 {
455 if binaryOnly && (mode&AllowBinary) != 0 {
459 dirs, err := ctxt.readDir(p.Dir)
464 var Sfiles []string // files with ".S" (capital S)
466 imported := make(map[string][]token.Position)
467 testImported := make(map[string][]token.Position)
468 xTestImported := make(map[string][]token.Position)
469 fset := token.NewFileSet()
470 for _, d := range dirs {
475 if strings.HasPrefix(name, "_") ||
476 strings.HasPrefix(name, ".") {
479 if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) {
483 i := strings.LastIndex(name, ".")
489 case ".go", ".c", ".s", ".h", ".S":
490 // tentatively okay - read to make sure
492 // binary objects to add to package archive
493 // Likely of the form foo_windows.syso, but
494 // the name was vetted above with goodOSArchFile.
495 p.SysoFiles = append(p.SysoFiles, name)
502 filename := ctxt.joinPath(p.Dir, name)
503 f, err := ctxt.openFile(filename)
507 data, err := ioutil.ReadAll(f)
510 return p, fmt.Errorf("read %s: %v", filename, err)
513 // Look for +build comments to accept or reject the file.
514 if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) {
518 // Going to save the file. For non-Go files, can stop here.
521 p.CFiles = append(p.CFiles, name)
524 p.HFiles = append(p.HFiles, name)
527 p.SFiles = append(p.SFiles, name)
530 Sfiles = append(Sfiles, name)
534 pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
539 pkg := string(pf.Name.Name)
540 if pkg == "documentation" {
544 isTest := strings.HasSuffix(name, "_test.go")
546 if isTest && strings.HasSuffix(pkg, "_test") {
548 pkg = pkg[:len(pkg)-len("_test")]
554 } else if pkg != p.Name {
555 return p, fmt.Errorf("found packages %s (%s) and %s (%s) in %s", p.Name, firstFile, pkg, name, p.Dir)
557 if pf.Doc != nil && p.Doc == "" {
558 p.Doc = doc.Synopsis(pf.Doc.Text())
561 // Record imports and information about cgo.
563 for _, decl := range pf.Decls {
564 d, ok := decl.(*ast.GenDecl)
568 for _, dspec := range d.Specs {
569 spec, ok := dspec.(*ast.ImportSpec)
573 quoted := string(spec.Path.Value)
574 path, err := strconv.Unquote(quoted)
576 log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
579 xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos()))
581 testImported[path] = append(testImported[path], fset.Position(spec.Pos()))
583 imported[path] = append(imported[path], fset.Position(spec.Pos()))
587 return p, fmt.Errorf("use of cgo in test %s not supported", filename)
590 if cg == nil && len(d.Specs) == 1 {
594 if err := ctxt.saveCgo(filename, p, cg); err != nil {
604 p.CgoFiles = append(p.CgoFiles, name)
607 p.XTestGoFiles = append(p.XTestGoFiles, name)
609 p.TestGoFiles = append(p.TestGoFiles, name)
611 p.GoFiles = append(p.GoFiles, name)
615 return p, &NoGoError{p.Dir}
618 p.Imports, p.ImportPos = cleanImports(imported)
619 p.TestImports, p.TestImportPos = cleanImports(testImported)
620 p.XTestImports, p.XTestImportPos = cleanImports(xTestImported)
622 // add the .S files only if we are using cgo
623 // (which means gcc will compile them).
624 // The standard assemblers expect .s files.
625 if len(p.CgoFiles) > 0 {
626 p.SFiles = append(p.SFiles, Sfiles...)
627 sort.Strings(p.SFiles)
633 func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
634 all := make([]string, 0, len(m))
635 for path := range m {
636 all = append(all, path)
642 // Import is shorthand for Default.Import.
643 func Import(path, srcDir string, mode ImportMode) (*Package, error) {
644 return Default.Import(path, srcDir, mode)
647 // ImportDir is shorthand for Default.ImportDir.
648 func ImportDir(dir string, mode ImportMode) (*Package, error) {
649 return Default.ImportDir(dir, mode)
652 var slashslash = []byte("//")
654 // shouldBuild reports whether it is okay to use this file,
655 // The rule is that in the file's leading run of // comments
656 // and blank lines, which must be followed by a blank line
657 // (to avoid including a Go package clause doc comment),
658 // lines beginning with '// +build' are taken as build directives.
660 // The file is accepted only if each such line lists something
661 // matching the file. For example:
663 // // +build windows linux
665 // marks the file as applicable only on Windows and Linux.
667 func (ctxt *Context) shouldBuild(content []byte) bool {
668 // Pass 1. Identify leading run of // comments and blank lines,
669 // which must be followed by a blank line.
674 if i := bytes.IndexByte(line, '\n'); i >= 0 {
675 line, p = line[:i], p[i+1:]
679 line = bytes.TrimSpace(line)
680 if len(line) == 0 { // Blank line
681 end = cap(content) - cap(line) // &line[0] - &content[0]
684 if !bytes.HasPrefix(line, slashslash) { // Not comment line
688 content = content[:end]
690 // Pass 2. Process each line in the run.
694 if i := bytes.IndexByte(line, '\n'); i >= 0 {
695 line, p = line[:i], p[i+1:]
699 line = bytes.TrimSpace(line)
700 if bytes.HasPrefix(line, slashslash) {
701 line = bytes.TrimSpace(line[len(slashslash):])
702 if len(line) > 0 && line[0] == '+' {
703 // Looks like a comment +line.
704 f := strings.Fields(string(line))
705 if f[0] == "+build" {
707 for _, tok := range f[1:] {
714 return false // this one doesn't match
720 return true // everything matches
723 // saveCgo saves the information from the #cgo lines in the import "C" comment.
724 // These lines set CFLAGS and LDFLAGS and pkg-config directives that affect
725 // the way cgo's C code is built.
727 // TODO(rsc): This duplicates code in cgo.
728 // Once the dust settles, remove this code from cgo.
729 func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error {
731 for _, line := range strings.Split(text, "\n") {
735 // #cgo [GOOS/GOARCH...] LDFLAGS: stuff
737 line = strings.TrimSpace(line)
738 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
743 line = strings.TrimSpace(line[4:])
744 i := strings.Index(line, ":")
746 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
748 line, argstr := line[:i], line[i+1:]
750 // Parse GOOS/GOARCH stuff.
751 f := strings.Fields(line)
753 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
756 cond, verb := f[:len(f)-1], f[len(f)-1]
759 for _, c := range cond {
770 args, err := splitQuoted(argstr)
772 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
774 for _, arg := range args {
776 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
782 di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
784 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
786 di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
788 return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
794 var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:")
796 func safeName(s string) bool {
800 for i := 0; i < len(s); i++ {
801 if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 {
808 // splitQuoted splits the string s around each instance of one or more consecutive
809 // white space characters while taking into account quotes and escaping, and
810 // returns an array of substrings of s or an empty list if s contains only white space.
811 // Single quotes and double quotes are recognized to prevent splitting within the
812 // quoted region, and are removed from the resulting substrings. If a quote in s
813 // isn't closed err will be set and r will have the unclosed argument as the
814 // last element. The backslash is used for escaping.
816 // For example, the following string:
818 // a b:"c d" 'e''f' "g\""
820 // Would be parsed as:
822 // []string{"a", "b:c d", "ef", `g"`}
824 func splitQuoted(s string) (r []string, err error) {
826 arg := make([]rune, len(s))
831 for _, rune := range s {
838 case quote != '\x00':
843 case rune == '"' || rune == '\'':
847 case unicode.IsSpace(rune):
850 args = append(args, string(arg[:i]))
859 args = append(args, string(arg[:i]))
862 err = errors.New("unclosed quote")
864 err = errors.New("unfinished escaping")
869 // match returns true if the name is one of:
873 // cgo (if cgo is enabled)
874 // !cgo (if cgo is disabled)
875 // tag (if tag is listed in ctxt.BuildTags)
876 // !tag (if tag is not listed in ctxt.BuildTags)
877 // a comma-separated list of any of these
879 func (ctxt *Context) match(name string) bool {
883 if i := strings.Index(name, ","); i >= 0 {
884 // comma-separated list
885 return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
887 if strings.HasPrefix(name, "!!") { // bad syntax, reject always
890 if strings.HasPrefix(name, "!") { // negation
891 return len(name) > 1 && !ctxt.match(name[1:])
894 // Tags must be letters, digits, underscores.
895 // Unlike in Go identifiers, all digits are fine (e.g., "386").
896 for _, c := range name {
897 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' {
903 if ctxt.CgoEnabled && name == "cgo" {
906 if name == ctxt.GOOS || name == ctxt.GOARCH {
911 for _, tag := range ctxt.BuildTags {
920 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
921 // suffix which does not match the current system.
922 // The recognized name formats are:
926 // name_$(GOOS)_$(GOARCH).*
927 // name_$(GOOS)_test.*
928 // name_$(GOARCH)_test.*
929 // name_$(GOOS)_$(GOARCH)_test.*
931 func (ctxt *Context) goodOSArchFile(name string) bool {
932 if dot := strings.Index(name, "."); dot != -1 {
935 l := strings.Split(name, "_")
936 if n := len(l); n > 0 && l[n-1] == "test" {
940 if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
941 return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
943 if n >= 1 && knownOS[l[n-1]] {
944 return l[n-1] == ctxt.GOOS
946 if n >= 1 && knownArch[l[n-1]] {
947 return l[n-1] == ctxt.GOARCH
952 var knownOS = make(map[string]bool)
953 var knownArch = make(map[string]bool)
956 for _, v := range strings.Fields(goosList) {
959 for _, v := range strings.Fields(goarchList) {
964 // ToolDir is the directory containing build tools.
965 var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)
967 // IsLocalImport reports whether the import path is
968 // a local import path, like ".", "..", "./foo", or "../foo".
969 func IsLocalImport(path string) bool {
970 return path == "." || path == ".." ||
971 strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
974 // ArchChar returns the architecture character for the given goarch.
975 // For example, ArchChar("amd64") returns "6".
976 func ArchChar(goarch string) (string, error) {
985 return "", errors.New("unsupported GOARCH " + goarch)