1
0
mirror of https://github.com/golang/go synced 2024-11-23 04:10:04 -07:00

cmd/go: clean up compile vs link vs shared library actions

Everything got a bit tangled together in b.action1.
Try to tease things apart again.

In general this is a refactoring of the existing code, with limited
changes to the effect of the code.

The main additional change is to complete a.Deps for link actions.
That list now directly contains all the inputs the linker will attempt
to read, eliminating the need for a transitive traversal of the entire
action graph to find those. The comepleteness of a.Deps will be
important when we eventually use it to decide whether an cached
action output can be reused.

all.bash passes, but it's possible I've broken some subtety of
buildmode=shared again. Certainly that code took the longest
to get working.

Change-Id: I34e849eda446dca45a9cfce02b07bec6edb6d0d4
Reviewed-on: https://go-review.googlesource.com/69831
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Russ Cox 2017-10-09 21:07:32 -04:00
parent 2595fe7fb6
commit 4e8be99590
6 changed files with 469 additions and 337 deletions

View File

@ -134,8 +134,10 @@ func cmdToRun(name string) []string {
}
func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
t.Helper()
cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
cmd.Env = gopathEnv
t.Log(buildcmd)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)

View File

@ -465,13 +465,13 @@ func TestGopathShlib(t *testing.T) {
// that is not mapped into memory.
func testPkgListNote(t *testing.T, f *elf.File, note *note) {
if note.section.Flags != 0 {
t.Errorf("package list section has flags %v", note.section.Flags)
t.Errorf("package list section has flags %v, want 0", note.section.Flags)
}
if isOffsetLoaded(f, note.section.Offset) {
t.Errorf("package list section contained in PT_LOAD segment")
}
if note.desc != "depBase\n" {
t.Errorf("incorrect package list %q", note.desc)
t.Errorf("incorrect package list %q, want %q", note.desc, "depBase\n")
}
}
@ -480,7 +480,7 @@ func testPkgListNote(t *testing.T, f *elf.File, note *note) {
// bytes into it.
func testABIHashNote(t *testing.T, f *elf.File, note *note) {
if note.section.Flags != elf.SHF_ALLOC {
t.Errorf("abi hash section has flags %v", note.section.Flags)
t.Errorf("abi hash section has flags %v, want SHF_ALLOC", note.section.Flags)
}
if !isOffsetLoaded(f, note.section.Offset) {
t.Errorf("abihash section not contained in PT_LOAD segment")
@ -501,13 +501,13 @@ func testABIHashNote(t *testing.T, f *elf.File, note *note) {
return
}
if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
t.Errorf("%s has incorrect binding %v", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
}
if f.Sections[hashbytes.Section] != note.section {
t.Errorf("%s has incorrect section %v", hashbytes.Name, f.Sections[hashbytes.Section].Name)
t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section)
}
if hashbytes.Value-note.section.Addr != 16 {
t.Errorf("%s has incorrect offset into section %d", hashbytes.Name, hashbytes.Value-note.section.Addr)
t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr)
}
}
@ -515,14 +515,14 @@ func testABIHashNote(t *testing.T, f *elf.File, note *note) {
// was linked against in an unmapped section.
func testDepsNote(t *testing.T, f *elf.File, note *note) {
if note.section.Flags != 0 {
t.Errorf("package list section has flags %v", note.section.Flags)
t.Errorf("package list section has flags %v, want 0", note.section.Flags)
}
if isOffsetLoaded(f, note.section.Offset) {
t.Errorf("package list section contained in PT_LOAD segment")
}
// libdepBase.so just links against the lib containing the runtime.
if note.desc != soname {
t.Errorf("incorrect dependency list %q", note.desc)
t.Errorf("incorrect dependency list %q, want %q", note.desc, soname)
}
}
@ -560,7 +560,7 @@ func TestNotes(t *testing.T) {
abiHashNoteFound = true
case 3: // ELF_NOTE_GODEPS_TAG
if depsNoteFound {
t.Error("multiple abi hash notes")
t.Error("multiple depedency list notes")
}
testDepsNote(t, f, note)
depsNoteFound = true

View File

@ -911,16 +911,16 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
if cfg.BuildLinkshared {
shlibnamefile := p.Internal.Target[:len(p.Internal.Target)-2] + ".shlibname"
shlib, err := ioutil.ReadFile(shlibnamefile)
if err != nil && !os.IsNotExist(err) {
base.Fatalf("reading shlibname: %v", err)
}
if err == nil {
libname := strings.TrimSpace(string(shlib))
if cfg.BuildContext.Compiler == "gccgo" {
p.Shlib = filepath.Join(p.Internal.Build.PkgTargetRoot, "shlibs", libname)
} else {
p.Shlib = filepath.Join(p.Internal.Build.PkgTargetRoot, libname)
}
} else if !os.IsNotExist(err) {
base.Fatalf("unexpected error reading %s: %v", shlibnamefile, err)
}
}
}

View File

@ -110,7 +110,7 @@ func runRun(cmd *base.Command, args []string) {
base.Fatalf("go run: no suitable source files%s", hint)
}
p.Internal.ExeName = src[:len(src)-len(".go")] // name temporary executable for first go file
a1 := b.Action(work.ModeBuild, work.ModeBuild, p)
a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p)
a := &work.Action{Mode: "go run", Func: buildRunProgram, Args: cmdArgs, Deps: []*work.Action{a1}}
b.Do(a)
}

