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 }