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