github.com/ajguerrer/rules_go@v0.20.3/go/tools/builders/cgo2.go (about)

     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  
    21  package main
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  )
    30  
    31  // cgo2 processes a set of mixed source files with cgo.
    32  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) {
    33  	// Report an error if the C/C++ toolchain wasn't configured.
    34  	if cc == "" {
    35  		err := cgoError(cgoSrcs[:])
    36  		err = append(err, cSrcs...)
    37  		err = append(err, cxxSrcs...)
    38  		err = append(err, objcSrcs...)
    39  		err = append(err, objcxxSrcs...)
    40  		err = append(err, sSrcs...)
    41  		return "", nil, nil, err
    42  	}
    43  
    44  	// If we only have C/C++ sources without cgo, just compile and pack them
    45  	// without generating code. The Go command forbids this, but we've
    46  	// historically allowed it.
    47  	// TODO(jayconrod): this doesn't write CGO_LDFLAGS into the archive. We
    48  	// might miss dependencies like -lstdc++ if they aren't referenced in
    49  	// some other way.
    50  	if len(cgoSrcs) == 0 {
    51  		cObjs, err = compileCSources(goenv, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags)
    52  		return ".", nil, cObjs, err
    53  	}
    54  
    55  	workDir, cleanup, err := goenv.workDir()
    56  	if err != nil {
    57  		return "", nil, nil, err
    58  	}
    59  	defer cleanup()
    60  
    61  	// Filter out -lstdc++ and -lc++ from ldflags if we don't have C++ sources,
    62  	// and set CGO_LDFLAGS. These flags get written as special comments into cgo
    63  	// generated sources. The compiler encodes those flags in the compiled .a
    64  	// file, and the linker passes them on to the external linker.
    65  	haveCxx := len(cxxSrcs)+len(objcxxSrcs) > 0
    66  	if !haveCxx {
    67  		for _, f := range ldFlags {
    68  			if strings.HasSuffix(f, ".a") {
    69  				// These flags come from cdeps options. Assume C++.
    70  				haveCxx = true
    71  				break
    72  			}
    73  		}
    74  	}
    75  	var combinedLdFlags []string
    76  	if haveCxx {
    77  		combinedLdFlags = append(combinedLdFlags, ldFlags...)
    78  	} else {
    79  		for _, f := range ldFlags {
    80  			if f != "-lc++" && f != "-lstdc++" {
    81  				combinedLdFlags = append(combinedLdFlags, f)
    82  			}
    83  		}
    84  	}
    85  	combinedLdFlags = append(combinedLdFlags, defaultLdFlags()...)
    86  	os.Setenv("CGO_LDFLAGS", strings.Join(combinedLdFlags, " "))
    87  
    88  	// If cgo sources are in different directories, gather them into a temporary
    89  	// directory so we can use -srcdir.
    90  	srcDir = filepath.Dir(cgoSrcs[0])
    91  	srcsInSingleDir := true
    92  	for _, src := range cgoSrcs[1:] {
    93  		if filepath.Dir(src) != srcDir {
    94  			srcsInSingleDir = false
    95  			break
    96  		}
    97  	}
    98  
    99  	if srcsInSingleDir {
   100  		for i := range cgoSrcs {
   101  			cgoSrcs[i] = filepath.Base(cgoSrcs[i])
   102  		}
   103  	} else {
   104  		srcDir = filepath.Join(workDir, "cgosrcs")
   105  		if err := os.Mkdir(srcDir, 0777); err != nil {
   106  			return "", nil, nil, err
   107  		}
   108  		copiedSrcs, err := gatherSrcs(srcDir, cgoSrcs)
   109  		if err != nil {
   110  			return "", nil, nil, err
   111  		}
   112  		cgoSrcs = copiedSrcs
   113  	}
   114  
   115  	// Generate Go and C code.
   116  	hdrDirs := map[string]bool{}
   117  	var hdrIncludes []string
   118  	for _, hdr := range hSrcs {
   119  		hdrDir := filepath.Dir(hdr)
   120  		if !hdrDirs[hdrDir] {
   121  			hdrDirs[hdrDir] = true
   122  			hdrIncludes = append(hdrIncludes, "-iquote", hdrDir)
   123  		}
   124  	}
   125  	hdrIncludes = append(hdrIncludes, "-iquote", workDir) // for _cgo_export.h
   126  
   127  	args := goenv.goTool("cgo", "-srcdir", srcDir, "-objdir", workDir)
   128  	if packagePath != "" {
   129  		args = append(args, "-importpath", packagePath)
   130  	}
   131  	args = append(args, "--")
   132  	args = append(args, cppFlags...)
   133  	args = append(args, hdrIncludes...)
   134  	args = append(args, cFlags...)
   135  	args = append(args, cgoSrcs...)
   136  	if err := goenv.runCommand(args); err != nil {
   137  		return "", nil, nil, err
   138  	}
   139  
   140  	if cgoExportHPath != "" {
   141  		if err := copyFile(filepath.Join(workDir, "_cgo_export.h"), cgoExportHPath); err != nil {
   142  			return "", nil, nil, err
   143  		}
   144  	}
   145  	genGoSrcs := make([]string, 1+len(cgoSrcs))
   146  	genGoSrcs[0] = filepath.Join(workDir, "_cgo_gotypes.go")
   147  	genCSrcs := make([]string, 1+len(cgoSrcs))
   148  	genCSrcs[0] = filepath.Join(workDir, "_cgo_export.c")
   149  	for i, src := range cgoSrcs {
   150  		stem := strings.TrimSuffix(filepath.Base(src), ".go")
   151  		genGoSrcs[i+1] = filepath.Join(workDir, stem+".cgo1.go")
   152  		genCSrcs[i+1] = filepath.Join(workDir, stem+".cgo2.c")
   153  	}
   154  	cgoMainC := filepath.Join(workDir, "_cgo_main.c")
   155  
   156  	// Compile C, C++, Objective-C/C++, and assembly code.
   157  	defaultCFlags := defaultCFlags(workDir)
   158  	combinedCFlags := combineFlags(cppFlags, hdrIncludes, cFlags, defaultCFlags)
   159  	for _, lang := range []struct{ srcs, flags []string }{
   160  		{genCSrcs, combinedCFlags},
   161  		{cSrcs, combinedCFlags},
   162  		{cxxSrcs, combineFlags(cppFlags, hdrIncludes, cxxFlags, defaultCFlags)},
   163  		{objcSrcs, combineFlags(cppFlags, hdrIncludes, objcFlags, defaultCFlags)},
   164  		{objcxxSrcs, combineFlags(cppFlags, hdrIncludes, objcxxFlags, defaultCFlags)},
   165  		{sSrcs, nil},
   166  	} {
   167  		for _, src := range lang.srcs {
   168  			obj := filepath.Join(workDir, fmt.Sprintf("_x%d.o", len(cObjs)))
   169  			cObjs = append(cObjs, obj)
   170  			if err := cCompile(goenv, src, cc, lang.flags, obj); err != nil {
   171  				return "", nil, nil, err
   172  			}
   173  		}
   174  	}
   175  
   176  	mainObj := filepath.Join(workDir, "_cgo_main.o")
   177  	if err := cCompile(goenv, cgoMainC, cc, combinedCFlags, mainObj); err != nil {
   178  		return "", nil, nil, err
   179  	}
   180  
   181  	// Link cgo binary and use the symbols to generate _cgo_import.go.
   182  	mainBin := filepath.Join(workDir, "_cgo_.o") // .o is a lie; it's an executable
   183  	args = append([]string{cc, "-o", mainBin, mainObj}, cObjs...)
   184  	args = append(args, combinedLdFlags...)
   185  	if err := goenv.runCommand(args); err != nil {
   186  		return "", nil, nil, err
   187  	}
   188  
   189  	cgoImportsGo := filepath.Join(workDir, "_cgo_imports.go")
   190  	args = goenv.goTool("cgo", "-dynpackage", packageName, "-dynimport", mainBin, "-dynout", cgoImportsGo)
   191  	if err := goenv.runCommand(args); err != nil {
   192  		return "", nil, nil, err
   193  	}
   194  	genGoSrcs = append(genGoSrcs, cgoImportsGo)
   195  
   196  	// Copy regular Go source files into the work directory so that we can
   197  	// use -trimpath=workDir.
   198  	goBases, err := gatherSrcs(workDir, goSrcs)
   199  	if err != nil {
   200  		return "", nil, nil, err
   201  	}
   202  
   203  	allGoSrcs = make([]string, len(goSrcs)+len(genGoSrcs))
   204  	for i := range goSrcs {
   205  		allGoSrcs[i] = filepath.Join(workDir, goBases[i])
   206  	}
   207  	copy(allGoSrcs[len(goSrcs):], genGoSrcs)
   208  	return workDir, allGoSrcs, cObjs, nil
   209  }
   210  
   211  // compileCSources compiles a list of C, C++, Objective-C, Objective-C++,
   212  // and assembly sources into .o files to be packed into the archive.
   213  // It does not run cgo. This is used for packages with "cgo = True" but
   214  // without any .go files that import "C". The Go command forbids this,
   215  // but we have historically allowed it.
   216  func compileCSources(goenv *env, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs []string, cc string, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags []string) (cObjs []string, err error) {
   217  	workDir, cleanup, err := goenv.workDir()
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	defer cleanup()
   222  
   223  	hdrDirs := map[string]bool{}
   224  	var hdrIncludes []string
   225  	for _, hdr := range hSrcs {
   226  		hdrDir := filepath.Dir(hdr)
   227  		if !hdrDirs[hdrDir] {
   228  			hdrDirs[hdrDir] = true
   229  			hdrIncludes = append(hdrIncludes, "-iquote", hdrDir)
   230  		}
   231  	}
   232  
   233  	defaultCFlags := defaultCFlags(workDir)
   234  	for _, lang := range []struct{ srcs, flags []string }{
   235  		{cSrcs, combineFlags(cppFlags, hdrIncludes, cFlags, defaultCFlags)},
   236  		{cxxSrcs, combineFlags(cppFlags, hdrIncludes, cxxFlags, defaultCFlags)},
   237  		{objcSrcs, combineFlags(cppFlags, hdrIncludes, objcFlags, defaultCFlags)},
   238  		{objcxxSrcs, combineFlags(cppFlags, hdrIncludes, objcxxFlags, defaultCFlags)},
   239  		{sSrcs, nil},
   240  	} {
   241  		for _, src := range lang.srcs {
   242  			obj := filepath.Join(workDir, fmt.Sprintf("_x%d.o", len(cObjs)))
   243  			cObjs = append(cObjs, obj)
   244  			if err := cCompile(goenv, src, cc, lang.flags, obj); err != nil {
   245  				return nil, err
   246  			}
   247  		}
   248  	}
   249  	return cObjs, nil
   250  }
   251  
   252  func combineFlags(lists ...[]string) []string {
   253  	n := 0
   254  	for _, list := range lists {
   255  		n += len(list)
   256  	}
   257  	flags := make([]string, 0, n)
   258  	for _, list := range lists {
   259  		flags = append(flags, list...)
   260  	}
   261  	return flags
   262  }
   263  
   264  func cCompile(goenv *env, src, cc string, flags []string, out string) error {
   265  	args := []string{cc}
   266  	args = append(args, flags...)
   267  	args = append(args, "-c", src, "-o", out)
   268  	return goenv.runCommand(args)
   269  }
   270  
   271  func defaultCFlags(workDir string) []string {
   272  	flags := []string{
   273  		"-fdebug-prefix-map=" + abs(".") + "=.",
   274  		"-fdebug-prefix-map=" + workDir + "=.",
   275  	}
   276  	goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
   277  	switch {
   278  	case goos == "darwin":
   279  		return flags
   280  	case goos == "windows" && goarch == "amd64":
   281  		return append(flags, "-mthreads")
   282  	default:
   283  		return append(flags, "-pthread")
   284  	}
   285  }
   286  
   287  func defaultLdFlags() []string {
   288  	goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
   289  	switch {
   290  	case goos == "android":
   291  		return []string{"-llog", "-ldl"}
   292  	case goos == "darwin":
   293  		return nil
   294  	case goos == "windows" && goarch == "amd64":
   295  		return []string{"-mthreads"}
   296  	default:
   297  		return []string{"-pthread"}
   298  	}
   299  }
   300  
   301  // gatherSrcs copies or links files listed in srcs into dir. This is needed
   302  // to effectively use -trimpath with generated sources. It's also needed by cgo.
   303  //
   304  // gatherSrcs returns the basenames of copied files in the directory.
   305  func gatherSrcs(dir string, srcs []string) ([]string, error) {
   306  	copiedBases := make([]string, len(srcs))
   307  	for i, src := range srcs {
   308  		base := filepath.Base(src)
   309  		ext := filepath.Ext(base)
   310  		stem := base[:len(base)-len(ext)]
   311  		var err error
   312  		for j := 1; j < 10000; j++ {
   313  			if err = copyOrLinkFile(src, filepath.Join(dir, base)); err == nil {
   314  				break
   315  			} else if !os.IsExist(err) {
   316  				return nil, err
   317  			} else {
   318  				base = fmt.Sprintf("%s_%d%s", stem, j, ext)
   319  			}
   320  		}
   321  		if err != nil {
   322  			return nil, fmt.Errorf("could not find unique name for file %s", src)
   323  		}
   324  		copiedBases[i] = base
   325  	}
   326  	return copiedBases, nil
   327  }
   328  
   329  type cgoError []string
   330  
   331  func (e cgoError) Error() string {
   332  	b := &bytes.Buffer{}
   333  	fmt.Fprint(b, "CC is not set and files need to be processed with cgo:\n")
   334  	for _, f := range e {
   335  		fmt.Fprintf(b, "\t%s\n", f)
   336  	}
   337  	fmt.Fprintf(b, "Ensure that 'cgo = True' is set and the C/C++ toolchain is configured.")
   338  	return b.String()
   339  }