github.com/gsquire/gb@v0.4.4-0.20161112235727-3982dc872064/cgo.go (about) 1 package gb 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/pkg/errors" 16 ) 17 18 func cgo(pkg *Package) (*Action, []string, []string, error) { 19 switch { 20 case goversion == 1.4: 21 return cgo14(pkg) 22 case goversion > 1.4: 23 return cgo15(pkg) 24 default: 25 return nil, nil, nil, errors.Errorf("unsupported Go version: %v", runtime.Version) 26 } 27 } 28 29 // cgo produces a an Action representing the cgo steps 30 // an ofile representing the result of the cgo steps 31 // a set of .go files for compilation, and an error. 32 func cgo14(pkg *Package) (*Action, []string, []string, error) { 33 34 // collect cflags and ldflags from the package 35 // the environment, and pkg-config. 36 cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := cflags(pkg, false) 37 pcCFLAGS, pcLDFLAGS, err := pkgconfig(pkg) 38 if err != nil { 39 return nil, nil, nil, err 40 } 41 cgoCFLAGS = append(cgoCFLAGS, pcCFLAGS...) 42 cgoLDFLAGS = append(cgoLDFLAGS, pcLDFLAGS...) 43 44 runcgo1 := []*Action{ 45 &Action{ 46 Name: "runcgo1: " + pkg.ImportPath, 47 Run: func() error { return runcgo1(pkg, cgoCFLAGS, cgoLDFLAGS) }, 48 }} 49 50 workdir := cgoworkdir(pkg) 51 defun := filepath.Join(workdir, "_cgo_defun.o") 52 rundefun := Action{ 53 Name: "cc: " + pkg.ImportPath + ": _cgo_defun_c", 54 Deps: runcgo1, 55 Run: func() error { return pkg.tc.Cc(pkg, defun, filepath.Join(workdir, "_cgo_defun.c")) }, 56 } 57 58 cgofiles := []string{filepath.Join(workdir, "_cgo_gotypes.go")} 59 for _, f := range pkg.CgoFiles { 60 cgofiles = append(cgofiles, filepath.Join(workdir, stripext(f)+".cgo1.go")) 61 } 62 cfiles := []string{ 63 filepath.Join(workdir, "_cgo_main.c"), 64 filepath.Join(workdir, "_cgo_export.c"), 65 } 66 cfiles = append(cfiles, pkg.CFiles...) 67 68 for _, f := range pkg.CgoFiles { 69 cfiles = append(cfiles, filepath.Join(workdir, stripext(f)+".cgo2.c")) 70 } 71 72 cflags := append(cgoCPPFLAGS, cgoCFLAGS...) 73 cxxflags := append(cgoCPPFLAGS, cgoCXXFLAGS...) 74 gcc1, ofiles := cgocc(pkg, cflags, cxxflags, cfiles, pkg.CXXFiles, runcgo1...) 75 ofiles = append(ofiles, pkg.SysoFiles...) 76 ofile := filepath.Join(filepath.Dir(ofiles[0]), "_cgo_.o") 77 gcc2 := Action{ 78 Name: "gccld: " + pkg.ImportPath + ": _cgo_.o", 79 Deps: gcc1, 80 Run: func() error { return gccld(pkg, cgoCFLAGS, cgoLDFLAGS, ofile, ofiles) }, 81 } 82 83 dynout := filepath.Join(workdir, "_cgo_import.c") 84 imports := stripext(dynout) + ".o" 85 runcgo2 := Action{ 86 Name: "runcgo2: " + pkg.ImportPath, 87 Deps: []*Action{&gcc2}, 88 Run: func() error { 89 if err := runcgo2(pkg, dynout, ofile); err != nil { 90 return err 91 } 92 return pkg.tc.Cc(pkg, imports, dynout) 93 }, 94 } 95 96 allo := filepath.Join(filepath.Dir(ofiles[0]), "_all.o") 97 action := Action{ 98 Name: "rungcc3: " + pkg.ImportPath, 99 Deps: []*Action{&runcgo2, &rundefun}, 100 Run: func() error { 101 return rungcc3(pkg, pkg.Dir, allo, ofiles[1:]) // skip _cgo_main.o 102 }, 103 } 104 return &action, []string{defun, imports, allo}, cgofiles, nil 105 } 106 107 // cgo produces a an Action representing the cgo steps 108 // an ofile representing the result of the cgo steps 109 // a set of .go files for compilation, and an error. 110 func cgo15(pkg *Package) (*Action, []string, []string, error) { 111 112 // collect cflags and ldflags from the package 113 // the environment, and pkg-config. 114 cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := cflags(pkg, false) 115 pcCFLAGS, pcLDFLAGS, err := pkgconfig(pkg) 116 if err != nil { 117 return nil, nil, nil, err 118 } 119 cgoCFLAGS = append(cgoCFLAGS, pcCFLAGS...) 120 cgoLDFLAGS = append(cgoLDFLAGS, pcLDFLAGS...) 121 122 runcgo1 := []*Action{ 123 &Action{ 124 Name: "runcgo1: " + pkg.ImportPath, 125 Run: func() error { return runcgo1(pkg, cgoCFLAGS, cgoLDFLAGS) }, 126 }, 127 } 128 129 workdir := cgoworkdir(pkg) 130 cgofiles := []string{filepath.Join(workdir, "_cgo_gotypes.go")} 131 for _, f := range pkg.CgoFiles { 132 cgofiles = append(cgofiles, filepath.Join(workdir, stripext(f)+".cgo1.go")) 133 } 134 cfiles := []string{ 135 filepath.Join(workdir, "_cgo_main.c"), 136 filepath.Join(workdir, "_cgo_export.c"), 137 } 138 cfiles = append(cfiles, pkg.CFiles...) 139 140 for _, f := range pkg.CgoFiles { 141 cfiles = append(cfiles, filepath.Join(workdir, stripext(f)+".cgo2.c")) 142 } 143 144 cflags := append(cgoCPPFLAGS, cgoCFLAGS...) 145 cxxflags := append(cgoCPPFLAGS, cgoCXXFLAGS...) 146 gcc1, ofiles := cgocc(pkg, cflags, cxxflags, cfiles, pkg.CXXFiles, runcgo1...) 147 ofiles = append(ofiles, pkg.SysoFiles...) 148 ofile := filepath.Join(filepath.Dir(ofiles[0]), "_cgo_.o") 149 gcc2 := Action{ 150 Name: "gccld: " + pkg.ImportPath + ": _cgo_.o", 151 Deps: gcc1, 152 Run: func() error { return gccld(pkg, cgoCFLAGS, cgoLDFLAGS, ofile, ofiles) }, 153 } 154 155 dynout := filepath.Join(workdir, "_cgo_import.go") 156 runcgo2 := Action{ 157 Name: "runcgo2: " + pkg.ImportPath, 158 Deps: []*Action{&gcc2}, 159 Run: func() error { return runcgo2(pkg, dynout, ofile) }, 160 } 161 cgofiles = append(cgofiles, dynout) 162 163 allo := filepath.Join(filepath.Dir(ofiles[0]), "_all.o") 164 action := Action{ 165 Name: "rungcc3: " + pkg.ImportPath, 166 Deps: []*Action{&runcgo2}, 167 Run: func() error { 168 return rungcc3(pkg, pkg.Dir, allo, ofiles[1:]) // skip _cgo_main.o 169 }, 170 } 171 172 return &action, []string{allo}, cgofiles, nil 173 } 174 175 // cgocc compiles all .c files. 176 // TODO(dfc) cxx not done 177 func cgocc(pkg *Package, cflags, cxxflags, cfiles, cxxfiles []string, deps ...*Action) ([]*Action, []string) { 178 workdir := cgoworkdir(pkg) 179 var cc []*Action 180 var ofiles []string 181 for _, cfile := range cfiles { 182 cfile := cfile 183 ofile := filepath.Join(workdir, stripext(filepath.Base(cfile))+".o") 184 ofiles = append(ofiles, ofile) 185 cc = append(cc, &Action{ 186 Name: "rungcc1: " + pkg.ImportPath + ": " + cfile, 187 Deps: deps, 188 Run: func() error { return rungcc1(pkg, cflags, ofile, cfile) }, 189 }) 190 } 191 192 for _, cxxfile := range cxxfiles { 193 cxxfile := cxxfile 194 ofile := filepath.Join(workdir, stripext(filepath.Base(cxxfile))+".o") 195 ofiles = append(ofiles, ofile) 196 cc = append(cc, &Action{ 197 Name: "rung++1: " + pkg.ImportPath + ": " + cxxfile, 198 Deps: deps, 199 Run: func() error { return rungpp1(pkg, cxxflags, ofile, cxxfile) }, 200 }) 201 } 202 203 return cc, ofiles 204 } 205 206 // rungcc1 invokes gcc to compile cfile into ofile 207 func rungcc1(pkg *Package, cgoCFLAGS []string, ofile, cfile string) error { 208 args := []string{"-g", "-O2", 209 "-I", pkg.Dir, 210 "-I", filepath.Dir(ofile), 211 } 212 args = append(args, cgoCFLAGS...) 213 args = append(args, 214 "-o", ofile, 215 "-c", cfile, 216 ) 217 t0 := time.Now() 218 gcc := gccCmd(pkg, pkg.Dir) 219 var buf bytes.Buffer 220 err := runOut(&buf, pkg.Dir, nil, gcc[0], append(gcc[1:], args...)...) 221 if err != nil { 222 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 223 io.Copy(os.Stderr, &buf) 224 } 225 pkg.Record(gcc[0], time.Since(t0)) 226 return err 227 } 228 229 // rungpp1 invokes g++ to compile cfile into ofile 230 func rungpp1(pkg *Package, cgoCFLAGS []string, ofile, cfile string) error { 231 args := []string{"-g", "-O2", 232 "-I", pkg.Dir, 233 "-I", filepath.Dir(ofile), 234 } 235 args = append(args, cgoCFLAGS...) 236 args = append(args, 237 "-o", ofile, 238 "-c", cfile, 239 ) 240 t0 := time.Now() 241 gxx := gxxCmd(pkg, pkg.Dir) 242 var buf bytes.Buffer 243 err := runOut(&buf, pkg.Dir, nil, gxx[0], append(gxx[1:], args...)...) 244 if err != nil { 245 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 246 io.Copy(os.Stderr, &buf) 247 } 248 pkg.Record(gxx[0], time.Since(t0)) 249 return err 250 } 251 252 // gccld links the o files from rungcc1 into a single _cgo_.o. 253 func gccld(pkg *Package, cgoCFLAGS, cgoLDFLAGS []string, ofile string, ofiles []string) error { 254 args := []string{} 255 args = append(args, "-o", ofile) 256 args = append(args, ofiles...) 257 args = append(args, cgoLDFLAGS...) // this has to go at the end, because reasons! 258 t0 := time.Now() 259 260 var cmd []string 261 if len(pkg.CXXFiles) > 0 || len(pkg.SwigCXXFiles) > 0 { 262 cmd = gxxCmd(pkg, pkg.Dir) 263 } else { 264 cmd = gccCmd(pkg, pkg.Dir) 265 } 266 var buf bytes.Buffer 267 err := runOut(&buf, pkg.Dir, nil, cmd[0], append(cmd[1:], args...)...) 268 if err != nil { 269 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 270 io.Copy(os.Stderr, &buf) 271 } 272 pkg.Record("gccld", time.Since(t0)) 273 return err 274 } 275 276 // rungcc3 links all previous ofiles together with libgcc into a single _all.o. 277 func rungcc3(pkg *Package, dir string, ofile string, ofiles []string) error { 278 args := []string{} 279 args = append(args, "-o", ofile) 280 args = append(args, ofiles...) 281 args = append(args, "-Wl,-r", "-nostdlib") 282 if gccSupportsNoPie(pkg) { 283 args = append(args, "-no-pie") 284 } 285 var cmd []string 286 if len(pkg.CXXFiles) > 0 || len(pkg.SwigCXXFiles) > 0 { 287 cmd = gxxCmd(pkg, dir) 288 } else { 289 cmd = gccCmd(pkg, dir) 290 } 291 if !strings.HasPrefix(cmd[0], "clang") { 292 libgcc, err := libgcc(pkg.Context) 293 if err != nil { 294 return nil 295 } 296 args = append(args, libgcc) 297 } 298 t0 := time.Now() 299 var buf bytes.Buffer 300 err := runOut(&buf, dir, nil, cmd[0], append(cmd[1:], args...)...) 301 if err != nil { 302 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 303 io.Copy(os.Stderr, &buf) 304 } 305 pkg.Record("gcc3", time.Since(t0)) 306 return err 307 } 308 309 // libgcc returns the value of gcc -print-libgcc-file-name. 310 func libgcc(ctx *Context) (string, error) { 311 args := []string{ 312 "-print-libgcc-file-name", 313 } 314 var buf bytes.Buffer 315 cmd := gccCmd(&Package{Context: ctx}, "") // TODO(dfc) hack 316 err := runOut(&buf, ".", nil, cmd[0], args...) 317 return strings.TrimSpace(buf.String()), err 318 } 319 320 // gccSupportsNoPie check if the -no-pie option is supported. On systems with 321 // PIE (position independent executables) enabled by default, -no-pie must be 322 // passed when doing a partial link with -Wl,-r. 323 func gccSupportsNoPie(pkg *Package) bool { 324 cmd := gccCmd(pkg, "") 325 args := []string{ 326 "-no-pie", 327 "-fsyntax-only", 328 "-xc", 329 "-", 330 } 331 return exec.Command(cmd[0], append(cmd[1:], args...)...).Run() == nil 332 } 333 334 func cgotool(ctx *Context) string { 335 return filepath.Join(runtime.GOROOT(), "pkg", "tool", ctx.gohostos+"_"+ctx.gohostarch, "cgo") 336 } 337 338 // envList returns the value of the given environment variable broken 339 // into fields, using the default value when the variable is empty. 340 func envList(key, def string) []string { 341 v := os.Getenv(key) 342 if v == "" { 343 v = def 344 } 345 return strings.Fields(v) 346 } 347 348 // Return the flags to use when invoking the C or C++ compilers, or cgo. 349 func cflags(p *Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { 350 var defaults string 351 if def { 352 defaults = "-g -O2" 353 } 354 355 cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) 356 cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) 357 cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) 358 ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) 359 return 360 } 361 362 // call pkg-config and return the cflags and ldflags. 363 func pkgconfig(p *Package) ([]string, []string, error) { 364 if len(p.CgoPkgConfig) == 0 { 365 return nil, nil, nil // nothing to do 366 } 367 args := []string{ 368 "--cflags", 369 } 370 args = append(args, p.CgoPkgConfig...) 371 var out bytes.Buffer 372 err := runOut(&out, p.Dir, nil, "pkg-config", args...) 373 if err != nil { 374 return nil, nil, err 375 } 376 cflags := strings.Fields(out.String()) 377 378 args = []string{ 379 "--libs", 380 } 381 args = append(args, p.CgoPkgConfig...) 382 out.Reset() 383 err = runOut(&out, p.Dir, nil, "pkg-config", args...) 384 if err != nil { 385 return nil, nil, err 386 } 387 ldflags := strings.Fields(out.String()) 388 return cflags, ldflags, nil 389 } 390 391 func quoteFlags(flags []string) []string { 392 quoted := make([]string, len(flags)) 393 for i, f := range flags { 394 quoted[i] = strconv.Quote(f) 395 } 396 return quoted 397 } 398 399 // runcgo1 invokes the cgo tool to process pkg.CgoFiles. 400 func runcgo1(pkg *Package, cflags, ldflags []string) error { 401 cgo := cgotool(pkg.Context) 402 workdir := cgoworkdir(pkg) 403 if err := mkdir(workdir); err != nil { 404 return err 405 } 406 407 args := []string{"-objdir", workdir} 408 switch { 409 case goversion == 1.4: 410 args = append(args, 411 "--", 412 "-I", pkg.Dir, 413 ) 414 case goversion > 1.4: 415 args = append(args, 416 "-importpath", pkg.ImportPath, 417 "--", 418 "-I", workdir, 419 "-I", pkg.Dir, 420 ) 421 default: 422 return errors.Errorf("unsupported Go version: %v", runtime.Version) 423 } 424 args = append(args, cflags...) 425 args = append(args, pkg.CgoFiles...) 426 427 cgoenv := []string{ 428 "CGO_CFLAGS=" + strings.Join(quoteFlags(cflags), " "), 429 "CGO_LDFLAGS=" + strings.Join(quoteFlags(ldflags), " "), 430 } 431 var buf bytes.Buffer 432 err := runOut(&buf, pkg.Dir, cgoenv, cgo, args...) 433 if err != nil { 434 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 435 io.Copy(os.Stderr, &buf) 436 } 437 return err 438 } 439 440 // runcgo2 invokes the cgo tool to create _cgo_import.go 441 func runcgo2(pkg *Package, dynout, ofile string) error { 442 cgo := cgotool(pkg.Context) 443 workdir := cgoworkdir(pkg) 444 445 args := []string{ 446 "-objdir", workdir, 447 } 448 switch { 449 case goversion == 1.4: 450 args = append(args, 451 "-dynimport", ofile, 452 "-dynout", dynout, 453 ) 454 case goversion > 1.4: 455 args = append(args, 456 "-dynpackage", pkg.Name, 457 "-dynimport", ofile, 458 "-dynout", dynout, 459 ) 460 default: 461 return errors.Errorf("unsuppored Go version: %v", runtime.Version) 462 } 463 var buf bytes.Buffer 464 err := runOut(&buf, pkg.Dir, nil, cgo, args...) 465 if err != nil { 466 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 467 io.Copy(os.Stderr, &buf) 468 } 469 return err 470 } 471 472 // cgoworkdir returns the cgo working directory for this package. 473 func cgoworkdir(pkg *Package) string { 474 return filepath.Join(Workdir(pkg), pkgname(pkg), "_cgo") 475 } 476 477 // gccCmd returns a gcc command line prefix. 478 func gccCmd(pkg *Package, objdir string) []string { 479 return ccompilerCmd(pkg, "CC", defaultCC, objdir) 480 } 481 482 // gxxCmd returns a g++ command line prefix. 483 func gxxCmd(pkg *Package, objdir string) []string { 484 return ccompilerCmd(pkg, "CXX", defaultCXX, objdir) 485 } 486 487 // ccompilerCmd returns a command line prefix for the given environment 488 // variable and using the default command when the variable is empty. 489 func ccompilerCmd(pkg *Package, envvar, defcmd, objdir string) []string { 490 compiler := envList(envvar, defcmd) 491 a := []string{compiler[0]} 492 if objdir != "" { 493 a = append(a, "-I", objdir) 494 } 495 a = append(a, compiler[1:]...) 496 497 // Definitely want -fPIC but on Windows gcc complains 498 // "-fPIC ignored for target (all code is position independent)" 499 if pkg.gotargetos != "windows" { 500 a = append(a, "-fPIC") 501 } 502 a = append(a, gccArchArgs(pkg.gotargetarch)...) 503 // gcc-4.5 and beyond require explicit "-pthread" flag 504 // for multithreading with pthread library. 505 switch pkg.gotargetos { 506 case "windows": 507 a = append(a, "-mthreads") 508 default: 509 a = append(a, "-pthread") 510 } 511 512 if strings.Contains(a[0], "clang") { 513 // disable ASCII art in clang errors, if possible 514 a = append(a, "-fno-caret-diagnostics") 515 // clang is too smart about command-line arguments 516 a = append(a, "-Qunused-arguments") 517 } 518 519 // disable word wrapping in error messages 520 a = append(a, "-fmessage-length=0") 521 522 // On OS X, some of the compilers behave as if -fno-common 523 // is always set, and the Mach-O linker in 6l/8l assumes this. 524 // See https://golang.org/issue/3253. 525 if pkg.gotargetos == "darwin" { 526 a = append(a, "-fno-common") 527 } 528 529 return a 530 } 531 532 // linkCmd returns the name of the binary to use for linking for the given 533 // environment variable and using the default command when the variable is 534 // empty. 535 func linkCmd(pkg *Package, envvar, defcmd string) string { 536 compiler := envList(envvar, defcmd) 537 return compiler[0] 538 } 539 540 // gccArchArgs returns arguments to pass to gcc based on the architecture. 541 func gccArchArgs(goarch string) []string { 542 switch goarch { 543 case "386": 544 return []string{"-m32"} 545 case "amd64", "amd64p32": 546 return []string{"-m64"} 547 case "arm": 548 return []string{"-marm"} // not thumb 549 } 550 return nil 551 }