github.com/ajguerrer/rules_go@v0.20.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  	"log"
    25  	"os"
    26  	"path/filepath"
    27  	"sort"
    28  	"strings"
    29  )
    30  
    31  type archive struct {
    32  	label, importPath, packagePath, aFile, xFile string
    33  	importPathAliases                            []string
    34  }
    35  
    36  // checkImports verifies that each import in files refers to a
    37  // direct dependendency in archives or to a standard library package
    38  // listed in the file at stdPackageListPath. checkImports returns
    39  // a map from source import paths to elements of archives or to nil
    40  // for standard library packages.
    41  func checkImports(files []fileInfo, archives []archive, stdPackageListPath string) (map[string]*archive, error) {
    42  	// Read the standard package list.
    43  	packagesTxt, err := ioutil.ReadFile(stdPackageListPath)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	stdPkgs := make(map[string]bool)
    48  	for len(packagesTxt) > 0 {
    49  		n := bytes.IndexByte(packagesTxt, '\n')
    50  		var line string
    51  		if n < 0 {
    52  			line = string(packagesTxt)
    53  			packagesTxt = nil
    54  		} else {
    55  			line = string(packagesTxt[:n])
    56  			packagesTxt = packagesTxt[n+1:]
    57  		}
    58  		line = strings.TrimSpace(line)
    59  		if line == "" {
    60  			continue
    61  		}
    62  		stdPkgs[line] = true
    63  	}
    64  
    65  	// Index the archives.
    66  	importToArchive := make(map[string]*archive)
    67  	importAliasToArchive := make(map[string]*archive)
    68  	for i := range archives {
    69  		arc := &archives[i]
    70  		importToArchive[arc.importPath] = arc
    71  		for _, imp := range arc.importPathAliases {
    72  			importAliasToArchive[imp] = arc
    73  		}
    74  	}
    75  
    76  	// Build the import map.
    77  	imports := make(map[string]*archive)
    78  	var derr depsError
    79  	for _, f := range files {
    80  		for _, path := range f.imports {
    81  			if _, ok := imports[path]; ok || path == "C" || isRelative(path) {
    82  				// TODO(#1645): Support local (relative) import paths. We don't emit
    83  				// errors for them here, but they will probably break something else.
    84  				continue
    85  			}
    86  			if stdPkgs[path] {
    87  				imports[path] = nil
    88  			} else if arc := importToArchive[path]; arc != nil {
    89  				imports[path] = arc
    90  			} else if arc := importAliasToArchive[path]; arc != nil {
    91  				imports[path] = arc
    92  			} else {
    93  				derr.missing = append(derr.missing, missingDep{f.filename, path})
    94  			}
    95  		}
    96  	}
    97  	if len(derr.missing) > 0 {
    98  		return nil, derr
    99  	}
   100  	return imports, nil
   101  }
   102  
   103  // buildImportcfgFileForCompile writes an importcfg file to be consumed by the
   104  // compiler. The file is constructed from direct dependencies and std imports.
   105  // The caller is responsible for deleting the importcfg file.
   106  func buildImportcfgFileForCompile(imports map[string]*archive, installSuffix, dir string) (string, error) {
   107  	buf := &bytes.Buffer{}
   108  	goroot, ok := os.LookupEnv("GOROOT")
   109  	if !ok {
   110  		return "", errors.New("GOROOT not set")
   111  	}
   112  	goroot = abs(goroot)
   113  
   114  	sortedImports := make([]string, 0, len(imports))
   115  	for imp := range imports {
   116  		sortedImports = append(sortedImports, imp)
   117  	}
   118  	sort.Strings(sortedImports)
   119  
   120  	for _, imp := range sortedImports {
   121  		if arc := imports[imp]; arc == nil {
   122  			// std package
   123  			path := filepath.Join(goroot, "pkg", installSuffix, filepath.FromSlash(imp))
   124  			fmt.Fprintf(buf, "packagefile %s=%s.a\n", imp, path)
   125  		} else {
   126  			if imp != arc.packagePath {
   127  				fmt.Fprintf(buf, "importmap %s=%s\n", imp, arc.packagePath)
   128  			}
   129  			fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.aFile)
   130  		}
   131  	}
   132  
   133  	f, err := ioutil.TempFile(dir, "importcfg")
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  	filename := f.Name()
   138  	if _, err := io.Copy(f, buf); err != nil {
   139  		f.Close()
   140  		os.Remove(filename)
   141  		return "", err
   142  	}
   143  	if err := f.Close(); err != nil {
   144  		os.Remove(filename)
   145  		return "", err
   146  	}
   147  	return filename, nil
   148  }
   149  
   150  func buildImportcfgFileForLink(archives []archive, stdPackageListPath, installSuffix, dir string) (string, error) {
   151  	buf := &bytes.Buffer{}
   152  	goroot, ok := os.LookupEnv("GOROOT")
   153  	if !ok {
   154  		return "", errors.New("GOROOT not set")
   155  	}
   156  	prefix := abs(filepath.Join(goroot, "pkg", installSuffix))
   157  	stdPackageListFile, err := os.Open(stdPackageListPath)
   158  	if err != nil {
   159  		return "", err
   160  	}
   161  	defer stdPackageListFile.Close()
   162  	scanner := bufio.NewScanner(stdPackageListFile)
   163  	for scanner.Scan() {
   164  		line := strings.TrimSpace(scanner.Text())
   165  		if line == "" {
   166  			continue
   167  		}
   168  		fmt.Fprintf(buf, "packagefile %s=%s.a\n", line, filepath.Join(prefix, filepath.FromSlash(line)))
   169  	}
   170  	if err := scanner.Err(); err != nil {
   171  		return "", err
   172  	}
   173  	depsSeen := map[string]string{}
   174  	for _, arc := range archives {
   175  		if conflictLabel, ok := depsSeen[arc.packagePath]; ok {
   176  			// TODO(#1327): link.bzl should report this as a failure after 0.11.0.
   177  			// At this point, we'll prepare an importcfg file and remove logic here.
   178  			log.Printf(`warning: package %q is provided by more than one rule:
   179      %s
   180      %s
   181  Set "importmap" to different paths in each library.
   182  This will be an error in the future.`, arc.packagePath, arc.label, conflictLabel)
   183  			continue
   184  		}
   185  		depsSeen[arc.packagePath] = arc.label
   186  		fmt.Fprintf(buf, "packagefile %s=%s\n", arc.packagePath, arc.aFile)
   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  
   205  type depsError struct {
   206  	missing []missingDep
   207  	known   []string
   208  }
   209  
   210  type missingDep struct {
   211  	filename, imp string
   212  }
   213  
   214  var _ error = depsError{}
   215  
   216  func (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  
   234  func isRelative(path string) bool {
   235  	return strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
   236  }
   237  
   238  // TODO(jayconrod): consolidate compile and link archive flags.
   239  
   240  type compileArchiveMultiFlag []archive
   241  
   242  func (m *compileArchiveMultiFlag) String() string {
   243  	if m == nil || len(*m) == 0 {
   244  		return ""
   245  	}
   246  	return fmt.Sprint(*m)
   247  }
   248  
   249  func (m *compileArchiveMultiFlag) Set(v string) error {
   250  	parts := strings.Split(v, "=")
   251  	if len(parts) != 4 {
   252  		return fmt.Errorf("badly formed -arc flag: %s", v)
   253  	}
   254  	importPaths := strings.Split(parts[0], ":")
   255  	a := archive{
   256  		importPath:        importPaths[0],
   257  		importPathAliases: importPaths[1:],
   258  		packagePath:       parts[1],
   259  		aFile:             abs(parts[2]),
   260  	}
   261  	if parts[3] != "" {
   262  		a.xFile = abs(parts[3])
   263  	}
   264  	*m = append(*m, a)
   265  	return nil
   266  }
   267  
   268  type linkArchiveMultiFlag []archive
   269  
   270  func (m *linkArchiveMultiFlag) String() string {
   271  	if m == nil || len(*m) == 0 {
   272  		return ""
   273  	}
   274  	return fmt.Sprint(m)
   275  }
   276  
   277  func (m *linkArchiveMultiFlag) Set(v string) error {
   278  	parts := strings.Split(v, "=")
   279  	if len(parts) != 3 {
   280  		return fmt.Errorf("badly formed -arc flag: %s", v)
   281  	}
   282  	*m = append(*m, archive{
   283  		label:       parts[0],
   284  		packagePath: parts[1],
   285  		aFile:       abs(parts[2]),
   286  	})
   287  	return nil
   288  }