| // 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. |
| func IsStandardPackage(goroot, compiler, path string) bool { |
| switch compiler { |
| case "gc": |
| dir := filepath.Join(goroot, "src", path) |
| dirents, err := os.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) |
| } |
| } |
| |
| // gccgoSearch 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 strings.HasPrefix(dirEntry, prefix) { |
| dirs = filepath.SplitList(strings.TrimPrefix(dirEntry, prefix)) |
| 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 |
| } |