modify Cache to store user-specified extra dependencies

This commit is contained in:
Josh Holtrop 2013-12-27 14:20:32 -05:00
parent a28cef9eb7
commit 7330e74ab8
9 changed files with 105 additions and 46 deletions

View File

@ -22,12 +22,12 @@ module Rscons
# Check if the cache is up to date for the target and if not execute the # Check if the cache is up to date for the target and if not execute the
# build command. # build command.
# Return the name of the target or false on failure. # Return the name of the target or false on failure.
def standard_build(short_cmd_string, target, command, sources, env, cache) def standard_build(short_cmd_string, target, command, sources, user_deps, env, cache)
unless cache.up_to_date?(target, command, sources) unless cache.up_to_date?(target, command, sources, user_deps)
cache.mkdir_p(File.dirname(target)) cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target) FileUtils.rm_f(target)
return false unless env.execute(short_cmd_string, command) return false unless env.execute(short_cmd_string, command)
cache.register_build(target, command, sources) cache.register_build(target, command, sources, user_deps)
end end
target target
end end

View File

@ -12,7 +12,7 @@ module Rscons
} }
end end
def run(target, sources, cache, env, vars) def run(target, sources, user_deps, cache, env, vars)
# build sources to linkable objects # build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
if objects if objects
@ -21,7 +21,7 @@ module Rscons
'_SOURCES' => objects, '_SOURCES' => objects,
}) })
command = env.build_command(env['ARCMD'], vars) command = env.build_command(env['ARCMD'], vars)
standard_build("AR #{target}", target, command, objects, env, cache) standard_build("AR #{target}", target, command, objects, user_deps, env, cache)
end end
end end
end end

View File

@ -50,7 +50,7 @@ module Rscons
end end
end end
def run(target, sources, cache, env, vars) def run(target, sources, user_deps, cache, env, vars)
vars = vars.merge({ vars = vars.merge({
'_TARGET' => target, '_TARGET' => target,
'_SOURCES' => sources, '_SOURCES' => sources,
@ -62,7 +62,7 @@ module Rscons
v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}" v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}"
end.first end.first
command = env.build_command(env["#{com_prefix}CMD"], vars) command = env.build_command(env["#{com_prefix}CMD"], vars)
unless cache.up_to_date?(target, command, sources) unless cache.up_to_date?(target, command, sources, user_deps)
cache.mkdir_p(File.dirname(target)) cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target) FileUtils.rm_f(target)
return false unless env.execute("#{com_prefix} #{target}", command) return false unless env.execute("#{com_prefix} #{target}", command)
@ -71,7 +71,7 @@ module Rscons
deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target) deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target)
FileUtils.rm_f(vars['_DEPFILE']) FileUtils.rm_f(vars['_DEPFILE'])
end end
cache.register_build(target, command, deps.uniq) cache.register_build(target, command, deps.uniq, user_deps)
end end
target target
end end

View File

@ -14,7 +14,7 @@ module Rscons
} }
end end
def run(target, sources, cache, env, vars) def run(target, sources, user_deps, cache, env, vars)
# build sources to linkable objects # build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
return false unless objects return false unless objects
@ -33,7 +33,7 @@ module Rscons
'LD' => ld, 'LD' => ld,
}) })
command = env.build_command(env['LDCMD'], vars) command = env.build_command(env['LDCMD'], vars)
standard_build("LD #{target}", target, command, objects, env, cache) standard_build("LD #{target}", target, command, objects, user_deps, env, cache)
end end
end end
end end

View File

