output []byte // output redirect buffer (nil means use b.Print)
// Execution state.
- pending int // number of deps yet to complete
- priority int // relative execution priority
- Failed bool // whether the action failed
+ pending int // number of deps yet to complete
+ priority int // relative execution priority
+ Failed bool // whether the action failed
+ json *actionJSON // action graph information
}
// BuildActionID returns the action ID section of a's build ID.
}
func (q *actionQueue) push(a *Action) {
+ if a.json != nil {
+ a.json.TimeReady = time.Now()
+ }
heap.Push(q, a)
}
ID int
Mode string
Package string
- Deps []int `json:",omitempty"`
- IgnoreFail bool `json:",omitempty"`
- Args []string `json:",omitempty"`
- Link bool `json:",omitempty"`
- Objdir string `json:",omitempty"`
- Target string `json:",omitempty"`
- Priority int `json:",omitempty"`
- Failed bool `json:",omitempty"`
- Built string `json:",omitempty"`
- VetxOnly bool `json:",omitempty"`
+ Deps []int `json:",omitempty"`
+ IgnoreFail bool `json:",omitempty"`
+ Args []string `json:",omitempty"`
+ Link bool `json:",omitempty"`
+ Objdir string `json:",omitempty"`
+ Target string `json:",omitempty"`
+ Priority int `json:",omitempty"`
+ Failed bool `json:",omitempty"`
+ Built string `json:",omitempty"`
+ VetxOnly bool `json:",omitempty"`
+ NeedVet bool `json:",omitempty"`
+ NeedBuild bool `json:",omitempty"`
+ ActionID string `json:",omitempty"`
+ BuildID string `json:",omitempty"`
+ TimeReady time.Time `json:",omitempty"`
+ TimeStart time.Time `json:",omitempty"`
+ TimeDone time.Time `json:",omitempty"`
+
+ Cmd []string // `json:",omitempty"`
+ CmdReal time.Duration `json:",omitempty"`
+ CmdUser time.Duration `json:",omitempty"`
+ CmdSys time.Duration `json:",omitempty"`
}
// cacheKey is the key for the action cache.
var list []*actionJSON
for id, a := range workq {
- aj := &actionJSON{
- Mode: a.Mode,
- ID: id,
- IgnoreFail: a.IgnoreFail,
- Args: a.Args,
- Objdir: a.Objdir,
- Target: a.Target,
- Failed: a.Failed,
- Priority: a.priority,
- Built: a.built,
- VetxOnly: a.VetxOnly,
- }
- if a.Package != nil {
- // TODO(rsc): Make this a unique key for a.Package somehow.
- aj.Package = a.Package.ImportPath
- }
- for _, a1 := range a.Deps {
- aj.Deps = append(aj.Deps, inWorkq[a1])
+ if a.json == nil {
+ a.json = &actionJSON{
+ Mode: a.Mode,
+ ID: id,
+ IgnoreFail: a.IgnoreFail,
+ Args: a.Args,
+ Objdir: a.Objdir,
+ Target: a.Target,
+ Failed: a.Failed,
+ Priority: a.priority,
+ Built: a.built,
+ VetxOnly: a.VetxOnly,
+ NeedBuild: a.needBuild,
+ NeedVet: a.needVet,
+ }
+ if a.Package != nil {
+ // TODO(rsc): Make this a unique key for a.Package somehow.
+ a.json.Package = a.Package.ImportPath
+ }
+ for _, a1 := range a.Deps {
+ a.json.Deps = append(a.json.Deps, inWorkq[a1])
+ }
}
- list = append(list, aj)
+ list = append(list, a.json)
}
js, err := json.MarshalIndent(list, "", "\t")
a.priority = i
}
- if cfg.DebugActiongraph != "" {
- js := actionGraphJSON(root)
- if err := ioutil.WriteFile(cfg.DebugActiongraph, []byte(js), 0666); err != nil {
- fmt.Fprintf(os.Stderr, "go: writing action graph: %v\n", err)
- base.SetExitStatus(1)
+ // Write action graph, without timing information, in case we fail and exit early.
+ writeActionGraph := func() {
+ if file := cfg.DebugActiongraph; file != "" {
+ if strings.HasSuffix(file, ".go") {
+ // Do not overwrite Go source code in:
+ // go build -debug-actiongraph x.go
+ base.Fatalf("go: refusing to write action graph to %v\n", file)
+ }
+ js := actionGraphJSON(root)
+ if err := ioutil.WriteFile(file, []byte(js), 0666); err != nil {
+ fmt.Fprintf(os.Stderr, "go: writing action graph: %v\n", err)
+ base.SetExitStatus(1)
+ }
}
}
+ writeActionGraph()
b.readySema = make(chan bool, len(all))
// Handle runs a single action and takes care of triggering
// any actions that are runnable as a result.
handle := func(a *Action) {
+ if a.json != nil {
+ a.json.TimeStart = time.Now()
+ }
var err error
-
if a.Func != nil && (!a.Failed || a.IgnoreFail) {
err = a.Func(b, a)
}
+ if a.json != nil {
+ a.json.TimeDone = time.Now()
+ }
// The actions run in parallel but all the updates to the
// shared work state are serialized through b.exec.
}
wg.Wait()
+
+ // Write action graph again, this time with timing information.
+ writeActionGraph()
}
// buildActionID computes the action ID for a build action.
}
}
var out []byte
- out, err = b.runOut(p.Dir, nil, b.PkgconfigCmd(), "--cflags", pcflags, "--", pkgs)
+ out, err = b.runOut(nil, p.Dir, nil, b.PkgconfigCmd(), "--cflags", pcflags, "--", pkgs)
if err != nil {
b.showOutput(nil, p.Dir, b.PkgconfigCmd()+" --cflags "+strings.Join(pcflags, " ")+" -- "+strings.Join(pkgs, " "), string(out))
b.Print(err.Error() + "\n")
return nil, nil, err
}
}
- out, err = b.runOut(p.Dir, nil, b.PkgconfigCmd(), "--libs", pcflags, "--", pkgs)
+ out, err = b.runOut(nil, p.Dir, nil, b.PkgconfigCmd(), "--libs", pcflags, "--", pkgs)
if err != nil {
b.showOutput(nil, p.Dir, b.PkgconfigCmd()+" --libs "+strings.Join(pcflags, " ")+" -- "+strings.Join(pkgs, " "), string(out))
b.Print(err.Error() + "\n")
a1 := a.Deps[0]
a.buildID = a1.buildID
+ if a.json != nil {
+ a.json.BuildID = a.buildID
+ }
// If we are using the eventual install target as an up-to-date
// cached copy of the thing we built, then there's no need to
// If the command fails, run prints information about the failure
// and returns a non-nil error.
func (b *Builder) run(a *Action, dir string, desc string, env []string, cmdargs ...interface{}) error {
- out, err := b.runOut(dir, env, cmdargs...)
+ out, err := b.runOut(a, dir, env, cmdargs...)
if len(out) > 0 {
if desc == "" {
desc = b.fmtcmd(dir, "%s", strings.Join(str.StringList(cmdargs...), " "))
// runOut runs the command given by cmdline in the directory dir.
// It returns the command output and any errors that occurred.
-func (b *Builder) runOut(dir string, env []string, cmdargs ...interface{}) ([]byte, error) {
+// It accumulates execution time in a.
+func (b *Builder) runOut(a *Action, dir string, env []string, cmdargs ...interface{}) ([]byte, error) {
cmdline := str.StringList(cmdargs...)
for _, arg := range cmdline {
cmd.Dir = dir
cmd.Env = base.EnvForDir(cmd.Dir, os.Environ())
cmd.Env = append(cmd.Env, env...)
+ start := time.Now()
err := cmd.Run()
+ if a != nil && a.json != nil {
+ aj := a.json
+ aj.Cmd = append(aj.Cmd, joinUnambiguously(cmdline))
+ aj.CmdReal += time.Since(start)
+ if ps := cmd.ProcessState; ps != nil {
+ aj.CmdUser += ps.UserTime()
+ aj.CmdSys += ps.SystemTime()
+ }
+ }
// err can be something like 'exit status 1'.
// Add information about what program was running.
if !filepath.IsAbs(outfile) {
outfile = filepath.Join(p.Dir, outfile)
}
- output, err := b.runOut(filepath.Dir(file), b.cCompilerEnv(), compiler, flags, "-o", outfile, "-c", filepath.Base(file))
+ output, err := b.runOut(a, filepath.Dir(file), b.cCompilerEnv(), compiler, flags, "-o", outfile, "-c", filepath.Base(file))
if len(output) > 0 {
// On FreeBSD 11, when we pass -g to clang 3.8 it
// invokes its internal assembler with -dwarf-version=2.
}
// gccld runs the gcc linker to create an executable from a set of object files.
-func (b *Builder) gccld(p *load.Package, objdir, outfile string, flags []string, objs []string) error {
+func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flags []string, objs []string) error {
var cmd []string
if len(p.CXXFiles) > 0 || len(p.SwigCXXFiles) > 0 {
cmd = b.GxxCmd(p.Dir, objdir)
cmdargs := []interface{}{cmd, "-o", outfile, objs, flags}
dir := p.Dir
- out, err := b.runOut(dir, b.cCompilerEnv(), cmdargs...)
+ out, err := b.runOut(a, dir, b.cCompilerEnv(), cmdargs...)
if len(out) > 0 {
// Filter out useless linker warnings caused by bugs outside Go.
// See also cmd/link/internal/ld's hostlink method.
}
ldflags = append(n, "-pie")
}
- if err := b.gccld(p, objdir, dynobj, ldflags, linkobj); err != nil {
+ if err := b.gccld(a, p, objdir, dynobj, ldflags, linkobj); err != nil {
return err
}
)
func (b *Builder) swigDoVersionCheck() error {
- out, err := b.runOut("", nil, "swig", "-version")
+ out, err := b.runOut(nil, "", nil, "swig", "-version")
if err != nil {
return err
}
args = append(args, "-c++")
}
- out, err := b.runOut(p.Dir, nil, "swig", args, file)
+ out, err := b.runOut(a, p.Dir, nil, "swig", args, file)
if err != nil {
if len(out) > 0 {
if bytes.Contains(out, []byte("-intgosize")) || bytes.Contains(out, []byte("-cgo")) {