xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/link.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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