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

     1  // Copyright 2017 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  // link combines the results of a compile step using "go tool link". It is invoked by the
    16  // Go rules as an action.
    17  package main
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"runtime"
    29  	"strings"
    30  )
    31  
    32  func link(args []string) error {
    33  	// Parse arguments.
    34  	args, err := expandParamsFiles(args)
    35  	if err != nil {
    36  		return err
    37  	}
    38  	builderArgs, toolArgs := splitArgs(args)
    39  	xstamps := multiFlag{}
    40  	stamps := multiFlag{}
    41  	xdefs := multiFlag{}
    42  	archives := archiveMultiFlag{}
    43  	flags := flag.NewFlagSet("link", flag.ExitOnError)
    44  	goenv := envFlags(flags)
    45  	main := flags.String("main", "", "Path to the main archive.")
    46  	packagePath := flags.String("p", "", "Package path of the main archive.")
    47  	outFile := flags.String("o", "", "Path to output file.")
    48  	flags.Var(&archives, "arc", "Label, package path, and file name of a dependency, separated by '='")
    49  	packageList := flags.String("package_list", "", "The file containing the list of standard library packages")
    50  	buildmode := flags.String("buildmode", "", "Build mode used.")
    51  	flags.Var(&xdefs, "X", "A string variable to replace in the linked binary (repeated).")
    52  	flags.Var(&xstamps, "Xstamp", "Like -X but the values are looked up in the -stamp file.")
    53  	flags.Var(&stamps, "stamp", "The name of a file with stamping values.")
    54  	packageConflictIsError := flags.Bool("package_conflict_is_error", false, "Whether importpath conflicts are errors.")
    55  	conflictErrMsg := flags.String("conflict_err", "", "Error message about conflicts to report if there's a link error.")
    56  	if err := flags.Parse(builderArgs); err != nil {
    57  		return err
    58  	}
    59  	if err := goenv.checkFlags(); err != nil {
    60  		return err
    61  	}
    62  
    63  	if *packageConflictIsError && *conflictErrMsg != "" {
    64  		return errors.New(*conflictErrMsg)
    65  	}
    66  
    67  	// On Windows, take the absolute path of the output file and main file.
    68  	// This is needed on Windows because the relative path is frequently too long.
    69  	// os.Open on Windows converts absolute paths to some other path format with
    70  	// longer length limits. Absolute paths do not work on macOS for .dylib
    71  	// outputs because they get baked in as the "install path".
    72  	if runtime.GOOS != "darwin" {
    73  		*outFile = abs(*outFile)
    74  	}
    75  	*main = abs(*main)
    76  
    77  	// If we were given any stamp value files, read and parse them
    78  	stampMap := map[string]string{}
    79  	for _, stampfile := range stamps {
    80  		stampbuf, err := ioutil.ReadFile(stampfile)
    81  		if err != nil {
    82  			return fmt.Errorf("Failed reading stamp file %s: %v", stampfile, err)
    83  		}
    84  		scanner := bufio.NewScanner(bytes.NewReader(stampbuf))
    85  		for scanner.Scan() {
    86  			line := strings.SplitN(scanner.Text(), " ", 2)
    87  			switch len(line) {
    88  			case 0:
    89  				// Nothing to do here
    90  			case 1:
    91  				// Map to the empty string
    92  				stampMap[line[0]] = ""
    93  			case 2:
    94  				// Key and value
    95  				stampMap[line[0]] = line[1]
    96  			}
    97  		}
    98  	}
    99  
   100  	// Build an importcfg file.
   101  	importcfgName, err := buildImportcfgFileForLink(archives, *packageList, goenv.installSuffix, filepath.Dir(*outFile))
   102  	if err != nil {
   103  		return err
   104  	}
   105  	defer os.Remove(importcfgName)
   106  
   107  	// generate any additional link options we need
   108  	goargs := goenv.goTool("link")
   109  	goargs = append(goargs, "-importcfg", importcfgName)
   110  
   111  	parseXdef := func(xdef string) (pkg, name, value string, err error) {
   112  		eq := strings.IndexByte(xdef, '=')
   113  		if eq < 0 {
   114  			return "", "", "", fmt.Errorf("-X or -Xstamp flag does not contain '=': %s", xdef)
   115  		}
   116  		dot := strings.LastIndexByte(xdef[:eq], '.')
   117  		if dot < 0 {
   118  			return "", "", "", fmt.Errorf("-X or -Xstamp flag does not contain '.': %s", xdef)
   119  		}
   120  		pkg, name, value = xdef[:dot], xdef[dot+1:eq], xdef[eq+1:]
   121  		if pkg == *packagePath {
   122  			pkg = "main"
   123  		}
   124  		return pkg, name, value, nil
   125  	}
   126  	for _, xdef := range xstamps {
   127  		pkg, name, key, err := parseXdef(xdef)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		if value, ok := stampMap[key]; ok {
   132  			goargs = append(goargs, "-X", fmt.Sprintf("%s.%s=%s", pkg, name, value))
   133  		}
   134  	}
   135  	for _, xdef := range xdefs {
   136  		pkg, name, value, err := parseXdef(xdef)
   137  		if err != nil {
   138  			return err
   139  		}
   140  		goargs = append(goargs, "-X", fmt.Sprintf("%s.%s=%s", pkg, name, value))
   141  	}
   142  
   143  	if *buildmode != "" {
   144  		goargs = append(goargs, "-buildmode", *buildmode)
   145  	}
   146  	goargs = append(goargs, "-o", *outFile)
   147  
   148  	// add in the unprocess pass through options
   149  	goargs = append(goargs, toolArgs...)
   150  	goargs = append(goargs, *main)
   151  	if err := goenv.runCommand(goargs); err != nil {
   152  		if *conflictErrMsg != "" {
   153  			// TODO(#1374): this should always be reported above.
   154  			err = fmt.Errorf("%v\n%s", err, *conflictErrMsg)
   155  		}
   156  		return err
   157  	}
   158  	if *conflictErrMsg != "" {
   159  		// TODO(#1374): this should always be reported above.
   160  		fmt.Fprintf(os.Stderr, "%s\nThis will be an error in the future.\n", *conflictErrMsg)
   161  	}
   162  
   163  	if *buildmode == "c-archive" {
   164  		if err := stripArMetadata(*outFile); err != nil {
   165  			return fmt.Errorf("error stripping archive metadata: %v", err)
   166  		}
   167  	}
   168  
   169  	return nil
   170  }