1// Copyright 2018 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//go:build gc
6
7package goroot
8
9import (
10	"os"
11	"os/exec"
12	"path/filepath"
13	"strings"
14	"sync"
15)
16
17// IsStandardPackage reports whether path is a standard package,
18// given goroot and compiler.
19func IsStandardPackage(goroot, compiler, path string) bool {
20	switch compiler {
21	case "gc":
22		dir := filepath.Join(goroot, "src", path)
23		dirents, err := os.ReadDir(dir)
24		if err != nil {
25			return false
26		}
27		for _, dirent := range dirents {
28			if strings.HasSuffix(dirent.Name(), ".go") {
29				return true
30			}
31		}
32		return false
33	case "gccgo":
34		return gccgoSearch.isStandard(path)
35	default:
36		panic("unknown compiler " + compiler)
37	}
38}
39
40// gccgoSearch holds the gccgo search directories.
41type gccgoDirs struct {
42	once sync.Once
43	dirs []string
44}
45
46// gccgoSearch is used to check whether a gccgo package exists in the
47// standard library.
48var gccgoSearch gccgoDirs
49
50// init finds the gccgo search directories. If this fails it leaves dirs == nil.
51func (gd *gccgoDirs) init() {
52	gccgo := os.Getenv("GCCGO")
53	if gccgo == "" {
54		gccgo = "gccgo"
55	}
56	bin, err := exec.LookPath(gccgo)
57	if err != nil {
58		return
59	}
60
61	allDirs, err := exec.Command(bin, "-print-search-dirs").Output()
62	if err != nil {
63		return
64	}
65	versionB, err := exec.Command(bin, "-dumpversion").Output()
66	if err != nil {
67		return
68	}
69	version := strings.TrimSpace(string(versionB))
70	machineB, err := exec.Command(bin, "-dumpmachine").Output()
71	if err != nil {
72		return
73	}
74	machine := strings.TrimSpace(string(machineB))
75
76	dirsEntries := strings.Split(string(allDirs), "\n")
77	const prefix = "libraries: ="
78	var dirs []string
79	for _, dirEntry := range dirsEntries {
80		if strings.HasPrefix(dirEntry, prefix) {
81			dirs = filepath.SplitList(strings.TrimPrefix(dirEntry, prefix))
82			break
83		}
84	}
85	if len(dirs) == 0 {
86		return
87	}
88
89	var lastDirs []string
90	for _, dir := range dirs {
91		goDir := filepath.Join(dir, "go", version)
92		if fi, err := os.Stat(goDir); err == nil && fi.IsDir() {
93			gd.dirs = append(gd.dirs, goDir)
94			goDir = filepath.Join(goDir, machine)
95			if fi, err = os.Stat(goDir); err == nil && fi.IsDir() {
96				gd.dirs = append(gd.dirs, goDir)
97			}
98		}
99		if fi, err := os.Stat(dir); err == nil && fi.IsDir() {
100			lastDirs = append(lastDirs, dir)
101		}
102	}
103	gd.dirs = append(gd.dirs, lastDirs...)
104}
105
106// isStandard reports whether path is a standard library for gccgo.
107func (gd *gccgoDirs) isStandard(path string) bool {
108	// Quick check: if the first path component has a '.', it's not
109	// in the standard library. This skips most GOPATH directories.
110	i := strings.Index(path, "/")
111	if i < 0 {
112		i = len(path)
113	}
114	if strings.Contains(path[:i], ".") {
115		return false
116	}
117
118	if path == "unsafe" {
119		// Special case.
120		return true
121	}
122
123	gd.once.Do(gd.init)
124	if gd.dirs == nil {
125		// We couldn't find the gccgo search directories.
126		// Best guess, since the first component did not contain
127		// '.', is that this is a standard library package.
128		return true
129	}
130
131	for _, dir := range gd.dirs {
132		full := filepath.Join(dir, path) + ".gox"
133		if fi, err := os.Stat(full); err == nil && !fi.IsDir() {
134			return true
135		}
136	}
137
138	return false
139}
140