xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/link.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1*9bb1b549SSpandan Das// Copyright 2017 The Bazel Authors. All rights reserved.
2*9bb1b549SSpandan Das//
3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License");
4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License.
5*9bb1b549SSpandan Das// You may obtain a copy of the License at
6*9bb1b549SSpandan Das//
7*9bb1b549SSpandan Das//    http://www.apache.org/licenses/LICENSE-2.0
8*9bb1b549SSpandan Das//
9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software
10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS,
11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and
13*9bb1b549SSpandan Das// limitations under the License.
14*9bb1b549SSpandan Das
15*9bb1b549SSpandan Das// link combines the results of a compile step using "go tool link". It is invoked by the
16*9bb1b549SSpandan Das// Go rules as an action.
17*9bb1b549SSpandan Daspackage main
18*9bb1b549SSpandan Das
19*9bb1b549SSpandan Dasimport (
20*9bb1b549SSpandan Das	"bufio"
21*9bb1b549SSpandan Das	"bytes"
22*9bb1b549SSpandan Das	"errors"
23*9bb1b549SSpandan Das	"flag"
24*9bb1b549SSpandan Das	"fmt"
25*9bb1b549SSpandan Das	"io/ioutil"
26*9bb1b549SSpandan Das	"os"
27*9bb1b549SSpandan Das	"path/filepath"
28*9bb1b549SSpandan Das	"regexp"
29*9bb1b549SSpandan Das	"runtime"
30*9bb1b549SSpandan Das	"strings"
31*9bb1b549SSpandan Das)
32*9bb1b549SSpandan Das
33*9bb1b549SSpandan Dasfunc link(args []string) error {
34*9bb1b549SSpandan Das	// Parse arguments.
35*9bb1b549SSpandan Das	args, _, err := expandParamsFiles(args)
36*9bb1b549SSpandan Das	if err != nil {
37*9bb1b549SSpandan Das		return err
38*9bb1b549SSpandan Das	}
39*9bb1b549SSpandan Das	builderArgs, toolArgs := splitArgs(args)
40*9bb1b549SSpandan Das	stamps := multiFlag{}
41*9bb1b549SSpandan Das	xdefs := multiFlag{}
42*9bb1b549SSpandan Das	archives := archiveMultiFlag{}
43*9bb1b549SSpandan Das	flags := flag.NewFlagSet("link", flag.ExitOnError)
44*9bb1b549SSpandan Das	goenv := envFlags(flags)
45*9bb1b549SSpandan Das	main := flags.String("main", "", "Path to the main archive.")
46*9bb1b549SSpandan Das	packagePath := flags.String("p", "", "Package path of the main archive.")
47*9bb1b549SSpandan Das	outFile := flags.String("o", "", "Path to output file.")
48*9bb1b549SSpandan Das	flags.Var(&archives, "arc", "Label, package path, and file name of a dependency, separated by '='")
49*9bb1b549SSpandan Das	packageList := flags.String("package_list", "", "The file containing the list of standard library packages")
50*9bb1b549SSpandan Das	buildmode := flags.String("buildmode", "", "Build mode used.")
51*9bb1b549SSpandan Das	flags.Var(&xdefs, "X", "A string variable to replace in the linked binary (repeated).")
52*9bb1b549SSpandan Das	flags.Var(&stamps, "stamp", "The name of a file with stamping values.")
53*9bb1b549SSpandan Das	conflictErrMsg := flags.String("conflict_err", "", "Error message about conflicts to report if there's a link error.")
54*9bb1b549SSpandan Das	if err := flags.Parse(builderArgs); err != nil {
55*9bb1b549SSpandan Das		return err
56*9bb1b549SSpandan Das	}
57*9bb1b549SSpandan Das	if err := goenv.checkFlags(); err != nil {
58*9bb1b549SSpandan Das		return err
59*9bb1b549SSpandan Das	}
60*9bb1b549SSpandan Das
61*9bb1b549SSpandan Das	if *conflictErrMsg != "" {
62*9bb1b549SSpandan Das		return errors.New(*conflictErrMsg)
63*9bb1b549SSpandan Das	}
64*9bb1b549SSpandan Das
65*9bb1b549SSpandan Das	// On Windows, take the absolute path of the output file and main file.
66*9bb1b549SSpandan Das	// This is needed on Windows because the relative path is frequently too long.
67*9bb1b549SSpandan Das	// os.Open on Windows converts absolute paths to some other path format with
68*9bb1b549SSpandan Das	// longer length limits. Absolute paths do not work on macOS for .dylib
69*9bb1b549SSpandan Das	// outputs because they get baked in as the "install path".
70*9bb1b549SSpandan Das	if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
71*9bb1b549SSpandan Das		*outFile = abs(*outFile)
72*9bb1b549SSpandan Das	}
73*9bb1b549SSpandan Das	*main = abs(*main)
74*9bb1b549SSpandan Das
75*9bb1b549SSpandan Das	// If we were given any stamp value files, read and parse them
76*9bb1b549SSpandan Das	stampMap := map[string]string{}
77*9bb1b549SSpandan Das	for _, stampfile := range stamps {
78*9bb1b549SSpandan Das		stampbuf, err := ioutil.ReadFile(stampfile)
79*9bb1b549SSpandan Das		if err != nil {
80*9bb1b549SSpandan Das			return fmt.Errorf("Failed reading stamp file %s: %v", stampfile, err)
81*9bb1b549SSpandan Das		}
82*9bb1b549SSpandan Das		scanner := bufio.NewScanner(bytes.NewReader(stampbuf))
83*9bb1b549SSpandan Das		for scanner.Scan() {
84*9bb1b549SSpandan Das			line := strings.SplitN(scanner.Text(), " ", 2)
85*9bb1b549SSpandan Das			switch len(line) {
86*9bb1b549SSpandan Das			case 0:
87*9bb1b549SSpandan Das				// Nothing to do here
88*9bb1b549SSpandan Das			case 1:
89*9bb1b549SSpandan Das				// Map to the empty string
90*9bb1b549SSpandan Das				stampMap[line[0]] = ""
91*9bb1b549SSpandan Das			case 2:
92*9bb1b549SSpandan Das				// Key and value
93*9bb1b549SSpandan Das				stampMap[line[0]] = line[1]
94*9bb1b549SSpandan Das			}
95*9bb1b549SSpandan Das		}
96*9bb1b549SSpandan Das	}
97*9bb1b549SSpandan Das
98*9bb1b549SSpandan Das	// Build an importcfg file.
99*9bb1b549SSpandan Das	importcfgName, err := buildImportcfgFileForLink(archives, *packageList, goenv.installSuffix, filepath.Dir(*outFile))
100*9bb1b549SSpandan Das	if err != nil {
101*9bb1b549SSpandan Das		return err
102*9bb1b549SSpandan Das	}
103*9bb1b549SSpandan Das	if !goenv.shouldPreserveWorkDir {
104*9bb1b549SSpandan Das		defer os.Remove(importcfgName)
105*9bb1b549SSpandan Das	}
106*9bb1b549SSpandan Das
107*9bb1b549SSpandan Das	// generate any additional link options we need
108*9bb1b549SSpandan Das	goargs := goenv.goTool("link")
109*9bb1b549SSpandan Das	goargs = append(goargs, "-importcfg", importcfgName)
110*9bb1b549SSpandan Das
111*9bb1b549SSpandan Das	parseXdef := func(xdef string) (pkg, name, value string, err error) {
112*9bb1b549SSpandan Das		eq := strings.IndexByte(xdef, '=')
113*9bb1b549SSpandan Das		if eq < 0 {
114*9bb1b549SSpandan Das			return "", "", "", fmt.Errorf("-X flag does not contain '=': %s", xdef)
115*9bb1b549SSpandan Das		}
116*9bb1b549SSpandan Das		dot := strings.LastIndexByte(xdef[:eq], '.')
117*9bb1b549SSpandan Das		if dot < 0 {
118*9bb1b549SSpandan Das			return "", "", "", fmt.Errorf("-X flag does not contain '.': %s", xdef)
119*9bb1b549SSpandan Das		}
120*9bb1b549SSpandan Das		pkg, name, value = xdef[:dot], xdef[dot+1:eq], xdef[eq+1:]
121*9bb1b549SSpandan Das		if pkg == *packagePath {
122*9bb1b549SSpandan Das			pkg = "main"
123*9bb1b549SSpandan Das		}
124*9bb1b549SSpandan Das		return pkg, name, value, nil
125*9bb1b549SSpandan Das	}
126*9bb1b549SSpandan Das	for _, xdef := range xdefs {
127*9bb1b549SSpandan Das		pkg, name, value, err := parseXdef(xdef)
128*9bb1b549SSpandan Das		if err != nil {
129*9bb1b549SSpandan Das			return err
130*9bb1b549SSpandan Das		}
131*9bb1b549SSpandan Das		var missingKey bool
132*9bb1b549SSpandan Das		value = regexp.MustCompile(`\{.+?\}`).ReplaceAllStringFunc(value, func(key string) string {
133*9bb1b549SSpandan Das			if value, ok := stampMap[key[1:len(key)-1]]; ok {
134*9bb1b549SSpandan Das				return value
135*9bb1b549SSpandan Das			}
136*9bb1b549SSpandan Das			missingKey = true
137*9bb1b549SSpandan Das			return key
138*9bb1b549SSpandan Das		})
139*9bb1b549SSpandan Das		if !missingKey {
140*9bb1b549SSpandan Das			goargs = append(goargs, "-X", fmt.Sprintf("%s.%s=%s", pkg, name, value))
141*9bb1b549SSpandan Das		}
142*9bb1b549SSpandan Das	}
143*9bb1b549SSpandan Das
144*9bb1b549SSpandan Das	if *buildmode != "" {
145*9bb1b549SSpandan Das		goargs = append(goargs, "-buildmode", *buildmode)
146*9bb1b549SSpandan Das	}
147*9bb1b549SSpandan Das	goargs = append(goargs, "-o", *outFile)
148*9bb1b549SSpandan Das
149*9bb1b549SSpandan Das	// add in the unprocess pass through options
150*9bb1b549SSpandan Das	goargs = append(goargs, toolArgs...)
151*9bb1b549SSpandan Das	goargs = append(goargs, *main)
152*9bb1b549SSpandan Das	if err := goenv.runCommand(goargs); err != nil {
153*9bb1b549SSpandan Das		return err
154*9bb1b549SSpandan Das	}
155*9bb1b549SSpandan Das
156*9bb1b549SSpandan Das	if *buildmode == "c-archive" {
157*9bb1b549SSpandan Das		if err := stripArMetadata(*outFile); err != nil {
158*9bb1b549SSpandan Das			return fmt.Errorf("error stripping archive metadata: %v", err)
159*9bb1b549SSpandan Das		}
160*9bb1b549SSpandan Das	}
161*9bb1b549SSpandan Das
162*9bb1b549SSpandan Das	return nil
163*9bb1b549SSpandan Das}
164