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
-