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 }