@ -18,9 +18,15 @@ module Rscons
# { # {
# 'fname' => 'program.o', # 'fname' => 'program.o',
# 'checksum' => '87654321', # 'checksum' => '87654321',
# } # },
# ], # ],
# } # 'user_deps' => [
# {
# 'fname' => 'lscript.ld',
# 'checksum' => '77551133',
# },
# ],
# },
# 'program.o' => { # 'program.o' => {
# 'checksum' => '87654321', # 'checksum' => '87654321',
# 'command' => ['gcc', '-c', '-o', 'program.o', 'program.c'], # 'command' => ['gcc', '-c', '-o', 'program.o', 'program.c'],
@ -32,15 +38,16 @@ module Rscons
# { # {
# 'fname' => 'program.h', # 'fname' => 'program.h',
# 'checksum' => '7979764643', # 'checksum' => '7979764643',
# } # },
# ] # ],
# 'user_deps' => [],
# } # }
# } # },
# directories: { # directories: {
# 'build' => true, # 'build' => true,
# 'build/one' => true, # 'build/one' => true,
# 'build/two' => true, # 'build/two' => true,
# } # },
# } # }
class Cache class Cache
#### Constants #### Constants
@ -82,6 +89,7 @@ module Rscons
# @param target [String] The name of the target file. # @param target [String] The name of the target file.
# @param command [Array] The command used to build the target. # @param command [Array] The command used to build the target.
# @param deps [Array] List of the target's dependency files. # @param deps [Array] List of the target's dependency files.
# @param user_deps [Array] List of user-specified extra dependencies.
# @param options [Hash] Optional options. Can contain the following keys: # @param options [Hash] Optional options. Can contain the following keys:
# :strict_deps:: # :strict_deps::
# Only consider a target up to date if its list of dependencies is # Only consider a target up to date if its list of dependencies is
@ -96,7 +104,7 @@ module Rscons
# exactly equal to those cached # exactly equal to those cached
# - each cached dependency file's current checksum matches the checksum # - each cached dependency file's current checksum matches the checksum
# stored in the cache file # stored in the cache file
def up_to_date?(target, command, deps, options = {}) def up_to_date?(target, command, deps, user_deps, options = {})
# target file must exist on disk # target file must exist on disk
return false unless File.exists?(target) return false unless File.exists?(target)
@ -118,8 +126,11 @@ module Rscons
return false unless (Set.new(deps) - Set.new(cached_deps)).empty? return false unless (Set.new(deps) - Set.new(cached_deps)).empty?
end end
# set of user dependencies must match
return false unless user_deps == @cache[:targets][target][:user_deps].map { |dc| dc[:fname] }
# all cached dependencies must have their checksums match # all cached dependencies must have their checksums match
@cache[:targets][target][:deps].each do |dep_cache| (@cache[:targets][target][:deps] + @cache[:targets][target][:user_deps]).each do |dep_cache|
return false unless dep_cache[:checksum] == lookup_checksum(dep_cache[:fname]) return false unless dep_cache[:checksum] == lookup_checksum(dep_cache[:fname])
end end
@ -130,7 +141,8 @@ module Rscons
# @param target [String] The name of the target. # @param target [String] The name of the target.
# @param command [Array] The command used to build the target. # @param command [Array] The command used to build the target.
# @param deps [Array] List of dependencies for the target. # @param deps [Array] List of dependencies for the target.
def register_build(target, command, deps) # @param user_deps [Array] List of user-specified extra dependencies.
def register_build(target, command, deps, user_deps)
@cache[:targets][target.encode(__ENCODING__)] = { @cache[:targets][target.encode(__ENCODING__)] = {
command: command, command: command,
checksum: calculate_checksum(target), checksum: calculate_checksum(target),
@ -139,7 +151,13 @@ module Rscons
fname: dep.encode(__ENCODING__), fname: dep.encode(__ENCODING__),
checksum: lookup_checksum(dep), checksum: lookup_checksum(dep),
} }
end end,
user_deps: user_deps.map do |dep|
{
fname: dep.encode(__ENCODING__),
checksum: lookup_checksum(dep),
}
end,
} }
end end

View File

@ -147,6 +147,7 @@ module Rscons
result = run_builder(@targets[target][:builder], result = run_builder(@targets[target][:builder],
target, target,
@targets[target][:source], @targets[target][:source],
[],
cache, cache,
@targets[target][:vars] || {}) @targets[target][:vars] || {})
unless result unless result
@ -238,7 +239,7 @@ module Rscons
converted_fname = get_build_fname(source, suffix) converted_fname = get_build_fname(source, suffix)
builder = @builders.values.find { |b| b.produces?(converted_fname, source, self) } builder = @builders.values.find { |b| b.produces?(converted_fname, source, self) }
if builder if builder
converted = run_builder(builder, converted_fname, [source], cache, vars) converted = run_builder(builder, converted_fname, [source], [], cache, vars)
return nil unless converted return nil unless converted
break break
end end
@ -255,18 +256,19 @@ module Rscons
# @param cache [Cache] The Cache. # @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder. # @param vars [Hash] Extra variables to pass to the builder.
# Return the result of the builder's run() method. # Return the result of the builder's run() method.
def run_builder(builder, target, sources, cache, vars) def run_builder(builder, target, sources, user_deps, cache, vars)
vars = @varset.merge(vars) vars = @varset.merge(vars)
@build_hooks.each do |build_hook_block| @build_hooks.each do |build_hook_block|
build_operation = { build_operation = {
builder: builder, builder: builder,
target: target, target: target,
sources: sources, sources: sources,
user_deps: user_deps,
vars: vars, vars: vars,
} }
build_hook_block.call(build_operation) build_hook_block.call(build_operation)
end end
builder.run(target, sources, cache, self, vars) builder.run(target, sources, user_deps, cache, self, vars)
end end
# Parse dependencies for a given target from a Makefile. # Parse dependencies for a given target from a Makefile.

View File

