// The heart of Fubsy: do a depth-first walk of the dependency graph // to discover nodes in topological order, then (re)build nodes that // are stale or missing. Skip target nodes that are "tainted" by // upstream failure. Returns a single error object summarizing what // (if anything) went wrong; error details are reported "live" as // builds fail (e.g. to the console or a GUI window) so the user gets // timely feedback. func (self *BuildState) BuildTargets(targets *dag.NodeSet) error { // What sort of nodes do we check for changes? self.setChangeStates() log.Debug(log.BUILD, "building %d targets", targets.Length()) builderr := new(BuildError) visit := func(node dag.Node) error { if node.State() == dag.SOURCE { // can't build original source nodes! return nil } if node.BuildRule() == nil { panic("node is a target, but has no build rule: " + node.Name()) } checkInitialState(node) // do we need to build this node? can we? build, tainted, err := self.considerNode(node) log.Debug(log.BUILD, "node %s: build=%v, tainted=%v err=%v\n", node, build, tainted, err) if err != nil { return err } if tainted { node.SetState(dag.TAINTED) } else if build { ok := self.buildNode(node, builderr) if !ok && !self.keepGoing() { // attempts counter is not very useful when we break // out of the build early builderr.attempts = -1 return builderr } if ok { err = self.recordNode(node) if err != nil { return err } } } return nil } err := self.graph.DFS(targets, visit) if err == nil && len(builderr.failed) > 0 { // build failures in keep-going mode err = builderr } return err }
func main() { if filepath.Base(os.Args[0]) == "fubsydebug" { debugmain() return } args := parseArgs() script, err := findScript(args.scriptFile) if err != nil { fmt.Fprintln(os.Stderr, "fubsy: error: "+err.Error()) os.Exit(2) } log.SetVerbosity(args.verbosity) err = log.EnableDebugTopics(args.debugTopics) if err != nil { fmt.Fprintln(os.Stderr, "fubsy: error: "+err.Error()) os.Exit(2) } ast, errors := dsl.Parse(script) if ast == nil && len(errors) == 0 { panic("ast == nil && len(errors) == 0") } checkErrors("parse error:", errors) log.Debug(log.AST, "ast:\n") log.DebugDump(log.AST, ast) rt := runtime.NewRuntime(args.options, script, ast) errors = rt.RunScript() checkErrors("error:", errors) }
// Build user's requested targets according to the dependency graph in // self.dag (as constructed by runMainPhase()). func (self *Runtime) runBuildPhase() []error { var errs []error errs = self.dag.ExpandNodes(self.stack) if len(errs) > 0 { return errs } self.dag.MarkSources() log.Debug(log.DAG, "dependency graph:") log.DebugDump(log.DAG, self.dag) goal, errs := self.dag.MatchTargets(self.options.Targets) if len(errs) > 0 { return errs } bdb, err := openBuildDB() if err != nil { errs = append(errs, err) return errs } defer bdb.Close() bstate := build.NewBuildState(self.dag, bdb, self.options) err = bstate.BuildTargets(goal) if err != nil { errs = append(errs, err) } return errs }
func (self *BuildRule) Execute() ([]dag.Node, []error) { stack := self.runtime.stack locals := types.NewValueMap() stack.Push(locals) defer stack.Pop() self.setLocals(locals) log.Debug(log.BUILD, "value stack:") log.DebugDump(log.BUILD, stack) err := self.action.Execute(self.runtime) return self.targets.Nodes(), err }
func (self KyotoDB) WriteNode(nodename string, record *BuildRecord) error { log.Debug(log.DB, "writing record for node %s", nodename) key := makekey(PREFIX_NODE, nodename) val, err := record.encode() if err != nil { return err } err = self.kcdb.Set(key, val) if err != nil { return err } return nil }
//export callBuiltin func callBuiltin( pfunc unsafe.Pointer, numargs C.int, cargs unsafe.Pointer) ( *C.char, *C.char) { log.Debug(log.PLUGINS, "callBuiltin: calling Go function at %p", pfunc) var fn types.FuCode fuargs := make([]types.FuObject, numargs) for i := uintptr(0); i < uintptr(numargs); i++ { // cargs is really a C char **, i.e. a pointer to an array of // char *. argp is a pointer to the i'th member of cargs. This // is just C-style array lookup with pointer arithmetic, but // in Go syntax. argp := unsafe.Pointer(uintptr(cargs) + i*unsafe.Sizeof(cargs)) arg := C.GoString(*(**C.char)(argp)) fuargs[i] = types.MakeFuString(arg) } args := types.MakeBasicArgs(nil, fuargs, nil) fn = *(*types.FuCode)(unsafe.Pointer(&pfunc)) log.Debug(log.PLUGINS, "followed unsafe.Pointer to get %p", fn) result, err := fn(args) if len(err) > 0 { errmsgs := make([]string, len(err)) for i, err := range err { errmsgs[i] = err.Error() } return nil, C.CString(strings.Join(errmsgs, "\n")) } var cresult *C.char if result != nil { cresult = C.CString(result.String()) } return cresult, nil }
func (self *BuildState) recordNode(node dag.Node) error { log.Debug(log.BUILD, "recording successful build of %s %s", node.Typename(), node) sig, err := node.Signature() log.Debug(log.BUILD, "sig=%v, err=%v", sig, err) if err != nil { return fmt.Errorf("could not compute signature of target %s: %s", node, err) } record := db.NewBuildRecord() record.SetTargetSignature(sig) for _, parent := range self.graph.ParentNodes(node) { sig, err = parent.Signature() if err != nil { return err } record.AddParent(parent.Name(), sig) } err = self.db.WriteNode(node.Name(), record) if err != nil { return err } return nil }
func (self KyotoDB) LookupNode(nodename string) (*BuildRecord, error) { log.Debug(log.DB, "loading record for node %s", nodename) key := makekey(PREFIX_NODE, nodename) val, err := self.kcdb.Get(key) if val == nil && (err == nil || kyotoNoRecord(err)) { return nil, nil } if err != nil { return nil, err } result := &BuildRecord{} err = result.decode(val) if err != nil { return nil, err } //log.DebugDump(log.DB, result) return result, nil }
func (self *Runtime) RunScript() []error { var errors []error for _, plugin := range self.ast.FindImports() { log.Debug(log.PLUGINS, "loading plugin '%s'", strings.Join(plugin, ".")) } errors = self.runInlinePlugins() if len(errors) > 0 { return errors } errors = self.runMainPhase() if len(errors) > 0 { return errors } errors = self.runBuildPhase() return errors }
func (self *Runtime) runInlinePlugins() []error { var errs []error var err error var meta plugins.MetaPlugin inlines := self.ast.FindInlinePlugins() ns := self.stack.Inner() for _, inline := range inlines { meta, err = plugins.LoadMetaPlugin(inline.Language(), self.builtins) if err != nil { errs = append(errs, MakeLocationError(inline, err)) continue } log.Debug(log.PLUGINS, "running %s inline plugin", inline.Language()) values, err := meta.Run(inline.Content()) if err != nil { errs = append(errs, MakeLocationError(inline, err)) } for name, val := range values { ns.Assign(name, val) } } return errs }
// Inspect node and its parents to see if we need to build it. Return // build=true if we should build it, tainted=true if we should skip // building this node due to upstream failure. Return non-nil err if // there were unexpected node errors (error checking existence or // change status). func (self *BuildState) considerNode(node dag.Node) ( build bool, tainted bool, err error) { var exists, changed bool exists, err = node.Exists() // obvious rebuild (unless tainted) if err != nil { return } missing := !exists var record *db.BuildRecord if !missing { // skip DB lookup for missing nodes: the only thing that will // stop us from rebuilding them is a failed parent, and that // check comes later record, err = self.db.LookupNode(node.Name()) if err != nil { return } if record != nil { log.Debug(log.BUILD, "old parents of %s:", node) log.DebugDump(log.BUILD, record) } } build = missing parents := self.graph.ParentNodes(node) // Check if any of node's former parents have been removed. if !build && record != nil { build = parentsRemoved(parents, record) } for _, parent := range parents { pstate := parent.State() if pstate == dag.FAILED || pstate == dag.TAINTED { build = false tainted = true return // no further inspection required } var oldsig []byte if record != nil { oldsig = record.SourceSignature(parent.Name()) } if oldsig == nil { // New parent for this node: rebuild unless another // parent is failed/tainted. build = true } if build { continue } changed, err = self.parentChanged(parent, pstate, oldsig) if err != nil { return } if changed { // Do NOT return here: we need to continue inspecting // parents to make sure they don't taint this node with // upstream failure. build = true } } return }