...

Source file src/github.com/rogpeppe/go-internal/imports/build.go

Documentation: github.com/rogpeppe/go-internal/imports

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Copied from Go distribution src/go/build/build.go, syslist.go
     6  
     7  package imports
     8  
     9  import (
    10  	"bytes"
    11  	"strings"
    12  	"unicode"
    13  )
    14  
    15  var slashslash = []byte("//")
    16  
    17  // ShouldBuild reports whether it is okay to use this file,
    18  // The rule is that in the file's leading run of // comments
    19  // and blank lines, which must be followed by a blank line
    20  // (to avoid including a Go package clause doc comment),
    21  // lines beginning with '// +build' are taken as build directives.
    22  //
    23  // The file is accepted only if each such line lists something
    24  // matching the file. For example:
    25  //
    26  //	// +build windows linux
    27  //
    28  // marks the file as applicable only on Windows and Linux.
    29  //
    30  // If tags["*"] is true, then ShouldBuild will consider every
    31  // build tag except "ignore" to be both true and false for
    32  // the purpose of satisfying build tags, in order to estimate
    33  // (conservatively) whether a file could ever possibly be used
    34  // in any build.
    35  func ShouldBuild(content []byte, tags map[string]bool) bool {
    36  	// Pass 1. Identify leading run of // comments and blank lines,
    37  	// which must be followed by a blank line.
    38  	end := 0
    39  	p := content
    40  	for len(p) > 0 {
    41  		line := p
    42  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
    43  			line, p = line[:i], p[i+1:]
    44  		} else {
    45  			p = p[len(p):]
    46  		}
    47  		line = bytes.TrimSpace(line)
    48  		if len(line) == 0 { // Blank line
    49  			end = len(content) - len(p)
    50  			continue
    51  		}
    52  		if !bytes.HasPrefix(line, slashslash) { // Not comment line
    53  			break
    54  		}
    55  	}
    56  	content = content[:end]
    57  
    58  	// Pass 2.  Process each line in the run.
    59  	p = content
    60  	allok := true
    61  	for len(p) > 0 {
    62  		line := p
    63  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
    64  			line, p = line[:i], p[i+1:]
    65  		} else {
    66  			p = p[len(p):]
    67  		}
    68  		line = bytes.TrimSpace(line)
    69  		if !bytes.HasPrefix(line, slashslash) {
    70  			continue
    71  		}
    72  		line = bytes.TrimSpace(line[len(slashslash):])
    73  		if len(line) > 0 && line[0] == '+' {
    74  			// Looks like a comment +line.
    75  			f := strings.Fields(string(line))
    76  			if f[0] == "+build" {
    77  				ok := false
    78  				for _, tok := range f[1:] {
    79  					if matchTags(tok, tags) {
    80  						ok = true
    81  					}
    82  				}
    83  				if !ok {
    84  					allok = false
    85  				}
    86  			}
    87  		}
    88  	}
    89  
    90  	return allok
    91  }
    92  
    93  // matchTags reports whether the name is one of:
    94  //
    95  //	tag (if tags[tag] is true)
    96  //	!tag (if tags[tag] is false)
    97  //	a comma-separated list of any of these
    98  func matchTags(name string, tags map[string]bool) bool {
    99  	if name == "" {
   100  		return false
   101  	}
   102  	if i := strings.Index(name, ","); i >= 0 {
   103  		// comma-separated list
   104  		ok1 := matchTags(name[:i], tags)
   105  		ok2 := matchTags(name[i+1:], tags)
   106  		return ok1 && ok2
   107  	}
   108  	if strings.HasPrefix(name, "!!") { // bad syntax, reject always
   109  		return false
   110  	}
   111  	if strings.HasPrefix(name, "!") { // negation
   112  		return len(name) > 1 && matchTag(name[1:], tags, false)
   113  	}
   114  	return matchTag(name, tags, true)
   115  }
   116  
   117  // matchTag reports whether the tag name is valid and satisfied by tags[name]==want.
   118  func matchTag(name string, tags map[string]bool, want bool) bool {
   119  	// Tags must be letters, digits, underscores or dots.
   120  	// Unlike in Go identifiers, all digits are fine (e.g., "386").
   121  	for _, c := range name {
   122  		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
   123  			return false
   124  		}
   125  	}
   126  
   127  	if tags["*"] && name != "" && name != "ignore" {
   128  		// Special case for gathering all possible imports:
   129  		// if we put * in the tags map then all tags
   130  		// except "ignore" are considered both present and not
   131  		// (so we return true no matter how 'want' is set).
   132  		return true
   133  	}
   134  
   135  	have := tags[name]
   136  	if name == "linux" {
   137  		have = have || tags["android"]
   138  	}
   139  	return have == want
   140  }
   141  
   142  // MatchFile returns false if the name contains a $GOOS or $GOARCH
   143  // suffix which does not match the current system.
   144  // The recognized name formats are:
   145  //
   146  //	name_$(GOOS).*
   147  //	name_$(GOARCH).*
   148  //	name_$(GOOS)_$(GOARCH).*
   149  //	name_$(GOOS)_test.*
   150  //	name_$(GOARCH)_test.*
   151  //	name_$(GOOS)_$(GOARCH)_test.*
   152  //
   153  // An exception: if GOOS=android, then files with GOOS=linux are also matched.
   154  //
   155  // If tags["*"] is true, then MatchFile will consider all possible
   156  // GOOS and GOARCH to be available and will consequently
   157  // always return true.
   158  func MatchFile(name string, tags map[string]bool) bool {
   159  	if tags["*"] {
   160  		return true
   161  	}
   162  	if dot := strings.Index(name, "."); dot != -1 {
   163  		name = name[:dot]
   164  	}
   165  
   166  	// Before Go 1.4, a file called "linux.go" would be equivalent to having a
   167  	// build tag "linux" in that file. For Go 1.4 and beyond, we require this
   168  	// auto-tagging to apply only to files with a non-empty prefix, so
   169  	// "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
   170  	// systems, such as android, to arrive without breaking existing code with
   171  	// innocuous source code in "android.go". The easiest fix: cut everything
   172  	// in the name before the initial _.
   173  	i := strings.Index(name, "_")
   174  	if i < 0 {
   175  		return true
   176  	}
   177  	name = name[i:] // ignore everything before first _
   178  
   179  	l := strings.Split(name, "_")
   180  	if n := len(l); n > 0 && l[n-1] == "test" {
   181  		l = l[:n-1]
   182  	}
   183  	n := len(l)
   184  	if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
   185  		return tags[l[n-2]] && tags[l[n-1]]
   186  	}
   187  	if n >= 1 && KnownOS[l[n-1]] {
   188  		return tags[l[n-1]]
   189  	}
   190  	if n >= 1 && KnownArch[l[n-1]] {
   191  		return tags[l[n-1]]
   192  	}
   193  	return true
   194  }
   195  
   196  var (
   197  	KnownOS   = make(map[string]bool)
   198  	UnixOS    = make(map[string]bool)
   199  	KnownArch = make(map[string]bool)
   200  )
   201  
   202  func init() {
   203  	for _, v := range strings.Fields(goosList) {
   204  		KnownOS[v] = true
   205  	}
   206  	for _, v := range strings.Fields(unixList) {
   207  		UnixOS[v] = true
   208  	}
   209  	for _, v := range strings.Fields(goarchList) {
   210  		KnownArch[v] = true
   211  	}
   212  }
   213  
   214  // These values come from Go's src/go/build/syslist.go and should be kept in
   215  // sync with that file.
   216  const (
   217  	goosList   = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd openbsd plan9 solaris windows zos "
   218  	unixList   = "aix android darwin dragonfly freebsd hurd illumos ios linux netbsd openbsd solaris "
   219  	goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be loong64 mips mipsle mips64 mips64le mips64p32 mips64p32le ppc ppc64 ppc64le riscv riscv64 s390 s390x sparc sparc64 wasm "
   220  )
   221  

View as plain text