summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/imports/build.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/cmd/go/internal/imports/build.go
parentInitial commit. (diff)
downloadgolang-1.20-upstream.tar.xz
golang-1.20-upstream.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/go/internal/imports/build.go')
-rw-r--r--src/cmd/go/internal/imports/build.go373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/cmd/go/internal/imports/build.go b/src/cmd/go/internal/imports/build.go
new file mode 100644
index 0000000..be308ce
--- /dev/null
+++ b/src/cmd/go/internal/imports/build.go
@@ -0,0 +1,373 @@
+// Copyright 2018 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.
+
+// Copied from Go distribution src/go/build/build.go, syslist.go.
+// That package does not export the ability to process raw file data,
+// although we could fake it with an appropriate build.Context
+// and a lot of unwrapping.
+// More importantly, that package does not implement the tags["*"]
+// special case, in which both tag and !tag are considered to be true
+// for essentially all tags (except "ignore").
+//
+// If we added this API to go/build directly, we wouldn't need this
+// file anymore, but this API is not terribly general-purpose and we
+// don't really want to commit to any public form of it, nor do we
+// want to move the core parts of go/build into a top-level internal package.
+// These details change very infrequently, so the copy is fine.
+
+package imports
+
+import (
+ "bytes"
+ "cmd/go/internal/cfg"
+ "errors"
+ "fmt"
+ "go/build/constraint"
+ "strings"
+ "unicode"
+)
+
+var (
+ bSlashSlash = []byte("//")
+ bStarSlash = []byte("*/")
+ bSlashStar = []byte("/*")
+ bPlusBuild = []byte("+build")
+
+ goBuildComment = []byte("//go:build")
+
+ errMultipleGoBuild = errors.New("multiple //go:build comments")
+)
+
+func isGoBuildComment(line []byte) bool {
+ if !bytes.HasPrefix(line, goBuildComment) {
+ return false
+ }
+ line = bytes.TrimSpace(line)
+ rest := line[len(goBuildComment):]
+ return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
+}
+
+// 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 tags["*"] is true, then ShouldBuild will consider every
+// build tag except "ignore" to be both true and false for
+// the purpose of satisfying build tags, in order to estimate
+// (conservatively) whether a file could ever possibly be used
+// in any build.
+func ShouldBuild(content []byte, tags map[string]bool) bool {
+ // Identify leading run of // comments and blank lines,
+ // which must be followed by a blank line.
+ // Also identify any //go:build comments.
+ content, goBuild, _, err := parseFileHeader(content)
+ if err != nil {
+ return false
+ }
+
+ // If //go:build line is present, it controls.
+ // Otherwise fall back to +build processing.
+ var shouldBuild bool
+ switch {
+ case goBuild != nil:
+ x, err := constraint.Parse(string(goBuild))
+ if err != nil {
+ return false
+ }
+ shouldBuild = eval(x, tags, true)
+
+ default:
+ shouldBuild = true
+ p := content
+ for len(p) > 0 {
+ line := p
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ line, p = line[:i], p[i+1:]
+ } else {
+ p = p[len(p):]
+ }
+ line = bytes.TrimSpace(line)
+ if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
+ continue
+ }
+ text := string(line)
+ if !constraint.IsPlusBuild(text) {
+ continue
+ }
+ if x, err := constraint.Parse(text); err == nil {
+ if !eval(x, tags, true) {
+ shouldBuild = false
+ }
+ }
+ }
+ }
+
+ return shouldBuild
+}
+
+func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
+ end := 0
+ p := content
+ ended := false // found non-blank, non-// line, so stopped accepting // +build lines
+ inSlashStar := false // in /* */ comment
+
+Lines:
+ for len(p) > 0 {
+ line := p
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ line, p = line[:i], p[i+1:]
+ } else {
+ p = p[len(p):]
+ }
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 && !ended { // Blank line
+ // Remember position of most recent blank line.
+ // When we find the first non-blank, non-// line,
+ // this "end" position marks the latest file position
+ // where a // +build line can appear.
+ // (It must appear _before_ a blank line before the non-blank, non-// line.
+ // Yes, that's confusing, which is part of why we moved to //go:build lines.)
+ // Note that ended==false here means that inSlashStar==false,
+ // since seeing a /* would have set ended==true.
+ end = len(content) - len(p)
+ continue Lines
+ }
+ if !bytes.HasPrefix(line, bSlashSlash) { // Not comment line
+ ended = true
+ }
+
+ if !inSlashStar && isGoBuildComment(line) {
+ if goBuild != nil {
+ return nil, nil, false, errMultipleGoBuild
+ }
+ goBuild = line
+ }
+
+ Comments:
+ for len(line) > 0 {
+ if inSlashStar {
+ if i := bytes.Index(line, bStarSlash); i >= 0 {
+ inSlashStar = false
+ line = bytes.TrimSpace(line[i+len(bStarSlash):])
+ continue Comments
+ }
+ continue Lines
+ }
+ if bytes.HasPrefix(line, bSlashSlash) {
+ continue Lines
+ }
+ if bytes.HasPrefix(line, bSlashStar) {
+ inSlashStar = true
+ line = bytes.TrimSpace(line[len(bSlashStar):])
+ continue Comments
+ }
+ // Found non-comment text.
+ break Lines
+ }
+ }
+
+ return content[:end], goBuild, sawBinaryOnly, nil
+}
+
+// matchTag reports whether the tag name is valid and tags[name] is true.
+// As a special case, if tags["*"] is true and name is not empty or ignore,
+// then matchTag will return prefer instead of the actual answer,
+// which allows the caller to pretend in that case that most tags are
+// both true and false.
+func matchTag(name string, tags map[string]bool, prefer bool) bool {
+ // 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
+ }
+ }
+
+ if tags["*"] && name != "" && name != "ignore" {
+ // Special case for gathering all possible imports:
+ // if we put * in the tags map then all tags
+ // except "ignore" are considered both present and not
+ // (so we return true no matter how 'want' is set).
+ return prefer
+ }
+
+ if tags[name] {
+ return true
+ }
+
+ switch name {
+ case "linux":
+ return tags["android"]
+ case "solaris":
+ return tags["illumos"]
+ case "darwin":
+ return tags["ios"]
+ case "unix":
+ return unixOS[cfg.BuildContext.GOOS]
+ default:
+ return false
+ }
+}
+
+// eval is like
+//
+// x.Eval(func(tag string) bool { return matchTag(tag, tags) })
+//
+// except that it implements the special case for tags["*"] meaning
+// all tags are both true and false at the same time.
+func eval(x constraint.Expr, tags map[string]bool, prefer bool) bool {
+ switch x := x.(type) {
+ case *constraint.TagExpr:
+ return matchTag(x.Tag, tags, prefer)
+ case *constraint.NotExpr:
+ return !eval(x.X, tags, !prefer)
+ case *constraint.AndExpr:
+ return eval(x.X, tags, prefer) && eval(x.Y, tags, prefer)
+ case *constraint.OrExpr:
+ return eval(x.X, tags, prefer) || eval(x.Y, tags, prefer)
+ }
+ panic(fmt.Sprintf("unexpected constraint expression %T", x))
+}
+
+// Eval is like
+//
+// x.Eval(func(tag string) bool { return matchTag(tag, tags) })
+//
+// except that it implements the special case for tags["*"] meaning
+// all tags are both true and false at the same time.
+func Eval(x constraint.Expr, tags map[string]bool, prefer bool) bool {
+ return eval(x, tags, prefer)
+}
+
+// MatchFile returns false if the name contains a $GOOS or $GOARCH
+// suffix which does not match the current system.
+// The recognized name formats are:
+//
+// name_$(GOOS).*
+// name_$(GOARCH).*
+// name_$(GOOS)_$(GOARCH).*
+// name_$(GOOS)_test.*
+// name_$(GOARCH)_test.*
+// name_$(GOOS)_$(GOARCH)_test.*
+//
+// Exceptions:
+//
+// if GOOS=android, then files with GOOS=linux are also matched.
+// if GOOS=illumos, then files with GOOS=solaris are also matched.
+// if GOOS=ios, then files with GOOS=darwin are also matched.
+//
+// If tags["*"] is true, then MatchFile will consider all possible
+// GOOS and GOARCH to be available and will consequently
+// always return true.
+func MatchFile(name string, tags map[string]bool) bool {
+ if tags["*"] {
+ return true
+ }
+ if dot := strings.Index(name, "."); dot != -1 {
+ name = name[:dot]
+ }
+
+ // Before Go 1.4, a file called "linux.go" would be equivalent to having a
+ // build tag "linux" in that file. For Go 1.4 and beyond, we require this
+ // auto-tagging to apply only to files with a non-empty prefix, so
+ // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
+ // systems, such as android, to arrive without breaking existing code with
+ // innocuous source code in "android.go". The easiest fix: cut everything
+ // in the name before the initial _.
+ i := strings.Index(name, "_")
+ if i < 0 {
+ return true
+ }
+ name = name[i:] // ignore everything before first _
+
+ l := strings.Split(name, "_")
+ if n := len(l); n > 0 && l[n-1] == "test" {
+ l = l[:n-1]
+ }
+ n := len(l)
+ if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
+ return matchTag(l[n-2], tags, true) && matchTag(l[n-1], tags, true)
+ }
+ if n >= 1 && KnownOS[l[n-1]] {
+ return matchTag(l[n-1], tags, true)
+ }
+ if n >= 1 && KnownArch[l[n-1]] {
+ return matchTag(l[n-1], tags, true)
+ }
+ return true
+}
+
+var KnownOS = map[string]bool{
+ "aix": true,
+ "android": true,
+ "darwin": true,
+ "dragonfly": true,
+ "freebsd": true,
+ "hurd": true,
+ "illumos": true,
+ "ios": true,
+ "js": true,
+ "linux": true,
+ "nacl": true, // legacy; don't remove
+ "netbsd": true,
+ "openbsd": true,
+ "plan9": true,
+ "solaris": true,
+ "windows": true,
+ "zos": true,
+}
+
+// unixOS is the set of GOOS values matched by the "unix" build tag.
+// This is not used for filename matching.
+// This is the same list as in go/build/syslist.go and cmd/dist/build.go.
+var unixOS = map[string]bool{
+ "aix": true,
+ "android": true,
+ "darwin": true,
+ "dragonfly": true,
+ "freebsd": true,
+ "hurd": true,
+ "illumos": true,
+ "ios": true,
+ "linux": true,
+ "netbsd": true,
+ "openbsd": true,
+ "solaris": true,
+}
+
+var KnownArch = map[string]bool{
+ "386": true,
+ "amd64": true,
+ "amd64p32": true, // legacy; don't remove
+ "arm": true,
+ "armbe": true,
+ "arm64": true,
+ "arm64be": true,
+ "ppc64": true,
+ "ppc64le": true,
+ "mips": true,
+ "mipsle": true,
+ "mips64": true,
+ "mips64le": true,
+ "mips64p32": true,
+ "mips64p32le": true,
+ "loong64": true,
+ "ppc": true,
+ "riscv": true,
+ "riscv64": true,
+ "s390": true,
+ "s390x": true,
+ "sparc": true,
+ "sparc64": true,
+ "wasm": true,
+}