You've already forked postgres_exporter
mirror of
https://github.com/prometheus-community/postgres_exporter.git
synced 2025-08-09 15:42:47 +03:00
This commit implements a massive refactor of the repository, and moves the build system over to use Mage (magefile.org) which should allow seamless building across multiple platforms.
1656 lines
48 KiB
Go
1656 lines
48 KiB
Go
// 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/doc"
|
|
"go/parser"
|
|
"go/token"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"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
|
|
CgoEnabled bool // whether cgo can be used
|
|
UseAllFiles bool // use files regardless of +build lines, file names
|
|
Compiler string // compiler to assume when computing target paths
|
|
|
|
// RequiredTags lists tags that the must exist in a build tag in order for
|
|
// the file to be included in the build. If RequiredTags is empty, no tags
|
|
// are required. Note that this is mostly useful in filtering the list of
|
|
// files in a single directory. Using required tags across an entire
|
|
// compile step will likely exclude much, if not all of the standard library
|
|
// files.
|
|
RequiredTags []string
|
|
|
|
// The build 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 ReleaseTags,
|
|
// which defaults to the list of Go releases the current release is compatible with.
|
|
// In addition to the BuildTags and ReleaseTags, build constraints
|
|
// consider the values of GOARCH and GOOS as satisfied tags.
|
|
BuildTags []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 os.FileInfo, sorted by Name,
|
|
// describing the content of the named directory.
|
|
// If ReadDir is nil, Import uses ioutil.ReadDir.
|
|
ReadDir func(dir string) ([]os.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 rel, ok
|
|
}
|
|
|
|
// 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 rel, ok
|
|
}
|
|
if rel, ok = hasSubdir(root, dirSym); ok {
|
|
return rel, ok
|
|
}
|
|
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) ([]os.FileInfo, error) {
|
|
if f := ctxt.ReadDir; f != nil {
|
|
return f(path)
|
|
}
|
|
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 != "" {
|
|
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 ""
|
|
}
|
|
|
|
func defaultContext() Context {
|
|
var c Context
|
|
|
|
c.GOARCH = envOr("GOARCH", runtime.GOARCH)
|
|
c.GOOS = envOr("GOOS", runtime.GOOS)
|
|
c.GOROOT = pathpkg.Clean(runtime.GOROOT())
|
|
c.GOPATH = envOr("GOPATH", defaultGOPATH())
|
|
c.Compiler = runtime.Compiler
|
|
|
|
// Each major Go release in the Go 1.x series should add a tag here.
|
|
// Old tags should not be removed. 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".
|
|
// NOTE: If you add to this list, also update the doc comment in doc.go.
|
|
c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3", "go1.4", "go1.5", "go1.6", "go1.7", "go1.8", "go1.9"}
|
|
|
|
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
|
|
InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on)
|
|
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
|
|
|
|
// Dependency information
|
|
Imports []string // import paths from GoFiles, CgoFiles
|
|
ImportPos map[string][]token.Position // line information for Imports
|
|
|
|
// Test information
|
|
TestGoFiles []string // _test.go files in package
|
|
TestImports []string // import paths from TestGoFiles
|
|
TestImportPos map[string][]token.Position // line information for TestImports
|
|
XTestGoFiles []string // _test.go files outside package
|
|
XTestImports []string // import paths from XTestGoFiles
|
|
XTestImportPos map[string][]token.Position // line information for XTestImports
|
|
}
|
|
|
|
// 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
|
|
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 != "" {
|
|
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
|
|
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)
|
|
}
|
|
|
|
// tried records the location of unsuccessful package lookups
|
|
var tried struct {
|
|
vendor []string
|
|
goroot string
|
|
gopath []string
|
|
}
|
|
gopath := ctxt.gopath()
|
|
|
|
// 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 searchVendor(ctxt.GOROOT, true) {
|
|
goto Found
|
|
}
|
|
for _, root := range gopath {
|
|
if searchVendor(root, false) {
|
|
goto Found
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine directory from import path.
|
|
if ctxt.GOROOT != "" {
|
|
dir := ctxt.joinPath(ctxt.GOROOT, "src", path)
|
|
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
|
|
}
|
|
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)
|
|
}
|
|
|
|
// 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) {
|
|
// 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
|
|
}
|
|
|
|
dirs, err := ctxt.readDir(p.Dir)
|
|
if err != nil {
|
|
return p, err
|
|
}
|
|
|
|
var badGoError error
|
|
var Sfiles []string // files with ".S" (capital S)
|
|
var firstFile, firstCommentFile string
|
|
imported := make(map[string][]token.Position)
|
|
testImported := make(map[string][]token.Position)
|
|
xTestImported := make(map[string][]token.Position)
|
|
allTags := make(map[string]bool)
|
|
fset := token.NewFileSet()
|
|
for _, d := range dirs {
|
|
if d.IsDir() {
|
|
continue
|
|
}
|
|
|
|
name := d.Name()
|
|
ext := nameExt(name)
|
|
|
|
badFile := func(err error) {
|
|
if badGoError == nil {
|
|
badGoError = err
|
|
}
|
|
p.InvalidGoFiles = append(p.InvalidGoFiles, name)
|
|
}
|
|
|
|
match, data, filename, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly)
|
|
if err != nil {
|
|
badFile(err)
|
|
continue
|
|
}
|
|
if !match {
|
|
if ext == ".go" {
|
|
p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Going to save the file. For non-Go files, can stop here.
|
|
switch ext {
|
|
case ".c":
|
|
p.CFiles = append(p.CFiles, name)
|
|
continue
|
|
case ".cc", ".cpp", ".cxx":
|
|
p.CXXFiles = append(p.CXXFiles, name)
|
|
continue
|
|
case ".m":
|
|
p.MFiles = append(p.MFiles, name)
|
|
continue
|
|
case ".h", ".hh", ".hpp", ".hxx":
|
|
p.HFiles = append(p.HFiles, name)
|
|
continue
|
|
case ".f", ".F", ".for", ".f90":
|
|
p.FFiles = append(p.FFiles, name)
|
|
continue
|
|
case ".s":
|
|
p.SFiles = append(p.SFiles, name)
|
|
continue
|
|
case ".S":
|
|
Sfiles = append(Sfiles, name)
|
|
continue
|
|
case ".swig":
|
|
p.SwigFiles = append(p.SwigFiles, name)
|
|
continue
|
|
case ".swigcxx":
|
|
p.SwigCXXFiles = append(p.SwigCXXFiles, name)
|
|
continue
|
|
case ".syso":
|
|
// binary objects to add to package archive
|
|
// Likely of the form foo_windows.syso, but
|
|
// the name was vetted above with goodOSArchFile.
|
|
p.SysoFiles = append(p.SysoFiles, name)
|
|
continue
|
|
}
|
|
|
|
pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
|
|
if err != nil {
|
|
badFile(err)
|
|
continue
|
|
}
|
|
|
|
pkg := pf.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 {
|
|
badFile(&MultiplePackageError{
|
|
Dir: p.Dir,
|
|
Packages: []string{p.Name, pkg},
|
|
Files: []string{firstFile, name},
|
|
})
|
|
p.InvalidGoFiles = append(p.InvalidGoFiles, name)
|
|
}
|
|
if pf.Doc != nil && p.Doc == "" {
|
|
p.Doc = doc.Synopsis(pf.Doc.Text())
|
|
}
|
|
|
|
if mode&ImportComment != 0 {
|
|
qcom, line := findImportComment(data)
|
|
if line != 0 {
|
|
com, err := strconv.Unquote(qcom)
|
|
if err != nil {
|
|
badFile(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(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 _, 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 := spec.Path.Value
|
|
path, err := strconv.Unquote(quoted)
|
|
if err != nil {
|
|
log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
|
|
}
|
|
if isXTest {
|
|
xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos()))
|
|
} else 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 {
|
|
badFile(fmt.Errorf("use of cgo in test %s not supported", filename))
|
|
} else {
|
|
cg := spec.Doc
|
|
if cg == nil && len(d.Specs) == 1 {
|
|
cg = d.Doc
|
|
}
|
|
if cg != nil {
|
|
if err := ctxt.saveCgo(filename, p, cg); err != nil {
|
|
badFile(err)
|
|
}
|
|
}
|
|
isCgo = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if isCgo {
|
|
allTags["cgo"] = true
|
|
if ctxt.CgoEnabled {
|
|
p.CgoFiles = append(p.CgoFiles, name)
|
|
} else {
|
|
p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
|
|
}
|
|
} else if isXTest {
|
|
p.XTestGoFiles = append(p.XTestGoFiles, name)
|
|
} else if isTest {
|
|
p.TestGoFiles = append(p.TestGoFiles, name)
|
|
} else {
|
|
p.GoFiles = append(p.GoFiles, name)
|
|
}
|
|
}
|
|
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}
|
|
}
|
|
|
|
for tag := range allTags {
|
|
p.AllTags = append(p.AllTags, tag)
|
|
}
|
|
sort.Strings(p.AllTags)
|
|
|
|
p.Imports, p.ImportPos = cleanImports(imported)
|
|
p.TestImports, p.TestImportPos = cleanImports(testImported)
|
|
p.XTestImports, p.XTestImportPos = cleanImports(xTestImported)
|
|
|
|
// add the .S 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)
|
|
}
|
|
|
|
return p, pkgerr
|
|
}
|
|
|
|
// 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) {
|
|
match, _, _, err = ctxt.matchFile(dir, name, nil, nil)
|
|
return match, err
|
|
|
|
}
|
|
|
|
// matchFile determines whether the file with the given name in the given directory
|
|
// should be included in the package being constructed.
|
|
// It returns the data read from the file.
|
|
// If name denotes a Go program, matchFile reads until the end of the
|
|
// imports (and returns that data) even though it only considers text
|
|
// until the first non-comment.
|
|
// 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) (match bool, data []byte, filename string, err error) {
|
|
if strings.HasPrefix(name, "_") ||
|
|
strings.HasPrefix(name, ".") {
|
|
return match, data, filename, err
|
|
}
|
|
|
|
i := strings.LastIndex(name, ".")
|
|
if i < 0 {
|
|
i = len(name)
|
|
}
|
|
ext := name[i:]
|
|
|
|
if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles {
|
|
return match, data, filename, err
|
|
}
|
|
|
|
switch ext {
|
|
case ".go", ".c", ".cc", ".cxx", ".cpp", ".m", ".s", ".h", ".hh", ".hpp", ".hxx", ".f", ".F", ".f90", ".S", ".swig", ".swigcxx":
|
|
// tentatively okay - read to make sure
|
|
case ".syso":
|
|
// binary, no reading
|
|
match = true
|
|
return match, data, filename, err
|
|
default:
|
|
// skip
|
|
return match, data, filename, err
|
|
}
|
|
|
|
filename = ctxt.joinPath(dir, name)
|
|
f, err := ctxt.openFile(filename)
|
|
if err != nil {
|
|
return match, data, filename, err
|
|
}
|
|
|
|
if strings.HasSuffix(filename, ".go") {
|
|
data, err = readImports(f, false, nil)
|
|
if strings.HasSuffix(filename, "_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
|
|
data, err = readComments(f)
|
|
}
|
|
f.Close()
|
|
if err != nil {
|
|
err = fmt.Errorf("read %s: %v", filename, err)
|
|
return match, data, filename, err
|
|
}
|
|
|
|
// Look for +build comments to accept or reject the file.
|
|
var sawBinaryOnly bool
|
|
if !ctxt.shouldBuild(data, allTags, &sawBinaryOnly) && !ctxt.UseAllFiles {
|
|
return match, data, filename, err
|
|
}
|
|
|
|
if binaryOnly != nil && sawBinaryOnly {
|
|
*binaryOnly = true
|
|
}
|
|
match = true
|
|
return match, data, filename, err
|
|
}
|
|
|
|
func cleanImports(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 slashslash = []byte("//")
|
|
|
|
// 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.
|
|
//
|
|
// If shouldBuild finds a //go:binary-only-package comment in the file,
|
|
// it sets *binaryOnly to true. Otherwise it does not change *binaryOnly.
|
|
//
|
|
func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binaryOnly *bool) bool {
|
|
sawBinaryOnly := false
|
|
|
|
// 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 = len(content) - len(p)
|
|
continue
|
|
}
|
|
if !bytes.HasPrefix(line, slashslash) { // Not comment line
|
|
break
|
|
}
|
|
}
|
|
content = content[:end]
|
|
|
|
// Pass 2. Process each line in the run.
|
|
p = content
|
|
hasReq := len(ctxt.RequiredTags) > 0
|
|
allok := !hasReq
|
|
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) {
|
|
if bytes.Equal(line, binaryOnlyComment) {
|
|
sawBinaryOnly = true
|
|
}
|
|
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:] {
|
|
tags := map[string]bool{}
|
|
if ctxt.match(tok, tags) {
|
|
if containsAll(tags, ctxt.RequiredTags) {
|
|
ok = true
|
|
}
|
|
}
|
|
merge(allTags, tags)
|
|
}
|
|
if !hasReq {
|
|
if !ok {
|
|
allok = false
|
|
}
|
|
} else {
|
|
if ok {
|
|
allok = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if binaryOnly != nil && sawBinaryOnly {
|
|
*binaryOnly = true
|
|
}
|
|
|
|
return allok
|
|
}
|
|
|
|
func merge(to, from map[string]bool) {
|
|
if to == nil {
|
|
return
|
|
}
|
|
for k, v := range from {
|
|
to[k] = v
|
|
}
|
|
}
|
|
|
|
func containsAll(m map[string]bool, vals []string) bool {
|
|
// yes this is N^2, but N is small.
|
|
for _, v := range vals {
|
|
if !m[v] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func contains(list []string, s string) bool {
|
|
for _, l := range list {
|
|
if l == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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.match(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.
|
|
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
|
|
}
|
|
|
|
// match reports whether the name is one of:
|
|
//
|
|
// $GOOS
|
|
// $GOARCH
|
|
// cgo (if cgo is enabled)
|
|
// !cgo (if cgo is disabled)
|
|
// ctxt.Compiler
|
|
// !ctxt.Compiler
|
|
// tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
|
|
// !tag (if tag is not listed in ctxt.BuildTags or ctxt.ReleaseTags)
|
|
// a comma-separated list of any of these
|
|
//
|
|
func (ctxt *Context) match(name string, allTags map[string]bool) bool {
|
|
if name == "" {
|
|
if allTags != nil {
|
|
allTags[name] = true
|
|
}
|
|
return false
|
|
}
|
|
if i := strings.Index(name, ","); i >= 0 {
|
|
// comma-separated list
|
|
ok1 := ctxt.match(name[:i], allTags)
|
|
ok2 := ctxt.match(name[i+1:], allTags)
|
|
return ok1 && ok2
|
|
}
|
|
if strings.HasPrefix(name, "!!") { // bad syntax, reject always
|
|
return false
|
|
}
|
|
if strings.HasPrefix(name, "!") { // negation
|
|
return len(name) > 1 && !ctxt.match(name[1:], allTags)
|
|
}
|
|
|
|
if allTags != nil {
|
|
allTags[name] = true
|
|
}
|
|
|
|
// Tags must be letters, digits, underscores or dots.
|
|
// Unlike in Go identifiers, all digits are fine (e.g., "386").
|
|
for _, c := range name {
|
|
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// other tags
|
|
for _, tag := range ctxt.BuildTags {
|
|
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.*
|
|
//
|
|
// An exception: if GOOS=android, then files with GOOS=linux 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]] {
|
|
if allTags != nil {
|
|
allTags[l[n-2]] = true
|
|
allTags[l[n-1]] = true
|
|
}
|
|
if l[n-1] != ctxt.GOARCH {
|
|
return false
|
|
}
|
|
if ctxt.GOOS == "android" && l[n-2] == "linux" {
|
|
return true
|
|
}
|
|
return l[n-2] == ctxt.GOOS
|
|
}
|
|
if n >= 1 && knownOS[l[n-1]] {
|
|
if allTags != nil {
|
|
allTags[l[n-1]] = true
|
|
}
|
|
if ctxt.GOOS == "android" && l[n-1] == "linux" {
|
|
return true
|
|
}
|
|
return l[n-1] == ctxt.GOOS
|
|
}
|
|
if n >= 1 && knownArch[l[n-1]] {
|
|
if allTags != nil {
|
|
allTags[l[n-1]] = true
|
|
}
|
|
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)
|
|
|
|
// 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")
|
|
}
|