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