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