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