From 38a03de6a16f3c7489fdc425eba607f8e7f08676 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 28 Feb 2012 16:34:41 -0500 Subject: [PATCH] go/build: move code around Only copy+paste here. Will make next code review's diffs a bit smaller. R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/5704050 --- src/pkg/go/build/build.go | 718 +++++++++++++++++++++++++++++++++++++- src/pkg/go/build/dir.go | 705 ------------------------------------- src/pkg/go/build/path.go | 13 - 3 files changed, 716 insertions(+), 720 deletions(-) delete mode 100644 src/pkg/go/build/dir.go diff --git a/src/pkg/go/build/build.go b/src/pkg/go/build/build.go index 68e8d34200..aeb9ad259b 100644 --- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -2,10 +2,724 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package build provides tools for building Go packages. +// Package build gathers information about Go packages. +// +// Go Path +// +// TODO: Document GOPATH. +// +// Build Constraints +// +// A build constraint is a line comment beginning with the directive +build +// that lists the conditions under which a file should be included in the package. +// Constraints may appear in any kind of source file (not just Go), but +// they must be appear near the top of the file, preceded +// only by blank lines and other line comments. +// +// A build constraint is evaluated as the OR of space-separated options; +// each option evaluates as the AND of its comma-separated terms; +// and each term is an alphanumeric word or, preceded by !, its negation. +// That is, the build constraint: +// +// // +build linux,386 darwin,!cgo +// +// corresponds to the boolean formula: +// +// (linux AND 386) OR (darwin AND (NOT cgo)) +// +// During a particular build, the following words are satisfied: +// +// - the target operating system, as spelled by runtime.GOOS +// - the target architecture, as spelled by runtime.GOARCH +// - "cgo", if ctxt.CgoEnabled is true +// - any additional words listed in ctxt.BuildTags +// +// If a file's name, after stripping the extension and a possible _test suffix, +// matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating +// system and architecture values, then the file is considered to have an implicit +// build constraint requiring those terms. +// +// To keep a file from being considered for the build: +// +// // +build ignore +// +// (any other unsatisfied word will work as well, but ``ignore'' is conventional.) +// +// To build a file only when using cgo, and only on Linux and OS X: +// +// // +build linux,cgo darwin,cgo +// +// Such a file is usually paired with another file implementing the +// default functionality for other systems, which in this case would +// carry the constraint: +// +// // +build !linux !darwin !cgo +// +// Naming a file dns_windows.go will cause it to be included only when +// building the package for Windows; similarly, math_386.s will be included +// only when building the package for 32-bit x86. +// package build -import "errors" +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "unicode" +) + +// A Context specifies the supporting context for a build. +type Context struct { + GOARCH string // target architecture + GOOS string // target operating system + CgoEnabled bool // whether cgo can be used + BuildTags []string // additional tags to recognize in +build lines + UseAllFiles bool // use files regardless of +build lines, file names + + // By default, ScanDir uses the operating system's + // file system calls to read directories and files. + // Callers can override those calls to provide other + // ways to read data by setting ReadDir and ReadFile. + // ScanDir does not make any assumptions about the + // format of the strings dir and file: they can be + // slash-separated, backslash-separated, even URLs. + + // ReadDir returns a slice of os.FileInfo, sorted by Name, + // describing the content of the named directory. + // The dir argument is the argument to ScanDir. + // If ReadDir is nil, ScanDir uses io.ReadDir. + ReadDir func(dir string) (fi []os.FileInfo, err error) + + // ReadFile returns the content of the file named file + // in the directory named dir. The dir argument is the + // argument to ScanDir, and the file argument is the + // Name field from an os.FileInfo returned by ReadDir. + // The returned path is the full name of the file, to be + // used in error messages. + // + // If ReadFile is nil, ScanDir uses filepath.Join(dir, file) + // as the path and ioutil.ReadFile to read the data. + ReadFile func(dir, file string) (path string, content []byte, err error) +} + +func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) { + if f := ctxt.ReadDir; f != nil { + return f(dir) + } + return ioutil.ReadDir(dir) +} + +func (ctxt *Context) readFile(dir, file string) (string, []byte, error) { + if f := ctxt.ReadFile; f != nil { + return f(dir, file) + } + p := filepath.Join(dir, file) + content, err := ioutil.ReadFile(p) + return p, content, err +} + +// The DefaultContext is the default Context for builds. +// It uses the GOARCH and GOOS environment variables +// if set, or else the compiled code's GOARCH and GOOS. +var DefaultContext Context = defaultContext() + +var cgoEnabled = map[string]bool{ + "darwin/386": true, + "darwin/amd64": true, + "linux/386": true, + "linux/amd64": true, + "freebsd/386": true, + "freebsd/amd64": true, + "windows/386": true, + "windows/amd64": true, +} + +func defaultContext() Context { + var c Context + + c.GOARCH = envOr("GOARCH", runtime.GOARCH) + c.GOOS = envOr("GOOS", runtime.GOOS) + + s := os.Getenv("CGO_ENABLED") + switch s { + case "1": + c.CgoEnabled = true + case "0": + c.CgoEnabled = false + default: + c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH] + } + + return c +} + +func envOr(name, def string) string { + s := os.Getenv(name) + if s == "" { + return def + } + return s +} + +type DirInfo struct { + Package string // Name of package in dir + PackageComment *ast.CommentGroup // Package comments from GoFiles + ImportPath string // Import path of package in dir + Imports []string // All packages imported by GoFiles + ImportPos map[string][]token.Position // Source code location of imports + + // Source files + GoFiles []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles) + HFiles []string // .h files in dir + CFiles []string // .c files in dir + SFiles []string // .s (and, when using cgo, .S files in dir) + CgoFiles []string // .go files that import "C" + + // Cgo directives + CgoPkgConfig []string // Cgo pkg-config directives + CgoCFLAGS []string // Cgo CFLAGS directives + CgoLDFLAGS []string // Cgo LDFLAGS directives + + // Test information + TestGoFiles []string // _test.go files in package + XTestGoFiles []string // _test.go files outside package + TestImports []string // All packages imported by (X)TestGoFiles + TestImportPos map[string][]token.Position +} + +func (d *DirInfo) IsCommand() bool { + // TODO(rsc): This is at least a little bogus. + return d.Package == "main" +} + +// ScanDir calls DefaultContext.ScanDir. +func ScanDir(dir string) (info *DirInfo, err error) { + return DefaultContext.ScanDir(dir) +} + +// TODO(rsc): Move this comment to a more appropriate place. + +// ScanDir returns a structure with details about the Go package +// found in the given directory. +// +// Most .go, .c, .h, and .s files in the directory are considered part +// of the package. The exceptions are: +// +// - .go files in package main (unless no other package is found) +// - .go files in package documentation +// - files starting with _ or . +// - files with build constraints not satisfied by the context +// +func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { + dirs, err := ctxt.readDir(dir) + if err != nil { + return nil, err + } + + var Sfiles []string // files with ".S" (capital S) + var di DirInfo + var firstFile string + imported := make(map[string][]token.Position) + testImported := make(map[string][]token.Position) + fset := token.NewFileSet() + for _, d := range dirs { + if d.IsDir() { + continue + } + name := d.Name() + if strings.HasPrefix(name, "_") || + strings.HasPrefix(name, ".") { + continue + } + if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) { + continue + } + + ext := path.Ext(name) + switch ext { + case ".go", ".c", ".s", ".h", ".S": + // tentatively okay + default: + // skip + continue + } + + filename, data, err := ctxt.readFile(dir, name) + if err != nil { + return nil, err + } + + // Look for +build comments to accept or reject the file. + if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) { + continue + } + + // Going to save the file. For non-Go files, can stop here. + switch ext { + case ".c": + di.CFiles = append(di.CFiles, name) + continue + case ".h": + di.HFiles = append(di.HFiles, name) + continue + case ".s": + di.SFiles = append(di.SFiles, name) + continue + case ".S": + Sfiles = append(Sfiles, name) + continue + } + + pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments) + if err != nil { + return nil, err + } + + pkg := string(pf.Name.Name) + if pkg == "documentation" { + continue + } + + isTest := strings.HasSuffix(name, "_test.go") + if isTest && strings.HasSuffix(pkg, "_test") { + pkg = pkg[:len(pkg)-len("_test")] + } + + if di.Package == "" { + di.Package = pkg + firstFile = name + } else if pkg != di.Package { + return nil, fmt.Errorf("%s: found packages %s (%s) and %s (%s)", dir, di.Package, firstFile, pkg, name) + } + if pf.Doc != nil { + if di.PackageComment != nil { + di.PackageComment.List = append(di.PackageComment.List, pf.Doc.List...) + } else { + di.PackageComment = pf.Doc + } + } + + // Record imports and information about cgo. + isCgo := false + for _, decl := range pf.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, dspec := range d.Specs { + spec, ok := dspec.(*ast.ImportSpec) + if !ok { + continue + } + quoted := string(spec.Path.Value) + path, err := strconv.Unquote(quoted) + if err != nil { + log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted) + } + if isTest { + testImported[path] = append(testImported[path], fset.Position(spec.Pos())) + } else { + imported[path] = append(imported[path], fset.Position(spec.Pos())) + } + if path == "C" { + if isTest { + return nil, fmt.Errorf("%s: use of cgo in test not supported", filename) + } + cg := spec.Doc + if cg == nil && len(d.Specs) == 1 { + cg = d.Doc + } + if cg != nil { + if err := ctxt.saveCgo(filename, &di, cg); err != nil { + return nil, err + } + } + isCgo = true + } + } + } + if isCgo { + if ctxt.CgoEnabled { + di.CgoFiles = append(di.CgoFiles, name) + } + } else if isTest { + if pkg == string(pf.Name.Name) { + di.TestGoFiles = append(di.TestGoFiles, name) + } else { + di.XTestGoFiles = append(di.XTestGoFiles, name) + } + } else { + di.GoFiles = append(di.GoFiles, name) + } + } + if di.Package == "" { + return nil, fmt.Errorf("%s: no Go source files", dir) + } + di.Imports = make([]string, len(imported)) + di.ImportPos = imported + i := 0 + for p := range imported { + di.Imports[i] = p + i++ + } + di.TestImports = make([]string, len(testImported)) + di.TestImportPos = testImported + i = 0 + for p := range testImported { + di.TestImports[i] = p + i++ + } + + // add the .S files only if we are using cgo + // (which means gcc will compile them). + // The standard assemblers expect .s files. + if len(di.CgoFiles) > 0 { + di.SFiles = append(di.SFiles, Sfiles...) + sort.Strings(di.SFiles) + } + + // File name lists are sorted because ReadDir sorts. + sort.Strings(di.Imports) + sort.Strings(di.TestImports) + return &di, nil +} + +var slashslash = []byte("//") + +// shouldBuild reports whether it is okay to use this file, +// The rule is that in the file's leading run of // comments +// and blank lines, which must be followed by a blank line +// (to avoid including a Go package clause doc comment), +// lines beginning with '// +build' are taken as build directives. +// +// The file is accepted only if each such line lists something +// matching the file. For example: +// +// // +build windows linux +// +// marks the file as applicable only on Windows and Linux. +// +func (ctxt *Context) shouldBuild(content []byte) bool { + // Pass 1. Identify leading run of // comments and blank lines, + // which must be followed by a blank line. + end := 0 + p := content + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if len(line) == 0 { // Blank line + end = cap(content) - cap(line) // &line[0] - &content[0] + continue + } + if !bytes.HasPrefix(line, slashslash) { // Not comment line + break + } + } + content = content[:end] + + // Pass 2. Process each line in the run. + 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, slashslash) { + line = bytes.TrimSpace(line[len(slashslash):]) + if len(line) > 0 && line[0] == '+' { + // Looks like a comment +line. + f := strings.Fields(string(line)) + if f[0] == "+build" { + ok := false + for _, tok := range f[1:] { + if ctxt.match(tok) { + ok = true + break + } + } + if !ok { + return false // this one doesn't match + } + } + } + } + } + return true // everything matches +} + +// saveCgo saves the information from the #cgo lines in the import "C" comment. +// These lines set CFLAGS and LDFLAGS and pkg-config directives that affect +// the way cgo's C code is built. +// +// TODO(rsc): This duplicates code in cgo. +// Once the dust settles, remove this code from cgo. +func (ctxt *Context) saveCgo(filename string, di *DirInfo, 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.match(c) { + ok = true + break + } + } + if !ok { + continue + } + } + + args, err := splitQuoted(argstr) + if err != nil { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + for _, arg := range args { + if !safeName(arg) { + return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) + } + } + + switch verb { + case "CFLAGS": + di.CgoCFLAGS = append(di.CgoCFLAGS, 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 +} + +var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:") + +func safeName(s string) bool { + if s == "" { + return false + } + for i := 0; i < len(s); i++ { + if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, 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 +} + +// match returns true if the name is one of: +// +// $GOOS +// $GOARCH +// cgo (if cgo is enabled) +// !cgo (if cgo is disabled) +// tag (if tag is listed in ctxt.BuildTags) +// !tag (if tag is not listed in ctxt.BuildTags) +// a slash-separated list of any of these +// +func (ctxt *Context) match(name string) bool { + if name == "" { + return false + } + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return !ctxt.match(name[1:]) + } + + // Tags must be letters, digits, underscores. + // Unlike in Go identifiers, all digits is fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { + return false + } + } + + // special tags + if ctxt.CgoEnabled && name == "cgo" { + return true + } + if name == ctxt.GOOS || name == ctxt.GOARCH { + return true + } + + // other tags + for _, tag := range ctxt.BuildTags { + 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.* +// +func (ctxt *Context) goodOSArchFile(name string) bool { + if dot := strings.Index(name, "."); dot != -1 { + name = name[:dot] + } + 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 l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH + } + if n >= 1 && knownOS[l[n-1]] { + return l[n-1] == ctxt.GOOS + } + if n >= 1 && knownArch[l[n-1]] { + return l[n-1] == ctxt.GOARCH + } + 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 = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) + +// isLocalPath returns whether the given path is local (/foo ./foo ../foo . ..) +// Windows paths that starts with drive letter (c:\foo c:foo) are considered local. +func isLocalPath(s string) bool { + const sep = string(filepath.Separator) + return s == "." || s == ".." || + filepath.HasPrefix(s, sep) || + filepath.HasPrefix(s, "."+sep) || filepath.HasPrefix(s, ".."+sep) || + filepath.VolumeName(s) != "" +} // ArchChar returns the architecture character for the given goarch. // For example, ArchChar("amd64") returns "6". diff --git a/src/pkg/go/build/dir.go b/src/pkg/go/build/dir.go deleted file mode 100644 index 6b30f76265..0000000000 --- a/src/pkg/go/build/dir.go +++ /dev/null @@ -1,705 +0,0 @@ -// 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/parser" - "go/token" - "io/ioutil" - "log" - "os" - "path" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "unicode" -) - -// A Context specifies the supporting context for a build. -type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - CgoEnabled bool // whether cgo can be used - BuildTags []string // additional tags to recognize in +build lines - UseAllFiles bool // use files regardless of +build lines, file names - - // By default, ScanDir uses the operating system's - // file system calls to read directories and files. - // Callers can override those calls to provide other - // ways to read data by setting ReadDir and ReadFile. - // ScanDir does not make any assumptions about the - // format of the strings dir and file: they can be - // slash-separated, backslash-separated, even URLs. - - // ReadDir returns a slice of os.FileInfo, sorted by Name, - // describing the content of the named directory. - // The dir argument is the argument to ScanDir. - // If ReadDir is nil, ScanDir uses io.ReadDir. - ReadDir func(dir string) (fi []os.FileInfo, err error) - - // ReadFile returns the content of the file named file - // in the directory named dir. The dir argument is the - // argument to ScanDir, and the file argument is the - // Name field from an os.FileInfo returned by ReadDir. - // The returned path is the full name of the file, to be - // used in error messages. - // - // If ReadFile is nil, ScanDir uses filepath.Join(dir, file) - // as the path and ioutil.ReadFile to read the data. - ReadFile func(dir, file string) (path string, content []byte, err error) -} - -func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) { - if f := ctxt.ReadDir; f != nil { - return f(dir) - } - return ioutil.ReadDir(dir) -} - -func (ctxt *Context) readFile(dir, file string) (string, []byte, error) { - if f := ctxt.ReadFile; f != nil { - return f(dir, file) - } - p := filepath.Join(dir, file) - content, err := ioutil.ReadFile(p) - return p, content, err -} - -// The DefaultContext is the default Context for builds. -// It uses the GOARCH and GOOS environment variables -// if set, or else the compiled code's GOARCH and GOOS. -var DefaultContext Context = defaultContext() - -var cgoEnabled = map[string]bool{ - "darwin/386": true, - "darwin/amd64": true, - "linux/386": true, - "linux/amd64": true, - "freebsd/386": true, - "freebsd/amd64": true, - "windows/386": true, - "windows/amd64": true, -} - -func defaultContext() Context { - var c Context - - c.GOARCH = envOr("GOARCH", runtime.GOARCH) - c.GOOS = envOr("GOOS", runtime.GOOS) - - s := os.Getenv("CGO_ENABLED") - switch s { - case "1": - c.CgoEnabled = true - case "0": - c.CgoEnabled = false - default: - c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH] - } - - return c -} - -func envOr(name, def string) string { - s := os.Getenv(name) - if s == "" { - return def - } - return s -} - -type DirInfo struct { - Package string // Name of package in dir - PackageComment *ast.CommentGroup // Package comments from GoFiles - ImportPath string // Import path of package in dir - Imports []string // All packages imported by GoFiles - ImportPos map[string][]token.Position // Source code location of imports - - // Source files - GoFiles []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles) - HFiles []string // .h files in dir - CFiles []string // .c files in dir - SFiles []string // .s (and, when using cgo, .S files in dir) - CgoFiles []string // .go files that import "C" - - // Cgo directives - CgoPkgConfig []string // Cgo pkg-config directives - CgoCFLAGS []string // Cgo CFLAGS directives - CgoLDFLAGS []string // Cgo LDFLAGS directives - - // Test information - TestGoFiles []string // _test.go files in package - XTestGoFiles []string // _test.go files outside package - TestImports []string // All packages imported by (X)TestGoFiles - TestImportPos map[string][]token.Position -} - -func (d *DirInfo) IsCommand() bool { - // TODO(rsc): This is at least a little bogus. - return d.Package == "main" -} - -// ScanDir calls DefaultContext.ScanDir. -func ScanDir(dir string) (info *DirInfo, err error) { - return DefaultContext.ScanDir(dir) -} - -// TODO(rsc): Move this comment to a more appropriate place. - -// ScanDir returns a structure with details about the Go package -// found in the given directory. -// -// Most .go, .c, .h, and .s files in the directory are considered part -// of the package. The exceptions are: -// -// - .go files in package main (unless no other package is found) -// - .go files in package documentation -// - files starting with _ or . -// - files with build constraints not satisfied by the context -// -// Build Constraints -// -// A build constraint is a line comment beginning with the directive +build -// that lists the conditions under which a file should be included in the package. -// Constraints may appear in any kind of source file (not just Go), but -// they must be appear near the top of the file, preceded -// only by blank lines and other line comments. -// -// A build constraint is evaluated as the OR of space-separated options; -// each option evaluates as the AND of its comma-separated terms; -// and each term is an alphanumeric word or, preceded by !, its negation. -// That is, the build constraint: -// -// // +build linux,386 darwin,!cgo -// -// corresponds to the boolean formula: -// -// (linux AND 386) OR (darwin AND (NOT cgo)) -// -// During a particular build, the following words are satisfied: -// -// - the target operating system, as spelled by runtime.GOOS -// - the target architecture, as spelled by runtime.GOARCH -// - "cgo", if ctxt.CgoEnabled is true -// - any additional words listed in ctxt.BuildTags -// -// If a file's name, after stripping the extension and a possible _test suffix, -// matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating -// system and architecture values, then the file is considered to have an implicit -// build constraint requiring those terms. -// -// Examples -// -// To keep a file from being considered for the build: -// -// // +build ignore -// -// (any other unsatisfied word will work as well, but ``ignore'' is conventional.) -// -// To build a file only when using cgo, and only on Linux and OS X: -// -// // +build linux,cgo darwin,cgo -// -// Such a file is usually paired with another file implementing the -// default functionality for other systems, which in this case would -// carry the constraint: -// -// // +build !linux !darwin !cgo -// -// Naming a file dns_windows.go will cause it to be included only when -// building the package for Windows; similarly, math_386.s will be included -// only when building the package for 32-bit x86. -// -func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { - dirs, err := ctxt.readDir(dir) - if err != nil { - return nil, err - } - - var Sfiles []string // files with ".S" (capital S) - var di DirInfo - var firstFile string - imported := make(map[string][]token.Position) - testImported := make(map[string][]token.Position) - fset := token.NewFileSet() - for _, d := range dirs { - if d.IsDir() { - continue - } - name := d.Name() - if strings.HasPrefix(name, "_") || - strings.HasPrefix(name, ".") { - continue - } - if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) { - continue - } - - ext := path.Ext(name) - switch ext { - case ".go", ".c", ".s", ".h", ".S": - // tentatively okay - default: - // skip - continue - } - - filename, data, err := ctxt.readFile(dir, name) - if err != nil { - return nil, err - } - - // Look for +build comments to accept or reject the file. - if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) { - continue - } - - // Going to save the file. For non-Go files, can stop here. - switch ext { - case ".c": - di.CFiles = append(di.CFiles, name) - continue - case ".h": - di.HFiles = append(di.HFiles, name) - continue - case ".s": - di.SFiles = append(di.SFiles, name) - continue - case ".S": - Sfiles = append(Sfiles, name) - continue - } - - pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments) - if err != nil { - return nil, err - } - - pkg := string(pf.Name.Name) - if pkg == "documentation" { - continue - } - - isTest := strings.HasSuffix(name, "_test.go") - if isTest && strings.HasSuffix(pkg, "_test") { - pkg = pkg[:len(pkg)-len("_test")] - } - - if di.Package == "" { - di.Package = pkg - firstFile = name - } else if pkg != di.Package { - return nil, fmt.Errorf("%s: found packages %s (%s) and %s (%s)", dir, di.Package, firstFile, pkg, name) - } - if pf.Doc != nil { - if di.PackageComment != nil { - di.PackageComment.List = append(di.PackageComment.List, pf.Doc.List...) - } else { - di.PackageComment = pf.Doc - } - } - - // Record imports and information about cgo. - isCgo := false - for _, decl := range pf.Decls { - d, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - for _, dspec := range d.Specs { - spec, ok := dspec.(*ast.ImportSpec) - if !ok { - continue - } - quoted := string(spec.Path.Value) - path, err := strconv.Unquote(quoted) - if err != nil { - log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted) - } - if isTest { - testImported[path] = append(testImported[path], fset.Position(spec.Pos())) - } else { - imported[path] = append(imported[path], fset.Position(spec.Pos())) - } - if path == "C" { - if isTest { - return nil, fmt.Errorf("%s: use of cgo in test not supported", filename) - } - cg := spec.Doc - if cg == nil && len(d.Specs) == 1 { - cg = d.Doc - } - if cg != nil { - if err := ctxt.saveCgo(filename, &di, cg); err != nil { - return nil, err - } - } - isCgo = true - } - } - } - if isCgo { - if ctxt.CgoEnabled { - di.CgoFiles = append(di.CgoFiles, name) - } - } else if isTest { - if pkg == string(pf.Name.Name) { - di.TestGoFiles = append(di.TestGoFiles, name) - } else { - di.XTestGoFiles = append(di.XTestGoFiles, name) - } - } else { - di.GoFiles = append(di.GoFiles, name) - } - } - if di.Package == "" { - return nil, fmt.Errorf("%s: no Go source files", dir) - } - di.Imports = make([]string, len(imported)) - di.ImportPos = imported - i := 0 - for p := range imported { - di.Imports[i] = p - i++ - } - di.TestImports = make([]string, len(testImported)) - di.TestImportPos = testImported - i = 0 - for p := range testImported { - di.TestImports[i] = p - i++ - } - - // add the .S files only if we are using cgo - // (which means gcc will compile them). - // The standard assemblers expect .s files. - if len(di.CgoFiles) > 0 { - di.SFiles = append(di.SFiles, Sfiles...) - sort.Strings(di.SFiles) - } - - // File name lists are sorted because ReadDir sorts. - sort.Strings(di.Imports) - sort.Strings(di.TestImports) - return &di, nil -} - -var slashslash = []byte("//") - -// shouldBuild reports whether it is okay to use this file, -// The rule is that in the file's leading run of // comments -// and blank lines, which must be followed by a blank line -// (to avoid including a Go package clause doc comment), -// lines beginning with '// +build' are taken as build directives. -// -// The file is accepted only if each such line lists something -// matching the file. For example: -// -// // +build windows linux -// -// marks the file as applicable only on Windows and Linux. -// -func (ctxt *Context) shouldBuild(content []byte) bool { - // Pass 1. Identify leading run of // comments and blank lines, - // which must be followed by a blank line. - end := 0 - p := content - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if len(line) == 0 { // Blank line - end = cap(content) - cap(line) // &line[0] - &content[0] - continue - } - if !bytes.HasPrefix(line, slashslash) { // Not comment line - break - } - } - content = content[:end] - - // Pass 2. Process each line in the run. - 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, slashslash) { - line = bytes.TrimSpace(line[len(slashslash):]) - if len(line) > 0 && line[0] == '+' { - // Looks like a comment +line. - f := strings.Fields(string(line)) - if f[0] == "+build" { - ok := false - for _, tok := range f[1:] { - if ctxt.match(tok) { - ok = true - break - } - } - if !ok { - return false // this one doesn't match - } - } - } - } - } - return true // everything matches -} - -// saveCgo saves the information from the #cgo lines in the import "C" comment. -// These lines set CFLAGS and LDFLAGS and pkg-config directives that affect -// the way cgo's C code is built. -// -// TODO(rsc): This duplicates code in cgo. -// Once the dust settles, remove this code from cgo. -func (ctxt *Context) saveCgo(filename string, di *DirInfo, 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.match(c) { - ok = true - break - } - } - if !ok { - continue - } - } - - args, err := splitQuoted(argstr) - if err != nil { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - for _, arg := range args { - if !safeName(arg) { - return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) - } - } - - switch verb { - case "CFLAGS": - di.CgoCFLAGS = append(di.CgoCFLAGS, 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 -} - -var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:") - -func safeName(s string) bool { - if s == "" { - return false - } - for i := 0; i < len(s); i++ { - if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, 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 -} - -// match returns true if the name is one of: -// -// $GOOS -// $GOARCH -// cgo (if cgo is enabled) -// !cgo (if cgo is disabled) -// tag (if tag is listed in ctxt.BuildTags) -// !tag (if tag is not listed in ctxt.BuildTags) -// a slash-separated list of any of these -// -func (ctxt *Context) match(name string) bool { - if name == "" { - return false - } - if i := strings.Index(name, ","); i >= 0 { - // comma-separated list - return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) - } - if strings.HasPrefix(name, "!!") { // bad syntax, reject always - return false - } - if strings.HasPrefix(name, "!") { // negation - return !ctxt.match(name[1:]) - } - - // Tags must be letters, digits, underscores. - // Unlike in Go identifiers, all digits is fine (e.g., "386"). - for _, c := range name { - if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { - return false - } - } - - // special tags - if ctxt.CgoEnabled && name == "cgo" { - return true - } - if name == ctxt.GOOS || name == ctxt.GOARCH { - return true - } - - // other tags - for _, tag := range ctxt.BuildTags { - 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.* -// -func (ctxt *Context) goodOSArchFile(name string) bool { - if dot := strings.Index(name, "."); dot != -1 { - name = name[:dot] - } - 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 l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH - } - if n >= 1 && knownOS[l[n-1]] { - return l[n-1] == ctxt.GOOS - } - if n >= 1 && knownArch[l[n-1]] { - return l[n-1] == ctxt.GOARCH - } - 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 - } -} diff --git a/src/pkg/go/build/path.go b/src/pkg/go/build/path.go index e160ac3b28..6115adafeb 100644 --- a/src/pkg/go/build/path.go +++ b/src/pkg/go/build/path.go @@ -12,9 +12,6 @@ import ( "runtime" ) -// ToolDir is the directory containing build tools. -var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) - // Path is a validated list of Trees derived from $GOROOT and $GOPATH at init. var Path []*Tree @@ -128,16 +125,6 @@ func FindTree(path string) (tree *Tree, pkg string, err error) { return } -// isLocalPath returns whether the given path is local (/foo ./foo ../foo . ..) -// Windows paths that starts with drive letter (c:\foo c:foo) are considered local. -func isLocalPath(s string) bool { - const sep = string(filepath.Separator) - return s == "." || s == ".." || - filepath.HasPrefix(s, sep) || - filepath.HasPrefix(s, "."+sep) || filepath.HasPrefix(s, ".."+sep) || - filepath.VolumeName(s) != "" -} - var ( // argument lists used by the build's gc and ld methods gcImportArgs []string -- 2.48.1