diff options
Diffstat (limited to 'go/tools/builders/importcfg.go')
-rw-r--r-- | go/tools/builders/importcfg.go | 261 |
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 +} |