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