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. 17package main 18 19import ( 20 "bufio" 21 "bytes" 22 "errors" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "regexp" 29 "runtime" 30 "strings" 31) 32 33func link(args []string) error { 34 // Parse arguments. 35 args, _, err := expandParamsFiles(args) 36 if err != nil { 37 return err 38 } 39 builderArgs, toolArgs := splitArgs(args) 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(&stamps, "stamp", "The name of a file with stamping values.") 53 conflictErrMsg := flags.String("conflict_err", "", "Error message about conflicts to report if there's a link error.") 54 if err := flags.Parse(builderArgs); err != nil { 55 return err 56 } 57 if err := goenv.checkFlags(); err != nil { 58 return err 59 } 60 61 if *conflictErrMsg != "" { 62 return errors.New(*conflictErrMsg) 63 } 64 65 // On Windows, take the absolute path of the output file and main file. 66 // This is needed on Windows because the relative path is frequently too long. 67 // os.Open on Windows converts absolute paths to some other path format with 68 // longer length limits. Absolute paths do not work on macOS for .dylib 69 // outputs because they get baked in as the "install path". 70 if runtime.GOOS != "darwin" && runtime.GOOS != "ios" { 71 *outFile = abs(*outFile) 72 } 73 *main = abs(*main) 74 75 // If we were given any stamp value files, read and parse them 76 stampMap := map[string]string{} 77 for _, stampfile := range stamps { 78 stampbuf, err := ioutil.ReadFile(stampfile) 79 if err != nil { 80 return fmt.Errorf("Failed reading stamp file %s: %v", stampfile, err) 81 } 82 scanner := bufio.NewScanner(bytes.NewReader(stampbuf)) 83 for scanner.Scan() { 84 line := strings.SplitN(scanner.Text(), " ", 2) 85 switch len(line) { 86 case 0: 87 // Nothing to do here 88 case 1: 89 // Map to the empty string 90 stampMap[line[0]] = "" 91 case 2: 92 // Key and value 93 stampMap[line[0]] = line[1] 94 } 95 } 96 } 97 98 // Build an importcfg file. 99 importcfgName, err := buildImportcfgFileForLink(archives, *packageList, goenv.installSuffix, filepath.Dir(*outFile)) 100 if err != nil { 101 return err 102 } 103 if !goenv.shouldPreserveWorkDir { 104 defer os.Remove(importcfgName) 105 } 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 flag does not contain '=': %s", xdef) 115 } 116 dot := strings.LastIndexByte(xdef[:eq], '.') 117 if dot < 0 { 118 return "", "", "", fmt.Errorf("-X 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 xdefs { 127 pkg, name, value, err := parseXdef(xdef) 128 if err != nil { 129 return err 130 } 131 var missingKey bool 132 value = regexp.MustCompile(`\{.+?\}`).ReplaceAllStringFunc(value, func(key string) string { 133 if value, ok := stampMap[key[1:len(key)-1]]; ok { 134 return value 135 } 136 missingKey = true 137 return key 138 }) 139 if !missingKey { 140 goargs = append(goargs, "-X", fmt.Sprintf("%s.%s=%s", pkg, name, value)) 141 } 142 } 143 144 if *buildmode != "" { 145 goargs = append(goargs, "-buildmode", *buildmode) 146 } 147 goargs = append(goargs, "-o", *outFile) 148 149 // add in the unprocess pass through options 150 goargs = append(goargs, toolArgs...) 151 goargs = append(goargs, *main) 152 if err := goenv.runCommand(goargs); err != nil { 153 return err 154 } 155 156 if *buildmode == "c-archive" { 157 if err := stripArMetadata(*outFile); err != nil { 158 return fmt.Errorf("error stripping archive metadata: %v", err) 159 } 160 } 161 162 return nil 163} 164