aboutsummaryrefslogtreecommitdiff
path: root/go/tools/builders/link.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/builders/link.go')
-rw-r--r--go/tools/builders/link.go163
1 files changed, 163 insertions, 0 deletions
diff --git a/go/tools/builders/link.go b/go/tools/builders/link.go
new file mode 100644
index 00000000..723bb193
--- /dev/null
+++ b/go/tools/builders/link.go
@@ -0,0 +1,163 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// link combines the results of a compile step using "go tool link". It is invoked by the
+// Go rules as an action.
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+func link(args []string) error {
+ // Parse arguments.
+ args, _, err := expandParamsFiles(args)
+ if err != nil {
+ return err
+ }
+ builderArgs, toolArgs := splitArgs(args)
+ stamps := multiFlag{}
+ xdefs := multiFlag{}
+ archives := archiveMultiFlag{}
+ flags := flag.NewFlagSet("link", flag.ExitOnError)
+ goenv := envFlags(flags)
+ main := flags.String("main", "", "Path to the main archive.")
+ packagePath := flags.String("p", "", "Package path of the main archive.")
+ outFile := flags.String("o", "", "Path to output file.")
+ flags.Var(&archives, "arc", "Label, package path, and file name of a dependency, separated by '='")
+ packageList := flags.String("package_list", "", "The file containing the list of standard library packages")
+ buildmode := flags.String("buildmode", "", "Build mode used.")
+ flags.Var(&xdefs, "X", "A string variable to replace in the linked binary (repeated).")
+ flags.Var(&stamps, "stamp", "The name of a file with stamping values.")
+ conflictErrMsg := flags.String("conflict_err", "", "Error message about conflicts to report if there's a link error.")
+ if err := flags.Parse(builderArgs); err != nil {
+ return err
+ }
+ if err := goenv.checkFlags(); err != nil {
+ return err
+ }
+
+ if *conflictErrMsg != "" {
+ return errors.New(*conflictErrMsg)
+ }
+
+ // On Windows, take the absolute path of the output file and main file.
+ // This is needed on Windows because the relative path is frequently too long.
+ // os.Open on Windows converts absolute paths to some other path format with
+ // longer length limits. Absolute paths do not work on macOS for .dylib
+ // outputs because they get baked in as the "install path".
+ if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
+ *outFile = abs(*outFile)
+ }
+ *main = abs(*main)
+
+ // If we were given any stamp value files, read and parse them
+ stampMap := map[string]string{}
+ for _, stampfile := range stamps {
+ stampbuf, err := ioutil.ReadFile(stampfile)
+ if err != nil {
+ return fmt.Errorf("Failed reading stamp file %s: %v", stampfile, err)
+ }
+ scanner := bufio.NewScanner(bytes.NewReader(stampbuf))
+ for scanner.Scan() {
+ line := strings.SplitN(scanner.Text(), " ", 2)
+ switch len(line) {
+ case 0:
+ // Nothing to do here
+ case 1:
+ // Map to the empty string
+ stampMap[line[0]] = ""
+ case 2:
+ // Key and value
+ stampMap[line[0]] = line[1]
+ }
+ }
+ }
+
+ // Build an importcfg file.
+ importcfgName, err := buildImportcfgFileForLink(archives, *packageList, goenv.installSuffix, filepath.Dir(*outFile))
+ if err != nil {
+ return err
+ }
+ if !goenv.shouldPreserveWorkDir {
+ defer os.Remove(importcfgName)
+ }
+
+ // generate any additional link options we need
+ goargs := goenv.goTool("link")
+ goargs = append(goargs, "-importcfg", importcfgName)
+
+ parseXdef := func(xdef string) (pkg, name, value string, err error) {
+ eq := strings.IndexByte(xdef, '=')
+ if eq < 0 {
+ return "", "", "", fmt.Errorf("-X flag does not contain '=': %s", xdef)
+ }
+ dot := strings.LastIndexByte(xdef[:eq], '.')
+ if dot < 0 {
+ return "", "", "", fmt.Errorf("-X flag does not contain '.': %s", xdef)
+ }
+ pkg, name, value = xdef[:dot], xdef[dot+1:eq], xdef[eq+1:]
+ if pkg == *packagePath {
+ pkg = "main"
+ }
+ return pkg, name, value, nil
+ }
+ for _, xdef := range xdefs {
+ pkg, name, value, err := parseXdef(xdef)
+ if err != nil {
+ return err
+ }
+ var missingKey bool
+ value = regexp.MustCompile(`\{.+?\}`).ReplaceAllStringFunc(value, func(key string) string {
+ if value, ok := stampMap[key[1:len(key)-1]]; ok {
+ return value
+ }
+ missingKey = true
+ return key
+ })
+ if !missingKey {
+ goargs = append(goargs, "-X", fmt.Sprintf("%s.%s=%s", pkg, name, value))
+ }
+ }
+
+ if *buildmode != "" {
+ goargs = append(goargs, "-buildmode", *buildmode)
+ }
+ goargs = append(goargs, "-o", *outFile)
+
+ // add in the unprocess pass through options
+ goargs = append(goargs, toolArgs...)
+ goargs = append(goargs, *main)
+ if err := goenv.runCommand(goargs); err != nil {
+ return err
+ }
+
+ if *buildmode == "c-archive" {
+ if err := stripArMetadata(*outFile); err != nil {
+ return fmt.Errorf("error stripping archive metadata: %v", err)
+ }
+ }
+
+ return nil
+}