View File

@ -524,7 +524,7 @@ func runTest(cmd *base.Command, args []string) {
a := &work.Action{Mode: "go test -i"}
for _, p := range load.PackagesForBuild(all) {
a.Deps = append(a.Deps, b.Action(work.ModeInstall, work.ModeInstall, p))
a.Deps = append(a.Deps, b.CompileAction(work.ModeInstall, work.ModeInstall, p))
}
b.Do(a)
if !testC || a.Failed {
@ -651,7 +651,7 @@ var windowsBadWords = []string{
func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, printAction *work.Action, err error) {
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
build := b.Action(work.ModeBuild, work.ModeBuild, p)
build := b.CompileAction(work.ModeBuild, work.ModeBuild, p)
run := &work.Action{Mode: "test run", Package: p, Deps: []*work.Action{build}}
print := &work.Action{Mode: "test print", Func: builderNoTest, Package: p, Deps: []*work.Action{run}}
return build, run, print, nil
@ -896,7 +896,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
load.ComputeStale(pmain)
a := b.Action(work.ModeBuild, work.ModeBuild, pmain)
a := b.LinkAction(work.ModeBuild, work.ModeBuild, pmain)
a.Target = testDir + testBinary + cfg.ExeSuffix
if cfg.Goos == "windows" {
// There are many reserved words on Windows that,

View File

@ -463,25 +463,19 @@ func runBuild(cmd *base.Command, args []string) {
p.Internal.Target = cfg.BuildO
p.Stale = true // must build - not up to date
p.StaleReason = "build -o flag in use"
a := b.Action(ModeInstall, depMode, p)
a := b.AutoAction(ModeInstall, depMode, p)
b.Do(a)
return
}
pkgs = pkgsFilter(load.Packages(args))
var a *Action
a := &Action{Mode: "go build"}
for _, p := range pkgs {
a.Deps = append(a.Deps, b.AutoAction(ModeBuild, depMode, p))
}
if cfg.BuildBuildmode == "shared" {
if libName, err := libname(args, pkgs); err != nil {
base.Fatalf("%s", err.Error())
} else {
a = b.libaction(libName, pkgs, ModeBuild, depMode)
}
} else {
a = &Action{Mode: "go build"}
for _, p := range pkgs {
a.Deps = append(a.Deps, b.Action(ModeBuild, depMode, p))
}
a = b.buildmodeShared(ModeBuild, depMode, args, pkgs, a)
}
b.Do(a)
}
@ -588,41 +582,42 @@ func InstallPackages(args []string, forGet bool) {
var b Builder
b.Init()
var a *Action
if cfg.BuildBuildmode == "shared" {
if libName, err := libname(args, pkgs); err != nil {
base.Fatalf("%s", err.Error())
} else {
a = b.libaction(libName, pkgs, ModeInstall, ModeInstall)
a := &Action{Mode: "go install"}
var tools []*Action
for _, p := range pkgs {
// During 'go get', don't attempt (and fail) to install packages with only tests.
// TODO(rsc): It's not clear why 'go get' should be different from 'go install' here. See #20760.
if forGet && len(p.GoFiles)+len(p.CgoFiles) == 0 && len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
continue
}
} else {
a = &Action{Mode: "go install"}
var tools []*Action
for _, p := range pkgs {
// During 'go get', don't attempt (and fail) to install packages with only tests.
// TODO(rsc): It's not clear why 'go get' should be different from 'go install' here. See #20760.
if forGet && len(p.GoFiles)+len(p.CgoFiles) == 0 && len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
continue
}
// If p is a tool, delay the installation until the end of the build.
// This avoids installing assemblers/compilers that are being executed
// by other steps in the build.
Action := b.Action(ModeInstall, ModeInstall, p)
if load.GoTools[p.ImportPath] == load.ToTool {
a.Deps = append(a.Deps, Action.Deps...)
Action.Deps = append(Action.Deps, a)
tools = append(tools, Action)
continue
}
a.Deps = append(a.Deps, Action)
// If p is a tool, delay the installation until the end of the build.
// This avoids installing assemblers/compilers that are being executed
// by other steps in the build.
a1 := b.AutoAction(ModeInstall, ModeInstall, p)
if load.GoTools[p.ImportPath] == load.ToTool {
a.Deps = append(a.Deps, a1.Deps...)
a1.Deps = append(a1.Deps, a)
tools = append(tools, a1)
continue
}
if len(tools) > 0 {
a = &Action{
Mode: "go install (tools)",
Deps: tools,
}
a.Deps = append(a.Deps, a1)
}
if len(tools) > 0 {
a = &Action{
Mode: "go install (tools)",
Deps: tools,
}
}
if cfg.BuildBuildmode == "shared" {
// Note: If buildmode=shared then only non-main packages
// are present in the pkgs list, so all the special case code about
// tools above did not apply, and a is just a simple Action
// with a list of Deps, one per package named in pkgs,
// the same as in runBuild.
a = b.buildmodeShared(ModeInstall, ModeInstall, args, pkgs, a)
}
b.Do(a)
base.ExitIfErrors()
@ -717,9 +712,8 @@ type actionJSON struct {
// cacheKey is the key for the action cache.
type cacheKey struct {
mode BuildMode
p *load.Package
shlib string
mode string
p *load.Package
}
func actionGraphJSON(a *Action) string {
@ -863,293 +857,439 @@ func readpkglist(shlibpath string) (pkgs []*load.Package) {
return
}
// Action returns the action for applying the given operation (mode) to the package.
// depMode is the action to use when building dependencies.
// action never looks for p in a shared library, but may find p's dependencies in a
// shared library if buildLinkshared is true.
func (b *Builder) Action(mode BuildMode, depMode BuildMode, p *load.Package) *Action {
return b.action1(mode, depMode, p, false, "")
// cacheAction looks up {mode, p} in the cache and returns the resulting action.
// If the cache has no such action, f() is recorded and returned.
func (b *Builder) cacheAction(mode string, p *load.Package, f func() *Action) *Action {
a := b.actionCache[cacheKey{mode, p}]
if a == nil {
a = f()
b.actionCache[cacheKey{mode, p}] = a
}
return a
}
// action1 returns the action for applying the given operation (mode) to the package.
// depMode is the action to use when building dependencies.
// action1 will look for p in a shared library if lookshared is true.
// forShlib is the shared library that p will become part of, if any.
func (b *Builder) action1(mode BuildMode, depMode BuildMode, p *load.Package, lookshared bool, forShlib string) *Action {
shlib := ""
if lookshared {
shlib = p.Shlib
// AutoAction returns the "right" action for go build or go install of p.
func (b *Builder) AutoAction(mode, depMode BuildMode, p *load.Package) *Action {
if p.Name == "main" {
return b.LinkAction(mode, depMode, p)
}
key := cacheKey{mode, p, shlib}
return b.CompileAction(mode, depMode, p)
}
a := b.actionCache[key]
if a != nil {
return a
}
if shlib != "" {
key2 := cacheKey{ModeInstall, nil, shlib}
a = b.actionCache[key2]
if a != nil {
b.actionCache[key] = a
return a
}
pkgs := readpkglist(shlib)
a = b.libaction(filepath.Base(shlib), pkgs, ModeInstall, depMode)
b.actionCache[key2] = a
b.actionCache[key] = a
return a
}
a = &Action{Mode: "???", Package: p}
b.actionCache[key] = a
for _, p1 := range p.Internal.Imports {
if forShlib != "" {
// p is part of a shared library.
if p1.Shlib != "" && p1.Shlib != forShlib {
// p1 is explicitly part of a different shared library.
// Put the action for that shared library into a.Deps.
a.Deps = append(a.Deps, b.action1(depMode, depMode, p1, true, p1.Shlib))
} else {
// p1 is (implicitly or not) part of this shared library.
// Put the action for p1 into a.Deps.
a.Deps = append(a.Deps, b.action1(depMode, depMode, p1, false, forShlib))
}
} else {
// p is not part of a shared library.
// If p1 is in a shared library, put the action for that into
// a.Deps, otherwise put the action for p1 into a.Deps.
a.Deps = append(a.Deps, b.action1(depMode, depMode, p1, cfg.BuildLinkshared, p1.Shlib))
}
}
if p.Standard {
switch p.ImportPath {
case "builtin", "unsafe":
// Fake packages - nothing to build.
a.Mode = "built-in package"
return a
}
// gccgo standard library is "fake" too.
if cfg.BuildToolchainName == "gccgo" {
// the target name is needed for cgo.
a.Mode = "gccgo stdlib"
a.Target = p.Internal.Target
return a
}
}
if !p.Stale && p.Internal.Target != "" {
// p.Stale==false implies that p.Internal.Target is up-to-date.
// Record target name for use by actions depending on this one.
a.Mode = "use installed"
a.Target = p.Internal.Target
p.Internal.Pkgfile = a.Target
return a
}
if p.Internal.Local && p.Internal.Target == "" {
// CompileAction returns the action for compiling and possibly installing
// (according to mode) the given package. The resulting action is only
// for building packages (archives), never for linking executables.
// depMode is the action (build or install) to use when building dependencies.
// To turn package main into an executable, call b.Link instead.
func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Action {
if mode == ModeInstall && p.Internal.Local && p.Internal.Target == "" {
// Imported via local path. No permanent target.
mode = ModeBuild
}
a.Objdir = b.NewObjdir()
link := p.Name == "main" && !p.Internal.ForceLibrary
if mode == ModeInstall && p.Name == "main" {
// We never install the .a file for a main package.
mode = ModeBuild
}
switch mode {
case ModeInstall:
a.Func = BuildInstallFunc
a.Deps = []*Action{b.action1(ModeBuild, depMode, p, lookshared, forShlib)}
a.Target = a.Package.Internal.Target
a.Package.Internal.Pkgfile = a.Target
// Install header for cgo in c-archive and c-shared modes.
if p.UsesCgo() && (cfg.BuildBuildmode == "c-archive" || cfg.BuildBuildmode == "c-shared") {
hdrTarget := a.Target[:len(a.Target)-len(filepath.Ext(a.Target))] + ".h"
if cfg.BuildContext.Compiler == "gccgo" {
// For the header file, remove the "lib"
// added by go/build, so we generate pkg.h
// rather than libpkg.h.
dir, file := filepath.Split(hdrTarget)
file = strings.TrimPrefix(file, "lib")
hdrTarget = filepath.Join(dir, file)
}
ah := &Action{
Package: a.Package,
Deps: []*Action{a.Deps[0]},
Func: (*Builder).installHeader,
Objdir: a.Deps[0].Objdir,
Target: hdrTarget,
}
a.Deps = append(a.Deps, ah)
// Construct package build action.
a := b.cacheAction("build", p, func() *Action {
a := &Action{
Mode: "build",
Package: p,
Func: (*Builder).build,
Objdir: b.NewObjdir(),
}
case ModeBuild:
a.Func = (*Builder).build
a.Target = a.Objdir + "_pkg_.a"
a.Package.Internal.Pkgfile = a.Target
if link {
a = &Action{
Mode: "link",
Func: (*Builder).link,
Package: a.Package,
Objdir: a.Objdir,
Deps: []*Action{a},
for _, p1 := range p.Internal.Imports {
a.Deps = append(a.Deps, b.CompileAction(depMode, depMode, p1))
}
if p.Standard {
switch p.ImportPath {
case "builtin", "unsafe":
// Fake packages - nothing to build.
a.Mode = "built-in package"
a.Func = nil
return a
}
// An executable file. (This is the name of a temporary file.)
// Because we run the temporary file in 'go run' and 'go test',
// the name will show up in ps listings. If the caller has specified
// a name, use that instead of a.out. The binary is generated
// in an otherwise empty subdirectory named exe to avoid
// naming conflicts. The only possible conflict is if we were
// to create a top-level package named exe.
name := "a.out"
if p.Internal.ExeName != "" {
name = p.Internal.ExeName
} else if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" && p.Internal.Target != "" {
// On OS X, the linker output name gets recorded in the
// shared library's LC_ID_DYLIB load command.
// The code invoking the linker knows to pass only the final
// path element. Arrange that the path element matches what
// we'll install it as; otherwise the library is only loadable as "a.out".
// On Windows, DLL file name is recorded in PE file
// export section, so do like on OS X.
_, name = filepath.Split(p.Internal.Target)
// gccgo standard library is "fake" too.
if cfg.BuildToolchainName == "gccgo" {
// the target name is needed for cgo.
a.Mode = "gccgo stdlib"
a.Target = p.Internal.Target
a.Func = nil
return a
}
a.Target = a.Objdir + filepath.Join("exe", name) + cfg.ExeSuffix
}
if !p.Stale && p.Internal.Target != "" && p.Name != "main" {
// p.Stale==false implies that p.Internal.Target is up-to-date.
// Record target name for use by actions depending on this one.
a.Mode = "use installed"
a.Target = p.Internal.Target
a.Func = nil
p.Internal.Pkgfile = a.Target
return a
}
return a
})
// Construct install action.
if mode == ModeInstall {
a = b.installAction(a)
}
return a
}
func (b *Builder) libaction(libname string, pkgs []*load.Package, mode, depMode BuildMode) *Action {
a := &Action{
Mode: "libaction", // should be overwritten below
Objdir: b.NewObjdir(),
}
switch mode {
default:
base.Fatalf("unrecognized mode %v", mode)
case ModeBuild:
a.Func = (*Builder).linkShared
a.Target = filepath.Join(b.WorkDir, libname)
for _, p := range pkgs {
if p.Internal.Target == "" {
continue
}
a.Deps = append(a.Deps, b.Action(depMode, depMode, p))
// LinkAction returns the action for linking p into an executable
// and possibly installing the result (according to mode).
// depMode is the action (build or install) to use when compiling dependencies.
func (b *Builder) LinkAction(mode, depMode BuildMode, p *load.Package) *Action {
// Construct link action.
a := b.cacheAction("link", p, func() *Action {
a := &Action{
Mode: "link",
Package: p,
}
case ModeInstall:
// Currently build mode shared forces external linking mode, and
if !p.Stale && p.Internal.Target != "" {
// p.Stale==false implies that p.Internal.Target is up-to-date.
// Record target name for use by actions depending on this one.
a.Mode = "use installed"
a.Func = nil
a.Target = p.Internal.Target
p.Internal.Pkgfile = a.Target
return a
}
a1 := b.CompileAction(ModeBuild, depMode, p)
a.Func = (*Builder).link
a.Deps = []*Action{a1}
a.Objdir = a1.Objdir
// An executable file. (This is the name of a temporary file.)
// Because we run the temporary file in 'go run' and 'go test',
// the name will show up in ps listings. If the caller has specified
// a name, use that instead of a.out. The binary is generated
// in an otherwise empty subdirectory named exe to avoid
// naming conflicts. The only possible conflict is if we were
// to create a top-level package named exe.
name := "a.out"
if p.Internal.ExeName != "" {
name = p.Internal.ExeName
} else if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" && p.Internal.Target != "" {
// On OS X, the linker output name gets recorded in the
// shared library's LC_ID_DYLIB load command.
// The code invoking the linker knows to pass only the final
// path element. Arrange that the path element matches what
// we'll install it as; otherwise the library is only loadable as "a.out".
// On Windows, DLL file name is recorded in PE file
// export section, so do like on OS X.
_, name = filepath.Split(p.Internal.Target)
}
a.Target = a.Objdir + filepath.Join("exe", name) + cfg.ExeSuffix
b.addTransitiveLinkDeps(a, a1, "")
return a
})
if mode == ModeInstall {
a = b.installAction(a)
}
return a
}
// installAction returns the action for installing the result of a1.
func (b *Builder) installAction(a1 *Action) *Action {
// If there's no actual action to build a1,
// there's nothing to install either.
// This happens if a1 corresponds to reusing an already-built object.
if a1.Func == nil {
return a1
}
p := a1.Package
return b.cacheAction(a1.Mode+"-install", p, func() *Action {
a := &Action{
Mode: a1.Mode + "-install",
Func: BuildInstallFunc,
Package: p,
Objdir: a1.Objdir,
Deps: []*Action{a1},
Target: p.Internal.Target,
}
p.Internal.Pkgfile = a.Target
b.addInstallHeaderAction(a)
return a
})
}
// addTransitiveLinkDeps adds to the link action a all packages
// that are transitive dependencies of a1.Deps.
// That is, if a is a link of package main, a1 is the compile of package main
// and a1.Deps is the actions for building packages directly imported by
// package main (what the compiler needs). The linker needs all packages
// transitively imported by the whole program; addTransitiveLinkDeps
// makes sure those are present in a.Deps.
// If shlib is non-empty, then a corresponds to the build and installation of shlib,
// so any rebuild of shlib should not be added as a dependency.
func (b *Builder) addTransitiveLinkDeps(a, a1 *Action, shlib string) {
// Expand Deps to include all built packages, for the linker.
// Use breadth-first search to find rebuilt-for-test packages
// before the standard ones.
// TODO(rsc): Eliminate the standard ones from the action graph,
// which will require doing a little bit more rebuilding.
workq := []*Action{a1}
haveDep := map[string]bool{}
if a1.Package != nil {
haveDep[a1.Package.ImportPath] = true
}
for i := 0; i < len(workq); i++ {
a1 := workq[i]
for _, a2 := range a1.Deps {
// TODO(rsc): Find a better discriminator than the Mode strings, once the dust settles.
if a2.Package == nil || (a2.Mode != "build-install" && a2.Mode != "build" && a2.Mode != "use installed") || haveDep[a2.Package.ImportPath] {
continue
}
haveDep[a2.Package.ImportPath] = true
a.Deps = append(a.Deps, a2)
if a2.Mode == "build-install" {
a2 = a2.Deps[0] // walk children of "build" action
}
workq = append(workq, a2)
}
}
// If this is go build -linkshared, then the link depends on the shared libraries
// in addition to the packages themselves. (The compile steps do not.)
if cfg.BuildLinkshared {
haveShlib := map[string]bool{shlib: true}
for _, a1 := range a.Deps {
p1 := a1.Package
if p1 == nil || p1.Shlib == "" || haveShlib[filepath.Base(p1.Shlib)] {
continue
}
haveShlib[filepath.Base(p1.Shlib)] = true
// TODO(rsc): The use of ModeInstall here is suspect, but if we only do ModeBuild,
// we'll end up building an overall library or executable that depends at runtime
// on other libraries that are out-of-date, which is clearly not good either.
a.Deps = append(a.Deps, b.linkSharedAction(ModeInstall, ModeInstall, p1.Shlib, nil))
}
}
}
// addInstallHeaderAction adds an install header action to a, if needed.
// The action a should be an install action as generated by either
// b.CompileAction or b.LinkAction with mode=ModeInstall,
// and so a.Deps[0] is the corresponding build action.
func (b *Builder) addInstallHeaderAction(a *Action) {
// Install header for cgo in c-archive and c-shared modes.
p := a.Package
if p.UsesCgo() && (cfg.BuildBuildmode == "c-archive" || cfg.BuildBuildmode == "c-shared") {
hdrTarget := a.Target[:len(a.Target)-len(filepath.Ext(a.Target))] + ".h"
if cfg.BuildContext.Compiler == "gccgo" {
// For the header file, remove the "lib"
// added by go/build, so we generate pkg.h
// rather than libpkg.h.
dir, file := filepath.Split(hdrTarget)
file = strings.TrimPrefix(file, "lib")
hdrTarget = filepath.Join(dir, file)
}
ah := &Action{
Mode: "install header",
Package: a.Package,
Deps: []*Action{a.Deps[0]},
Func: (*Builder).installHeader,
Objdir: a.Deps[0].Objdir,
Target: hdrTarget,
}
a.Deps = append(a.Deps, ah)
}
}
// buildmodeShared takes the "go build" action a1 into the building of a shared library of a1.Deps.
// That is, the input a1 represents "go build pkgs" and the result represents "go build -buidmode=shared pkgs".
func (b *Builder) buildmodeShared(mode, depMode BuildMode, args []string, pkgs []*load.Package, a1 *Action) *Action {
name, err := libname(args, pkgs)
if err != nil {
base.Fatalf("%v", err)
}
return b.linkSharedAction(mode, depMode, name, a1)
}
// linkSharedAction takes a grouping action a1 corresponding to a list of built packages
// and returns an action that links them together into a shared library with the name shlib.
// If a1 is nil, shlib should be an absolute path to an existing shared library,
// and then linkSharedAction reads that library to find out the package list.
func (b *Builder) linkSharedAction(mode, depMode BuildMode, shlib string, a1 *Action) *Action {
fullShlib := shlib
shlib = filepath.Base(shlib)
a := b.cacheAction("build-shlib "+shlib, nil, func() *Action {
if a1 == nil {
// TODO(rsc): Need to find some other place to store config,
// not in pkg directory. See golang.org/issue/22196.
pkgs := readpkglist(fullShlib)
a1 = &Action{
Mode: "shlib packages",
}
for _, p := range pkgs {
a1.Deps = append(a1.Deps, b.CompileAction(mode, depMode, p))
}
}
// Add implicit dependencies to pkgs list.
// Currently buildmode=shared forces external linking mode, and
// external linking mode forces an import of runtime/cgo (and
// math on arm). So if it was not passed on the command line and
// it is not present in another shared library, add it here.
// TODO(rsc): Maybe this should only happen if "runtime" is in the original package set.
// TODO(rsc): This should probably be changed to use load.LinkerDeps(p).
gccgo := cfg.BuildToolchainName == "gccgo"
if !gccgo {
seencgo := false
for _, p := range pkgs {
seencgo = seencgo || (p.Standard && p.ImportPath == "runtime/cgo")
}
if !seencgo {
// TODO(rsc): Find out and explain here why gccgo is excluded.
// If the answer is that gccgo is different in implicit linker deps, maybe
// load.LinkerDeps should be used and updated.
if cfg.BuildToolchainName != "gccgo" {
add := func(pkg string) {
for _, a2 := range a1.Deps {
if a2.Package.ImportPath == pkg {
return
}
}
var stk load.ImportStack
p := load.LoadPackage("runtime/cgo", &stk)
p := load.LoadPackage(pkg, &stk)
if p.Error != nil {
base.Fatalf("load runtime/cgo: %v", p.Error)
base.Fatalf("load %s: %v", pkg, p.Error)
}
load.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([]*load.Package{}, pkgs...)
pkgs = append(pkgs, p)
// Assume that if pkg (runtime/cgo or math)
// is already accounted for in a different shared library,
// then that shared library also contains runtime,
// so that anything we do will depend on that library,
// so we don't need to include pkg in our shared library.
if p.Shlib == "" || filepath.Base(p.Shlib) == pkg {
a1.Deps = append(a1.Deps, b.CompileAction(depMode, depMode, p))
}
}
add("runtime/cgo")
if cfg.Goarch == "arm" {
seenmath := false
for _, p := range pkgs {
seenmath = seenmath || (p.Standard && p.ImportPath == "math")
}
if !seenmath {
var stk load.ImportStack
p := load.LoadPackage("math", &stk)
if p.Error != nil {
base.Fatalf("load math: %v", p.Error)
}
load.ComputeStale(p)
// If math is in another shared library, then that's
// also the shared library that contains runtime, so
// something will depend on it and so math's staleness
// will be checked when processing that library.
if p.Shlib == "" || p.Shlib == libname {
pkgs = append([]*load.Package{}, pkgs...)
pkgs = append(pkgs, p)
}
}
add("math")
}
}
// Figure out where the library will go.
var libdir string
for _, p := range pkgs {
plibdir := p.Internal.Build.PkgTargetRoot
if gccgo {
plibdir = filepath.Join(plibdir, "shlibs")
}
if libdir == "" {
libdir = plibdir
} else if libdir != plibdir {
base.Fatalf("multiple roots %s & %s", libdir, plibdir)
// Determine the eventual install target and compute staleness.
// TODO(rsc): This doesn't belong here and should be with the
// other staleness code. When we move to content-based staleness
// determination, that will happen for us.
// The install target is root/pkg/shlib, where root is the source root
// in which all the packages lie.
// TODO(rsc): Perhaps this cross-root check should apply to the full
// transitive package dependency list, not just the ones named
// on the command line?
pkgDir := a1.Deps[0].Package.Internal.Build.PkgTargetRoot
for _, a2 := range a1.Deps {
if dir := a2.Package.Internal.Build.PkgTargetRoot; dir != pkgDir {
// TODO(rsc): Misuse of base.Fatalf?
base.Fatalf("installing shared library: cannot use packages %s and %s from different roots %s and %s",
a1.Deps[0].Package.ImportPath,
a2.Package.ImportPath,
pkgDir,
dir)
}
}
a.Target = filepath.Join(libdir, libname)
// TODO(rsc): Find out and explain here why gccgo is different.
if cfg.BuildToolchainName == "gccgo" {
pkgDir = filepath.Join(pkgDir, "shlibs")
}
target := filepath.Join(pkgDir, shlib)
// Now we can check whether we need to rebuild it.
stale := false
// The install target is stale if it doesn't exist or if it is older than
// any of the .a files that are written into it.
// TODO(rsc): This computation does not detect packages that
// have been removed from a wildcard used to construct the package list
// but are still present in the installed list.
// It would be possible to detect this by reading the pkg list
// out of any installed target, but content-based staleness
// determination should discover that too.
var built time.Time
if fi, err := os.Stat(a.Target); err == nil {
if fi, err := os.Stat(target); err == nil {
built = fi.ModTime()
}
for _, p := range pkgs {
if p.Internal.Target == "" {
continue
stale := cfg.BuildA
if !stale {
for _, a2 := range a1.Deps {
if a2.Target == "" {
continue
}
if a2.Func != nil {
// a2 is going to be rebuilt (reuse of existing target would have Func==nil).
stale = true
break
}
info, err := os.Stat(a2.Target)
if err != nil || info.ModTime().After(built) {
stale = true
break
}
}
stale = stale || p.Stale
lstat, err := os.Stat(p.Internal.Target)
if err != nil || lstat.ModTime().After(built) {
stale = true
}
a.Deps = append(a.Deps, b.action1(depMode, depMode, p, false, a.Target))
}
if !stale {
return &Action{
Mode: "use installed buildmode=shared",
Target: target,
Deps: []*Action{a1},
}
}
// Link packages into a shared library.
a := &Action{
Mode: "go build -buildmode=shared",
Objdir: b.NewObjdir(),
Func: (*Builder).linkShared,
Deps: []*Action{a1},
Args: []string{target}, // awful side-channel for install action
}
a.Target = filepath.Join(a.Objdir, shlib)
b.addTransitiveLinkDeps(a, a1, shlib)
return a
})
if stale {
a.Func = BuildInstallFunc
buildAction := b.libaction(libname, pkgs, ModeBuild, depMode)
a.Deps = []*Action{buildAction}
for _, p := range pkgs {
// Install result.
if mode == ModeInstall && a.Func != nil {
buildAction := a
a = b.cacheAction("install-shlib "+shlib, nil, func() *Action {
a := &Action{
Mode: "go install -buildmode=shared",
Objdir: buildAction.Objdir,
Func: BuildInstallFunc,
Deps: []*Action{buildAction},
Target: buildAction.Args[0],
}
for _, a2 := range buildAction.Deps[0].Deps {
p := a2.Package
if p.Internal.Target == "" {
continue
}
shlibnameaction := &Action{Mode: "shlibname"}
shlibnameaction.Func = (*Builder).installShlibname
shlibnameaction.Target = p.Internal.Target[:len(p.Internal.Target)-2] + ".shlibname"
a.Deps = append(a.Deps, shlibnameaction)
shlibnameaction.Deps = append(shlibnameaction.Deps, buildAction)
a.Deps = append(a.Deps, &Action{
Mode: "shlibname",
Package: p,
Func: (*Builder).installShlibname,
Target: strings.TrimSuffix(p.Internal.Target, ".a") + ".shlibname",
Deps: []*Action{a.Deps[0]},
})
}
}
return a
})
}
return a
}
// ActionList returns the list of actions in the dag rooted at root
// actionList returns the list of actions in the dag rooted at root
// as visited in a depth-first post-order traversal.
func ActionList(root *Action) []*Action {
func actionList(root *Action) []*Action {
seen := map[*Action]bool{}
all := []*Action{}
var walk func(*Action)
@ -1180,7 +1320,7 @@ func (b *Builder) Do(root *Action) {
// ensure that, all else being equal, the execution prefers
// to do what it would have done first in a simple depth-first
// dependency order traversal.
all := ActionList(root)
all := actionList(root)
for i, a := range all {
a.priority = i
}
@ -1449,17 +1589,6 @@ func (b *Builder) build(a *Action) (err error) {
}
}
// NOTE: We used to call allArchiveActions(a) here and use it for -I.
// The comment on allArchiveActions(a) said:
//
// allArchiveActions returns a list of the archive dependencies of root.
// This is needed because if package p depends on package q that is in libr.so, the
// action graph looks like p->libr.so->q and so just scanning through p's
// dependencies does not find the import dir for q.
//
// If that's true, then the action graph is wrong, and q should be listed
// as a direct dependency of p as well as indirectly through libr.so.
// Prepare Go import config.
var icfg bytes.Buffer
for _, path := range a.Package.Imports {
@ -1583,12 +1712,8 @@ func (b *Builder) link(a *Action) (err error) {
}
}
// The compiler only cares about direct imports, but the
// linker needs the whole dependency tree.
all := ActionList(a)
all = all[:len(all)-1] // drop a
objpkg := a.Objdir + "_pkg_.a"
if err := BuildToolchain.ld(b, a, a.Target, importcfg, all, objpkg, nil); err != nil { // TODO: ofiles
if err := BuildToolchain.ld(b, a, a.Target, importcfg, objpkg); err != nil {
return err
}
@ -1598,19 +1723,9 @@ func (b *Builder) link(a *Action) (err error) {
func (b *Builder) writeLinkImportcfg(a *Action, file string) error {
// Prepare Go import cfg.
var icfg bytes.Buffer
p := a.Package
if p == nil {
// For linkShared, build fake package to serve as root
// for InternalDeps call.
p = new(load.Package)
for _, a1 := range a.Deps {
if a1.Package != nil {
p.Internal.Imports = append(p.Internal.Imports, a1.Package)
}
}
}
for _, p1 := range p.InternalDeps() {
if p1.ImportPath == "unsafe" {
for _, a1 := range a.Deps {
p1 := a1.Package
if p1 == nil {
continue
}
fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, p1.Internal.Pkgfile)
@ -1705,17 +1820,21 @@ func (b *Builder) linkShared(a *Action) (err error) {
if err := b.writeLinkImportcfg(a, importcfg); err != nil {
return err
}
allactions := ActionList(a)
allactions = allactions[:len(allactions)-1]
return BuildToolchain.ldShared(b, a.Deps, a.Target, importcfg, allactions)
return BuildToolchain.ldShared(b, a.Deps[0].Deps, a.Target, importcfg, a.Deps)
}
// BuildInstallFunc is the action for installing a single package or executable.
func BuildInstallFunc(b *Builder, a *Action) (err error) {
defer func() {
if err != nil && err != errPrintedOutput {
err = fmt.Errorf("go install %s: %v", a.Package.ImportPath, err)
// a.Package == nil is possible for the go install -buildmode=shared
// action that installs libmangledname.so, which corresponds to
// a list of packages, not just one.
sep, path := "", ""
if a.Package != nil {
sep, path = " ", a.Package.ImportPath
}
err = fmt.Errorf("go install%s%s: %v", sep, path, err)
}
}()
a1 := a.Deps[0]
@ -2199,6 +2318,17 @@ func (b *Builder) Mkdir(dir string) error {
return nil
}
// symlink creates a symlink newname -> oldname.
func (b *Builder) Symlink(oldname, newname string) error {
if cfg.BuildN || cfg.BuildX {
b.Showcmd("", "ln -s %s %s", oldname, newname)
if cfg.BuildN {
return nil
}
}
return os.Symlink(oldname, newname)
}
// mkAbs returns an absolute path corresponding to
// evaluating f in the directory dir.
// We always pass absolute paths of source files so that
@ -2230,7 +2360,7 @@ type toolchain interface {
// typically it is run in the object directory.
pack(b *Builder, a *Action, afile string, ofiles []string) error
// ld runs the linker to create an executable starting at mainpkg.
ld(b *Builder, root *Action, out, importcfg string, allactions []*Action, mainpkg string, ofiles []string) error
ld(b *Builder, root *Action, out, importcfg, mainpkg string) error
// ldShared runs the linker to create a shared library containing the pkgs built by toplevelactions
ldShared(b *Builder, toplevelactions []*Action, out, importcfg string, allactions []*Action) error
@ -2267,7 +2397,7 @@ func (noToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) er
return noCompiler()
}
func (noToolchain) ld(b *Builder, root *Action, out, importcfg string, allactions []*Action, mainpkg string, ofiles []string) error {
func (noToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
return noCompiler()
}
@ -2615,9 +2745,9 @@ func setextld(ldflags []string, compiler []string) []string {
return ldflags
}
func (gcToolchain) ld(b *Builder, root *Action, out, importcfg string, allactions []*Action, mainpkg string, ofiles []string) error {
func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
cxx := len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
for _, a := range allactions {
for _, a := range root.Deps {
if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
cxx = true
}
@ -2818,7 +2948,7 @@ func buildImportcfgSymlinks(b *Builder, root string, importcfg []byte) error {
if err := b.Mkdir(filepath.Dir(archive)); err != nil {
return err
}
if err := os.Symlink(after, archive); err != nil {
if err := b.Symlink(after, archive); err != nil {
return err
}
case "importmap":
@ -2833,7 +2963,7 @@ func buildImportcfgSymlinks(b *Builder, root string, importcfg []byte) error {
if err := b.Mkdir(filepath.Dir(afterA)); err != nil {
return err
}
if err := os.Symlink(afterA, beforeA); err != nil {
if err := b.Symlink(afterA, beforeA); err != nil {
return err
}
case "packageshlib":
@ -3133,8 +3263,8 @@ func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string
return nil
}
func (tools gccgoToolchain) ld(b *Builder, root *Action, out, importcfg string, allactions []*Action, mainpkg string, ofiles []string) error {
return tools.link(b, root, out, importcfg, allactions, ldBuildmode, root.Package.ImportPath)
func (tools gccgoToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
return tools.link(b, root, out, importcfg, root.Deps, ldBuildmode, root.Package.ImportPath)
}
func (tools gccgoToolchain) ldShared(b *Builder, toplevelactions []*Action, out, importcfg string, allactions []*Action) error {