xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/cgo2.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1// Copyright 2019 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// cgo2.go provides new cgo functionality for use by the GoCompilePkg action.
16// We can't use the functionality in cgo.go, since it relies too heavily
17// on logic in cgo.bzl. Ideally, we'd be able to replace cgo.go with this
18// file eventually, but not until Bazel gives us enough toolchain information
19// to compile ObjC files.
20
21package main
22
23import (
24	"bytes"
25	"fmt"
26	"io/ioutil"
27	"os"
28	"path/filepath"
29	"strings"
30)
31
32// cgo2 processes a set of mixed source files with cgo.
33func cgo2(goenv *env, goSrcs, cgoSrcs, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs []string, packagePath, packageName string, cc string, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags []string, cgoExportHPath string) (srcDir string, allGoSrcs, cObjs []string, err error) {
34	// Report an error if the C/C++ toolchain wasn't configured.
35	if cc == "" {
36		err := cgoError(cgoSrcs[:])
37		err = append(err, cSrcs...)
38		err = append(err, cxxSrcs...)
39		err = append(err, objcSrcs...)
40		err = append(err, objcxxSrcs...)
41		err = append(err, sSrcs...)
42		return "", nil, nil, err
43	}
44
45	// If we only have C/C++ sources without cgo, just compile and pack them
46	// without generating code. The Go command forbids this, but we've
47	// historically allowed it.
48	// TODO(jayconrod): this doesn't write CGO_LDFLAGS into the archive. We
49	// might miss dependencies like -lstdc++ if they aren't referenced in
50	// some other way.
51	if len(cgoSrcs) == 0 {
52		cObjs, err = compileCSources(goenv, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags)
53		return ".", nil, cObjs, err
54	}
55
56	workDir, cleanup, err := goenv.workDir()
57	if err != nil {
58		return "", nil, nil, err
59	}
60	defer cleanup()
61
62	// cgo2 will gather sources into a single temporary directory, since nogo
63	// scanners might want to include or exclude these sources we need to ensure
64	// that a fragment of the path is stable and human friendly enough to be
65	// referenced in nogo configuration.
66	workDir = filepath.Join(workDir, "cgo", packagePath)
67	if err := os.MkdirAll(workDir, 0700); err != nil {
68		return "", nil, nil, err
69	}
70
71	// Filter out -lstdc++ and -lc++ from ldflags if we don't have C++ sources,
72	// and set CGO_LDFLAGS. These flags get written as special comments into cgo
73	// generated sources. The compiler encodes those flags in the compiled .a
74	// file, and the linker passes them on to the external linker.
75	haveCxx := len(cxxSrcs)+len(objcxxSrcs) > 0
76	if !haveCxx {
77		for _, f := range ldFlags {
78			if strings.HasSuffix(f, ".a") {
79				// These flags come from cdeps options. Assume C++.
80				haveCxx = true
81				break
82			}
83		}
84	}
85	var combinedLdFlags []string
86	if haveCxx {
87		combinedLdFlags = append(combinedLdFlags, ldFlags...)
88	} else {
89		for _, f := range ldFlags {
90			if f != "-lc++" && f != "-lstdc++" {
91				combinedLdFlags = append(combinedLdFlags, f)
92			}
93		}
94	}
95	combinedLdFlags = append(combinedLdFlags, defaultLdFlags()...)
96	os.Setenv("CGO_LDFLAGS", strings.Join(combinedLdFlags, " "))
97
98	// If cgo sources are in different directories, gather them into a temporary
99	// directory so we can use -srcdir.
100	srcDir = filepath.Dir(cgoSrcs[0])
101	srcsInSingleDir := true
102	for _, src := range cgoSrcs[1:] {
103		if filepath.Dir(src) != srcDir {
104			srcsInSingleDir = false
105			break
106		}
107	}
108
109	if srcsInSingleDir {
110		for i := range cgoSrcs {
111			cgoSrcs[i] = filepath.Base(cgoSrcs[i])
112		}
113	} else {
114		srcDir = filepath.Join(workDir, "cgosrcs")
115		if err := os.Mkdir(srcDir, 0777); err != nil {
116			return "", nil, nil, err
117		}
118		copiedSrcs, err := gatherSrcs(srcDir, cgoSrcs)
119		if err != nil {
120			return "", nil, nil, err
121		}
122		cgoSrcs = copiedSrcs
123	}
124
125	// Generate Go and C code.
126	hdrDirs := map[string]bool{}
127	var hdrIncludes []string
128	for _, hdr := range hSrcs {
129		hdrDir := filepath.Dir(hdr)
130		if !hdrDirs[hdrDir] {
131			hdrDirs[hdrDir] = true
132			hdrIncludes = append(hdrIncludes, "-iquote", hdrDir)
133		}
134	}
135	hdrIncludes = append(hdrIncludes, "-iquote", workDir) // for _cgo_export.h
136
137	execRoot, err := bazelExecRoot()
138	if err != nil {
139		return "", nil, nil, err
140	}
141	// Trim the execroot from the //line comments emitted by cgo.
142	args := goenv.goTool("cgo", "-srcdir", srcDir, "-objdir", workDir, "-trimpath", execRoot)
143	if packagePath != "" {
144		args = append(args, "-importpath", packagePath)
145	}
146	args = append(args, "--")
147	args = append(args, cppFlags...)
148	args = append(args, hdrIncludes...)
149	args = append(args, cFlags...)
150	args = append(args, cgoSrcs...)
151	if err := goenv.runCommand(args); err != nil {
152		return "", nil, nil, err
153	}
154
155	if cgoExportHPath != "" {
156		if err := copyFile(filepath.Join(workDir, "_cgo_export.h"), cgoExportHPath); err != nil {
157			return "", nil, nil, err
158		}
159	}
160	genGoSrcs := make([]string, 1+len(cgoSrcs))
161	genGoSrcs[0] = filepath.Join(workDir, "_cgo_gotypes.go")
162	genCSrcs := make([]string, 1+len(cgoSrcs))
163	genCSrcs[0] = filepath.Join(workDir, "_cgo_export.c")
164	for i, src := range cgoSrcs {
165		stem := strings.TrimSuffix(filepath.Base(src), ".go")
166		genGoSrcs[i+1] = filepath.Join(workDir, stem+".cgo1.go")
167		genCSrcs[i+1] = filepath.Join(workDir, stem+".cgo2.c")
168	}
169	cgoMainC := filepath.Join(workDir, "_cgo_main.c")
170
171	// Compile C, C++, Objective-C/C++, and assembly code.
172	defaultCFlags := defaultCFlags(workDir)
173	combinedCFlags := combineFlags(cppFlags, hdrIncludes, cFlags, defaultCFlags)
174	for _, lang := range []struct{ srcs, flags []string }{
175		{genCSrcs, combinedCFlags},
176		{cSrcs, combinedCFlags},
177		{cxxSrcs, combineFlags(cppFlags, hdrIncludes, cxxFlags, defaultCFlags)},
178		{objcSrcs, combineFlags(cppFlags, hdrIncludes, objcFlags, defaultCFlags)},
179		{objcxxSrcs, combineFlags(cppFlags, hdrIncludes, objcxxFlags, defaultCFlags)},
180		{sSrcs, nil},
181	} {
182		for _, src := range lang.srcs {
183			obj := filepath.Join(workDir, fmt.Sprintf("_x%d.o", len(cObjs)))
184			cObjs = append(cObjs, obj)
185			if err := cCompile(goenv, src, cc, lang.flags, obj); err != nil {
186				return "", nil, nil, err
187			}
188		}
189	}
190
191	mainObj := filepath.Join(workDir, "_cgo_main.o")
192	if err := cCompile(goenv, cgoMainC, cc, combinedCFlags, mainObj); err != nil {
193		return "", nil, nil, err
194	}
195
196	// Link cgo binary and use the symbols to generate _cgo_import.go.
197	mainBin := filepath.Join(workDir, "_cgo_.o") // .o is a lie; it's an executable
198	args = append([]string{cc, "-o", mainBin, mainObj}, cObjs...)
199	args = append(args, combinedLdFlags...)
200	var originalErrBuf bytes.Buffer
201	if err := goenv.runCommandToFile(os.Stdout, &originalErrBuf, args); err != nil {
202		// If linking the binary for cgo fails, this is usually because the
203		// object files reference external symbols that can't be resolved yet.
204		// Since the binary is only produced to have its symbols read by the cgo
205		// command, there is no harm in trying to build it allowing unresolved
206		// symbols - the real link that happens at the end will fail if they
207		// rightfully can't be resolved.
208		var allowUnresolvedSymbolsLdFlag string
209		switch os.Getenv("GOOS") {
210		case "windows":
211			// MinGW's linker doesn't seem to support --unresolved-symbols
212			// and MSVC isn't supported at all.
213			return "", nil, nil, err
214		case "darwin", "ios":
215			allowUnresolvedSymbolsLdFlag = "-Wl,-undefined,dynamic_lookup"
216		default:
217			allowUnresolvedSymbolsLdFlag = "-Wl,--unresolved-symbols=ignore-all"
218		}
219		// Print and return the original error if we can't link the binary with
220		// the additional linker flags as they may simply be incorrect for the
221		// particular compiler/linker pair and would obscure the true reason for
222		// the failure of the original command.
223		if err2 := goenv.runCommandToFile(
224			os.Stdout,
225			ioutil.Discard,
226			append(args, allowUnresolvedSymbolsLdFlag),
227		); err2 != nil {
228			os.Stderr.Write(relativizePaths(originalErrBuf.Bytes()))
229			return "", nil, nil, err
230		}
231		// Do not print the original error - rerunning the command with the
232		// additional linker flag fixed it.
233	}
234
235	cgoImportsGo := filepath.Join(workDir, "_cgo_imports.go")
236	args = goenv.goTool("cgo", "-dynpackage", packageName, "-dynimport", mainBin, "-dynout", cgoImportsGo)
237	if err := goenv.runCommand(args); err != nil {
238		return "", nil, nil, err
239	}
240	genGoSrcs = append(genGoSrcs, cgoImportsGo)
241
242	// Copy regular Go source files into the work directory so that we can
243	// use -trimpath=workDir.
244	goBases, err := gatherSrcs(workDir, goSrcs)
245	if err != nil {
246		return "", nil, nil, err
247	}
248
249	allGoSrcs = make([]string, len(goSrcs)+len(genGoSrcs))
250	for i := range goSrcs {
251		allGoSrcs[i] = filepath.Join(workDir, goBases[i])
252	}
253	copy(allGoSrcs[len(goSrcs):], genGoSrcs)
254	return workDir, allGoSrcs, cObjs, nil
255}
256
257// compileCSources compiles a list of C, C++, Objective-C, Objective-C++,
258// and assembly sources into .o files to be packed into the archive.
259// It does not run cgo. This is used for packages with "cgo = True" but
260// without any .go files that import "C". The Go command forbids this,
261// but we have historically allowed it.
262func compileCSources(goenv *env, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs []string, cc string, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags []string) (cObjs []string, err error) {
263	workDir, cleanup, err := goenv.workDir()
264	if err != nil {
265		return nil, err
266	}
267	defer cleanup()
268
269	hdrDirs := map[string]bool{}
270	var hdrIncludes []string
271	for _, hdr := range hSrcs {
272		hdrDir := filepath.Dir(hdr)
273		if !hdrDirs[hdrDir] {
274			hdrDirs[hdrDir] = true
275			hdrIncludes = append(hdrIncludes, "-iquote", hdrDir)
276		}
277	}
278
279	defaultCFlags := defaultCFlags(workDir)
280	for _, lang := range []struct{ srcs, flags []string }{
281		{cSrcs, combineFlags(cppFlags, hdrIncludes, cFlags, defaultCFlags)},
282		{cxxSrcs, combineFlags(cppFlags, hdrIncludes, cxxFlags, defaultCFlags)},
283		{objcSrcs, combineFlags(cppFlags, hdrIncludes, objcFlags, defaultCFlags)},
284		{objcxxSrcs, combineFlags(cppFlags, hdrIncludes, objcxxFlags, defaultCFlags)},
285		{sSrcs, nil},
286	} {
287		for _, src := range lang.srcs {
288			obj := filepath.Join(workDir, fmt.Sprintf("_x%d.o", len(cObjs)))
289			cObjs = append(cObjs, obj)
290			if err := cCompile(goenv, src, cc, lang.flags, obj); err != nil {
291				return nil, err
292			}
293		}
294	}
295	return cObjs, nil
296}
297
298func combineFlags(lists ...[]string) []string {
299	n := 0
300	for _, list := range lists {
301		n += len(list)
302	}
303	flags := make([]string, 0, n)
304	for _, list := range lists {
305		flags = append(flags, list...)
306	}
307	return flags
308}
309
310func cCompile(goenv *env, src, cc string, flags []string, out string) error {
311	args := []string{cc}
312	args = append(args, flags...)
313	args = append(args, "-c", src, "-o", out)
314	return goenv.runCommand(args)
315}
316
317func defaultCFlags(workDir string) []string {
318	flags := []string{
319		"-fdebug-prefix-map=" + abs(".") + "=.",
320		"-fdebug-prefix-map=" + workDir + "=.",
321	}
322	goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
323	switch {
324	case goos == "darwin" || goos == "ios":
325		return flags
326	case goos == "windows" && goarch == "amd64":
327		return append(flags, "-mthreads")
328	default:
329		return append(flags, "-pthread")
330	}
331}
332
333func defaultLdFlags() []string {
334	goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
335	switch {
336	case goos == "android":
337		return []string{"-llog", "-ldl"}
338	case goos == "darwin" || goos == "ios":
339		return nil
340	case goos == "windows" && goarch == "amd64":
341		return []string{"-mthreads"}
342	default:
343		return []string{"-pthread"}
344	}
345}
346
347// gatherSrcs copies or links files listed in srcs into dir. This is needed
348// to effectively use -trimpath with generated sources. It's also needed by cgo.
349//
350// gatherSrcs returns the basenames of copied files in the directory.
351func gatherSrcs(dir string, srcs []string) ([]string, error) {
352	copiedBases := make([]string, len(srcs))
353	for i, src := range srcs {
354		base := filepath.Base(src)
355		ext := filepath.Ext(base)
356		stem := base[:len(base)-len(ext)]
357		var err error
358		for j := 1; j < 10000; j++ {
359			if err = copyOrLinkFile(src, filepath.Join(dir, base)); err == nil {
360				break
361			} else if !os.IsExist(err) {
362				return nil, err
363			} else {
364				base = fmt.Sprintf("%s_%d%s", stem, j, ext)
365			}
366		}
367		if err != nil {
368			return nil, fmt.Errorf("could not find unique name for file %s", src)
369		}
370		copiedBases[i] = base
371	}
372	return copiedBases, nil
373}
374
375func bazelExecRoot() (string, error) {
376	// Bazel executes the builder with a working directory of the form
377	// .../execroot/<workspace name>. By stripping the last segment, we obtain a
378	// prefix of all possible source files, even when contained in external
379	// repositories.
380	cwd, err := os.Getwd()
381	if err != nil {
382		return "", err
383	}
384	return filepath.Dir(cwd), nil
385}
386
387type cgoError []string
388
389func (e cgoError) Error() string {
390	b := &bytes.Buffer{}
391	fmt.Fprint(b, "CC is not set and files need to be processed with cgo:\n")
392	for _, f := range e {
393		fmt.Fprintf(b, "\t%s\n", f)
394	}
395	fmt.Fprintf(b, "Ensure that 'cgo = True' is set and the C/C++ toolchain is configured.")
396	return b.String()
397}
398