| // 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 |
| } |
| 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 |
| } |