1// Copyright 2011 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package work 6 7import ( 8 "fmt" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 15 "cmd/go/internal/base" 16 "cmd/go/internal/cfg" 17 "cmd/go/internal/load" 18 "cmd/go/internal/str" 19) 20 21// The Gccgo toolchain. 22 23type gccgoToolchain struct{} 24 25var GccgoName, GccgoBin string 26var gccgoErr error 27 28func init() { 29 GccgoName = os.Getenv("GCCGO") 30 if GccgoName == "" { 31 GccgoName = cfg.DefaultGCCGO(cfg.Goos, cfg.Goarch) 32 } 33 GccgoBin, gccgoErr = exec.LookPath(GccgoName) 34} 35 36func (gccgoToolchain) compiler() string { 37 checkGccgoBin() 38 return GccgoBin 39} 40 41func (gccgoToolchain) linker() string { 42 checkGccgoBin() 43 return GccgoBin 44} 45 46func (gccgoToolchain) ar() string { 47 ar := os.Getenv("AR") 48 if ar == "" { 49 ar = "ar" 50 } 51 return ar 52} 53 54func checkGccgoBin() { 55 if gccgoErr == nil { 56 return 57 } 58 fmt.Fprintf(os.Stderr, "cmd/go: gccgo: %s\n", gccgoErr) 59 base.SetExitStatus(2) 60 base.Exit() 61} 62 63func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) { 64 p := a.Package 65 objdir := a.Objdir 66 out := "_go_.o" 67 ofile = objdir + out 68 gcargs := []string{"-g"} 69 gcargs = append(gcargs, b.gccArchArgs()...) 70 gcargs = append(gcargs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build") 71 gcargs = append(gcargs, "-gno-record-gcc-switches") 72 if pkgpath := gccgoPkgpath(p); pkgpath != "" { 73 gcargs = append(gcargs, "-fgo-pkgpath="+pkgpath) 74 } 75 if p.Internal.LocalPrefix != "" { 76 gcargs = append(gcargs, "-fgo-relative-import-path="+p.Internal.LocalPrefix) 77 } 78 79 args := str.StringList(tools.compiler(), "-c", "-O2", gcargs, "-o", ofile, forcedGccgoflags) 80 if importcfg != nil { 81 if b.gccSupportsFlag(args[:1], "-fgo-importcfg=/dev/null") { 82 if err := b.writeFile(objdir+"importcfg", importcfg); err != nil { 83 return "", nil, err 84 } 85 args = append(args, "-fgo-importcfg="+objdir+"importcfg") 86 } else { 87 root := objdir + "_importcfgroot_" 88 if err := buildImportcfgSymlinks(b, root, importcfg); err != nil { 89 return "", nil, err 90 } 91 args = append(args, "-I", root) 92 } 93 } 94 args = append(args, a.Package.Internal.Gccgoflags...) 95 for _, f := range gofiles { 96 args = append(args, mkAbs(p.Dir, f)) 97 } 98 99 output, err = b.runOut(p.Dir, nil, args) 100 return ofile, output, err 101} 102 103// buildImportcfgSymlinks builds in root a tree of symlinks 104// implementing the directives from importcfg. 105// This serves as a temporary transition mechanism until 106// we can depend on gccgo reading an importcfg directly. 107// (The Go 1.9 and later gc compilers already do.) 108func buildImportcfgSymlinks(b *Builder, root string, importcfg []byte) error { 109 for lineNum, line := range strings.Split(string(importcfg), "\n") { 110 lineNum++ // 1-based 111 line = strings.TrimSpace(line) 112 if line == "" { 113 continue 114 } 115 if line == "" || strings.HasPrefix(line, "#") { 116 continue 117 } 118 var verb, args string 119 if i := strings.Index(line, " "); i < 0 { 120 verb = line 121 } else { 122 verb, args = line[:i], strings.TrimSpace(line[i+1:]) 123 } 124 var before, after string 125 if i := strings.Index(args, "="); i >= 0 { 126 before, after = args[:i], args[i+1:] 127 } 128 switch verb { 129 default: 130 base.Fatalf("importcfg:%d: unknown directive %q", lineNum, verb) 131 case "packagefile": 132 if before == "" || after == "" { 133 return fmt.Errorf(`importcfg:%d: invalid packagefile: syntax is "packagefile path=filename": %s`, lineNum, line) 134 } 135 archive := gccgoArchive(root, before) 136 if err := b.Mkdir(filepath.Dir(archive)); err != nil { 137 return err 138 } 139 if err := b.Symlink(after, archive); err != nil { 140 return err 141 } 142 case "importmap": 143 if before == "" || after == "" { 144 return fmt.Errorf(`importcfg:%d: invalid importmap: syntax is "importmap old=new": %s`, lineNum, line) 145 } 146 beforeA := gccgoArchive(root, before) 147 afterA := gccgoArchive(root, after) 148 if err := b.Mkdir(filepath.Dir(beforeA)); err != nil { 149 return err 150 } 151 if err := b.Mkdir(filepath.Dir(afterA)); err != nil { 152 return err 153 } 154 if err := b.Symlink(afterA, beforeA); err != nil { 155 return err 156 } 157 case "packageshlib": 158 return fmt.Errorf("gccgo -importcfg does not support shared libraries") 159 } 160 } 161 return nil 162} 163 164func (tools gccgoToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) { 165 p := a.Package 166 var ofiles []string 167 for _, sfile := range sfiles { 168 base := filepath.Base(sfile) 169 ofile := a.Objdir + base[:len(base)-len(".s")] + ".o" 170 ofiles = append(ofiles, ofile) 171 sfile = mkAbs(p.Dir, sfile) 172 defs := []string{"-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch} 173 if pkgpath := gccgoCleanPkgpath(p); pkgpath != "" { 174 defs = append(defs, `-D`, `GOPKGPATH=`+pkgpath) 175 } 176 defs = tools.maybePIC(defs) 177 defs = append(defs, b.gccArchArgs()...) 178 err := b.run(a, p.Dir, p.ImportPath, nil, tools.compiler(), "-xassembler-with-cpp", "-I", a.Objdir, "-c", "-o", ofile, defs, sfile) 179 if err != nil { 180 return nil, err 181 } 182 } 183 return ofiles, nil 184} 185 186func (gccgoToolchain) symabis(b *Builder, a *Action, sfiles []string) (string, error) { 187 return "", nil 188} 189 190func gccgoArchive(basedir, imp string) string { 191 end := filepath.FromSlash(imp + ".a") 192 afile := filepath.Join(basedir, end) 193 // add "lib" to the final element 194 return filepath.Join(filepath.Dir(afile), "lib"+filepath.Base(afile)) 195} 196 197func (tools gccgoToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error { 198 p := a.Package 199 objdir := a.Objdir 200 var absOfiles []string 201 for _, f := range ofiles { 202 absOfiles = append(absOfiles, mkAbs(objdir, f)) 203 } 204 var arArgs []string 205 if cfg.Goos == "aix" && cfg.Goarch == "ppc64" { 206 // AIX puts both 32-bit and 64-bit objects in the same archive. 207 // Tell the AIX "ar" command to only care about 64-bit objects. 208 arArgs = []string{"-X64"} 209 } 210 absAfile := mkAbs(objdir, afile) 211 // Try with D modifier first, then without if that fails. 212 if b.run(a, p.Dir, p.ImportPath, nil, tools.ar(), arArgs, "rcD", absAfile, absOfiles) != nil { 213 return b.run(a, p.Dir, p.ImportPath, nil, tools.ar(), arArgs, "rc", absAfile, absOfiles) 214 } 215 return nil 216} 217 218func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string, allactions []*Action, buildmode, desc string) error { 219 // gccgo needs explicit linking with all package dependencies, 220 // and all LDFLAGS from cgo dependencies. 221 afiles := []string{} 222 shlibs := []string{} 223 ldflags := b.gccArchArgs() 224 cgoldflags := []string{} 225 usesCgo := false 226 cxx := false 227 objc := false 228 fortran := false 229 if root.Package != nil { 230 cxx = len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0 231 objc = len(root.Package.MFiles) > 0 232 fortran = len(root.Package.FFiles) > 0 233 } 234 235 readCgoFlags := func(flagsFile string) error { 236 flags, err := ioutil.ReadFile(flagsFile) 237 if err != nil { 238 return err 239 } 240 const ldflagsPrefix = "_CGO_LDFLAGS=" 241 for _, line := range strings.Split(string(flags), "\n") { 242 if strings.HasPrefix(line, ldflagsPrefix) { 243 line = line[len(ldflagsPrefix):] 244 quote := byte(0) 245 start := true 246 var nl []byte 247 for len(line) > 0 { 248 b := line[0] 249 line = line[1:] 250 if quote == 0 && (b == ' ' || b == '\t') { 251 if len(nl) > 0 { 252 cgoldflags = append(cgoldflags, string(nl)) 253 nl = nil 254 } 255 start = true 256 continue 257 } else if b == '"' || b == '\'' { 258 quote = b 259 } else if b == quote { 260 quote = 0 261 } else if quote == 0 && start && b == '-' && strings.HasPrefix(line, "g") { 262 line = line[1:] 263 continue 264 } else if quote == 0 && start && b == '-' && strings.HasPrefix(line, "O") { 265 for len(line) > 0 && line[0] != ' ' && line[0] != '\t' { 266 line = line[1:] 267 } 268 continue 269 } 270 nl = append(nl, b) 271 start = false 272 } 273 if len(nl) > 0 { 274 cgoldflags = append(cgoldflags, string(nl)) 275 } 276 } 277 } 278 return nil 279 } 280 281 var arArgs []string 282 if cfg.Goos == "aix" && cfg.Goarch == "ppc64" { 283 // AIX puts both 32-bit and 64-bit objects in the same archive. 284 // Tell the AIX "ar" command to only care about 64-bit objects. 285 arArgs = []string{"-X64"} 286 } 287 288 newID := 0 289 readAndRemoveCgoFlags := func(archive string) (string, error) { 290 newID++ 291 newArchive := root.Objdir + fmt.Sprintf("_pkg%d_.a", newID) 292 if err := b.copyFile(newArchive, archive, 0666, false); err != nil { 293 return "", err 294 } 295 if cfg.BuildN { 296 // TODO(rsc): We could do better about showing the right _cgo_flags even in -n mode. 297 // Either the archive is already built and we can read them out, 298 // or we're printing commands to build the archive and can 299 // forward the _cgo_flags directly to this step. 300 b.Showcmd("", "ar d %s _cgo_flags", newArchive) 301 return "", nil 302 } 303 err := b.run(root, root.Objdir, desc, nil, tools.ar(), arArgs, "x", newArchive, "_cgo_flags") 304 if err != nil { 305 return "", err 306 } 307 err = b.run(root, ".", desc, nil, tools.ar(), arArgs, "d", newArchive, "_cgo_flags") 308 if err != nil { 309 return "", err 310 } 311 err = readCgoFlags(filepath.Join(root.Objdir, "_cgo_flags")) 312 if err != nil { 313 return "", err 314 } 315 return newArchive, nil 316 } 317 318 // If using -linkshared, find the shared library deps. 319 haveShlib := make(map[string]bool) 320 targetBase := filepath.Base(root.Target) 321 if cfg.BuildLinkshared { 322 for _, a := range root.Deps { 323 p := a.Package 324 if p == nil || p.Shlib == "" { 325 continue 326 } 327 328 // The .a we are linking into this .so 329 // will have its Shlib set to this .so. 330 // Don't start thinking we want to link 331 // this .so into itself. 332 base := filepath.Base(p.Shlib) 333 if base != targetBase { 334 haveShlib[base] = true 335 } 336 } 337 } 338 339 // Arrange the deps into afiles and shlibs. 340 addedShlib := make(map[string]bool) 341 for _, a := range root.Deps { 342 p := a.Package 343 if p != nil && p.Shlib != "" && haveShlib[filepath.Base(p.Shlib)] { 344 // This is a package linked into a shared 345 // library that we will put into shlibs. 346 continue 347 } 348 349 if haveShlib[filepath.Base(a.Target)] { 350 // This is a shared library we want to link againt. 351 if !addedShlib[a.Target] { 352 shlibs = append(shlibs, a.Target) 353 addedShlib[a.Target] = true 354 } 355 continue 356 } 357 358 if p != nil { 359 target := a.built 360 if p.UsesCgo() || p.UsesSwig() { 361 var err error 362 target, err = readAndRemoveCgoFlags(target) 363 if err != nil { 364 continue 365 } 366 } 367 368 afiles = append(afiles, target) 369 } 370 } 371 372 for _, a := range allactions { 373 // Gather CgoLDFLAGS, but not from standard packages. 374 // The go tool can dig up runtime/cgo from GOROOT and 375 // think that it should use its CgoLDFLAGS, but gccgo 376 // doesn't use runtime/cgo. 377 if a.Package == nil { 378 continue 379 } 380 if !a.Package.Standard { 381 cgoldflags = append(cgoldflags, a.Package.CgoLDFLAGS...) 382 } 383 if len(a.Package.CgoFiles) > 0 { 384 usesCgo = true 385 } 386 if a.Package.UsesSwig() { 387 usesCgo = true 388 } 389 if len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0 { 390 cxx = true 391 } 392 if len(a.Package.MFiles) > 0 { 393 objc = true 394 } 395 if len(a.Package.FFiles) > 0 { 396 fortran = true 397 } 398 } 399 400 wholeArchive := []string{"-Wl,--whole-archive"} 401 noWholeArchive := []string{"-Wl,--no-whole-archive"} 402 if cfg.Goos == "aix" { 403 wholeArchive = nil 404 noWholeArchive = nil 405 } 406 ldflags = append(ldflags, wholeArchive...) 407 ldflags = append(ldflags, afiles...) 408 ldflags = append(ldflags, noWholeArchive...) 409 410 ldflags = append(ldflags, cgoldflags...) 411 ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...) 412 if root.Package != nil { 413 ldflags = append(ldflags, root.Package.CgoLDFLAGS...) 414 } 415 if cfg.Goos != "aix" { 416 ldflags = str.StringList("-Wl,-(", ldflags, "-Wl,-)") 417 } 418 419 if root.buildID != "" { 420 // On systems that normally use gold or the GNU linker, 421 // use the --build-id option to write a GNU build ID note. 422 switch cfg.Goos { 423 case "android", "dragonfly", "linux", "netbsd": 424 ldflags = append(ldflags, fmt.Sprintf("-Wl,--build-id=0x%x", root.buildID)) 425 } 426 } 427 428 var rLibPath string 429 if cfg.Goos == "aix" { 430 rLibPath = "-Wl,-blibpath=" 431 } else { 432 rLibPath = "-Wl,-rpath=" 433 } 434 for _, shlib := range shlibs { 435 ldflags = append( 436 ldflags, 437 "-L"+filepath.Dir(shlib), 438 rLibPath+filepath.Dir(shlib), 439 "-l"+strings.TrimSuffix( 440 strings.TrimPrefix(filepath.Base(shlib), "lib"), 441 ".so")) 442 } 443 444 var realOut string 445 goLibBegin := str.StringList(wholeArchive, "-lgolibbegin", noWholeArchive) 446 switch buildmode { 447 case "exe": 448 if usesCgo && cfg.Goos == "linux" { 449 ldflags = append(ldflags, "-Wl,-E") 450 } 451 452 case "c-archive": 453 // Link the Go files into a single .o, and also link 454 // in -lgolibbegin. 455 // 456 // We need to use --whole-archive with -lgolibbegin 457 // because it doesn't define any symbols that will 458 // cause the contents to be pulled in; it's just 459 // initialization code. 460 // 461 // The user remains responsible for linking against 462 // -lgo -lpthread -lm in the final link. We can't use 463 // -r to pick them up because we can't combine 464 // split-stack and non-split-stack code in a single -r 465 // link, and libgo picks up non-split-stack code from 466 // libffi. 467 ldflags = append(ldflags, "-Wl,-r", "-nostdlib") 468 ldflags = append(ldflags, goLibBegin...) 469 470 if nopie := b.gccNoPie([]string{tools.linker()}); nopie != "" { 471 ldflags = append(ldflags, nopie) 472 } 473 474 // We are creating an object file, so we don't want a build ID. 475 if root.buildID == "" { 476 ldflags = b.disableBuildID(ldflags) 477 } 478 479 realOut = out 480 out = out + ".o" 481 482 case "c-shared": 483 ldflags = append(ldflags, "-shared", "-nostdlib") 484 ldflags = append(ldflags, goLibBegin...) 485 ldflags = append(ldflags, "-lgo", "-lgcc_s", "-lgcc", "-lc", "-lgcc") 486 case "shared": 487 if cfg.Goos != "aix" { 488 ldflags = append(ldflags, "-zdefs") 489 } 490 ldflags = append(ldflags, "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc") 491 492 case "pie": 493 ldflags = append(ldflags, "-pie") 494 495 default: 496 base.Fatalf("-buildmode=%s not supported for gccgo", buildmode) 497 } 498 499 switch buildmode { 500 case "exe", "c-shared": 501 if cxx { 502 ldflags = append(ldflags, "-lstdc++") 503 } 504 if objc { 505 ldflags = append(ldflags, "-lobjc") 506 } 507 if fortran { 508 fc := os.Getenv("FC") 509 if fc == "" { 510 fc = "gfortran" 511 } 512 // support gfortran out of the box and let others pass the correct link options 513 // via CGO_LDFLAGS 514 if strings.Contains(fc, "gfortran") { 515 ldflags = append(ldflags, "-lgfortran") 516 } 517 } 518 } 519 520 if err := b.run(root, ".", desc, nil, tools.linker(), "-o", out, ldflags, forcedGccgoflags, root.Package.Internal.Gccgoflags); err != nil { 521 return err 522 } 523 524 switch buildmode { 525 case "c-archive": 526 if err := b.run(root, ".", desc, nil, tools.ar(), arArgs, "rc", realOut, out); err != nil { 527 return err 528 } 529 } 530 return nil 531} 532 533func (tools gccgoToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error { 534 return tools.link(b, root, out, importcfg, root.Deps, ldBuildmode, root.Package.ImportPath) 535} 536 537func (tools gccgoToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error { 538 return tools.link(b, root, out, importcfg, allactions, "shared", out) 539} 540 541func (tools gccgoToolchain) cc(b *Builder, a *Action, ofile, cfile string) error { 542 p := a.Package 543 inc := filepath.Join(cfg.GOROOT, "pkg", "include") 544 cfile = mkAbs(p.Dir, cfile) 545 defs := []string{"-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch} 546 defs = append(defs, b.gccArchArgs()...) 547 if pkgpath := gccgoCleanPkgpath(p); pkgpath != "" { 548 defs = append(defs, `-D`, `GOPKGPATH="`+pkgpath+`"`) 549 } 550 compiler := envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch)) 551 if b.gccSupportsFlag(compiler, "-fsplit-stack") { 552 defs = append(defs, "-fsplit-stack") 553 } 554 defs = tools.maybePIC(defs) 555 if b.gccSupportsFlag(compiler, "-fdebug-prefix-map=a=b") { 556 defs = append(defs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build") 557 } 558 if b.gccSupportsFlag(compiler, "-gno-record-gcc-switches") { 559 defs = append(defs, "-gno-record-gcc-switches") 560 } 561 return b.run(a, p.Dir, p.ImportPath, nil, compiler, "-Wall", "-g", 562 "-I", a.Objdir, "-I", inc, "-o", ofile, defs, "-c", cfile) 563} 564 565// maybePIC adds -fPIC to the list of arguments if needed. 566func (tools gccgoToolchain) maybePIC(args []string) []string { 567 switch cfg.BuildBuildmode { 568 case "c-archive", "c-shared", "shared", "plugin": 569 args = append(args, "-fPIC") 570 } 571 return args 572} 573 574func gccgoPkgpath(p *load.Package) string { 575 if p.Internal.Build.IsCommand() && !p.Internal.ForceLibrary { 576 return "" 577 } 578 if p.ImportPath == "main" && p.Internal.ForceLibrary { 579 return "testmain" 580 } 581 return p.ImportPath 582} 583 584func gccgoCleanPkgpath(p *load.Package) string { 585 clean := func(r rune) rune { 586 switch { 587 case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z', 588 '0' <= r && r <= '9': 589 return r 590 } 591 return '_' 592 } 593 return strings.Map(clean, gccgoPkgpath(p)) 594} 595