diff --git a/lib/rscons.rb b/lib/rscons.rb index 38c115e..9a89dc9 100644 --- a/lib/rscons.rb +++ b/lib/rscons.rb @@ -65,6 +65,15 @@ module Rscons path =~ %r{^(/|\w:[\\/])} end + # Return whether the given target is a phony target. + # + # @param target [Symbol, String] Target name. + # + # @return [Boolean] Whether the given target is a phony target. + def self.phony_target?(target) + target.is_a?(Symbol) + end + # Return a new path by changing the suffix in path to suffix. # # @param path [String] The path to alter. diff --git a/lib/rscons/builder.rb b/lib/rscons/builder.rb index 265ceed..0cfeb69 100644 --- a/lib/rscons/builder.rb +++ b/lib/rscons/builder.rb @@ -87,8 +87,10 @@ module Rscons # The name of the target on success or false on failure. def standard_build(short_cmd_string, target, command, sources, env, cache) unless cache.up_to_date?(target, command, sources, env) - cache.mkdir_p(File.dirname(target)) - FileUtils.rm_f(target) + unless Rscons.phony_target?(target) + cache.mkdir_p(File.dirname(target)) + FileUtils.rm_f(target) + end return false unless env.execute(short_cmd_string, command) cache.register_build(target, command, sources, env) end diff --git a/lib/rscons/cache.rb b/lib/rscons/cache.rb index 385e99d..50c5afb 100644 --- a/lib/rscons/cache.rb +++ b/lib/rscons/cache.rb @@ -56,6 +56,9 @@ module Rscons # Name of the file to store cache information in CACHE_FILE = ".rsconscache" + # Prefix for phony cache entries. + PHONY_PREFIX = ":PHONY:" + # Create a Cache object and load in the previous contents from the cache # file. def initialize @@ -92,7 +95,8 @@ module Rscons # Check if target(s) are up to date. # - # @param targets [String, Array] The name(s) of the target file(s). + # @param targets [Symbol, String, Array] + # The name(s) of the target file(s). # @param command [String, Array, Hash] # The command used to build the target. The command parameter can # actually be a String, Array, or Hash and could contain information @@ -120,19 +124,25 @@ module Rscons # stored in the cache file def up_to_date?(targets, command, deps, env, options = {}) Array(targets).each do |target| - # target file must exist on disk - return false unless File.exists?(target) + cache_key = get_cache_key(target) + + unless Rscons.phony_target?(target) + # target file must exist on disk + return false unless File.exists?(target) + end # target must be registered in the cache - return false unless @cache["targets"].has_key?(target) + return false unless @cache["targets"].has_key?(cache_key) - # target must have the same checksum as when it was built last - return false unless @cache["targets"][target]["checksum"] == lookup_checksum(target) + unless Rscons.phony_target?(target) + # target must have the same checksum as when it was built last + return false unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target) + end # command used to build target must be identical - return false unless @cache["targets"][target]["command"] == Digest::MD5.hexdigest(command.inspect) + return false unless @cache["targets"][cache_key]["command"] == Digest::MD5.hexdigest(command.inspect) - cached_deps = @cache["targets"][target]["deps"] || [] + cached_deps = @cache["targets"][cache_key]["deps"] || [] cached_deps_fnames = cached_deps.map { |dc| dc["fname"] } if options[:strict_deps] # depedencies passed in must exactly equal those in the cache @@ -144,7 +154,7 @@ module Rscons # set of user dependencies must match user_deps = env.get_user_deps(target) || [] - cached_user_deps = @cache["targets"][target]["user_deps"] || [] + cached_user_deps = @cache["targets"][cache_key]["user_deps"] || [] cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] } return false unless user_deps == cached_user_deps_fnames @@ -159,7 +169,8 @@ module Rscons # Store cache information about target(s) built by a builder. # - # @param targets [String, Array] The name of the target(s) built. + # @param targets [Symbol, String, Array] + # The name of the target(s) built. # @param command [String, Array, Hash] # The command used to build the target. The command parameter can # actually be a String, Array, or Hash and could contain information @@ -172,9 +183,10 @@ module Rscons # @return [void] def register_build(targets, command, deps, env) Array(targets).each do |target| - @cache["targets"][target] = { + target_checksum = Rscons.phony_target?(target) ? "" : calculate_checksum(target) + @cache["targets"][get_cache_key(target)] = { "command" => Digest::MD5.hexdigest(command.inspect), - "checksum" => calculate_checksum(target), + "checksum" => target_checksum, "deps" => deps.map do |dep| { "fname" => dep, @@ -228,6 +240,21 @@ module Rscons private + # Return a String key based on the target name to use in the on-disk cache. + # + # @param target_name [Symbol, String] + # Target name. + # + # @return [String] + # Key name. + def get_cache_key(target_name) + if Rscons.phony_target?(target_name) + PHONY_PREFIX + target_name.to_s + else + target_name + end + end + # Create a Cache object and load in the previous contents from the cache # file. def initialize! diff --git a/lib/rscons/varset.rb b/lib/rscons/varset.rb index 4f8c6d6..38647cb 100644 --- a/lib/rscons/varset.rb +++ b/lib/rscons/varset.rb @@ -116,6 +116,8 @@ module Rscons expand_varref(varref[*lambda_args], lambda_args) elsif varref.nil? nil + elsif varref.is_a?(Symbol) + varref else raise "Unknown varref type: #{varref.class} (#{varref.inspect})" end diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index fa66ee6..4eb7204 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -932,4 +932,35 @@ EOF end end + context "phony targets" do + it "allows specifying a Symbol as a target name and reruns the builder if the sources or command have changed" do + test_dir("simple") + env = Rscons::Environment.new do |env| + env.add_builder(:Checker) do |target, sources, cache, env, vars| + unless cache.up_to_date?(target, :Checker, sources, env) + puts "Checker #{sources.first}" if env.echo != :off + cache.register_build(target, :Checker, sources, env) + end + target + end + env.Program("simple.exe", "simple.c") + env.Checker(:checker, "simple.exe") + end + expect(lines).to eq(["CC simple.o", "LD simple.exe", "Checker simple.exe"]) + + env.clone do |env| + env.Checker(:checker, "simple.exe") + end + expect(lines).to eq([]) + + File.open("simple.exe", "w") do |fh| + fh.puts "Changed simple.exe" + end + env.clone do |env| + env.Checker(:checker, "simple.exe") + end + expect(lines).to eq(["Checker simple.exe"]) + end + end + end