blob: 9fe55b420f122bfa9d556cbadab096eb51fb1784 [file] [log] [blame]
// 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
}