| // 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. | |
| //go:build gc | |
| package goroot | |
| import ( | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strings" | |
| "sync" | |
| ) | |
| // IsStandardPackage reports whether path is a standard package, | |
| // given goroot and compiler. readDir accepts OS filesystem paths. | |
| func IsStandardPackage(readDir func(string) ([]os.DirEntry, error), goroot, compiler, path string) bool { | |
| switch compiler { | |
| case "gc": | |
| dir := filepath.Join(goroot, "src", path) | |
| dirents, err := readDir(dir) | |
| if err != nil { | |
| return false | |
| } | |
| for _, dirent := range dirents { | |
| if strings.HasSuffix(dirent.Name(), ".go") { | |
| return true | |
| } | |
| } | |
| return false | |
| case "gccgo": | |
| return gccgoSearch.isStandard(path) | |
| default: | |
| panic("unknown compiler " + compiler) | |
| } | |
| } | |
| // gccgoDirs holds the gccgo search directories. | |
| type gccgoDirs struct { | |
| once sync.Once | |
| dirs []string | |
| } | |
| // gccgoSearch is used to check whether a gccgo package exists in the | |
| // standard library. | |
| var gccgoSearch gccgoDirs | |
| // init finds the gccgo search directories. If this fails it leaves dirs == nil. | |
| func (gd *gccgoDirs) init() { | |
| gccgo := os.Getenv("GCCGO") | |
| if gccgo == "" { | |
| gccgo = "gccgo" | |
| } | |
| bin, err := exec.LookPath(gccgo) | |
| if err != nil { | |
| return | |
| } | |
| allDirs, err := exec.Command(bin, "-print-search-dirs").Output() | |
| if err != nil { | |
| return | |
| } | |
| versionB, err := exec.Command(bin, "-dumpversion").Output() | |
| if err != nil { | |
| return | |
| } | |
| version := strings.TrimSpace(string(versionB)) | |
| machineB, err := exec.Command(bin, "-dumpmachine").Output() | |
| if err != nil { | |
| return | |
| } | |
| machine := strings.TrimSpace(string(machineB)) | |
| dirsEntries := strings.Split(string(allDirs), "\n") | |
| const prefix = "libraries: =" | |
| var dirs []string | |
| for _, dirEntry := range dirsEntries { | |
| if after, ok := strings.CutPrefix(dirEntry, prefix); ok { | |
| dirs = filepath.SplitList(after) | |
| break | |
| } | |
| } | |
| if len(dirs) == 0 { | |
| return | |
| } | |
| var lastDirs []string | |
| for _, dir := range dirs { | |
| goDir := filepath.Join(dir, "go", version) | |
| if fi, err := os.Stat(goDir); err == nil && fi.IsDir() { | |
| gd.dirs = append(gd.dirs, goDir) | |
| goDir = filepath.Join(goDir, machine) | |
| if fi, err = os.Stat(goDir); err == nil && fi.IsDir() { | |
| gd.dirs = append(gd.dirs, goDir) | |
| } | |
| } | |
| if fi, err := os.Stat(dir); err == nil && fi.IsDir() { | |
| lastDirs = append(lastDirs, dir) | |
| } | |
| } | |
| gd.dirs = append(gd.dirs, lastDirs...) | |
| } | |
| // isStandard reports whether path is a standard library for gccgo. | |
| func (gd *gccgoDirs) isStandard(path string) bool { | |
| // Quick check: if the first path component has a '.', it's not | |
| // in the standard library. This skips most GOPATH directories. | |
| i := strings.Index(path, "/") | |
| if i < 0 { | |
| i = len(path) | |
| } | |
| if strings.Contains(path[:i], ".") { | |
| return false | |
| } | |
| if path == "unsafe" { | |
| // Special case. | |
| return true | |
| } | |
| gd.once.Do(gd.init) | |
| if gd.dirs == nil { | |
| // We couldn't find the gccgo search directories. | |
| // Best guess, since the first component did not contain | |
| // '.', is that this is a standard library package. | |
| return true | |
| } | |
| for _, dir := range gd.dirs { | |
| full := filepath.Join(dir, path) + ".gox" | |
| if fi, err := os.Stat(full); err == nil && !fi.IsDir() { | |
| return true | |
| } | |
| } | |
| return false | |
| } |