golang.org/x/tools/gopls@v0.15.3/internal/cache/port.go (about)

     1  // Copyright 2023 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  package cache
     6  
     7  import (
     8  	"bytes"
     9  	"go/build"
    10  	"go/parser"
    11  	"go/token"
    12  	"io"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"golang.org/x/tools/gopls/internal/util/bug"
    17  )
    18  
    19  type port struct{ GOOS, GOARCH string }
    20  
    21  var (
    22  	// preferredPorts holds GOOS/GOARCH combinations for which we dynamically
    23  	// create new Views, by setting GOOS=... and GOARCH=... on top of
    24  	// user-provided configuration when we detect that the default build
    25  	// configuration does not match an open file. Ports are matched in the order
    26  	// defined below, so that when multiple ports match a file we use the port
    27  	// occurring at a lower index in the slice. For that reason, we sort first
    28  	// class ports ahead of secondary ports, and (among first class ports) 64-bit
    29  	// ports ahead of the less common 32-bit ports.
    30  	preferredPorts = []port{
    31  		// First class ports, from https://go.dev/wiki/PortingPolicy.
    32  		{"darwin", "amd64"},
    33  		{"darwin", "arm64"},
    34  		{"linux", "amd64"},
    35  		{"linux", "arm64"},
    36  		{"windows", "amd64"},
    37  		{"linux", "arm"},
    38  		{"linux", "386"},
    39  		{"windows", "386"},
    40  
    41  		// Secondary ports, from GOROOT/src/internal/platform/zosarch.go.
    42  		// (First class ports are commented out.)
    43  		{"aix", "ppc64"},
    44  		{"dragonfly", "amd64"},
    45  		{"freebsd", "386"},
    46  		{"freebsd", "amd64"},
    47  		{"freebsd", "arm"},
    48  		{"freebsd", "arm64"},
    49  		{"illumos", "amd64"},
    50  		{"linux", "ppc64"},
    51  		{"linux", "ppc64le"},
    52  		{"linux", "mips"},
    53  		{"linux", "mipsle"},
    54  		{"linux", "mips64"},
    55  		{"linux", "mips64le"},
    56  		{"linux", "riscv64"},
    57  		{"linux", "s390x"},
    58  		{"android", "386"},
    59  		{"android", "amd64"},
    60  		{"android", "arm"},
    61  		{"android", "arm64"},
    62  		{"ios", "arm64"},
    63  		{"ios", "amd64"},
    64  		{"js", "wasm"},
    65  		{"netbsd", "386"},
    66  		{"netbsd", "amd64"},
    67  		{"netbsd", "arm"},
    68  		{"netbsd", "arm64"},
    69  		{"openbsd", "386"},
    70  		{"openbsd", "amd64"},
    71  		{"openbsd", "arm"},
    72  		{"openbsd", "arm64"},
    73  		{"openbsd", "mips64"},
    74  		{"plan9", "386"},
    75  		{"plan9", "amd64"},
    76  		{"plan9", "arm"},
    77  		{"solaris", "amd64"},
    78  		{"windows", "arm"},
    79  		{"windows", "arm64"},
    80  
    81  		{"aix", "ppc64"},
    82  		{"android", "386"},
    83  		{"android", "amd64"},
    84  		{"android", "arm"},
    85  		{"android", "arm64"},
    86  		// {"darwin", "amd64"},
    87  		// {"darwin", "arm64"},
    88  		{"dragonfly", "amd64"},
    89  		{"freebsd", "386"},
    90  		{"freebsd", "amd64"},
    91  		{"freebsd", "arm"},
    92  		{"freebsd", "arm64"},
    93  		{"freebsd", "riscv64"},
    94  		{"illumos", "amd64"},
    95  		{"ios", "amd64"},
    96  		{"ios", "arm64"},
    97  		{"js", "wasm"},
    98  		// {"linux", "386"},
    99  		// {"linux", "amd64"},
   100  		// {"linux", "arm"},
   101  		// {"linux", "arm64"},
   102  		{"linux", "loong64"},
   103  		{"linux", "mips"},
   104  		{"linux", "mips64"},
   105  		{"linux", "mips64le"},
   106  		{"linux", "mipsle"},
   107  		{"linux", "ppc64"},
   108  		{"linux", "ppc64le"},
   109  		{"linux", "riscv64"},
   110  		{"linux", "s390x"},
   111  		{"linux", "sparc64"},
   112  		{"netbsd", "386"},
   113  		{"netbsd", "amd64"},
   114  		{"netbsd", "arm"},
   115  		{"netbsd", "arm64"},
   116  		{"openbsd", "386"},
   117  		{"openbsd", "amd64"},
   118  		{"openbsd", "arm"},
   119  		{"openbsd", "arm64"},
   120  		{"openbsd", "mips64"},
   121  		{"openbsd", "ppc64"},
   122  		{"openbsd", "riscv64"},
   123  		{"plan9", "386"},
   124  		{"plan9", "amd64"},
   125  		{"plan9", "arm"},
   126  		{"solaris", "amd64"},
   127  		{"wasip1", "wasm"},
   128  		// {"windows", "386"},
   129  		// {"windows", "amd64"},
   130  		{"windows", "arm"},
   131  		{"windows", "arm64"},
   132  	}
   133  )
   134  
   135  // matches reports whether the port matches a file with the given absolute path
   136  // and content.
   137  //
   138  // Note that this function accepts content rather than e.g. a file.Handle,
   139  // because we trim content before matching for performance reasons, and
   140  // therefore need to do this outside of matches when considering multiple ports.
   141  func (p port) matches(path string, content []byte) bool {
   142  	ctxt := build.Default // make a copy
   143  	ctxt.UseAllFiles = false
   144  	dir, name := filepath.Split(path)
   145  
   146  	// The only virtualized operation called by MatchFile is OpenFile.
   147  	ctxt.OpenFile = func(p string) (io.ReadCloser, error) {
   148  		if p != path {
   149  			return nil, bug.Errorf("unexpected file %q", p)
   150  		}
   151  		return io.NopCloser(bytes.NewReader(content)), nil
   152  	}
   153  
   154  	ctxt.GOOS = p.GOOS
   155  	ctxt.GOARCH = p.GOARCH
   156  	ok, err := ctxt.MatchFile(dir, name)
   157  	return err == nil && ok
   158  }
   159  
   160  // trimContentForPortMatch trims the given Go file content to a minimal file
   161  // containing the same build constraints, if any.
   162  //
   163  // This is an unfortunate but necessary optimization, as matching build
   164  // constraints using go/build has significant overhead, and involves parsing
   165  // more than just the build constraint.
   166  //
   167  // TestMatchingPortsConsistency enforces consistency by comparing results
   168  // without trimming content.
   169  func trimContentForPortMatch(content []byte) []byte {
   170  	buildComment := buildComment(content)
   171  	return []byte(buildComment + "\npackage p") // package name does not matter
   172  }
   173  
   174  // buildComment returns the first matching //go:build comment in the given
   175  // content, or "" if none exists.
   176  func buildComment(content []byte) string {
   177  	f, err := parser.ParseFile(token.NewFileSet(), "", content, parser.PackageClauseOnly|parser.ParseComments)
   178  	if err != nil {
   179  		return ""
   180  	}
   181  
   182  	for _, cg := range f.Comments {
   183  		for _, c := range cg.List {
   184  			if isGoBuildComment(c.Text) {
   185  				return c.Text
   186  			}
   187  		}
   188  	}
   189  	return ""
   190  }
   191  
   192  // Adapted from go/build/build.go.
   193  //
   194  // TODO(rfindley): use constraint.IsGoBuild once we are on 1.19+.
   195  func isGoBuildComment(line string) bool {
   196  	const goBuildComment = "//go:build"
   197  	if !strings.HasPrefix(line, goBuildComment) {
   198  		return false
   199  	}
   200  	// Report whether //go:build is followed by a word boundary.
   201  	line = strings.TrimSpace(line)
   202  	rest := line[len(goBuildComment):]
   203  	return len(rest) == 0 || len(strings.TrimSpace(rest)) < len(rest)
   204  }