support phony targets - close #25

This commit is contained in:
Josh Holtrop 2015-02-15 18:29:33 -05:00
parent 7698fbf618
commit 7a04bec2ff
5 changed files with 85 additions and 14 deletions

View File

@ -65,6 +65,15 @@ module Rscons
path =~ %r{^(/|\w:[\\/])} path =~ %r{^(/|\w:[\\/])}
end 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. # Return a new path by changing the suffix in path to suffix.
# #
# @param path [String] The path to alter. # @param path [String] The path to alter.

View File

@ -87,8 +87,10 @@ module Rscons
# The name of the target on success or false on failure. # The name of the target on success or false on failure.
def standard_build(short_cmd_string, target, command, sources, env, cache) def standard_build(short_cmd_string, target, command, sources, env, cache)
unless cache.up_to_date?(target, command, sources, env) unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target)) unless Rscons.phony_target?(target)
FileUtils.rm_f(target) cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
end
return false unless env.execute(short_cmd_string, command) return false unless env.execute(short_cmd_string, command)
cache.register_build(target, command, sources, env) cache.register_build(target, command, sources, env)
end end

View File

@ -56,6 +56,9 @@ module Rscons
# Name of the file to store cache information in # Name of the file to store cache information in
CACHE_FILE = ".rsconscache" CACHE_FILE = ".rsconscache"
# Prefix for phony cache entries.
PHONY_PREFIX = ":PHONY:"
# Create a Cache object and load in the previous contents from the cache # Create a Cache object and load in the previous contents from the cache
# file. # file.
def initialize def initialize
@ -92,7 +95,8 @@ module Rscons
# Check if target(s) are up to date. # Check if target(s) are up to date.
# #
# @param targets [String, Array<String>] The name(s) of the target file(s). # @param targets [Symbol, String, Array<String>]
# The name(s) of the target file(s).
# @param command [String, Array, Hash] # @param command [String, Array, Hash]
# The command used to build the target. The command parameter can # The command used to build the target. The command parameter can
# actually be a String, Array, or Hash and could contain information # actually be a String, Array, or Hash and could contain information
@ -120,19 +124,25 @@ module Rscons
# stored in the cache file # stored in the cache file
def up_to_date?(targets, command, deps, env, options = {}) def up_to_date?(targets, command, deps, env, options = {})
Array(targets).each do |target| Array(targets).each do |target|
# target file must exist on disk cache_key = get_cache_key(target)
return false unless File.exists?(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 # 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 unless Rscons.phony_target?(target)
return false unless @cache["targets"][target]["checksum"] == lookup_checksum(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 # 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"] } cached_deps_fnames = cached_deps.map { |dc| dc["fname"] }
if options[:strict_deps] if options[:strict_deps]
# depedencies passed in must exactly equal those in the cache # depedencies passed in must exactly equal those in the cache
@ -144,7 +154,7 @@ module Rscons
# set of user dependencies must match # set of user dependencies must match
user_deps = env.get_user_deps(target) || [] 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"] } cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] }
return false unless user_deps == cached_user_deps_fnames 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. # Store cache information about target(s) built by a builder.
# #
# @param targets [String, Array<String>] The name of the target(s) built. # @param targets [Symbol, String, Array<String>]
# The name of the target(s) built.
# @param command [String, Array, Hash] # @param command [String, Array, Hash]
# The command used to build the target. The command parameter can # The command used to build the target. The command parameter can
# actually be a String, Array, or Hash and could contain information # actually be a String, Array, or Hash and could contain information
@ -172,9 +183,10 @@ module Rscons
# @return [void] # @return [void]
def register_build(targets, command, deps, env) def register_build(targets, command, deps, env)
Array(targets).each do |target| 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), "command" => Digest::MD5.hexdigest(command.inspect),
"checksum" => calculate_checksum(target), "checksum" => target_checksum,
"deps" => deps.map do |dep| "deps" => deps.map do |dep|
{ {
"fname" => dep, "fname" => dep,
@ -228,6 +240,21 @@ module Rscons
private 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 # Create a Cache object and load in the previous contents from the cache
# file. # file.
def initialize! def initialize!

View File

@ -116,6 +116,8 @@ module Rscons
expand_varref(varref[*lambda_args], lambda_args) expand_varref(varref[*lambda_args], lambda_args)
elsif varref.nil? elsif varref.nil?
nil nil
elsif varref.is_a?(Symbol)
varref
else else
raise "Unknown varref type: #{varref.class} (#{varref.inspect})" raise "Unknown varref type: #{varref.class} (#{varref.inspect})"
end end

View File

@ -932,4 +932,35 @@ EOF
end end
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 end