|  | // Copyright 2023 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 cache | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "go/build" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "io" | 
|  | "path/filepath" | 
|  | "strings" | 
|  |  | 
|  | "golang.org/x/tools/gopls/internal/util/bug" | 
|  | ) | 
|  |  | 
|  | type port struct{ GOOS, GOARCH string } | 
|  |  | 
|  | var ( | 
|  | // preferredPorts holds GOOS/GOARCH combinations for which we dynamically | 
|  | // create new Views, by setting GOOS=... and GOARCH=... on top of | 
|  | // user-provided configuration when we detect that the default build | 
|  | // configuration does not match an open file. Ports are matched in the order | 
|  | // defined below, so that when multiple ports match a file we use the port | 
|  | // occurring at a lower index in the slice. For that reason, we sort first | 
|  | // class ports ahead of secondary ports, and (among first class ports) 64-bit | 
|  | // ports ahead of the less common 32-bit ports. | 
|  | preferredPorts = []port{ | 
|  | // First class ports, from https://go.dev/wiki/PortingPolicy. | 
|  | {"darwin", "amd64"}, | 
|  | {"darwin", "arm64"}, | 
|  | {"linux", "amd64"}, | 
|  | {"linux", "arm64"}, | 
|  | {"windows", "amd64"}, | 
|  | {"linux", "arm"}, | 
|  | {"linux", "386"}, | 
|  | {"windows", "386"}, | 
|  |  | 
|  | // Secondary ports, from GOROOT/src/internal/platform/zosarch.go. | 
|  | // (First class ports are commented out.) | 
|  | {"aix", "ppc64"}, | 
|  | {"dragonfly", "amd64"}, | 
|  | {"freebsd", "386"}, | 
|  | {"freebsd", "amd64"}, | 
|  | {"freebsd", "arm"}, | 
|  | {"freebsd", "arm64"}, | 
|  | {"illumos", "amd64"}, | 
|  | {"linux", "ppc64"}, | 
|  | {"linux", "ppc64le"}, | 
|  | {"linux", "mips"}, | 
|  | {"linux", "mipsle"}, | 
|  | {"linux", "mips64"}, | 
|  | {"linux", "mips64le"}, | 
|  | {"linux", "riscv64"}, | 
|  | {"linux", "s390x"}, | 
|  | {"android", "386"}, | 
|  | {"android", "amd64"}, | 
|  | {"android", "arm"}, | 
|  | {"android", "arm64"}, | 
|  | {"ios", "arm64"}, | 
|  | {"ios", "amd64"}, | 
|  | {"js", "wasm"}, | 
|  | {"netbsd", "386"}, | 
|  | {"netbsd", "amd64"}, | 
|  | {"netbsd", "arm"}, | 
|  | {"netbsd", "arm64"}, | 
|  | {"openbsd", "386"}, | 
|  | {"openbsd", "amd64"}, | 
|  | {"openbsd", "arm"}, | 
|  | {"openbsd", "arm64"}, | 
|  | {"openbsd", "mips64"}, | 
|  | {"plan9", "386"}, | 
|  | {"plan9", "amd64"}, | 
|  | {"plan9", "arm"}, | 
|  | {"solaris", "amd64"}, | 
|  | {"windows", "arm"}, | 
|  | {"windows", "arm64"}, | 
|  |  | 
|  | {"aix", "ppc64"}, | 
|  | {"android", "386"}, | 
|  | {"android", "amd64"}, | 
|  | {"android", "arm"}, | 
|  | {"android", "arm64"}, | 
|  | // {"darwin", "amd64"}, | 
|  | // {"darwin", "arm64"}, | 
|  | {"dragonfly", "amd64"}, | 
|  | {"freebsd", "386"}, | 
|  | {"freebsd", "amd64"}, | 
|  | {"freebsd", "arm"}, | 
|  | {"freebsd", "arm64"}, | 
|  | {"freebsd", "riscv64"}, | 
|  | {"illumos", "amd64"}, | 
|  | {"ios", "amd64"}, | 
|  | {"ios", "arm64"}, | 
|  | {"js", "wasm"}, | 
|  | // {"linux", "386"}, | 
|  | // {"linux", "amd64"}, | 
|  | // {"linux", "arm"}, | 
|  | // {"linux", "arm64"}, | 
|  | {"linux", "loong64"}, | 
|  | {"linux", "mips"}, | 
|  | {"linux", "mips64"}, | 
|  | {"linux", "mips64le"}, | 
|  | {"linux", "mipsle"}, | 
|  | {"linux", "ppc64"}, | 
|  | {"linux", "ppc64le"}, | 
|  | {"linux", "riscv64"}, | 
|  | {"linux", "s390x"}, | 
|  | {"linux", "sparc64"}, | 
|  | {"netbsd", "386"}, | 
|  | {"netbsd", "amd64"}, | 
|  | {"netbsd", "arm"}, | 
|  | {"netbsd", "arm64"}, | 
|  | {"openbsd", "386"}, | 
|  | {"openbsd", "amd64"}, | 
|  | {"openbsd", "arm"}, | 
|  | {"openbsd", "arm64"}, | 
|  | {"openbsd", "mips64"}, | 
|  | {"openbsd", "ppc64"}, | 
|  | {"openbsd", "riscv64"}, | 
|  | {"plan9", "386"}, | 
|  | {"plan9", "amd64"}, | 
|  | {"plan9", "arm"}, | 
|  | {"solaris", "amd64"}, | 
|  | {"wasip1", "wasm"}, | 
|  | // {"windows", "386"}, | 
|  | // {"windows", "amd64"}, | 
|  | {"windows", "arm"}, | 
|  | {"windows", "arm64"}, | 
|  | } | 
|  | ) | 
|  |  | 
|  | // matches reports whether the port matches a file with the given absolute path | 
|  | // and content. | 
|  | // | 
|  | // Note that this function accepts content rather than e.g. a file.Handle, | 
|  | // because we trim content before matching for performance reasons, and | 
|  | // therefore need to do this outside of matches when considering multiple ports. | 
|  | func (p port) matches(path string, content []byte) bool { | 
|  | ctxt := build.Default // make a copy | 
|  | ctxt.UseAllFiles = false | 
|  | path = filepath.Clean(path) | 
|  | if !filepath.IsAbs(path) { | 
|  | bug.Reportf("non-abs file path %q", path) | 
|  | return false // fail closed | 
|  | } | 
|  | dir, name := filepath.Split(path) | 
|  |  | 
|  | // The only virtualized operation called by MatchFile is OpenFile. | 
|  | ctxt.OpenFile = func(p string) (io.ReadCloser, error) { | 
|  | if p != path { | 
|  | return nil, bug.Errorf("unexpected file %q", p) | 
|  | } | 
|  | return io.NopCloser(bytes.NewReader(content)), nil | 
|  | } | 
|  |  | 
|  | ctxt.GOOS = p.GOOS | 
|  | ctxt.GOARCH = p.GOARCH | 
|  | ok, err := ctxt.MatchFile(dir, name) | 
|  | return err == nil && ok | 
|  | } | 
|  |  | 
|  | // trimContentForPortMatch trims the given Go file content to a minimal file | 
|  | // containing the same build constraints, if any. | 
|  | // | 
|  | // This is an unfortunate but necessary optimization, as matching build | 
|  | // constraints using go/build has significant overhead, and involves parsing | 
|  | // more than just the build constraint. | 
|  | // | 
|  | // TestMatchingPortsConsistency enforces consistency by comparing results | 
|  | // without trimming content. | 
|  | func trimContentForPortMatch(content []byte) []byte { | 
|  | buildComment := buildComment(content) | 
|  | return []byte(buildComment + "\npackage p") // package name does not matter | 
|  | } | 
|  |  | 
|  | // buildComment returns the first matching //go:build comment in the given | 
|  | // content, or "" if none exists. | 
|  | func buildComment(content []byte) string { | 
|  | f, err := parser.ParseFile(token.NewFileSet(), "", content, parser.PackageClauseOnly|parser.ParseComments) | 
|  | if err != nil { | 
|  | return "" | 
|  | } | 
|  |  | 
|  | for _, cg := range f.Comments { | 
|  | for _, c := range cg.List { | 
|  | if isGoBuildComment(c.Text) { | 
|  | return c.Text | 
|  | } | 
|  | } | 
|  | } | 
|  | return "" | 
|  | } | 
|  |  | 
|  | // Adapted from go/build/build.go. | 
|  | // | 
|  | // TODO(rfindley): use constraint.IsGoBuild once we are on 1.19+. | 
|  | func isGoBuildComment(line string) bool { | 
|  | const goBuildComment = "//go:build" | 
|  | if !strings.HasPrefix(line, goBuildComment) { | 
|  | return false | 
|  | } | 
|  | // Report whether //go:build is followed by a word boundary. | 
|  | line = strings.TrimSpace(line) | 
|  | rest := line[len(goBuildComment):] | 
|  | return len(rest) == 0 || len(strings.TrimSpace(rest)) < len(rest) | 
|  | } |