// 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.
// +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)
_, err := os.Stat(dir)
return err == nil
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)
pkgdir, pkg := filepath.Split(full)
for _, p := range [...]string{
full,
full + ".gox",
pkgdir + "lib" + pkg + ".so",
pkgdir + "lib" + pkg + ".a",
full + ".o",
} {
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
return true
}
}
}
return false
}