lib/teapot/build.rb in teapot-0.9.10 vs lib/teapot/build.rb in teapot-1.0.0.pre.rc1

- old
+ new

@@ -16,37 +16,185 @@ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -require 'teapot/build/targets/directory' -require 'teapot/build/targets/files' -require 'teapot/build/targets/library' -require 'teapot/build/targets/executable' -require 'teapot/build/targets/application' -require 'teapot/build/targets/external' +require 'teapot/rulebook' +require 'build/files' +require 'build/graph' +require 'build/makefile' + +require 'teapot/name' + +require 'process/group' +require 'system' + module Teapot module Build - def self.top(path) - Targets::Directory.target(nil, path) + Graph = ::Build::Graph + Files = ::Build::Files + Paths = ::Build::Files::Paths + Makefile = ::Build::Makefile + + class Node < Graph::Node + def initialize(controller, rule, arguments, &block) + @arguments = arguments + @rule = rule + + @callback = block + + inputs, outputs = rule.files(arguments) + + super(controller, inputs, outputs) + end + + attr :arguments + attr :rule + attr :callback + + def hash + [@rule.name, @arguments].hash + end + + def eql?(other) + other.kind_of?(self.class) and @rule.eql?(other.rule) and @arguments.eql?(other.arguments) + end + + def apply!(scope) + @rule.apply!(scope, @arguments) + + if @callback + scope.instance_exec(@arguments, &@callback) + end + end end - module Helpers - def build_directory(root, directory, *args) - target = Build.top(root) + class Top < Graph::Node + def initialize(controller, task_class, &update) + @update = update + @task_class = task_class + + super(controller, Paths::NONE, Paths::NONE) + end + + attr :task_class + + def apply!(scope) + scope.instance_exec(&@update) + end + + # Top level nodes are always considered dirty. This ensures that enclosed nodes are run if they are dirty. The top level node has no inputs or outputs by default, so children who become dirty wouldn't mark it as dirty and thus wouldn't be run. + def requires_update? + true + end + end - target.add_directory(directory) - - target.execute(:build, *args) + class Task < Graph::Task + def initialize(controller, walker, node, group = nil) + super(controller, walker, node) + + @group = group + + if wet? + #@file_system = FileUtils + @file_system = FileUtils::Verbose + else + @file_system = FileUtils::NoWrite + end end + + attr :file_system + alias fs file_system + + def wet? + @group && @node.requires_update? + end + + def update(rule, arguments, &block) + arguments = rule.normalize(arguments) + + # A sub-graph for a particular build is isolated based on the task class used to instantiate it, so we use this as part of the key. + child_node = @controller.nodes.fetch([self.class, rule.name, arguments]) do |key| + @controller.nodes[key] = Node.new(@controller, rule, arguments, &block) + end + + @children << child_node + + child_node.update!(@walker) + + return child_node.rule.result(arguments) + end + + def run!(*arguments) + if wet? + # puts Rainbow("Scheduling #{arguments.inspect}").blue + status = @group.spawn(*arguments) + # puts Rainbow("Finished #{arguments.inspect} with status #{status}").blue + + if status != 0 + raise Graph::CommandFailure.new(arguments, status) + end + end + end + + def visit + super do + @node.apply!(self) + end + end + end - def build_external(root, directory, *args, &block) - target = Build.top(root) + class Controller < Graph::Controller + def initialize + @module = Module.new + + @top = [] + + yield self + + @top.freeze + + @task_class = nil + + super() + end - target << Targets::External.new(target, directory, &block) + attr :top - target.execute(:build, *args) + # Because we do a depth first traversal, we can capture global state per branch, such as `@task_class`. + def traverse!(walker) + @top.each do |node| + # Capture the task class for each top level node: + @task_class = node.task_class + + node.update!(walker) + end + end + + def add_target(target, environment, &block) + task_class = Rulebook.for(environment).with(Task, environment: environment, target: target) + + # Not sure if this is a good idea - makes debugging slightly easier. + Object.const_set("TaskClassFor#{Name.from_target(target.name).identifier}_#{self.object_id}", task_class) + + @top << Top.new(self, task_class, &target.build) + end + + def build_graph! + super do |walker, node| + @task_class.new(self, walker, node) + end + end + + def update! + group = Process::Group.new + + super do |walker, node| + @task_class.new(self, walker, node, group) + end + + group.wait end end end end