xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/importcfg.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1// Copyright 2019 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"bufio"
19	"bytes"
20	"errors"
21	"fmt"
22	"io"
23	"io/ioutil"
24	"os"
25	"path/filepath"
26	"sort"
27	"strings"
28)
29
30type archive struct {
31	label, importPath, packagePath, file string
32	importPathAliases                    []string
33}
34
35// checkImports verifies that each import in files refers to a
36// direct dependency in archives or to a standard library package
37// listed in the file at stdPackageListPath. checkImports returns
38// a map from source import paths to elements of archives or to nil
39// for standard library packages.
40func checkImports(files []fileInfo, archives []archive, stdPackageListPath string, importPath string, recompileInternalDeps []string) (map[string]*archive, error) {
41	// Read the standard package list.
42	packagesTxt, err := ioutil.ReadFile(stdPackageListPath)
43	if err != nil {
44		return nil, err
45	}
46	stdPkgs := make(map[string]bool)
47	for len(packagesTxt) > 0 {
48		n := bytes.IndexByte(packagesTxt, '\n')
49		var line string
50		if n < 0 {
51			line = string(packagesTxt)
52			packagesTxt = nil
53		} else {
54			line = string(packagesTxt[:n])
55			packagesTxt = packagesTxt[n+1:]
56		}
57		line = strings.TrimSpace(line)
58		if line == "" {
59			continue
60		}
61		stdPkgs[line] = true
62	}
63
64	// Index the archives.
65	importToArchive := make(map[string]*archive)
66	importAliasToArchive := make(map[string]*archive)
67	for i := range archives {
68		arc := &archives[i]
69		importToArchive[arc.importPath] = arc
70		for _, imp := range arc.importPathAliases {
71			importAliasToArchive[imp] = arc
72		}
73	}
74	// Construct recompileInternalDeps as a map to check if there are imports that are disallowed.
75	recompileInternalDepMap := make(map[string]struct{})
76	for _, dep := range recompileInternalDeps {
77		recompileInternalDepMap[dep] = struct{}{}
78	}
79	// Build the import map.
80	imports := make(map[string]*archive)
81	var derr depsError
82	for _, f := range files {
83		for _, imp := range f.imports {
84			path := imp.path
85			if _, ok := imports[path]; ok || path == "C" || isRelative(path) {
86				// TODO(#1645): Support local (relative) import paths. We don't emit
87				// errors for them here, but they will probably break something else.
88				continue
89			}
90			if _, ok := recompileInternalDepMap[path]; ok {
91				return nil, fmt.Errorf("dependency cycle detected between %q and %q in file %q", importPath, path, f.filename)
92			}
93			if stdPkgs[path] {
94				imports[path] = nil
95			} else if arc := importToArchive[path]; arc != nil {
96				imports[path] = arc
97			} else if arc := importAliasToArchive[path]; arc != nil {
98				imports[path] = arc
99			} else {
100				derr.missing = append(derr.missing, missingDep{f.filename, path})
101			}
102		}
103	}
104	if len(derr.missing) > 0 {
105		return nil, derr
106	}
107	return imports, nil
108}
109
110// buildImportcfgFileForCompile writes an importcfg file to be consumed by the
111// compiler. The file is constructed from direct dependencies and std imports.
112// The caller is responsible for deleting the importcfg file.
113func buildImportcfgFileForCompile(imports map[string]*archive, installSuffix, dir string) (string, error) {
114	buf := &bytes.Buffer{}
115	goroot, ok := os.LookupEnv("GOROOT")
116	if !ok {
117		return "", errors.New("GOROOT not set")
118	}
119	goroot = abs(goroot)
120
121	sortedImports := make([]string, 0, len(imports))
122	for imp := range imports {
123		sortedImports = append(sortedImports, imp)
124	}
125	sort.Strings(sortedImports)
126
127	for _, imp := range sortedImports {
128		if arc := imports[imp]; arc == nil {
129			// std package
130			path := filepath.Join(goroot, "pkg", installSuffix, filepath.FromSlash(imp))
131			fmt.Fprintf(buf, "packagefile %s=%s.a\n", imp, path)
132		} else {
133			if imp != arc.packagePath {
134				fmt.Fprintf(buf, "importmap %s=%s\n", imp, arc.packagePath)
135			}
136			fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.file)
137		}
138	}
139
140	f, err := ioutil.TempFile(dir, "importcfg")
141	if err != nil {
142		return "", err
143	}
144	filename := f.Name()
145	if _, err := io.Copy(f, buf); err != nil {
146		f.Close()
147		os.Remove(filename)
148		return "", err
149	}
150	if err := f.Close(); err != nil {
151		os.Remove(filename)
152		return "", err
153	}
154	return filename, nil
155}
156
157func buildImportcfgFileForLink(archives []archive, stdPackageListPath, installSuffix, dir string) (string, error) {
158	buf := &bytes.Buffer{}
159	goroot, ok := os.LookupEnv("GOROOT")
160	if !ok {
161		return "", errors.New("GOROOT not set")
162	}
163	prefix := abs(filepath.Join(goroot, "pkg", installSuffix))
164	stdPackageListFile, err := os.Open(stdPackageListPath)
165	if err != nil {
166		return "", err
167	}
168	defer stdPackageListFile.Close()
169	scanner := bufio.NewScanner(stdPackageListFile)
170	for scanner.Scan() {
171		line := strings.TrimSpace(scanner.Text())
172		if line == "" {
173			continue
174		}
175		fmt.Fprintf(buf, "packagefile %s=%s.a\n", line, filepath.Join(prefix, filepath.FromSlash(line)))
176	}
177	if err := scanner.Err(); err != nil {
178		return "", err
179	}
180	depsSeen := map[string]string{}
181	for _, arc := range archives {
182		if _, ok := depsSeen[arc.packagePath]; ok {
183			return "", fmt.Errorf("internal error: package %s provided multiple times. This should have been detected during analysis.", arc.packagePath)
184		}
185		depsSeen[arc.packagePath] = arc.label
186		fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.file)
187	}
188	f, err := ioutil.TempFile(dir, "importcfg")
189	if err != nil {
190		return "", err
191	}
192	filename := f.Name()
193	if _, err := io.Copy(f, buf); err != nil {
194		f.Close()
195		os.Remove(filename)
196		return "", err
197	}
198	if err := f.Close(); err != nil {
199		os.Remove(filename)
200		return "", err
201	}
202	return filename, nil
203}
204
205type depsError struct {
206	missing []missingDep
207	known   []string
208}
209
210type missingDep struct {
211	filename, imp string
212}
213
214var _ error = depsError{}
215
216func (e depsError) Error() string {
217	buf := bytes.NewBuffer(nil)
218	fmt.Fprintf(buf, "missing strict dependencies:\n")
219	for _, dep := range e.missing {
220		fmt.Fprintf(buf, "\t%s: import of %q\n", dep.filename, dep.imp)
221	}
222	if len(e.known) == 0 {
223		fmt.Fprintln(buf, "No dependencies were provided.")
224	} else {
225		fmt.Fprintln(buf, "Known dependencies are:")
226		for _, imp := range e.known {
227			fmt.Fprintf(buf, "\t%s\n", imp)
228		}
229	}
230	fmt.Fprint(buf, "Check that imports in Go sources match importpath attributes in deps.")
231	return buf.String()
232}
233
234func isRelative(path string) bool {
235	return strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
236}
237
238type archiveMultiFlag []archive
239
240func (m *archiveMultiFlag) String() string {
241	if m == nil || len(*m) == 0 {
242		return ""
243	}
244	return fmt.Sprint(*m)
245}
246
247func (m *archiveMultiFlag) Set(v string) error {
248	parts := strings.Split(v, "=")
249	if len(parts) != 3 {
250		return fmt.Errorf("badly formed -arc flag: %s", v)
251	}
252	importPaths := strings.Split(parts[0], ":")
253	a := archive{
254		importPath:        importPaths[0],
255		importPathAliases: importPaths[1:],
256		packagePath:       parts[1],
257		file:              abs(parts[2]),
258	}
259	*m = append(*m, a)
260	return nil
261}
262