aboutsummaryrefslogtreecommitdiff
path: root/go/tools/builders/importcfg.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/builders/importcfg.go')
-rw-r--r--go/tools/builders/importcfg.go261
1 files changed, 261 insertions, 0 deletions
diff --git a/go/tools/builders/importcfg.go b/go/tools/builders/importcfg.go
new file mode 100644
index 00000000..9fe55b42
--- /dev/null
+++ b/go/tools/builders/importcfg.go
@@ -0,0 +1,261 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+type archive struct {
+ label, importPath, packagePath, file string
+ importPathAliases []string
+}
+
+// checkImports verifies that each import in files refers to a
+// direct dependency in archives or to a standard library package
+// listed in the file at stdPackageListPath. checkImports returns
+// a map from source import paths to elements of archives or to nil
+// for standard library packages.
+func checkImports(files []fileInfo, archives []archive, stdPackageListPath string, importPath string, recompileInternalDeps []string) (map[string]*archive, error) {
+ // Read the standard package list.
+ packagesTxt, err := ioutil.ReadFile(stdPackageListPath)
+ if err != nil {
+ return nil, err
+ }
+ stdPkgs := make(map[string]bool)
+ for len(packagesTxt) > 0 {
+ n := bytes.IndexByte(packagesTxt, '\n')
+ var line string
+ if n < 0 {
+ line = string(packagesTxt)
+ packagesTxt = nil
+ } else {
+ line = string(packagesTxt[:n])
+ packagesTxt = packagesTxt[n+1:]
+ }
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ stdPkgs[line] = true
+ }
+
+ // Index the archives.
+ importToArchive := make(map[string]*archive)
+ importAliasToArchive := make(map[string]*archive)
+ for i := range archives {
+ arc := &archives[i]
+ importToArchive[arc.importPath] = arc
+ for _, imp := range arc.importPathAliases {
+ importAliasToArchive[imp] = arc
+ }
+ }
+ // Construct recompileInternalDeps as a map to check if there are imports that are disallowed.
+ recompileInternalDepMap := make(map[string]struct{})
+ for _, dep := range recompileInternalDeps {
+ recompileInternalDepMap[dep] = struct{}{}
+ }
+ // Build the import map.
+ imports := make(map[string]*archive)
+ var derr depsError
+ for _, f := range files {
+ for _, imp := range f.imports {
+ path := imp.path
+ if _, ok := imports[path]; ok || path == "C" || isRelative(path) {
+ // TODO(#1645): Support local (relative) import paths. We don't emit
+ // errors for them here, but they will probably break something else.
+ continue
+ }
+ if _, ok := recompileInternalDepMap[path]; ok {
+ return nil, fmt.Errorf("dependency cycle detected between %q and %q in file %q", importPath, path, f.filename)
+ }
+ if stdPkgs[path] {
+ imports[path] = nil
+ } else if arc := importToArchive[path]; arc != nil {
+ imports[path] = arc
+ } else if arc := importAliasToArchive[path]; arc != nil {
+ imports[path] = arc
+ } else {
+ derr.missing = append(derr.missing, missingDep{f.filename, path})
+ }
+ }
+ }
+ if len(derr.missing) > 0 {
+ return nil, derr
+ }
+ return imports, nil
+}
+
+// buildImportcfgFileForCompile writes an importcfg file to be consumed by the
+// compiler. The file is constructed from direct dependencies and std imports.
+// The caller is responsible for deleting the importcfg file.
+func buildImportcfgFileForCompile(imports map[string]*archive, installSuffix, dir string) (string, error) {
+ buf := &bytes.Buffer{}
+ goroot, ok := os.LookupEnv("GOROOT")
+ if !ok {
+ return "", errors.New("GOROOT not set")
+ }
+ goroot = abs(goroot)
+
+ sortedImports := make([]string, 0, len(imports))
+ for imp := range imports {
+ sortedImports = append(sortedImports, imp)
+ }
+ sort.Strings(sortedImports)
+
+ for _, imp := range sortedImports {
+ if arc := imports[imp]; arc == nil {
+ // std package
+ path := filepath.Join(goroot, "pkg", installSuffix, filepath.FromSlash(imp))
+ fmt.Fprintf(buf, "packagefile %s=%s.a\n", imp, path)
+ } else {
+ if imp != arc.packagePath {
+ fmt.Fprintf(buf, "importmap %s=%s\n", imp, arc.packagePath)
+ }
+ fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.file)
+ }
+ }
+
+ f, err := ioutil.TempFile(dir, "importcfg")
+ if err != nil {
+ return "", err
+ }
+ filename := f.Name()
+ if _, err := io.Copy(f, buf); err != nil {
+ f.Close()
+ os.Remove(filename)
+ return "", err
+ }
+ if err := f.Close(); err != nil {
+ os.Remove(filename)
+ return "", err
+ }
+ return filename, nil
+}
+
+func buildImportcfgFileForLink(archives []archive, stdPackageListPath, installSuffix, dir string) (string, error) {
+ buf := &bytes.Buffer{}
+ goroot, ok := os.LookupEnv("GOROOT")
+ if !ok {
+ return "", errors.New("GOROOT not set")
+ }
+ prefix := abs(filepath.Join(goroot, "pkg", installSuffix))
+ stdPackageListFile, err := os.Open(stdPackageListPath)
+ if err != nil {
+ return "", err
+ }
+ defer stdPackageListFile.Close()
+ scanner := bufio.NewScanner(stdPackageListFile)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" {
+ continue
+ }
+ fmt.Fprintf(buf, "packagefile %s=%s.a\n", line, filepath.Join(prefix, filepath.FromSlash(line)))
+ }
+ if err := scanner.Err(); err != nil {
+ return "", err
+ }
+ depsSeen := map[string]string{}
+ for _, arc := range archives {
+ if _, ok := depsSeen[arc.packagePath]; ok {
+ return "", fmt.Errorf("internal error: package %s provided multiple times. This should have been detected during analysis.", arc.packagePath)
+ }
+ depsSeen[arc.packagePath] = arc.label
+ fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.file)
+ }
+ f, err := ioutil.TempFile(dir, "importcfg")
+ if err != nil {
+ return "", err
+ }
+ filename := f.Name()
+ if _, err := io.Copy(f, buf); err != nil {
+ f.Close()
+ os.Remove(filename)
+ return "", err
+ }
+ if err := f.Close(); err != nil {
+ os.Remove(filename)
+ return "", err
+ }
+ return filename, nil
+}
+
+type depsError struct {
+ missing []missingDep
+ known []string
+}
+
+type missingDep struct {
+ filename, imp string
+}
+
+var _ error = depsError{}
+
+func (e depsError) Error() string {
+ buf := bytes.NewBuffer(nil)
+ fmt.Fprintf(buf, "missing strict dependencies:\n")
+ for _, dep := range e.missing {
+ fmt.Fprintf(buf, "\t%s: import of %q\n", dep.filename, dep.imp)
+ }
+ if len(e.known) == 0 {
+ fmt.Fprintln(buf, "No dependencies were provided.")
+ } else {
+ fmt.Fprintln(buf, "Known dependencies are:")
+ for _, imp := range e.known {
+ fmt.Fprintf(buf, "\t%s\n", imp)
+ }
+ }
+ fmt.Fprint(buf, "Check that imports in Go sources match importpath attributes in deps.")
+ return buf.String()
+}
+
+func isRelative(path string) bool {
+ return strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
+}
+
+type archiveMultiFlag []archive
+
+func (m *archiveMultiFlag) String() string {
+ if m == nil || len(*m) == 0 {
+ return ""
+ }
+ return fmt.Sprint(*m)
+}
+
+func (m *archiveMultiFlag) Set(v string) error {
+ parts := strings.Split(v, "=")
+ if len(parts) != 3 {
+ return fmt.Errorf("badly formed -arc flag: %s", v)
+ }
+ importPaths := strings.Split(parts[0], ":")
+ a := archive{
+ importPath: importPaths[0],
+ importPathAliases: importPaths[1:],
+ packagePath: parts[1],
+ file: abs(parts[2]),
+ }
+ *m = append(*m, a)
+ return nil
+}