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