"os"
"os/exec"
"path/filepath"
+ "regexp"
"strings"
"testing"
"time"
return dynstrings
}
-func AssertIsLinkedTo(t *testing.T, path, lib string) {
+func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) {
for _, dynstring := range dynStrings(path, elf.DT_NEEDED) {
- if dynstring == lib {
+ if re.MatchString(dynstring) {
return
}
}
- t.Errorf("%s is not linked to %s", path, lib)
+ t.Errorf("%s is not linked to anything matching %v", path, re)
+}
+
+func AssertIsLinkedTo(t *testing.T, path, lib string) {
+ AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
}
func AssertHasRPath(t *testing.T, path, dir string) {
// Build a GOPATH package into a shared library that links against the goroot runtime
// and an executable that links against both.
-func TestGOPathShlib(t *testing.T) {
+func TestGopathShlib(t *testing.T) {
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdep.so"), soname)
goCmd(t, "install", "-linkshared", "exe")
// Build a GOPATH package (dep) into a shared library that links against the goroot
// runtime, another package (dep2) that links against the first, and and an
// executable that links against dep2.
-func TestTwoGOPathShlibs(t *testing.T) {
+func TestTwoGopathShlibs(t *testing.T) {
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
goCmd(t, "install", "-linkshared", "exe2")
run(t, "executable linked to GOPATH library", "./bin/exe2")
}
+// Build a GOPATH package into a shared library with gccgo and an executable that
+// links against it.
+func TestGoPathShlibGccgo(t *testing.T) {
+ gccgoName := os.Getenv("GCCGO")
+ if gccgoName == "" {
+ gccgoName = "gccgo"
+ }
+ _, err := exec.LookPath(gccgoName)
+ if err != nil {
+ t.Skip("gccgo not found")
+ }
+
+ libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
+
+ gccgoContext := build.Default
+ gccgoContext.InstallSuffix = suffix + "_fPIC"
+ gccgoContext.Compiler = "gccgo"
+ gccgoContext.GOPATH = os.Getenv("GOPATH")
+ depP, err := gccgoContext.Import("dep", ".", build.ImportComment)
+ if err != nil {
+ t.Fatalf("import failed: %v", err)
+ }
+ gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
+ goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep")
+ AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep.so"), libgoRE)
+ goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe")
+ AssertIsLinkedToRegexp(t, "./bin/exe", libgoRE)
+ AssertIsLinkedTo(t, "./bin/exe", "libdep.so")
+ AssertHasRPath(t, "./bin/exe", gccgoInstallDir)
+ // And check it runs.
+ run(t, "gccgo-built", "./bin/exe")
+}
+
+// The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared
+// library with gccgo, another GOPATH package that depends on the first and an
+// executable that links the second library.
+func TestTwoGopathShlibsGccgo(t *testing.T) {
+ gccgoName := os.Getenv("GCCGO")
+ if gccgoName == "" {
+ gccgoName = "gccgo"
+ }
+ _, err := exec.LookPath(gccgoName)
+ if err != nil {
+ t.Skip("gccgo not found")
+ }
+
+ libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
+
+ gccgoContext := build.Default
+ gccgoContext.InstallSuffix = suffix + "_fPIC"
+ gccgoContext.Compiler = "gccgo"
+ gccgoContext.GOPATH = os.Getenv("GOPATH")
+ depP, err := gccgoContext.Import("dep", ".", build.ImportComment)
+ if err != nil {
+ t.Fatalf("import failed: %v", err)
+ }
+ gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
+ goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep")
+ goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep2")
+ goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe2")
+
+ AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep.so"), libgoRE)
+ AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep2.so"), libgoRE)
+ AssertIsLinkedTo(t, filepath.Join(gccgoInstallDir, "libdep2.so"), "libdep.so")
+ AssertIsLinkedToRegexp(t, "./bin/exe2", libgoRE)
+ AssertIsLinkedTo(t, "./bin/exe2", "libdep2")
+ AssertIsLinkedTo(t, "./bin/exe2", "libdep.so")
+
+ // And check it runs.
+ run(t, "gccgo-built", "./bin/exe2")
+}
+
// Testing rebuilding of shared libraries when they are stale is a bit more
// complicated that it seems like it should be. First, we make everything "old": but
// only a few seconds old, or it might be older than 6g (or the runtime source) and
"bufio"
"bytes"
"container/heap"
+ "debug/elf"
"errors"
"flag"
"fmt"
return pkg
}
-func readpkglist(shlibpath string) []*Package {
- pkglistbytes, err := readELFNote(shlibpath, "Go\x00\x00", 1)
- if err != nil {
- fatalf("readELFNote failed: %v", err)
- }
- scanner := bufio.NewScanner(bytes.NewBuffer(pkglistbytes))
- var pkgs []*Package
+// readpkglist returns the list of packages that were built into the shared library
+// at shlibpath. For the native toolchain this list is stored, newline separated, in
+// an ELF note with name "Go\x00\x00" and type 1. For GCCGO it is extracted from the
+// .go_export section.
+func readpkglist(shlibpath string) (pkgs []*Package) {
var stk importStack
- for scanner.Scan() {
- t := scanner.Text()
- pkgs = append(pkgs, loadPackage(t, &stk))
+ if _, gccgo := buildToolchain.(gccgoToolchain); gccgo {
+ f, _ := elf.Open(shlibpath)
+ sect := f.Section(".go_export")
+ data, _ := sect.Data()
+ scanner := bufio.NewScanner(bytes.NewBuffer(data))
+ for scanner.Scan() {
+ t := scanner.Text()
+ if strings.HasPrefix(t, "pkgpath ") {
+ t = strings.TrimPrefix(t, "pkgpath ")
+ t = strings.TrimSuffix(t, ";")
+ pkgs = append(pkgs, loadPackage(t, &stk))
+ }
+ }
+ } else {
+ pkglistbytes, err := readELFNote(shlibpath, "Go\x00\x00", 1)
+ if err != nil {
+ fatalf("readELFNote failed: %v", err)
+ }
+ scanner := bufio.NewScanner(bytes.NewBuffer(pkglistbytes))
+ for scanner.Scan() {
+ t := scanner.Text()
+ pkgs = append(pkgs, loadPackage(t, &stk))
+ }
}
- return pkgs
+ return
}
// action returns the action for applying the given operation (mode) to the package.
// was not passed on the command line and it is not present in
// another shared library, add it here.
seencgo := false
- for _, p := range pkgs {
- seencgo = seencgo || (p.Standard && p.ImportPath == "runtime/cgo")
- }
- if !seencgo {
- var stk importStack
- p := loadPackage("runtime/cgo", &stk)
- if p.Error != nil {
- fatalf("load runtime/cgo: %v", p.Error)
+ _, gccgo := buildToolchain.(gccgoToolchain)
+ if !gccgo {
+ for _, p := range pkgs {
+ seencgo = seencgo || (p.Standard && p.ImportPath == "runtime/cgo")
}
- computeStale(p)
- // If runtime/cgo is in another shared library, then that's
- // also the shared library that contains runtime, so
- // something will depend on it and so runtime/cgo's staleness
- // will be checked when processing that library.
- if p.Shlib == "" || p.Shlib == libname {
- pkgs = append([]*Package{}, pkgs...)
- pkgs = append(pkgs, p)
+ if !seencgo {
+ var stk importStack
+ p := loadPackage("runtime/cgo", &stk)
+ if p.Error != nil {
+ fatalf("load runtime/cgo: %v", p.Error)
+ }
+ computeStale(p)
+ // If runtime/cgo is in another shared library, then that's
+ // also the shared library that contains runtime, so
+ // something will depend on it and so runtime/cgo's staleness
+ // will be checked when processing that library.
+ if p.Shlib == "" || p.Shlib == libname {
+ pkgs = append([]*Package{}, pkgs...)
+ pkgs = append(pkgs, p)
+ }
}
}
var libdir string
for _, p := range pkgs {
plibdir := p.build.PkgTargetRoot
+ if gccgo {
+ plibdir = filepath.Join(plibdir, "shlibs")
+ }
if libdir == "" {
libdir = plibdir
} else if libdir != plibdir {
// linker needs the whole dependency tree.
all := actionList(a)
all = all[:len(all)-1] // drop a
- if err := buildToolchain.ld(b, a.p, a.target, all, a.objpkg, objects); err != nil {
+ if err := buildToolchain.ld(b, a, a.target, all, a.objpkg, objects); err != nil {
return err
}
}
return nil
}
-// setextld sets the appropriate linker flags for the specified compiler.
-func setextld(ldflags []string, compiler []string) []string {
- for _, f := range ldflags {
- if f == "-extld" || strings.HasPrefix(f, "-extld=") {
- // don't override -extld if supplied
- return ldflags
- }
- }
- ldflags = append(ldflags, "-extld="+compiler[0])
- if len(compiler) > 1 {
- extldflags := false
- add := strings.Join(compiler[1:], " ")
- for i, f := range ldflags {
- if f == "-extldflags" && i+1 < len(ldflags) {
- ldflags[i+1] = add + " " + ldflags[i+1]
- extldflags = true
- break
- } else if strings.HasPrefix(f, "-extldflags=") {
- ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
- extldflags = true
- break
- }
- }
- if !extldflags {
- ldflags = append(ldflags, "-extldflags="+add)
- }
- }
- return ldflags
-}
-
func (b *builder) linkShared(a *action) (err error) {
- // TODO(mwhudson): obvious copy pasting from gcToolchain.ld, should make a few
- // changes to that function and then call it. And support gccgo.
allactions := actionList(a)
- importArgs := b.includeArgs("-L", allactions[:len(allactions)-1])
- ldflags := []string{"-installsuffix", buildContext.InstallSuffix}
- ldflags = append(ldflags, "-buildmode=shared")
- ldflags = append(ldflags, buildLdflags...)
- cxx := a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0)
- for _, a := range allactions {
- if a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0) {
- cxx = true
- }
- }
- // If the user has not specified the -extld option, then specify the
- // appropriate linker. In case of C++ code, use the compiler named
- // by the CXX environment variable or defaultCXX if CXX is not set.
- // Else, use the CC environment variable and defaultCC as fallback.
- var compiler []string
- if cxx {
- compiler = envList("CXX", defaultCXX)
- } else {
- compiler = envList("CC", defaultCC)
- }
- ldflags = setextld(ldflags, compiler)
- for _, d := range a.deps {
- if !strings.HasSuffix(d.target, ".a") { // omit unsafe etc and actions for other shared libraries
- continue
- }
- ldflags = append(ldflags, d.p.ImportPath+"="+d.target)
- }
- return b.run(".", a.target, nil, buildToolExec, tool("link"), "-o", a.target, importArgs, ldflags)
+ allactions = allactions[:len(allactions)-1]
+ return buildToolchain.ldShared(b, a.deps, a.target, allactions)
}
// install is the action for installing a single package or executable.
// an archive from a set of object files.
// typically it is run in the object directory.
pack(b *builder, p *Package, objDir, afile string, ofiles []string) error
- // ld runs the linker to create a package starting at mainpkg.
- ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error
+ // ld runs the linker to create an executable starting at mainpkg.
+ ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error
+ // ldShared runs the linker to create a shared library containing the pkgs built by toplevelactions
+ ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error
compiler() string
linker() string
return noCompiler()
}
-func (noToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
+func (noToolchain) ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error {
+ return noCompiler()
+}
+
+func (noToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error {
return noCompiler()
}
return dst.Close()
}
-func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
+// setextld sets the appropriate linker flags for the specified compiler.
+func setextld(ldflags []string, compiler []string) []string {
+ for _, f := range ldflags {
+ if f == "-extld" || strings.HasPrefix(f, "-extld=") {
+ // don't override -extld if supplied
+ return ldflags
+ }
+ }
+ ldflags = append(ldflags, "-extld="+compiler[0])
+ if len(compiler) > 1 {
+ extldflags := false
+ add := strings.Join(compiler[1:], " ")
+ for i, f := range ldflags {
+ if f == "-extldflags" && i+1 < len(ldflags) {
+ ldflags[i+1] = add + " " + ldflags[i+1]
+ extldflags = true
+ break
+ } else if strings.HasPrefix(f, "-extldflags=") {
+ ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
+ extldflags = true
+ break
+ }
+ }
+ if !extldflags {
+ ldflags = append(ldflags, "-extldflags="+add)
+ }
+ }
+ return ldflags
+}
+
+func (gcToolchain) ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error {
importArgs := b.includeArgs("-L", allactions)
- cxx := len(p.CXXFiles) > 0 || len(p.SwigCXXFiles) > 0
+ cxx := len(root.p.CXXFiles) > 0 || len(root.p.SwigCXXFiles) > 0
for _, a := range allactions {
if a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0) {
cxx = true
if buildContext.InstallSuffix != "" {
ldflags = append(ldflags, "-installsuffix", buildContext.InstallSuffix)
}
- if p.omitDWARF {
+ if root.p.omitDWARF {
ldflags = append(ldflags, "-w")
}
}
ldflags = setextld(ldflags, compiler)
ldflags = append(ldflags, "-buildmode="+ldBuildmode)
- if p.buildID != "" {
- ldflags = append(ldflags, "-buildid="+p.buildID)
+ if root.p.buildID != "" {
+ ldflags = append(ldflags, "-buildid="+root.p.buildID)
}
ldflags = append(ldflags, buildLdflags...)
- return b.run(".", p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg)
+ return b.run(".", root.p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg)
+}
+
+func (gcToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error {
+ importArgs := b.includeArgs("-L", allactions)
+ ldflags := []string{"-installsuffix", buildContext.InstallSuffix}
+ ldflags = append(ldflags, "-buildmode=shared")
+ ldflags = append(ldflags, buildLdflags...)
+ cxx := false
+ for _, a := range allactions {
+ if a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0) {
+ cxx = true
+ }
+ }
+ // If the user has not specified the -extld option, then specify the
+ // appropriate linker. In case of C++ code, use the compiler named
+ // by the CXX environment variable or defaultCXX if CXX is not set.
+ // Else, use the CC environment variable and defaultCC as fallback.
+ var compiler []string
+ if cxx {
+ compiler = envList("CXX", defaultCXX)
+ } else {
+ compiler = envList("CC", defaultCC)
+ }
+ ldflags = setextld(ldflags, compiler)
+ for _, d := range toplevelactions {
+ if !strings.HasSuffix(d.target, ".a") { // omit unsafe etc and actions for other shared libraries
+ continue
+ }
+ ldflags = append(ldflags, d.p.ImportPath+"="+d.target)
+ }
+ return b.run(".", out, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags)
}
func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
return b.run(p.Dir, p.ImportPath, nil, "ar", "cru", mkAbs(objDir, afile), absOfiles)
}
-func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
+func (tools gccgoToolchain) ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error {
// gccgo needs explicit linking with all package dependencies,
// and all LDFLAGS from cgo dependencies.
apackagesSeen := make(map[*Package]bool)
afiles := []string{}
+ shlibs := []string{}
xfiles := []string{}
ldflags := b.gccArchArgs()
cgoldflags := []string{}
usesCgo := false
- cxx := len(p.CXXFiles) > 0 || len(p.SwigCXXFiles) > 0
- objc := len(p.MFiles) > 0
-
- // Prefer the output of an install action to the output of a build action,
- // because the install action will delete the output of the build action.
- // Iterate over the list backward (reverse dependency order) so that we
- // always see the install before the build.
- for i := len(allactions) - 1; i >= 0; i-- {
- a := allactions[i]
- if !a.p.Standard {
- if a.p != nil && !apackagesSeen[a.p] {
+ cxx := len(root.p.CXXFiles) > 0 || len(root.p.SwigCXXFiles) > 0
+ objc := len(root.p.MFiles) > 0
+
+ actionsSeen := make(map[*action]bool)
+ // Make a pre-order depth-first traversal of the action graph, taking note of
+ // whether a shared library action has been seen on the way to an action (the
+ // construction of the graph means that if any path to a node passes through
+ // a shared library action, they all do).
+ var walk func(a *action, seenShlib bool)
+ walk = func(a *action, seenShlib bool) {
+ if actionsSeen[a] {
+ return
+ }
+ actionsSeen[a] = true
+ if a.p != nil && !seenShlib {
+ if a.p.Standard {
+ return
+ }
+ // We record the target of the first time we see a .a file
+ // for a package to make sure that we prefer the 'install'
+ // rather than the 'build' location (which may not exist any
+ // more). We still need to traverse the dependencies of the
+ // build action though so saying
+ // if apackagesSeen[a.p] { return }
+ // doesn't work.
+ if !apackagesSeen[a.p] {
apackagesSeen[a.p] = true
if a.p.fake && a.p.external {
// external _tests, if present must come before
}
}
}
+ if strings.HasSuffix(a.target, ".so") {
+ shlibs = append(shlibs, a.target)
+ seenShlib = true
+ }
+ for _, a1 := range a.deps {
+ walk(a1, seenShlib)
+ }
+ }
+ for _, a1 := range root.deps {
+ walk(a1, false)
}
afiles = append(xfiles, afiles...)
// The go tool can dig up runtime/cgo from GOROOT and
// think that it should use its CgoLDFLAGS, but gccgo
// doesn't use runtime/cgo.
+ if a.p == nil {
+ continue
+ }
if !a.p.Standard {
cgoldflags = append(cgoldflags, a.p.CgoLDFLAGS...)
}
ldflags = append(ldflags, cgoldflags...)
ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...)
- ldflags = append(ldflags, p.CgoLDFLAGS...)
+ ldflags = append(ldflags, root.p.CgoLDFLAGS...)
ldflags = stringList("-Wl,-(", ldflags, "-Wl,-)")
+ for _, shlib := range shlibs {
+ ldflags = append(
+ ldflags,
+ "-L"+filepath.Dir(shlib),
+ "-Wl,-rpath="+filepath.Dir(shlib),
+ "-l"+strings.TrimSuffix(
+ strings.TrimPrefix(filepath.Base(shlib), "lib"),
+ ".so"))
+ }
+
var realOut string
switch ldBuildmode {
case "exe":
}
}
- if err := b.run(".", p.ImportPath, nil, tools.linker(), "-o", out, ofiles, ldflags, buildGccgoflags); err != nil {
+ if err := b.run(".", root.p.ImportPath, nil, tools.linker(), "-o", out, ofiles, ldflags, buildGccgoflags); err != nil {
return err
}
switch ldBuildmode {
case "c-archive":
- if err := b.run(".", p.ImportPath, nil, "ar", "rc", realOut, out); err != nil {
+ if err := b.run(".", root.p.ImportPath, nil, "ar", "rc", realOut, out); err != nil {
return err
}
}
-
return nil
}
+func (tools gccgoToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error {
+ args := []string{"-o", out, "-shared", "-nostdlib", "-zdefs", "-Wl,--whole-archive"}
+ for _, a := range toplevelactions {
+ args = append(args, a.target)
+ }
+ args = append(args, "-Wl,--no-whole-archive", "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc")
+ shlibs := []string{}
+ for _, a := range allactions {
+ if strings.HasSuffix(a.target, ".so") {
+ shlibs = append(shlibs, a.target)
+ }
+ }
+ for _, shlib := range shlibs {
+ args = append(
+ args,
+ "-L"+filepath.Dir(shlib),
+ "-Wl,-rpath="+filepath.Dir(shlib),
+ "-l"+strings.TrimSuffix(
+ strings.TrimPrefix(filepath.Base(shlib), "lib"),
+ ".so"))
+ }
+ return b.run(".", out, nil, tools.linker(), args, buildGccgoflags)
+}
+
func (tools gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", "include")
cfile = mkAbs(p.Dir, cfile)