lib/ratch/buildable.rb in ratch-0.2.2 vs lib/ratch/buildable.rb in ratch-0.2.3

- old
+ new

@@ -1,107 +1,182 @@ +# TITLE: +# +# Buildable +# +# COPYING: +# +# Copyright (c) 2007 Psi T Corp. +# +# This file is part of the ProUtils' Ratch program. +# +# Ratch is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ratch is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ratch. If not, see <http://www.gnu.org/licenses/>. + module Ratch # = Buildable mixin # module Buildable # Reference task manager. def build_manager - @build_manager ||= BuildManager.new + @build_manager ||= BuildManager.new(self) end # Define a build target. def file(name, &block) name, deps, block = *parse_build_dependencies(name, &block) - @build_manager.define_file(name, *deps, &block) + build_manager.define_file(name, *deps, &block) end # Build a file. def build(file) - @build_manager.call(file) + build_manager.call(file) end private # - def parse_build_dependencies + def parse_build_dependencies(name_deps, &block) if Hash===name_deps name = name_deps.keys[0] deps = name_deps.values[0] else name = name_deps deps = [] end - [name, *deps, &block] + [name, deps, block] end end # = BuildManager class # class BuildManager attr :builds - def initialize - @builds = {} + def initialize(runspace) + @runspace = runspace + @builds = [] end + def force? ; @runspace.force? ; end + + # Define a file build task. def define_file(name, *depend, &block) build = Build.new(name, *depend, &block) - builds[build.name] = task + builds << build end - # - def call(file) - plan.each{ |name| @@tasks[name].action_call } - #action_call + # Call build. + def call(path) + # TODO How to handle more than one matching means of building? + #warn "More than one build definition matches #{path} using #{means.first}" if means.size > 1 + if build = find(path) + if build.needed_for?(path) || force? + list, todo = *plan(build, path) + todo.each{|bld, pth| bld.call(pth) } #@builds[name].call } + build.call(path) + end + else + raise + end end - # TODO Check for circular dependencies. - def plan(list=[]) - if list.include?(name) - raise "Circular dependency #{name}." + def find(path) + builds.find{ |b| b.match?(path) } + end + + # Prepare plan, checking for circular dependencies. + def plan(build, path, list=[], todo=[]) + #if list.include?(build) + # raise "Circular build dependency #{build.name}." + #end + + build.needed_paths.each do |npath| + next if list.include?(npath) + if nbuild = find(npath) + plan(nbuild, npath, list, todo) + todo << [nbuild, npath] + else + list << npath + end end - @preqs.each do |need| - need = need.to_s - next if list.include?(need) - @@tasks[need].plan(list) - end - list << name - return list + + return list, todo end end # = Build class # class Build - attr :name - attr :preqs + attr :match + attr :needs attr :action - # - def initialize(name, *preqs, &action) - @name = name.to_s - @preqs = preqs + alias_method :name, :match + + # Create a new build definition. + + def initialize(match, *needs, &action) + @match = match + @needs = needs @action = action end - # - def call - files = Dir.glob(@name) - files.each do |name| - if File.exist?(name) - mtime = File.mtime(name) - needs = @preqs.find do |file| - !File.exist?(file) || File.mtime(file) > mtime - end - else - needs = true + # Call this build process for the given path. + + def call(path) + if File.exist?(path) + mtime = File.mtime(path) + dated = needs.find do |file| + !File.exist?(file) || File.mtime(file) > mtime end - @action.call(@name) if needs + else + dated = true end + action.call(path) if dated end + # Does a file match this build definition? + + def match?(path) + case match + when String + File.fnmatch(match, path) + when Regexp + match =~ path + else + false # ??? + end + end + + # Is this build needed to update/create path? + + def needed_for?(path) + return true unless File.exist?(path) + mtimes = needed_paths.collect{|f| File.mtime(f)} + mtimes.max > File.mtime(path) + end + + # Glob expanded needs. + + def needed_paths + #exact = needs.select{|n| File.fnmatch?(n,n)} + + exact = needs.select{|n| n !~ /[\[*?]/ } + globs = needs.collect{|n| Dir.glob(n)}.flatten + (exact + globs).uniq + end + end end -