github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/importcfg.go (about)

     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  
    15  package main
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"sort"
    27  	"strings"
    28  )
    29  
    30  type 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 dependendency 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.
    40  func checkImports(files []fileInfo, archives []archive, stdPackageListPath 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  
    75  	// Build the import map.
    76  	imports := make(map[string]*archive)
    77  	var derr depsError
    78  	for _, f := range files {
    79  		for _, path := range f.imports {
    80  			if _, ok := imports[path]; ok || path == "C" || isRelative(path) {
    81  				// TODO(#1645): Support local (relative) import paths. We don't emit
    82  				// errors for them here, but they will probably break something else.
    83  				continue
    84  			}
    85  			if stdPkgs[path] {
    86  				imports[path] = nil
    87  			} else if arc := importToArchive[path]; arc != nil {
    88  				imports[path] = arc
    89  			} else if arc := importAliasToArchive[path]; arc != nil {
    90  				imports[path] = arc
    91  			} else {
    92  				derr.missing = append(derr.missing, missingDep{f.filename, path})
    93  			}
    94  		}
    95  	}
    96  	if len(derr.missing) > 0 {
    97  		return nil, derr
    98  	}
    99  	return imports, nil
   100  }
   101  
   102  // buildImportcfgFileForCompile writes an importcfg file to be consumed by the
   103  // compiler. The file is constructed from direct dependencies and std imports.
   104  // The caller is responsible for deleting the importcfg file.
   105  func buildImportcfgFileForCompile(imports map[string]*archive, installSuffix, dir string) (string, error) {
   106  	buf := &bytes.Buffer{}
   107  	goroot, ok := os.LookupEnv("GOROOT")
   108  	if !ok {
   109  		return "", errors.New("GOROOT not set")
   110  	}
   111  	goroot = abs(goroot)
   112  
   113  	sortedImports := make([]string, 0, len(imports))
   114  	for imp := range imports {
   115  		sortedImports = append(sortedImports, imp)
   116  	}
   117  	sort.Strings(sortedImports)
   118  
   119  	for _, imp := range sortedImports {
   120  		if arc := imports[imp]; arc == nil {
   121  			// std package
   122  			path := filepath.Join(goroot, "pkg", installSuffix, filepath.FromSlash(imp))
   123  			fmt.Fprintf(buf, "packagefile %s=%s.a\n", imp, path)
   124  		} else {
   125  			if imp != arc.packagePath {
   126  				fmt.Fprintf(buf, "importmap %s=%s\n", imp, arc.packagePath)
   127  			}
   128  			fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.file)
   129  		}
   130  	}
   131  
   132  	f, err := ioutil.TempFile(dir, "importcfg")
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  	filename := f.Name()
   137  	if _, err := io.Copy(f, buf); err != nil {
   138  		f.Close()
   139  		os.Remove(filename)
   140  		return "", err
   141  	}
   142  	if err := f.Close(); err != nil {
   143  		os.Remove(filename)
   144  		return "", err
   145  	}
   146  	return filename, nil
   147  }
   148  
   149  func buildImportcfgFileForLink(archives []archive, stdPackageListPath, installSuffix, dir string) (string, error) {
   150  	buf := &bytes.Buffer{}
   151  	goroot, ok := os.LookupEnv("GOROOT")
   152  	if !ok {
   153  		return "", errors.New("GOROOT not set")
   154  	}
   155  	prefix := abs(filepath.Join(goroot, "pkg", installSuffix))
   156  	stdPackageListFile, err := os.Open(stdPackageListPath)
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  	defer stdPackageListFile.Close()
   161  	scanner := bufio.NewScanner(stdPackageListFile)
   162  	for scanner.Scan() {
   163  		line := strings.TrimSpace(scanner.Text())
   164  		if line == "" {
   165  			continue
   166  		}
   167  		fmt.Fprintf(buf, "packagefile %s=%s.a\n", line, filepath.Join(prefix, filepath.FromSlash(line)))
   168  	}
   169  	if err := scanner.Err(); err != nil {
   170  		return "", err
   171  	}
   172  	depsSeen := map[string]string{}
   173  	for _, arc := range archives {
   174  		if _, ok := depsSeen[arc.packagePath]; ok {
   175  			// If this is detected during analysis, the -conflict_err flag will be set.
   176  			// We'll report that error if -package_conflict_is_error is set or if
   177  			// the link command fails.
   178  			// TODO(#1374): This should always be an error. Panic.
   179  			continue
   180  		}
   181  		depsSeen[arc.packagePath] = arc.label
   182  		fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.file)
   183  	}
   184  	f, err := ioutil.TempFile(dir, "importcfg")
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  	filename := f.Name()
   189  	if _, err := io.Copy(f, buf); err != nil {
   190  		f.Close()
   191  		os.Remove(filename)
   192  		return "", err
   193  	}
   194  	if err := f.Close(); err != nil {
   195  		os.Remove(filename)
   196  		return "", err
   197  	}
   198  	return filename, nil
   199  }
   200  
   201  type depsError struct {
   202  	missing []missingDep
   203  	known   []string
   204  }
   205  
   206  type missingDep struct {
   207  	filename, imp string
   208  }
   209  
   210  var _ error = depsError{}
   211  
   212  func (e depsError) Error() string {
   213  	buf := bytes.NewBuffer(nil)
   214  	fmt.Fprintf(buf, "missing strict dependencies:\n")
   215  	for _, dep := range e.missing {
   216  		fmt.Fprintf(buf, "\t%s: import of %q\n", dep.filename, dep.imp)
   217  	}
   218  	if len(e.known) == 0 {
   219  		fmt.Fprintln(buf, "No dependencies were provided.")
   220  	} else {
   221  		fmt.Fprintln(buf, "Known dependencies are:")
   222  		for _, imp := range e.known {
   223  			fmt.Fprintf(buf, "\t%s\n", imp)
   224  		}
   225  	}
   226  	fmt.Fprint(buf, "Check that imports in Go sources match importpath attributes in deps.")
   227  	return buf.String()
   228  }
   229  
   230  func isRelative(path string) bool {
   231  	return strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
   232  }
   233  
   234  type archiveMultiFlag []archive
   235  
   236  func (m *archiveMultiFlag) String() string {
   237  	if m == nil || len(*m) == 0 {
   238  		return ""
   239  	}
   240  	return fmt.Sprint(*m)
   241  }
   242  
   243  func (m *archiveMultiFlag) Set(v string) error {
   244  	parts := strings.Split(v, "=")
   245  	if len(parts) != 3 {
   246  		return fmt.Errorf("badly formed -arc flag: %s", v)
   247  	}
   248  	importPaths := strings.Split(parts[0], ":")
   249  	a := archive{
   250  		importPath:        importPaths[0],
   251  		importPathAliases: importPaths[1:],
   252  		packagePath:       parts[1],
   253  		file:              abs(parts[2]),
   254  	}
   255  	*m = append(*m, a)
   256  	return nil
   257  }