@ -220,7 +220,7 @@ describe Rscons do
it 'allows Ruby classes as custom builders to be used to construct files' do it 'allows Ruby classes as custom builders to be used to construct files' do
test_dir('custom_builder') test_dir('custom_builder')
class MySource < Rscons::Builder class MySource < Rscons::Builder
def run(target, sources, cache, env, vars = {}) def run(target, sources, user_deps, cache, env, vars = {})
File.open(target, 'w') do |fh| File.open(target, 'w') do |fh|
fh.puts <<EOF fh.puts <<EOF
#define THE_VALUE 5678 #define THE_VALUE 5678

View File

@ -40,12 +40,12 @@ module Rscons
describe "#up_to_date?" do describe "#up_to_date?" do
it "returns false when target file does not exist" do it "returns false when target file does not exist" do
File.should_receive(:exists?).with("target").and_return(false) File.should_receive(:exists?).with("target").and_return(false)
build_from({}).up_to_date?("target", "command", []).should be_false build_from({}).up_to_date?("target", "command", [], []).should be_false
end end
it "returns false when target is not registered in the cache" do it "returns false when target is not registered in the cache" do
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
build_from({}).up_to_date?("target", "command", []).should be_false build_from({}).up_to_date?("target", "command", [], []).should be_false
end end
it "returns false when the target's checksum does not match" do it "returns false when the target's checksum does not match" do
@ -53,7 +53,7 @@ module Rscons
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("def") cache.should_receive(:calculate_checksum).with("target").and_return("def")
cache.up_to_date?("target", "command", []).should be_false cache.up_to_date?("target", "command", [], []).should be_false
end end
it "returns false when the build command has changed" do it "returns false when the build command has changed" do
@ -61,7 +61,7 @@ module Rscons
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", []).should be_false cache.up_to_date?("target", "command", [], []).should be_false
end end
it "returns false when there is a new dependency" do it "returns false when there is a new dependency" do
@ -71,7 +71,7 @@ module Rscons
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"]).should be_false cache.up_to_date?("target", "command", ["dep.1", "dep.2"], []).should be_false
end end
it "returns false when a dependency's checksum has changed" do it "returns false when a dependency's checksum has changed" do
@ -82,13 +82,14 @@ module Rscons
{fname: "dep.2", {fname: "dep.2",
checksum: "dep.2.chk"}, checksum: "dep.2.chk"},
{fname: "extra.dep", {fname: "extra.dep",
checksum: "extra.dep.chk"}]}}} checksum: "extra.dep.chk"}],
user_deps: []}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed") cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"]).should be_false cache.up_to_date?("target", "command", ["dep.1", "dep.2"], []).should be_false
end end
it "returns false with strict_deps=true when cache has an extra dependency" do it "returns false with strict_deps=true when cache has an extra dependency" do
@ -99,11 +100,44 @@ module Rscons
{fname: "dep.2", {fname: "dep.2",
checksum: "dep.2.chk"}, checksum: "dep.2.chk"},
{fname: "extra.dep", {fname: "extra.dep",
checksum: "extra.dep.chk"}]}}} checksum: "extra.dep.chk"}],
user_deps: []}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], strict_deps: true).should be_false cache.up_to_date?("target", "command", ["dep.1", "dep.2"], [], strict_deps: true).should be_false
end
it "returns false when there is a new user dependency" do
_cache = {targets: {"target" => {checksum: "abc",
command: "command",
deps: [{fname: "dep.1"}],
user_deps: []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1"], ["file.ld"]).should be_false
end
it "returns false when a user dependency checksum has changed" do
_cache = {targets: {"target" => {checksum: "abc",
command: "command",
deps: [{fname: "dep.1",
checksum: "dep.1.chk"},
{fname: "dep.2",
checksum: "dep.2.chk"},
{fname: "extra.dep",
checksum: "extra.dep.chk"}],
user_deps: [{fname: "user.dep",
checksum: "user.dep.chk"}]}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.should_receive(:calculate_checksum).with("user.dep").and_return("INCORRECT")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], ["user.dep"]).should be_false
end end
it "returns true when no condition for false is met" do it "returns true when no condition for false is met" do
@ -114,14 +148,15 @@ module Rscons
{fname: "dep.2", {fname: "dep.2",
checksum: "dep.2.chk"}, checksum: "dep.2.chk"},
{fname: "extra.dep", {fname: "extra.dep",
checksum: "extra.dep.chk"}]}}} checksum: "extra.dep.chk"}],
user_deps: []}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk") cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk") cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"]).should be_true cache.up_to_date?("target", "command", ["dep.1", "dep.2"], []).should be_true
end end
end end
@ -132,7 +167,8 @@ module Rscons
cache.should_receive(:calculate_checksum).with("the target").and_return("the checksum") cache.should_receive(:calculate_checksum).with("the target").and_return("the checksum")
cache.should_receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum") cache.should_receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum")
cache.should_receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum") cache.should_receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum")
cache.register_build("the target", "the command", ["dep 1", "dep 2"]) cache.should_receive(:calculate_checksum).with("user.dep").and_return("user.dep checksum")
cache.register_build("the target", "the command", ["dep 1", "dep 2"], ["user.dep"])
cached_target = cache.instance_variable_get(:@cache)[:targets]["the target"] cached_target = cache.instance_variable_get(:@cache)[:targets]["the target"]
cached_target.should_not be_nil cached_target.should_not be_nil
cached_target[:command].should == "the command" cached_target[:command].should == "the command"
@ -141,6 +177,9 @@ module Rscons
{fname: "dep 1", checksum: "dep 1 checksum"}, {fname: "dep 1", checksum: "dep 1 checksum"},
{fname: "dep 2", checksum: "dep 2 checksum"}, {fname: "dep 2", checksum: "dep 2 checksum"},
] ]
cached_target[:user_deps].should == [
{fname: "user.dep", checksum: "user.dep checksum"},
]
end end
end end

