aboutsummaryrefslogtreecommitdiff
path: root/go/tools/builders/cgo2.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/builders/cgo2.go')
-rw-r--r--go/tools/builders/cgo2.go397
1 files changed, 397 insertions, 0 deletions
diff --git a/go/tools/builders/cgo2.go b/go/tools/builders/cgo2.go
new file mode 100644
index 00000000..fc2876a9
--- /dev/null
+++ b/go/tools/builders/cgo2.go
@@ -0,0 +1,397 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// cgo2.go provides new cgo functionality for use by the GoCompilePkg action.
+// We can't use the functionality in cgo.go, since it relies too heavily
+// on logic in cgo.bzl. Ideally, we'd be able to replace cgo.go with this
+// file eventually, but not until Bazel gives us enough toolchain information
+// to compile ObjC files.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// cgo2 processes a set of mixed source files with cgo.
+func 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) {
+ // Report an error if the C/C++ toolchain wasn't configured.
+ if cc == "" {
+ err := cgoError(cgoSrcs[:])
+ err = append(err, cSrcs...)
+ err = append(err, cxxSrcs...)
+ err = append(err, objcSrcs...)
+ err = append(err, objcxxSrcs...)
+ err = append(err, sSrcs...)
+ return "", nil, nil, err
+ }
+
+ // If we only have C/C++ sources without cgo, just compile and pack them
+ // without generating code. The Go command forbids this, but we've
+ // historically allowed it.
+ // TODO(jayconrod): this doesn't write CGO_LDFLAGS into the archive. We
+ // might miss dependencies like -lstdc++ if they aren't referenced in
+ // some other way.
+ if len(cgoSrcs) == 0 {
+ cObjs, err = compileCSources(goenv, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags)
+ return ".", nil, cObjs, err
+ }
+
+ workDir, cleanup, err := goenv.workDir()
+ if err != nil {
+ return "", nil, nil, err
+ }
+ defer cleanup()
+
+ // cgo2 will gather sources into a single temporary directory, since nogo
+ // scanners might want to include or exclude these sources we need to ensure
+ // that a fragment of the path is stable and human friendly enough to be
+ // referenced in nogo configuration.
+ workDir = filepath.Join(workDir, "cgo", packagePath)
+ if err := os.MkdirAll(workDir, 0700); err != nil {
+ return "", nil, nil, err
+ }
+
+ // Filter out -lstdc++ and -lc++ from ldflags if we don't have C++ sources,
+ // and set CGO_LDFLAGS. These flags get written as special comments into cgo
+ // generated sources. The compiler encodes those flags in the compiled .a
+ // file, and the linker passes them on to the external linker.
+ haveCxx := len(cxxSrcs)+len(objcxxSrcs) > 0
+ if !haveCxx {
+ for _, f := range ldFlags {
+ if strings.HasSuffix(f, ".a") {
+ // These flags come from cdeps options. Assume C++.
+ haveCxx = true
+ break
+ }
+ }
+ }
+ var combinedLdFlags []string
+ if haveCxx {
+ combinedLdFlags = append(combinedLdFlags, ldFlags...)
+ } else {
+ for _, f := range ldFlags {
+ if f != "-lc++" && f != "-lstdc++" {
+ combinedLdFlags = append(combinedLdFlags, f)
+ }
+ }
+ }
+ combinedLdFlags = append(combinedLdFlags, defaultLdFlags()...)
+ os.Setenv("CGO_LDFLAGS", strings.Join(combinedLdFlags, " "))
+
+ // If cgo sources are in different directories, gather them into a temporary
+ // directory so we can use -srcdir.
+ srcDir = filepath.Dir(cgoSrcs[0])
+ srcsInSingleDir := true
+ for _, src := range cgoSrcs[1:] {
+ if filepath.Dir(src) != srcDir {
+ srcsInSingleDir = false
+ break
+ }
+ }
+
+ if srcsInSingleDir {
+ for i := range cgoSrcs {
+ cgoSrcs[i] = filepath.Base(cgoSrcs[i])
+ }
+ } else {
+ srcDir = filepath.Join(workDir, "cgosrcs")
+ if err := os.Mkdir(srcDir, 0777); err != nil {
+ return "", nil, nil, err
+ }
+ copiedSrcs, err := gatherSrcs(srcDir, cgoSrcs)
+ if err != nil {
+ return "", nil, nil, err
+ }
+ cgoSrcs = copiedSrcs
+ }
+
+ // Generate Go and C code.
+ hdrDirs := map[string]bool{}
+ var hdrIncludes []string
+ for _, hdr := range hSrcs {
+ hdrDir := filepath.Dir(hdr)
+ if !hdrDirs[hdrDir] {
+ hdrDirs[hdrDir] = true
+ hdrIncludes = append(hdrIncludes, "-iquote", hdrDir)
+ }
+ }
+ hdrIncludes = append(hdrIncludes, "-iquote", workDir) // for _cgo_export.h
+
+ execRoot, err := bazelExecRoot()
+ if err != nil {
+ return "", nil, nil, err
+ }
+ // Trim the execroot from the //line comments emitted by cgo.
+ args := goenv.goTool("cgo", "-srcdir", srcDir, "-objdir", workDir, "-trimpath", execRoot)
+ if packagePath != "" {
+ args = append(args, "-importpath", packagePath)
+ }
+ args = append(args, "--")
+ args = append(args, cppFlags...)
+ args = append(args, hdrIncludes...)
+ args = append(args, cFlags...)
+ args = append(args, cgoSrcs...)
+ if err := goenv.runCommand(args); err != nil {
+ return "", nil, nil, err
+ }
+
+ if cgoExportHPath != "" {
+ if err := copyFile(filepath.Join(workDir, "_cgo_export.h"), cgoExportHPath); err != nil {
+ return "", nil, nil, err
+ }
+ }
+ genGoSrcs := make([]string, 1+len(cgoSrcs))
+ genGoSrcs[0] = filepath.Join(workDir, "_cgo_gotypes.go")
+ genCSrcs := make([]string, 1+len(cgoSrcs))
+ genCSrcs[0] = filepath.Join(workDir, "_cgo_export.c")
+ for i, src := range cgoSrcs {
+ stem := strings.TrimSuffix(filepath.Base(src), ".go")
+ genGoSrcs[i+1] = filepath.Join(workDir, stem+".cgo1.go")
+ genCSrcs[i+1] = filepath.Join(workDir, stem+".cgo2.c")
+ }
+ cgoMainC := filepath.Join(workDir, "_cgo_main.c")
+
+ // Compile C, C++, Objective-C/C++, and assembly code.
+ defaultCFlags := defaultCFlags(workDir)
+ combinedCFlags := combineFlags(cppFlags, hdrIncludes, cFlags, defaultCFlags)
+ for _, lang := range []struct{ srcs, flags []string }{
+ {genCSrcs, combinedCFlags},
+ {cSrcs, combinedCFlags},
+ {cxxSrcs, combineFlags(cppFlags, hdrIncludes, cxxFlags, defaultCFlags)},
+ {objcSrcs, combineFlags(cppFlags, hdrIncludes, objcFlags, defaultCFlags)},
+ {objcxxSrcs, combineFlags(cppFlags, hdrIncludes, objcxxFlags, defaultCFlags)},
+ {sSrcs, nil},
+ } {
+ for _, src := range lang.srcs {
+ obj := filepath.Join(workDir, fmt.Sprintf("_x%d.o", len(cObjs)))
+ cObjs = append(cObjs, obj)
+ if err := cCompile(goenv, src, cc, lang.flags, obj); err != nil {
+ return "", nil, nil, err
+ }
+ }
+ }
+
+ mainObj := filepath.Join(workDir, "_cgo_main.o")
+ if err := cCompile(goenv, cgoMainC, cc, combinedCFlags, mainObj); err != nil {
+ return "", nil, nil, err
+ }
+
+ // Link cgo binary and use the symbols to generate _cgo_import.go.
+ mainBin := filepath.Join(workDir, "_cgo_.o") // .o is a lie; it's an executable
+ args = append([]string{cc, "-o", mainBin, mainObj}, cObjs...)
+ args = append(args, combinedLdFlags...)
+ var originalErrBuf bytes.Buffer
+ if err := goenv.runCommandToFile(os.Stdout, &originalErrBuf, args); err != nil {
+ // If linking the binary for cgo fails, this is usually because the
+ // object files reference external symbols that can't be resolved yet.
+ // Since the binary is only produced to have its symbols read by the cgo
+ // command, there is no harm in trying to build it allowing unresolved
+ // symbols - the real link that happens at the end will fail if they
+ // rightfully can't be resolved.
+ var allowUnresolvedSymbolsLdFlag string
+ switch os.Getenv("GOOS") {
+ case "windows":
+ // MinGW's linker doesn't seem to support --unresolved-symbols
+ // and MSVC isn't supported at all.
+ return "", nil, nil, err
+ case "darwin", "ios":
+ allowUnresolvedSymbolsLdFlag = "-Wl,-undefined,dynamic_lookup"
+ default:
+ allowUnresolvedSymbolsLdFlag = "-Wl,--unresolved-symbols=ignore-all"
+ }
+ // Print and return the original error if we can't link the binary with
+ // the additional linker flags as they may simply be incorrect for the
+ // particular compiler/linker pair and would obscure the true reason for
+ // the failure of the original command.
+ if err2 := goenv.runCommandToFile(
+ os.Stdout,
+ ioutil.Discard,
+ append(args, allowUnresolvedSymbolsLdFlag),
+ ); err2 != nil {
+ os.Stderr.Write(relativizePaths(originalErrBuf.Bytes()))
+ return "", nil, nil, err
+ }
+ // Do not print the original error - rerunning the command with the
+ // additional linker flag fixed it.
+ }
+
+ cgoImportsGo := filepath.Join(workDir, "_cgo_imports.go")
+ args = goenv.goTool("cgo", "-dynpackage", packageName, "-dynimport", mainBin, "-dynout", cgoImportsGo)
+ if err := goenv.runCommand(args); err != nil {
+ return "", nil, nil, err
+ }
+ genGoSrcs = append(genGoSrcs, cgoImportsGo)
+
+ // Copy regular Go source files into the work directory so that we can
+ // use -trimpath=workDir.
+ goBases, err := gatherSrcs(workDir, goSrcs)
+ if err != nil {
+ return "", nil, nil, err
+ }
+
+ allGoSrcs = make([]string, len(goSrcs)+len(genGoSrcs))
+ for i := range goSrcs {
+ allGoSrcs[i] = filepath.Join(workDir, goBases[i])
+ }
+ copy(allGoSrcs[len(goSrcs):], genGoSrcs)
+ return workDir, allGoSrcs, cObjs, nil
+}
+
+// compileCSources compiles a list of C, C++, Objective-C, Objective-C++,
+// and assembly sources into .o files to be packed into the archive.
+// It does not run cgo. This is used for packages with "cgo = True" but
+// without any .go files that import "C". The Go command forbids this,
+// but we have historically allowed it.
+func compileCSources(goenv *env, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs []string, cc string, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags []string) (cObjs []string, err error) {
+ workDir, cleanup, err := goenv.workDir()
+ if err != nil {
+ return nil, err
+ }
+ defer cleanup()
+
+ hdrDirs := map[string]bool{}
+ var hdrIncludes []string
+ for _, hdr := range hSrcs {
+ hdrDir := filepath.Dir(hdr)
+ if !hdrDirs[hdrDir] {
+ hdrDirs[hdrDir] = true
+ hdrIncludes = append(hdrIncludes, "-iquote", hdrDir)
+ }
+ }
+
+ defaultCFlags := defaultCFlags(workDir)
+ for _, lang := range []struct{ srcs, flags []string }{
+ {cSrcs, combineFlags(cppFlags, hdrIncludes, cFlags, defaultCFlags)},
+ {cxxSrcs, combineFlags(cppFlags, hdrIncludes, cxxFlags, defaultCFlags)},
+ {objcSrcs, combineFlags(cppFlags, hdrIncludes, objcFlags, defaultCFlags)},
+ {objcxxSrcs, combineFlags(cppFlags, hdrIncludes, objcxxFlags, defaultCFlags)},
+ {sSrcs, nil},
+ } {
+ for _, src := range lang.srcs {
+ obj := filepath.Join(workDir, fmt.Sprintf("_x%d.o", len(cObjs)))
+ cObjs = append(cObjs, obj)
+ if err := cCompile(goenv, src, cc, lang.flags, obj); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return cObjs, nil
+}
+
+func combineFlags(lists ...[]string) []string {
+ n := 0
+ for _, list := range lists {
+ n += len(list)
+ }
+ flags := make([]string, 0, n)
+ for _, list := range lists {
+ flags = append(flags, list...)
+ }
+ return flags
+}
+
+func cCompile(goenv *env, src, cc string, flags []string, out string) error {
+ args := []string{cc}
+ args = append(args, flags...)
+ args = append(args, "-c", src, "-o", out)
+ return goenv.runCommand(args)
+}
+
+func defaultCFlags(workDir string) []string {
+ flags := []string{
+ "-fdebug-prefix-map=" + abs(".") + "=.",
+ "-fdebug-prefix-map=" + workDir + "=.",
+ }
+ goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
+ switch {
+ case goos == "darwin" || goos == "ios":
+ return flags
+ case goos == "windows" && goarch == "amd64":
+ return append(flags, "-mthreads")
+ default:
+ return append(flags, "-pthread")
+ }
+}
+
+func defaultLdFlags() []string {
+ goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
+ switch {
+ case goos == "android":
+ return []string{"-llog", "-ldl"}
+ case goos == "darwin" || goos == "ios":
+ return nil
+ case goos == "windows" && goarch == "amd64":
+ return []string{"-mthreads"}
+ default:
+ return []string{"-pthread"}
+ }
+}
+
+// gatherSrcs copies or links files listed in srcs into dir. This is needed
+// to effectively use -trimpath with generated sources. It's also needed by cgo.
+//
+// gatherSrcs returns the basenames of copied files in the directory.
+func gatherSrcs(dir string, srcs []string) ([]string, error) {
+ copiedBases := make([]string, len(srcs))
+ for i, src := range srcs {
+ base := filepath.Base(src)
+ ext := filepath.Ext(base)
+ stem := base[:len(base)-len(ext)]
+ var err error
+ for j := 1; j < 10000; j++ {
+ if err = copyOrLinkFile(src, filepath.Join(dir, base)); err == nil {
+ break
+ } else if !os.IsExist(err) {
+ return nil, err
+ } else {
+ base = fmt.Sprintf("%s_%d%s", stem, j, ext)
+ }
+ }
+ if err != nil {
+ return nil, fmt.Errorf("could not find unique name for file %s", src)
+ }
+ copiedBases[i] = base
+ }
+ return copiedBases, nil
+}
+
+func bazelExecRoot() (string, error) {
+ // Bazel executes the builder with a working directory of the form
+ // .../execroot/<workspace name>. By stripping the last segment, we obtain a
+ // prefix of all possible source files, even when contained in external
+ // repositories.
+ cwd, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Dir(cwd), nil
+}
+
+type cgoError []string
+
+func (e cgoError) Error() string {
+ b := &bytes.Buffer{}
+ fmt.Fprint(b, "CC is not set and files need to be processed with cgo:\n")
+ for _, f := range e {
+ fmt.Fprintf(b, "\t%s\n", f)
+ }
+ fmt.Fprintf(b, "Ensure that 'cgo = True' is set and the C/C++ toolchain is configured.")
+ return b.String()
+}