| // Copyright 2011 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 build |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "go/ast" |
| "go/build/constraint" |
| "go/doc" |
| "go/token" |
| "internal/buildcfg" |
| exec "internal/execabs" |
| "internal/goroot" |
| "internal/goversion" |
| "io" |
| "io/fs" |
| "io/ioutil" |
| "os" |
| pathpkg "path" |
| "path/filepath" |
| "runtime" |
| "sort" |
| "strconv" |
| "strings" |
| "unicode" |
| "unicode/utf8" |
| ) |
| |
| // A Context specifies the supporting context for a build. |
| type Context struct { |
| GOARCH string // target architecture |
| GOOS string // target operating system |
| GOROOT string // Go root |
| GOPATH string // Go path |
| |
| // Dir is the caller's working directory, or the empty string to use |
| // the current directory of the running process. In module mode, this is used |
| // to locate the main module. |
| // |
| // If Dir is non-empty, directories passed to Import and ImportDir must |
| // be absolute. |
| Dir string |
| |
| CgoEnabled bool // whether cgo files are included |
| UseAllFiles bool // use files regardless of +build lines, file names |
| Compiler string // compiler to assume when computing target paths |
| |
| // The build, tool, and release tags specify build constraints |
| // that should be considered satisfied when processing +build lines. |
| // Clients creating a new context may customize BuildTags, which |
| // defaults to empty, but it is usually an error to customize ToolTags or ReleaseTags. |
| // ToolTags defaults to build tags appropriate to the current Go toolchain configuration. |
| // ReleaseTags defaults to the list of Go releases the current release is compatible with. |
| // BuildTags is not set for the Default build Context. |
| // In addition to the BuildTags, ToolTags, and ReleaseTags, build constraints |
| // consider the values of GOARCH and GOOS as satisfied tags. |
| // The last element in ReleaseTags is assumed to be the current release. |
| BuildTags []string |
| ToolTags []string |
| ReleaseTags []string |
| |
| // The install suffix specifies a suffix to use in the name of the installation |
| // directory. By default it is empty, but custom builds that need to keep |
| // their outputs separate can set InstallSuffix to do so. For example, when |
| // using the race detector, the go command uses InstallSuffix = "race", so |
| // that on a Linux/386 system, packages are written to a directory named |
| // "linux_386_race" instead of the usual "linux_386". |
| InstallSuffix string |
| |
| // By default, Import uses the operating system's file system calls |
| // to read directories and files. To read from other sources, |
| // callers can set the following functions. They all have default |
| // behaviors that use the local file system, so clients need only set |
| // the functions whose behaviors they wish to change. |
| |
| // JoinPath joins the sequence of path fragments into a single path. |
| // If JoinPath is nil, Import uses filepath.Join. |
| JoinPath func(elem ...string) string |
| |
| // SplitPathList splits the path list into a slice of individual paths. |
| // If SplitPathList is nil, Import uses filepath.SplitList. |
| SplitPathList func(list string) []string |
| |
| // IsAbsPath reports whether path is an absolute path. |
| // If IsAbsPath is nil, Import uses filepath.IsAbs. |
| IsAbsPath func(path string) bool |
| |
| // IsDir reports whether the path names a directory. |
| // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. |
| IsDir func(path string) bool |
| |
| // HasSubdir reports whether dir is lexically a subdirectory of |
| // root, perhaps multiple levels below. It does not try to check |
| // whether dir exists. |
| // If so, HasSubdir sets rel to a slash-separated path that |
| // can be joined to root to produce a path equivalent to dir. |
| // If HasSubdir is nil, Import uses an implementation built on |
| // filepath.EvalSymlinks. |
| HasSubdir func(root, dir string) (rel string, ok bool) |
| |
| // 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) ([]fs.FileInfo, error) |
| |
| // OpenFile opens a file (not a directory) for reading. |
| // If OpenFile is nil, Import uses os.Open. |
| OpenFile func(path string) (io.ReadCloser, error) |
| } |
| |
| // joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join. |
| func (ctxt *Context) joinPath(elem ...string) string { |
| if f := ctxt.JoinPath; f != nil { |
| return f(elem...) |
| } |
| return filepath.Join(elem...) |
| } |
| |
| // splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList. |
| func (ctxt *Context) splitPathList(s string) []string { |
| if f := ctxt.SplitPathList; f != nil { |
| return f(s) |
| } |
| return filepath.SplitList(s) |
| } |
| |
| // isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs. |
| func (ctxt *Context) isAbsPath(path string) bool { |
| if f := ctxt.IsAbsPath; f != nil { |
| return f(path) |
| } |
| return filepath.IsAbs(path) |
| } |
| |
| // isDir calls ctxt.IsDir (if not nil) or else uses os.Stat. |
| func (ctxt *Context) isDir(path string) bool { |
| if f := ctxt.IsDir; f != nil { |
| return f(path) |
| } |
| fi, err := os.Stat(path) |
| return err == nil && fi.IsDir() |
| } |
| |
| // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses |
| // the local file system to answer the question. |
| func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) { |
| if f := ctxt.HasSubdir; f != nil { |
| return f(root, dir) |
| } |
| |
| // Try using paths we received. |
| if rel, ok = hasSubdir(root, dir); ok { |
| return |
| } |
| |
| // Try expanding symlinks and comparing |
| // expanded against unexpanded and |
| // expanded against expanded. |
| rootSym, _ := filepath.EvalSymlinks(root) |
| dirSym, _ := filepath.EvalSymlinks(dir) |
| |
| if rel, ok = hasSubdir(rootSym, dir); ok { |
| return |
| } |
| if rel, ok = hasSubdir(root, dirSym); ok { |
| return |
| } |
| return hasSubdir(rootSym, dirSym) |
| } |
| |
| // hasSubdir reports if dir is within root by performing lexical analysis only. |
| func hasSubdir(root, dir string) (rel string, ok bool) { |
| const sep = string(filepath.Separator) |
| root = filepath.Clean(root) |
| if !strings.HasSuffix(root, sep) { |
| root += sep |
| } |
| dir = filepath.Clean(dir) |
| if !strings.HasPrefix(dir, root) { |
| return "", false |
| } |
| return filepath.ToSlash(dir[len(root):]), true |
| } |
| |
| // readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir. |
| func (ctxt *Context) readDir(path string) ([]fs.FileInfo, error) { |
| if f := ctxt.ReadDir; f != nil { |
| return f(path) |
| } |
| // TODO: use os.ReadDir |
| return ioutil.ReadDir(path) |
| } |
| |
| // openFile calls ctxt.OpenFile (if not nil) or else os.Open. |
| func (ctxt *Context) openFile(path string) (io.ReadCloser, error) { |
| if fn := ctxt.OpenFile; fn != nil { |
| return fn(path) |
| } |
| |
| f, err := os.Open(path) |
| if err != nil { |
| return nil, err // nil interface |
| } |
| return f, nil |
| } |
| |
| // isFile determines whether path is a file by trying to open it. |
| // It reuses openFile instead of adding another function to the |
| // list in Context. |
| func (ctxt *Context) isFile(path string) bool { |
| f, err := ctxt.openFile(path) |
| if err != nil { |
| return false |
| } |
| f.Close() |
| return true |
| } |
| |
| // gopath returns the list of Go path directories. |
| func (ctxt *Context) gopath() []string { |
| var all []string |
| for _, p := range ctxt.splitPathList(ctxt.GOPATH) { |
| if p == "" || p == ctxt.GOROOT { |
| // Empty paths are uninteresting. |
| // If the path is the GOROOT, ignore it. |
| // People sometimes set GOPATH=$GOROOT. |
| // Do not get confused by this common mistake. |
| continue |
| } |
| if strings.HasPrefix(p, "~") { |
| // Path segments starting with ~ on Unix are almost always |
| // users who have incorrectly quoted ~ while setting GOPATH, |
| // preventing it from expanding to $HOME. |
| // The situation is made more confusing by the fact that |
| // bash allows quoted ~ in $PATH (most shells do not). |
| // Do not get confused by this, and do not try to use the path. |
| // It does not exist, and printing errors about it confuses |
| // those users even more, because they think "sure ~ exists!". |
| // The go command diagnoses this situation and prints a |
| // useful error. |
| // On Windows, ~ is used in short names, such as c:\progra~1 |
| // for c:\program files. |
| continue |
| } |
| all = append(all, p) |
| } |
| return all |
| } |
| |
| // SrcDirs returns a list of package source root directories. |
| // It draws from the current Go root and Go path but omits directories |
| // that do not exist. |
| func (ctxt *Context) SrcDirs() []string { |
| var all []string |
| if ctxt.GOROOT != "" && ctxt.Compiler != "gccgo" { |
| dir := ctxt.joinPath(ctxt.GOROOT, "src") |
| if ctxt.isDir(dir) { |
| all = append(all, dir) |
| } |
| } |
| for _, p := range ctxt.gopath() { |
| dir := ctxt.joinPath(p, "src") |
| if ctxt.isDir(dir) { |
| all = append(all, dir) |
| } |
| } |
| return all |
| } |
| |
| // Default is the default Context for builds. |
| // It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables |
| // if set, or else the compiled code's GOARCH, GOOS, and GOROOT. |
| var Default Context = defaultContext() |
| |
| func defaultGOPATH() string { |
| env := "HOME" |
| if runtime.GOOS == "windows" { |
| env = "USERPROFILE" |
| } else if runtime.GOOS == "plan9" { |
| env = "home" |
| } |
| if home := os.Getenv(env); home != "" { |
| def := filepath.Join(home, "go") |
| if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) { |
| // Don't set the default GOPATH to GOROOT, |
| // as that will trigger warnings from the go tool. |
| return "" |
| } |
| return def |
| } |
| return "" |
| } |
| |
| var defaultToolTags, defaultReleaseTags []string |
| |
| func defaultContext() Context { |
| var c Context |
| |
| c.GOARCH = buildcfg.GOARCH |
| c.GOOS = buildcfg.GOOS |
| c.GOROOT = pathpkg.Clean(runtime.GOROOT()) |
| c.GOPATH = envOr("GOPATH", defaultGOPATH()) |
| c.Compiler = runtime.Compiler |
| |
| // For each experiment that has been enabled in the toolchain, define a |
| // build tag with the same name but prefixed by "goexperiment." which can be |
| // used for compiling alternative files for the experiment. This allows |
| // changes for the experiment, like extra struct fields in the runtime, |
| // without affecting the base non-experiment code at all. |
| for _, exp := range buildcfg.EnabledExperiments() { |
| c.ToolTags = append(c.ToolTags, "goexperiment."+exp) |
| } |
| defaultToolTags = append([]string{}, c.ToolTags...) // our own private copy |
| |
| // Each major Go release in the Go 1.x series adds a new |
| // "go1.x" release tag. That is, the go1.x tag is present in |
| // all releases >= Go 1.x. Code that requires Go 1.x or later |
| // should say "+build go1.x", and code that should only be |
| // built before Go 1.x (perhaps it is the stub to use in that |
| // case) should say "+build !go1.x". |
| // The last element in ReleaseTags is the current release. |
| for i := 1; i <= goversion.Version; i++ { |
| c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i)) |
| } |
| |
| defaultReleaseTags = append([]string{}, c.ReleaseTags...) // our own private copy |
| |
| env := os.Getenv("CGO_ENABLED") |
| if env == "" { |
| env = defaultCGO_ENABLED |
| } |
| switch env { |
| case "1": |
| c.CgoEnabled = true |
| case "0": |
| c.CgoEnabled = false |
| default: |
| // cgo must be explicitly enabled for cross compilation builds |
| if runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS { |
| c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH] |
| break |
| } |
| c.CgoEnabled = false |
| } |
| |
| return c |
| } |
| |
| func envOr(name, def string) string { |
| s := os.Getenv(name) |
| if s == "" { |
| return def |
| } |
| return s |
| } |
| |
| // An ImportMode controls the behavior of the Import method. |
| type ImportMode uint |
| |
| const ( |
| // If FindOnly is set, Import stops after locating the directory |
| // that should contain the sources for a package. It does not |
| // read any files in the directory. |
| FindOnly ImportMode = 1 << iota |
| |
| // If AllowBinary is set, Import can be satisfied by a compiled |
| // package object without corresponding sources. |
| // |
| // Deprecated: |
| // The supported way to create a compiled-only package is to |
| // write source code containing a //go:binary-only-package comment at |
| // the top of the file. Such a package will be recognized |
| // regardless of this flag setting (because it has source code) |
| // and will have BinaryOnly set to true in the returned Package. |
| AllowBinary |
| |
| // If ImportComment is set, parse import comments on package statements. |
| // Import returns an error if it finds a comment it cannot understand |
| // or finds conflicting comments in multiple source files. |
| // See golang.org/s/go14customimport for more information. |
| ImportComment |
| |
| // By default, Import searches vendor directories |
| // that apply in the given source directory before searching |
| // the GOROOT and GOPATH roots. |
| // If an Import finds and returns a package using a vendor |
| // directory, the resulting ImportPath is the complete path |
| // to the package, including the path elements leading up |
| // to and including "vendor". |
| // For example, if Import("y", "x/subdir", 0) finds |
| // "x/vendor/y", the returned package's ImportPath is "x/vendor/y", |
| // not plain "y". |
| // See golang.org/s/go15vendor for more information. |
| // |
| // Setting IgnoreVendor ignores vendor directories. |
| // |
| // In contrast to the package's ImportPath, |
| // the returned package's Imports, TestImports, and XTestImports |
| // are always the exact import paths from the source files: |
| // Import makes no attempt to resolve or check those paths. |
| IgnoreVendor |
| ) |
| |
| // A Package describes the Go package found in a directory. |
| type Package struct { |
| Dir string // directory containing package sources |
| Name string // package name |
| ImportComment string // path in import comment on package statement |
| Doc string // documentation synopsis |
| ImportPath string // import path of package ("" if unknown) |
| Root string // root of Go tree where this package lives |
| SrcRoot string // package source root directory ("" if unknown) |
| PkgRoot string // package install root directory ("" if unknown) |
| PkgTargetRoot string // architecture dependent install root directory ("" if unknown) |
| BinDir string // command install directory ("" if unknown) |
| Goroot bool // package found in Go root |
| PkgObj string // installed .a file |
| AllTags []string // tags that can influence file selection in this directory |
| ConflictDir string // this directory shadows Dir in $GOPATH |
| 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 (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 |
| CgoCPPFLAGS []string // Cgo CPPFLAGS directives |
| CgoCXXFLAGS []string // Cgo CXXFLAGS directives |
| CgoFFLAGS []string // Cgo FFLAGS directives |
| CgoLDFLAGS []string // Cgo LDFLAGS directives |
| CgoPkgConfig []string // Cgo pkg-config directives |
| |
| // Test information |
| 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 |
| 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 |
| EmbedPatternPos map[string][]token.Position // line information for EmbedPatterns |
| TestEmbedPatterns []string // patterns from TestGoFiles |
| TestEmbedPatternPos map[string][]token.Position // line information for TestEmbedPatterns |
| XTestEmbedPatterns []string // patterns from XTestGoFiles |
| XTestEmbedPatternPos map[string][]token.Position // line information for XTestEmbedPatternPos |
| } |
| |
| // IsCommand reports whether the package is considered a |
| // command to be installed (not just a library). |
| // Packages named "main" are treated as commands. |
| func (p *Package) IsCommand() bool { |
| return p.Name == "main" |
| } |
| |
| // ImportDir is like Import but processes the Go package found in |
| // the named directory. |
| func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { |
| return ctxt.Import(".", dir, mode) |
| } |
| |
| // NoGoError is the error used by Import to describe a directory |
| // containing no buildable Go source files. (It may still contain |
| // test files, files hidden by build tags, and so on.) |
| type NoGoError struct { |
| Dir string |
| } |
| |
| func (e *NoGoError) Error() string { |
| return "no buildable Go source files in " + e.Dir |
| } |
| |
| // MultiplePackageError describes a directory containing |
| // multiple buildable Go source files for multiple packages. |
| type MultiplePackageError struct { |
| Dir string // directory containing files |
| Packages []string // package names found |
| Files []string // corresponding files: Files[i] declares package Packages[i] |
| } |
| |
| func (e *MultiplePackageError) Error() string { |
| // Error string limited to two entries for compatibility. |
| return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir) |
| } |
| |
| func nameExt(name string) string { |
| i := strings.LastIndex(name, ".") |
| if i < 0 { |
| return "" |
| } |
| return name[i:] |
| } |
| |
| // Import returns details about the Go package named by the import path, |
| // interpreting local import paths relative to the srcDir directory. |
| // If the path is a local import path naming a package that can be imported |
| // using a standard import path, the returned package will set p.ImportPath |
| // to that path. |
| // |
| // In the directory containing the package, .go, .c, .h, and .s files are |
| // considered part of the package except for: |
| // |
| // - .go files in package documentation |
| // - files starting with _ or . (likely editor temporary files) |
| // - files with build constraints not satisfied by the context |
| // |
| // If an error occurs, Import returns a non-nil error and a non-nil |
| // *Package containing partial information. |
| // |
| func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) { |
| p := &Package{ |
| ImportPath: path, |
| } |
| if path == "" { |
| return p, fmt.Errorf("import %q: invalid import path", path) |
| } |
| |
| var pkgtargetroot string |
| var pkga string |
| var pkgerr error |
| suffix := "" |
| if ctxt.InstallSuffix != "" { |
| suffix = "_" + ctxt.InstallSuffix |
| } |
| switch ctxt.Compiler { |
| case "gccgo": |
| pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix |
| case "gc": |
| pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix |
| default: |
| // Save error for end of function. |
| pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler) |
| } |
| setPkga := func() { |
| switch ctxt.Compiler { |
| case "gccgo": |
| dir, elem := pathpkg.Split(p.ImportPath) |
| pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a" |
| case "gc": |
| pkga = pkgtargetroot + "/" + p.ImportPath + ".a" |
| } |
| } |
| setPkga() |
| |
| binaryOnly := false |
| if IsLocalImport(path) { |
| pkga = "" // local imports have no installed path |
| if srcDir == "" { |
| return p, fmt.Errorf("import %q: import relative to unknown directory", path) |
| } |
| if !ctxt.isAbsPath(path) { |
| p.Dir = ctxt.joinPath(srcDir, path) |
| } |
| // p.Dir directory may or may not exist. Gather partial information first, check if it exists later. |
| // Determine canonical import path, if any. |
| // Exclude results where the import path would include /testdata/. |
| inTestdata := func(sub string) bool { |
| return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || strings.HasPrefix(sub, "testdata/") || sub == "testdata" |
| } |
| if ctxt.GOROOT != "" { |
| root := ctxt.joinPath(ctxt.GOROOT, "src") |
| if sub, ok := ctxt.hasSubdir(root, p.Dir); ok && !inTestdata(sub) { |
| p.Goroot = true |
| p.ImportPath = sub |
| p.Root = ctxt.GOROOT |
| setPkga() // p.ImportPath changed |
| goto Found |
| } |
| } |
| all := ctxt.gopath() |
| for i, root := range all { |
| rootsrc := ctxt.joinPath(root, "src") |
| if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok && !inTestdata(sub) { |
| // We found a potential import path for dir, |
| // but check that using it wouldn't find something |
| // else first. |
| if ctxt.GOROOT != "" && ctxt.Compiler != "gccgo" { |
| if dir := ctxt.joinPath(ctxt.GOROOT, "src", sub); ctxt.isDir(dir) { |
| p.ConflictDir = dir |
| goto Found |
| } |
| } |
| for _, earlyRoot := range all[:i] { |
| if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) { |
| p.ConflictDir = dir |
| goto Found |
| } |
| } |
| |
| // sub would not name some other directory instead of this one. |
| // Record it. |
| p.ImportPath = sub |
| p.Root = root |
| setPkga() // p.ImportPath changed |
| goto Found |
| } |
| } |
| // It's okay that we didn't find a root containing dir. |
| // Keep going with the information we have. |
| } else { |
| if strings.HasPrefix(path, "/") { |
| return p, fmt.Errorf("import %q: cannot import absolute path", path) |
| } |
| |
| if err := ctxt.importGo(p, path, srcDir, mode); err == nil { |
| goto Found |
| } else if err != errNoModules { |
| return p, err |
| } |
| |
| gopath := ctxt.gopath() // needed twice below; avoid computing many times |
| |
| // tried records the location of unsuccessful package lookups |
| var tried struct { |
| vendor []string |
| goroot string |
| gopath []string |
| } |
| |
| // Vendor directories get first chance to satisfy import. |
| if mode&IgnoreVendor == 0 && srcDir != "" { |
| searchVendor := func(root string, isGoroot bool) bool { |
| sub, ok := ctxt.hasSubdir(root, srcDir) |
| if !ok || !strings.HasPrefix(sub, "src/") || strings.Contains(sub, "/testdata/") { |
| return false |
| } |
| for { |
| vendor := ctxt.joinPath(root, sub, "vendor") |
| if ctxt.isDir(vendor) { |
| dir := ctxt.joinPath(vendor, path) |
| if ctxt.isDir(dir) && hasGoFiles(ctxt, dir) { |
| p.Dir = dir |
| p.ImportPath = strings.TrimPrefix(pathpkg.Join(sub, "vendor", path), "src/") |
| p.Goroot = isGoroot |
| p.Root = root |
| setPkga() // p.ImportPath changed |
| return true |
| } |
| tried.vendor = append(tried.vendor, dir) |
| } |
| i := strings.LastIndex(sub, "/") |
| if i < 0 { |
| break |
| } |
| sub = sub[:i] |
| } |
| return false |
| } |
| if ctxt.Compiler != "gccgo" && searchVendor(ctxt.GOROOT, true) { |
| goto Found |
| } |
| for _, root := range gopath { |
| if searchVendor(root, false) { |
| goto Found |
| } |
| } |
| } |
| |
| // Determine directory from import path. |
| if ctxt.GOROOT != "" { |
| // If the package path starts with "vendor/", only search GOROOT before |
| // GOPATH if the importer is also within GOROOT. That way, if the user has |
| // vendored in a package that is subsequently included in the standard |
| // distribution, they'll continue to pick up their own vendored copy. |
| gorootFirst := srcDir == "" || !strings.HasPrefix(path, "vendor/") |
| if !gorootFirst { |
| _, gorootFirst = ctxt.hasSubdir(ctxt.GOROOT, srcDir) |
| } |
| if gorootFirst { |
| dir := ctxt.joinPath(ctxt.GOROOT, "src", path) |
| if ctxt.Compiler != "gccgo" { |
| isDir := ctxt.isDir(dir) |
| binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) |
| if isDir || binaryOnly { |
| p.Dir = dir |
| p.Goroot = true |
| p.Root = ctxt.GOROOT |
| goto Found |
| } |
| } |
| tried.goroot = dir |
| } |
| } |
| if ctxt.Compiler == "gccgo" && goroot.IsStandardPackage(ctxt.GOROOT, ctxt.Compiler, path) { |
| p.Dir = ctxt.joinPath(ctxt.GOROOT, "src", path) |
| p.Goroot = true |
| p.Root = ctxt.GOROOT |
| goto Found |
| } |
| for _, root := range gopath { |
| dir := ctxt.joinPath(root, "src", path) |
| isDir := ctxt.isDir(dir) |
| binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga)) |
| if isDir || binaryOnly { |
| p.Dir = dir |
| p.Root = root |
| goto Found |
| } |
| tried.gopath = append(tried.gopath, dir) |
| } |
| |
| // If we tried GOPATH first due to a "vendor/" prefix, fall back to GOPATH. |
| // That way, the user can still get useful results from 'go list' for |
| // standard-vendored paths passed on the command line. |
| if ctxt.GOROOT != "" && tried.goroot == "" { |
| dir := ctxt.joinPath(ctxt.GOROOT, "src", path) |
| if ctxt.Compiler != "gccgo" { |
| isDir := ctxt.isDir(dir) |
| binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) |
| if isDir || binaryOnly { |
| p.Dir = dir |
| p.Goroot = true |
| p.Root = ctxt.GOROOT |
| goto Found |
| } |
| } |
| tried.goroot = dir |
| } |
| |
| // package was not found |
| var paths []string |
| format := "\t%s (vendor tree)" |
| for _, dir := range tried.vendor { |
| paths = append(paths, fmt.Sprintf(format, dir)) |
| format = "\t%s" |
| } |
| if tried.goroot != "" { |
| paths = append(paths, fmt.Sprintf("\t%s (from $GOROOT)", tried.goroot)) |
| } else { |
| paths = append(paths, "\t($GOROOT not set)") |
| } |
| format = "\t%s (from $GOPATH)" |
| for _, dir := range tried.gopath { |
| paths = append(paths, fmt.Sprintf(format, dir)) |
| format = "\t%s" |
| } |
| if len(tried.gopath) == 0 { |
| paths = append(paths, "\t($GOPATH not set. For more details see: 'go help gopath')") |
| } |
| return p, fmt.Errorf("cannot find package %q in any of:\n%s", path, strings.Join(paths, "\n")) |
| } |
| |
| Found: |
| if p.Root != "" { |
| p.SrcRoot = ctxt.joinPath(p.Root, "src") |
| p.PkgRoot = ctxt.joinPath(p.Root, "pkg") |
| p.BinDir = ctxt.joinPath(p.Root, "bin") |
| if pkga != "" { |
| p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot) |
| p.PkgObj = ctxt.joinPath(p.Root, pkga) |
| } |
| } |
| |
| // If it's a local import path, by the time we get here, we still haven't checked |
| // that p.Dir directory exists. This is the right time to do that check. |
| // We can't do it earlier, because we want to gather partial information for the |
| // non-nil *Package returned when an error occurs. |
| // We need to do this before we return early on FindOnly flag. |
| if IsLocalImport(path) && !ctxt.isDir(p.Dir) { |
| if ctxt.Compiler == "gccgo" && p.Goroot { |
| // gccgo has no sources for GOROOT packages. |
| return p, nil |
| } |
| |
| // package was not found |
| return p, fmt.Errorf("cannot find package %q in:\n\t%s", path, p.Dir) |
| } |
| |
| if mode&FindOnly != 0 { |
| return p, pkgerr |
| } |
| if binaryOnly && (mode&AllowBinary) != 0 { |
| return p, pkgerr |
| } |
| |
| if ctxt.Compiler == "gccgo" && p.Goroot { |
| // gccgo has no sources for GOROOT packages. |
| return p, nil |
| } |
| |
| dirs, err := ctxt.readDir(p.Dir) |
| if err != nil { |
| return p, err |
| } |
| |
| var badGoError error |
| badFiles := make(map[string]bool) |
| badFile := func(name string, err error) { |
| if badGoError == nil { |
| badGoError = err |
| } |
| if !badFiles[name] { |
| p.InvalidGoFiles = append(p.InvalidGoFiles, name) |
| badFiles[name] = true |
| } |
| } |
| |
| var Sfiles []string // files with ".S"(capital S)/.sx(capital s equivalent for case insensitive filesystems) |
| var firstFile, firstCommentFile string |
| embedPos := make(map[string][]token.Position) |
| testEmbedPos := make(map[string][]token.Position) |
| xTestEmbedPos := make(map[string][]token.Position) |
| importPos := make(map[string][]token.Position) |
| testImportPos := make(map[string][]token.Position) |
| xTestImportPos := make(map[string][]token.Position) |
| allTags := make(map[string]bool) |
| fset := token.NewFileSet() |
| for _, d := range dirs { |
| if d.IsDir() { |
| continue |
| } |
| if d.Mode()&fs.ModeSymlink != 0 { |
| if ctxt.isDir(ctxt.joinPath(p.Dir, d.Name())) { |
| // Symlinks to directories are not source files. |
| continue |
| } |
| } |
| |
| name := d.Name() |
| ext := nameExt(name) |
| |
| info, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly, fset) |
| if err != nil { |
| badFile(name, err) |
| continue |
| } |
| 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 ".go": |
| // keep going |
| case ".S", ".sx": |
| // special case for cgo, handled at end |
| Sfiles = append(Sfiles, name) |
| continue |
| default: |
| if list := fileListForExt(p, ext); list != nil { |
| *list = append(*list, name) |
| } |
| continue |
| } |
| |
| if info.parseErr != nil { |
| badFile(name, info.parseErr) |
| // Fall through: we might still have a partial AST in info.parsed, |
| // and we want to list files with parse errors anyway. |
| } |
| |
| var pkg string |
| if info.parsed != nil { |
| pkg = info.parsed.Name.Name |
| if pkg == "documentation" { |
| p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) |
| continue |
| } |
| } |
| |
| isTest := strings.HasSuffix(name, "_test.go") |
| isXTest := false |
| if isTest && strings.HasSuffix(pkg, "_test") { |
| isXTest = true |
| pkg = pkg[:len(pkg)-len("_test")] |
| } |
| |
| if p.Name == "" { |
| p.Name = pkg |
| firstFile = name |
| } else if pkg != p.Name { |
| // TODO(#45999): The choice of p.Name is arbitrary based on file iteration |
| // order. Instead of resolving p.Name arbitrarily, we should clear out the |
| // existing name and mark the existing files as also invalid. |
| badFile(name, &MultiplePackageError{ |
| Dir: p.Dir, |
| Packages: []string{p.Name, pkg}, |
| Files: []string{firstFile, name}, |
| }) |
| } |
| // Grab the first package comment as docs, provided it is not from a test file. |
| if info.parsed != nil && info.parsed.Doc != nil && p.Doc == "" && !isTest && !isXTest { |
| p.Doc = doc.Synopsis(info.parsed.Doc.Text()) |
| } |
| |
| if mode&ImportComment != 0 { |
| qcom, line := findImportComment(data) |
| if line != 0 { |
| com, err := strconv.Unquote(qcom) |
| if err != nil { |
| badFile(name, fmt.Errorf("%s:%d: cannot parse import comment", filename, line)) |
| } else if p.ImportComment == "" { |
| p.ImportComment = com |
| firstCommentFile = name |
| } else if p.ImportComment != com { |
| badFile(name, fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, firstCommentFile, com, name, p.Dir)) |
| } |
| } |
| } |
| |
| // Record imports and information about cgo. |
| isCgo := false |
| for _, imp := range info.imports { |
| if imp.path == "C" { |
| if isTest { |
| badFile(name, fmt.Errorf("use of cgo in test %s not supported", filename)) |
| continue |
| } |
| isCgo = true |
| if imp.doc != nil { |
| if err := ctxt.saveCgo(filename, p, imp.doc); err != nil { |
| badFile(name, err) |
| } |
| } |
| } |
| } |
| |
| var fileList *[]string |
| var importMap, embedMap map[string][]token.Position |
| switch { |
| case isCgo: |
| allTags["cgo"] = true |
| if ctxt.CgoEnabled { |
| fileList = &p.CgoFiles |
| importMap = importPos |
| embedMap = embedPos |
| } else { |
| // Ignore imports and embeds from cgo files if cgo is disabled. |
| fileList = &p.IgnoredGoFiles |
| } |
| case isXTest: |
| fileList = &p.XTestGoFiles |
| importMap = xTestImportPos |
| embedMap = xTestEmbedPos |
| case isTest: |
| fileList = &p.TestGoFiles |
| importMap = testImportPos |
| embedMap = testEmbedPos |
| default: |
| fileList = &p.GoFiles |
| importMap = importPos |
| embedMap = embedPos |
| } |
| *fileList = append(*fileList, name) |
| if importMap != nil { |
| for _, imp := range info.imports { |
| importMap[imp.path] = append(importMap[imp.path], fset.Position(imp.pos)) |
| } |
| } |
| if embedMap != nil { |
| for _, emb := range info.embeds { |
| embedMap[emb.pattern] = append(embedMap[emb.pattern], emb.pos) |
| } |
| } |
| } |
| |
| for tag := range allTags { |
| p.AllTags = append(p.AllTags, tag) |
| } |
| sort.Strings(p.AllTags) |
| |
| p.EmbedPatterns, p.EmbedPatternPos = cleanDecls(embedPos) |
| p.TestEmbedPatterns, p.TestEmbedPatternPos = cleanDecls(testEmbedPos) |
| p.XTestEmbedPatterns, p.XTestEmbedPatternPos = cleanDecls(xTestEmbedPos) |
| |
| p.Imports, p.ImportPos = cleanDecls(importPos) |
| p.TestImports, p.TestImportPos = cleanDecls(testImportPos) |
| p.XTestImports, p.XTestImportPos = cleanDecls(xTestImportPos) |
| |
| // add the .S/.sx files only if we are using cgo |
| // (which means gcc will compile them). |
| // The standard assemblers expect .s files. |
| 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 { |
| return p, badGoError |
| } |
| if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { |
| return p, &NoGoError{p.Dir} |
| } |
| 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. |
| // If using the go command is not appropriate, importGo returns errNoModules. |
| // Otherwise, importGo tries using the go command and reports whether that succeeded. |
| // Using the go command lets build.Import and build.Context.Import find code |
| // in Go modules. In the long term we want tools to use go/packages (currently golang.org/x/tools/go/packages), |
| // which will also use the go command. |
| // Invoking the go command here is not very efficient in that it computes information |
| // about the requested package and all dependencies and then only reports about the requested package. |
| // Then we reinvoke it for every dependency. But this is still better than not working at all. |
| // See golang.org/issue/26504. |
| func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error { |
| // To invoke the go command, |
| // we must not being doing special things like AllowBinary or IgnoreVendor, |
| // and all the file system callbacks must be nil (we're meant to use the local file system). |
| if mode&AllowBinary != 0 || mode&IgnoreVendor != 0 || |
| ctxt.JoinPath != nil || ctxt.SplitPathList != nil || ctxt.IsAbsPath != nil || ctxt.IsDir != nil || ctxt.HasSubdir != nil || ctxt.ReadDir != nil || ctxt.OpenFile != nil || !equal(ctxt.ToolTags, defaultToolTags) || !equal(ctxt.ReleaseTags, defaultReleaseTags) { |
| return errNoModules |
| } |
| |
| // Predict whether module aware mode is enabled by checking the value of |
| // GO111MODULE and looking for a go.mod file in the source directory or |
| // one of its parents. Running 'go env GOMOD' in the source directory would |
| // give a canonical answer, but we'd prefer not to execute another command. |
| go111Module := os.Getenv("GO111MODULE") |
| switch go111Module { |
| case "off": |
| return errNoModules |
| default: // "", "on", "auto", anything else |
| // Maybe use modules. |
| } |
| |
| if srcDir != "" { |
| var absSrcDir string |
| if filepath.IsAbs(srcDir) { |
| absSrcDir = srcDir |
| } else if ctxt.Dir != "" { |
| return fmt.Errorf("go/build: Dir is non-empty, so relative srcDir is not allowed: %v", srcDir) |
| } else { |
| // Find the absolute source directory. hasSubdir does not handle |
| // relative paths (and can't because the callbacks don't support this). |
| var err error |
| absSrcDir, err = filepath.Abs(srcDir) |
| if err != nil { |
| return errNoModules |
| } |
| } |
| |
| // If the source directory is in GOROOT, then the in-process code works fine |
| // and we should keep using it. Moreover, the 'go list' approach below doesn't |
| // take standard-library vendoring into account and will fail. |
| if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok { |
| return errNoModules |
| } |
| } |
| |
| // For efficiency, if path is a standard library package, let the usual lookup code handle it. |
| if ctxt.GOROOT != "" { |
| dir := ctxt.joinPath(ctxt.GOROOT, "src", path) |
| if ctxt.isDir(dir) { |
| return errNoModules |
| } |
| } |
| |
| // If GO111MODULE=auto, look to see if there is a go.mod. |
| // Since go1.13, it doesn't matter if we're inside GOPATH. |
| if go111Module == "auto" { |
| var ( |
| parent string |
| err error |
| ) |
| if ctxt.Dir == "" { |
| parent, err = os.Getwd() |
| if err != nil { |
| // A nonexistent working directory can't be in a module. |
| return errNoModules |
| } |
| } else { |
| parent, err = filepath.Abs(ctxt.Dir) |
| if err != nil { |
| // If the caller passed a bogus Dir explicitly, that's materially |
| // different from not having modules enabled. |
| return err |
| } |
| } |
| for { |
| if f, err := ctxt.openFile(ctxt.joinPath(parent, "go.mod")); err == nil { |
| buf := make([]byte, 100) |
| _, err := f.Read(buf) |
| f.Close() |
| if err == nil || err == io.EOF { |
| // go.mod exists and is readable (is a file, not a directory). |
| break |
| } |
| } |
| d := filepath.Dir(parent) |
| if len(d) >= len(parent) { |
| return errNoModules // reached top of file system, no go.mod |
| } |
| parent = d |
| } |
| } |
| |
| cmd := exec.Command("go", "list", "-e", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n{{if .Error}}{{.Error}}{{end}}\n", "--", path) |
| |
| if ctxt.Dir != "" { |
| cmd.Dir = ctxt.Dir |
| } |
| |
| var stdout, stderr strings.Builder |
| cmd.Stdout = &stdout |
| cmd.Stderr = &stderr |
| |
| cgo := "0" |
| if ctxt.CgoEnabled { |
| cgo = "1" |
| } |
| cmd.Env = append(os.Environ(), |
| "GOOS="+ctxt.GOOS, |
| "GOARCH="+ctxt.GOARCH, |
| "GOROOT="+ctxt.GOROOT, |
| "GOPATH="+ctxt.GOPATH, |
| "CGO_ENABLED="+cgo, |
| ) |
| |
| if err := cmd.Run(); err != nil { |
| return fmt.Errorf("go/build: go list %s: %v\n%s\n", path, err, stderr.String()) |
| } |
| |
| f := strings.SplitN(stdout.String(), "\n", 5) |
| if len(f) != 5 { |
| return fmt.Errorf("go/build: importGo %s: unexpected output:\n%s\n", path, stdout.String()) |
| } |
| dir := f[0] |
| errStr := strings.TrimSpace(f[4]) |
| if errStr != "" && dir == "" { |
| // If 'go list' could not locate the package (dir is empty), |
| // return the same error that 'go list' reported. |
| return errors.New(errStr) |
| } |
| |
| // If 'go list' did locate the package, ignore the error. |
| // It was probably related to loading source files, and we'll |
| // encounter it ourselves shortly if the FindOnly flag isn't set. |
| p.Dir = dir |
| p.ImportPath = f[1] |
| p.Root = f[2] |
| p.Goroot = f[3] == "true" |
| return nil |
| } |
| |
| func equal(x, y []string) bool { |
| if len(x) != len(y) { |
| return false |
| } |
| for i, xi := range x { |
| if xi != y[i] { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // hasGoFiles reports whether dir contains any files with names ending in .go. |
| // For a vendor check we must exclude directories that contain no .go files. |
| // Otherwise it is not possible to vendor just a/b/c and still import the |
| // non-vendored a/b. See golang.org/issue/13832. |
| func hasGoFiles(ctxt *Context, dir string) bool { |
| ents, _ := ctxt.readDir(dir) |
| for _, ent := range ents { |
| if !ent.IsDir() && strings.HasSuffix(ent.Name(), ".go") { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func findImportComment(data []byte) (s string, line int) { |
| // expect keyword package |
| word, data := parseWord(data) |
| if string(word) != "package" { |
| return "", 0 |
| } |
| |
| // expect package name |
| _, data = parseWord(data) |
| |
| // now ready for import comment, a // or /* */ comment |
| // beginning and ending on the current line. |
| for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') { |
| data = data[1:] |
| } |
| |
| var comment []byte |
| switch { |
| case bytes.HasPrefix(data, slashSlash): |
| i := bytes.Index(data, newline) |
| if i < 0 { |
| i = len(data) |
| } |
| comment = data[2:i] |
| case bytes.HasPrefix(data, slashStar): |
| data = data[2:] |
| i := bytes.Index(data, starSlash) |
| if i < 0 { |
| // malformed comment |
| return "", 0 |
| } |
| comment = data[:i] |
| if bytes.Contains(comment, newline) { |
| return "", 0 |
| } |
| } |
| comment = bytes.TrimSpace(comment) |
| |
| // split comment into `import`, `"pkg"` |
| word, arg := parseWord(comment) |
| if string(word) != "import" { |
| return "", 0 |
| } |
| |
| line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline) |
| return strings.TrimSpace(string(arg)), line |
| } |
| |
| var ( |
| slashSlash = []byte("//") |
| slashStar = []byte("/*") |
| starSlash = []byte("*/") |
| newline = []byte("\n") |
| ) |
| |
| // skipSpaceOrComment returns data with any leading spaces or comments removed. |
| func skipSpaceOrComment(data []byte) []byte { |
| for len(data) > 0 { |
| switch data[0] { |
| case ' ', '\t', '\r', '\n': |
| data = data[1:] |
| continue |
| case '/': |
| if bytes.HasPrefix(data, slashSlash) { |
| i := bytes.Index(data, newline) |
| if i < 0 { |
| return nil |
| } |
| data = data[i+1:] |
| continue |
| } |
| if bytes.HasPrefix(data, slashStar) { |
| data = data[2:] |
| i := bytes.Index(data, starSlash) |
| if i < 0 { |
| return nil |
| } |
| data = data[i+2:] |
| continue |
| } |
| } |
| break |
| } |
| return data |
| } |
| |
| // parseWord skips any leading spaces or comments in data |
| // and then parses the beginning of data as an identifier or keyword, |
| // returning that word and what remains after the word. |
| func parseWord(data []byte) (word, rest []byte) { |
| data = skipSpaceOrComment(data) |
| |
| // Parse past leading word characters. |
| rest = data |
| for { |
| r, size := utf8.DecodeRune(rest) |
| if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' { |
| rest = rest[size:] |
| continue |
| } |
| break |
| } |
| |
| word = data[:len(data)-len(rest)] |
| if len(word) == 0 { |
| return nil, nil |
| } |
| |
| return word, rest |
| } |
| |
| // MatchFile reports whether the file with the given name in the given directory |
| // matches the context and would be included in a Package created by ImportDir |
| // of that directory. |
| // |
| // 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) { |
| 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 []fileEmbed |
| embedErr error |
| } |
| |
| type fileImport struct { |
| path string |
| pos token.Pos |
| doc *ast.CommentGroup |
| } |
| |
| type fileEmbed struct { |
| pattern string |
| pos token.Position |
| } |
| |
| // matchFile determines whether the file with the given name in the given directory |
| // should be included in the package being constructed. |
| // 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 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, fset *token.FileSet) (*fileInfo, error) { |
| if strings.HasPrefix(name, "_") || |
| strings.HasPrefix(name, ".") { |
| return nil, nil |
| } |
| |
| i := strings.LastIndex(name, ".") |
| if i < 0 { |
| i = len(name) |
| } |
| ext := name[i:] |
| |
| if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { |
| return nil, nil |
| } |
| |
| if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil { |
| // skip |
| return nil, nil |
| } |
| |
| info := &fileInfo{name: ctxt.joinPath(dir, name), fset: fset} |
| if ext == ".syso" { |
| // binary, no reading |
| return info, nil |
| } |
| |
| f, err := ctxt.openFile(info.name) |
| if err != nil { |
| return nil, err |
| } |
| |
| 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 |
| info.header, err = readComments(f) |
| } |
| f.Close() |
| if err != nil { |
| return nil, fmt.Errorf("read %s: %v", info.name, err) |
| } |
| |
| // Look for +build comments to accept or reject the file. |
| ok, sawBinaryOnly, err := ctxt.shouldBuild(info.header, allTags) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", name, err) |
| } |
| if !ok && !ctxt.UseAllFiles { |
| return nil, nil |
| } |
| |
| if binaryOnly != nil && sawBinaryOnly { |
| *binaryOnly = true |
| } |
| |
| return info, nil |
| } |
| |
| func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) { |
| all := make([]string, 0, len(m)) |
| for path := range m { |
| all = append(all, path) |
| } |
| sort.Strings(all) |
| return all, m |
| } |
| |
| // Import is shorthand for Default.Import. |
| func Import(path, srcDir string, mode ImportMode) (*Package, error) { |
| return Default.Import(path, srcDir, mode) |
| } |
| |
| // ImportDir is shorthand for Default.ImportDir. |
| func ImportDir(dir string, mode ImportMode) (*Package, error) { |
| return Default.ImportDir(dir, mode) |
| } |
| |
| var ( |
| bSlashSlash = []byte(slashSlash) |
| bStarSlash = []byte(starSlash) |
| bSlashStar = []byte(slashStar) |
| bPlusBuild = []byte("+build") |
| |
| goBuildComment = []byte("//go:build") |
| |
| errGoBuildWithoutBuild = errors.New("//go:build comment without // +build comment") |
| errMultipleGoBuild = errors.New("multiple //go:build comments") |
| ) |
| |
| 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 |
| // for more about the design of binary-only packages. |
| var binaryOnlyComment = []byte("//go:binary-only-package") |
| |
| // 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. |
| // |
| // For each build tag it consults, shouldBuild sets allTags[tag] = true. |
| // |
| // 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) { |
| // Identify leading run of // comments and blank lines, |
| // which must be followed by a blank line. |
| // Also identify any //go:build comments. |
| content, goBuild, sawBinaryOnly, err := parseFileHeader(content) |
| if err != nil { |
| return false, false, err |
| } |
| |
| // If //go:build line is present, it controls. |
| // Otherwise fall back to +build processing. |
| switch { |
| case goBuild != nil: |
| x, err := constraint.Parse(string(goBuild)) |
| if err != nil { |
| return false, false, fmt.Errorf("parsing //go:build line: %v", err) |
| } |
| shouldBuild = ctxt.eval(x, allTags) |
| |
| default: |
| shouldBuild = true |
| 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 !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) { |
| continue |
| } |
| text := string(line) |
| if !constraint.IsPlusBuild(text) { |
| continue |
| } |
| if x, err := constraint.Parse(text); err == nil { |
| if !ctxt.eval(x, allTags) { |
| shouldBuild = false |
| } |
| } |
| } |
| } |
| |
| 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 goBuild != nil { |
| 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 content[:end], goBuild, sawBinaryOnly, nil |
| } |
| |
| // saveCgo saves the information from the #cgo lines in the import "C" comment. |
| // These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives |
| // that affect the way cgo's C code is built. |
| func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error { |
| text := cg.Text() |
| for _, line := range strings.Split(text, "\n") { |
| orig := line |
| |
| // Line is |
| // #cgo [GOOS/GOARCH...] LDFLAGS: stuff |
| // |
| line = strings.TrimSpace(line) |
| if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { |
| continue |
| } |
| |
| // Split at colon. |
| line = strings.TrimSpace(line[4:]) |
| i := strings.Index(line, ":") |
| if i < 0 { |
| return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) |
| } |
| line, argstr := line[:i], line[i+1:] |
| |
| // Parse GOOS/GOARCH stuff. |
| f := strings.Fields(line) |
| if len(f) < 1 { |
| return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) |
| } |
| |
| cond, verb := f[:len(f)-1], f[len(f)-1] |
| if len(cond) > 0 { |
| ok := false |
| for _, c := range cond { |
| if ctxt.matchAuto(c, nil) { |
| ok = true |
| break |
| } |
| } |
| if !ok { |
| continue |
| } |
| } |
| |
| args, err := splitQuoted(argstr) |
| if err != nil { |
| return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) |
| } |
| var ok bool |
| for i, arg := range args { |
| if arg, ok = expandSrcDir(arg, di.Dir); !ok { |
| return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) |
| } |
| args[i] = arg |
| } |
| |
| switch verb { |
| case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS": |
| // Change relative paths to absolute. |
| ctxt.makePathsAbsolute(args, di.Dir) |
| } |
| |
| switch verb { |
| case "CFLAGS": |
| di.CgoCFLAGS = append(di.CgoCFLAGS, args...) |
| case "CPPFLAGS": |
| di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...) |
| case "CXXFLAGS": |
| di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...) |
| case "FFLAGS": |
| di.CgoFFLAGS = append(di.CgoFFLAGS, args...) |
| case "LDFLAGS": |
| di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) |
| case "pkg-config": |
| di.CgoPkgConfig = append(di.CgoPkgConfig, args...) |
| default: |
| return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig) |
| } |
| } |
| return nil |
| } |
| |
| // expandSrcDir expands any occurrence of ${SRCDIR}, making sure |
| // the result is safe for the shell. |
| func expandSrcDir(str string, srcdir string) (string, bool) { |
| // "\" delimited paths cause safeCgoName to fail |
| // so convert native paths with a different delimiter |
| // to "/" before starting (eg: on windows). |
| srcdir = filepath.ToSlash(srcdir) |
| |
| chunks := strings.Split(str, "${SRCDIR}") |
| if len(chunks) < 2 { |
| return str, safeCgoName(str) |
| } |
| ok := true |
| for _, chunk := range chunks { |
| ok = ok && (chunk == "" || safeCgoName(chunk)) |
| } |
| ok = ok && (srcdir == "" || safeCgoName(srcdir)) |
| res := strings.Join(chunks, srcdir) |
| return res, ok && res != "" |
| } |
| |
| // makePathsAbsolute looks for compiler options that take paths and |
| // makes them absolute. We do this because through the 1.8 release we |
| // ran the compiler in the package directory, so any relative -I or -L |
| // options would be relative to that directory. In 1.9 we changed to |
| // running the compiler in the build directory, to get consistent |
| // build results (issue #19964). To keep builds working, we change any |
| // relative -I or -L options to be absolute. |
| // |
| // Using filepath.IsAbs and filepath.Join here means the results will be |
| // different on different systems, but that's OK: -I and -L options are |
| // inherently system-dependent. |
| func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) { |
| nextPath := false |
| for i, arg := range args { |
| if nextPath { |
| if !filepath.IsAbs(arg) { |
| args[i] = filepath.Join(srcDir, arg) |
| } |
| nextPath = false |
| } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { |
| if len(arg) == 2 { |
| nextPath = true |
| } else { |
| if !filepath.IsAbs(arg[2:]) { |
| args[i] = arg[:2] + filepath.Join(srcDir, arg[2:]) |
| } |
| } |
| } |
| } |
| } |
| |
| // NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN. |
| // We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay. |
| // See golang.org/issue/6038. |
| // The @ is for OS X. See golang.org/issue/13720. |
| // The % is for Jenkins. See golang.org/issue/16959. |
| // The ! is because module paths may use them. See golang.org/issue/26716. |
| // The ~ and ^ are for sr.ht. See golang.org/issue/32260. |
| const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^" |
| |
| func safeCgoName(s string) bool { |
| if s == "" { |
| return false |
| } |
| for i := 0; i < len(s); i++ { |
| if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // splitQuoted splits the string s around each instance of one or more consecutive |
| // white space characters while taking into account quotes and escaping, and |
| // returns an array of substrings of s or an empty list if s contains only white space. |
| // Single quotes and double quotes are recognized to prevent splitting within the |
| // quoted region, and are removed from the resulting substrings. If a quote in s |
| // isn't closed err will be set and r will have the unclosed argument as the |
| // last element. The backslash is used for escaping. |
| // |
| // For example, the following string: |
| // |
| // a b:"c d" 'e''f' "g\"" |
| // |
| // Would be parsed as: |
| // |
| // []string{"a", "b:c d", "ef", `g"`} |
| // |
| func splitQuoted(s string) (r []string, err error) { |
| var args []string |
| arg := make([]rune, len(s)) |
| escaped := false |
| quoted := false |
| quote := '\x00' |
| i := 0 |
| for _, rune := range s { |
| switch { |
| case escaped: |
| escaped = false |
| case rune == '\\': |
| escaped = true |
| continue |
| case quote != '\x00': |
| if rune == quote { |
| quote = '\x00' |
| continue |
| } |
| case rune == '"' || rune == '\'': |
| quoted = true |
| quote = rune |
| continue |
| case unicode.IsSpace(rune): |
| if quoted || i > 0 { |
| quoted = false |
| args = append(args, string(arg[:i])) |
| i = 0 |
| } |
| continue |
| } |
| arg[i] = rune |
| i++ |
| } |
| if quoted || i > 0 { |
| args = append(args, string(arg[:i])) |
| } |
| if quote != 0 { |
| err = errors.New("unclosed quote") |
| } else if escaped { |
| err = errors.New("unfinished escaping") |
| } |
| return args, err |
| } |
| |
| // matchAuto interprets text as either a +build or //go:build expression (whichever works), |
| // reporting whether the expression matches the build context. |
| // |
| // matchAuto is only used for testing of tag evaluation |
| // and in #cgo lines, which accept either syntax. |
| func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool { |
| if strings.ContainsAny(text, "&|()") { |
| text = "//go:build " + text |
| } else { |
| text = "// +build " + text |
| } |
| x, err := constraint.Parse(text) |
| if err != nil { |
| return false |
| } |
| return ctxt.eval(x, allTags) |
| } |
| |
| func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool { |
| return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) }) |
| } |
| |
| // matchTag reports whether the name is one of: |
| // |
| // cgo (if cgo is enabled) |
| // $GOOS |
| // $GOARCH |
| // ctxt.Compiler |
| // linux (if GOOS = android) |
| // solaris (if GOOS = illumos) |
| // tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags) |
| // |
| // It records all consulted tags in allTags. |
| func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool { |
| if allTags != nil { |
| allTags[name] = true |
| } |
| |
| // special tags |
| if ctxt.CgoEnabled && name == "cgo" { |
| return true |
| } |
| if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler { |
| return true |
| } |
| if ctxt.GOOS == "android" && name == "linux" { |
| return true |
| } |
| if ctxt.GOOS == "illumos" && name == "solaris" { |
| return true |
| } |
| if ctxt.GOOS == "ios" && name == "darwin" { |
| return true |
| } |
| |
| // other tags |
| for _, tag := range ctxt.BuildTags { |
| if tag == name { |
| return true |
| } |
| } |
| for _, tag := range ctxt.ToolTags { |
| if tag == name { |
| return true |
| } |
| } |
| for _, tag := range ctxt.ReleaseTags { |
| if tag == name { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // goodOSArchFile 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. |
| func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool { |
| 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 ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags) |
| } |
| if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) { |
| return ctxt.matchTag(l[n-1], allTags) |
| } |
| return true |
| } |
| |
| var knownOS = make(map[string]bool) |
| var knownArch = make(map[string]bool) |
| |
| func init() { |
| for _, v := range strings.Fields(goosList) { |
| knownOS[v] = true |
| } |
| for _, v := range strings.Fields(goarchList) { |
| knownArch[v] = true |
| } |
| } |
| |
| // ToolDir is the directory containing build tools. |
| var ToolDir = getToolDir() |
| |
| // IsLocalImport reports whether the import path is |
| // a local import path, like ".", "..", "./foo", or "../foo". |
| func IsLocalImport(path string) bool { |
| return path == "." || path == ".." || |
| strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") |
| } |
| |
| // ArchChar returns "?" and an error. |
| // In earlier versions of Go, the returned string was used to derive |
| // the compiler and linker tool names, the default object file suffix, |
| // and the default linker output name. As of Go 1.5, those strings |
| // no longer vary by architecture; they are compile, link, .o, and a.out, respectively. |
| func ArchChar(goarch string) (string, error) { |
| return "?", errors.New("architecture letter no longer used") |
| } |