diff options
Diffstat (limited to 'go/tools/builders/link.go')
-rw-r--r-- | go/tools/builders/link.go | 163 |
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 +} |