View File

@ -114,7 +114,7 @@ module Rscons
cache = "cache" cache = "cache"
Cache.should_receive(:new).and_return(cache) Cache.should_receive(:new).and_return(cache)
env.should_receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true) env.should_receive(:run_builder).with(anything, "a.out", ["main.c"], [], cache, {}).and_return(true)
cache.should_receive(:write) cache.should_receive(:write)
env.process env.process
@ -127,8 +127,8 @@ module Rscons
cache = "cache" cache = "cache"
Cache.should_receive(:new).and_return(cache) Cache.should_receive(:new).and_return(cache)
env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return("main.o") env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], [], cache, {}).and_return("main.o")
env.should_receive(:run_builder).with(anything, "a.out", ["main.o"], cache, {}).and_return("a.out") env.should_receive(:run_builder).with(anything, "a.out", ["main.o"], [], cache, {}).and_return("a.out")
cache.should_receive(:write) cache.should_receive(:write)
env.process env.process
@ -141,7 +141,7 @@ module Rscons
cache = "cache" cache = "cache"
Cache.should_receive(:new).and_return(cache) Cache.should_receive(:new).and_return(cache)
env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return(false) env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], [], cache, {}).and_return(false)
cache.should_receive(:write) cache.should_receive(:write)
expect { env.process }.to raise_error BuildError, /Failed.to.build.main.o/ expect { env.process }.to raise_error BuildError, /Failed.to.build.main.o/
@ -241,8 +241,8 @@ module Rscons
cache = "cache" cache = "cache"
env = Environment.new env = Environment.new
env.add_builder(ABuilder.new) env.add_builder(ABuilder.new)
env.builders["Object"].should_receive(:run).with("mod.o", ["mod.c"], cache, env, anything).and_return("mod.o") env.builders["Object"].should_receive(:run).with("mod.o", ["mod.c"], [], cache, env, anything).and_return("mod.o")
env.builders["ABuilder"].should_receive(:run).with("mod2.ab_out", ["mod2.ab_in"], cache, env, anything).and_return("mod2.ab_out") env.builders["ABuilder"].should_receive(:run).with("mod2.ab_out", ["mod2.ab_in"], [], cache, env, anything).and_return("mod2.ab_out")
env.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {}).should == ["precompiled.o", "mod.o", "mod2.ab_out"] env.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {}).should == ["precompiled.o", "mod.o", "mod2.ab_out"]
end end
end end
@ -255,14 +255,14 @@ module Rscons
build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"] build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"]
end end
end end
env.builders["Object"].stub(:run) do |target, sources, cache, env, vars| env.builders["Object"].stub(:run) do |target, sources, user_deps, cache, env, vars|
vars["CFLAGS"].should == [] vars["CFLAGS"].should == []
end end
env.run_builder(env.builders["Object"], "build/normal/module.o", ["src/normal/module.c"], "cache", {}) env.run_builder(env.builders["Object"], "build/normal/module.o", ["src/normal/module.c"], [], "cache", {})
env.builders["Object"].stub(:run) do |target, sources, cache, env, vars| env.builders["Object"].stub(:run) do |target, sources, user_deps, cache, env, vars|
vars["CFLAGS"].should == ["-O3", "-DSPECIAL"] vars["CFLAGS"].should == ["-O3", "-DSPECIAL"]
end end
env.run_builder(env.builders["Object"], "build/special/module.o", ["src/special/module.c"], "cache", {}) env.run_builder(env.builders["Object"], "build/special/module.o", ["src/special/module.c"], [], "cache", {})
end end
end end