diff --git a/lib/rscons/builder.rb b/lib/rscons/builder.rb index f715291..a2c27fb 100644 --- a/lib/rscons/builder.rb +++ b/lib/rscons/builder.rb @@ -22,12 +22,12 @@ module Rscons # Check if the cache is up to date for the target and if not execute the # build command. # Return the name of the target or false on failure. - def standard_build(short_cmd_string, target, command, sources, env, cache) - unless cache.up_to_date?(target, command, sources) + def standard_build(short_cmd_string, target, command, sources, user_deps, env, cache) + unless cache.up_to_date?(target, command, sources, user_deps) cache.mkdir_p(File.dirname(target)) FileUtils.rm_f(target) return false unless env.execute(short_cmd_string, command) - cache.register_build(target, command, sources) + cache.register_build(target, command, sources, user_deps) end target end diff --git a/lib/rscons/builders/library.rb b/lib/rscons/builders/library.rb index 8d91370..cefbd3f 100644 --- a/lib/rscons/builders/library.rb +++ b/lib/rscons/builders/library.rb @@ -12,7 +12,7 @@ module Rscons } end - def run(target, sources, cache, env, vars) + def run(target, sources, user_deps, cache, env, vars) # build sources to linkable objects objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) if objects @@ -21,7 +21,7 @@ module Rscons '_SOURCES' => objects, }) 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 diff --git a/lib/rscons/builders/object.rb b/lib/rscons/builders/object.rb index c03f3fb..7489dd3 100644 --- a/lib/rscons/builders/object.rb +++ b/lib/rscons/builders/object.rb @@ -50,7 +50,7 @@ module Rscons end end - def run(target, sources, cache, env, vars) + def run(target, sources, user_deps, cache, env, vars) vars = vars.merge({ '_TARGET' => target, '_SOURCES' => sources, @@ -62,7 +62,7 @@ module Rscons v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}" end.first 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)) FileUtils.rm_f(target) return false unless env.execute("#{com_prefix} #{target}", command) @@ -71,7 +71,7 @@ module Rscons deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target) FileUtils.rm_f(vars['_DEPFILE']) end - cache.register_build(target, command, deps.uniq) + cache.register_build(target, command, deps.uniq, user_deps) end target end diff --git a/lib/rscons/builders/program.rb b/lib/rscons/builders/program.rb index 850c80e..1b08de8 100644 --- a/lib/rscons/builders/program.rb +++ b/lib/rscons/builders/program.rb @@ -14,7 +14,7 @@ module Rscons } end - def run(target, sources, cache, env, vars) + def run(target, sources, user_deps, cache, env, vars) # build sources to linkable objects objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) return false unless objects @@ -33,7 +33,7 @@ module Rscons 'LD' => ld, }) 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 diff --git a/lib/rscons/cache.rb b/lib/rscons/cache.rb index 06133e6..c361ac0 100644 --- a/lib/rscons/cache.rb +++ b/lib/rscons/cache.rb @@ -18,9 +18,15 @@ module Rscons # { # 'fname' => 'program.o', # 'checksum' => '87654321', - # } + # }, # ], - # } + # 'user_deps' => [ + # { + # 'fname' => 'lscript.ld', + # 'checksum' => '77551133', + # }, + # ], + # }, # 'program.o' => { # 'checksum' => '87654321', # 'command' => ['gcc', '-c', '-o', 'program.o', 'program.c'], @@ -32,15 +38,16 @@ module Rscons # { # 'fname' => 'program.h', # 'checksum' => '7979764643', - # } - # ] + # }, + # ], + # 'user_deps' => [], # } - # } + # }, # directories: { # 'build' => true, # 'build/one' => true, # 'build/two' => true, - # } + # }, # } class Cache #### Constants @@ -82,6 +89,7 @@ module Rscons # @param target [String] The name of the target file. # @param command [Array] The command used to build the target. # @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: # :strict_deps:: # Only consider a target up to date if its list of dependencies is @@ -96,7 +104,7 @@ module Rscons # exactly equal to those cached # - each cached dependency file's current checksum matches the checksum # 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 return false unless File.exists?(target) @@ -118,8 +126,11 @@ module Rscons return false unless (Set.new(deps) - Set.new(cached_deps)).empty? 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 - @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]) end @@ -130,7 +141,8 @@ module Rscons # @param target [String] The name of the target. # @param command [Array] The command used to build 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__)] = { command: command, checksum: calculate_checksum(target), @@ -139,7 +151,13 @@ module Rscons fname: dep.encode(__ENCODING__), checksum: lookup_checksum(dep), } - end + end, + user_deps: user_deps.map do |dep| + { + fname: dep.encode(__ENCODING__), + checksum: lookup_checksum(dep), + } + end, } end diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 573d390..ba51fae 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -147,6 +147,7 @@ module Rscons result = run_builder(@targets[target][:builder], target, @targets[target][:source], + [], cache, @targets[target][:vars] || {}) unless result @@ -238,7 +239,7 @@ module Rscons converted_fname = get_build_fname(source, suffix) builder = @builders.values.find { |b| b.produces?(converted_fname, source, self) } if builder - converted = run_builder(builder, converted_fname, [source], cache, vars) + converted = run_builder(builder, converted_fname, [source], [], cache, vars) return nil unless converted break end @@ -255,18 +256,19 @@ module Rscons # @param cache [Cache] The Cache. # @param vars [Hash] Extra variables to pass to the builder. # 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) @build_hooks.each do |build_hook_block| build_operation = { builder: builder, target: target, sources: sources, + user_deps: user_deps, vars: vars, } build_hook_block.call(build_operation) end - builder.run(target, sources, cache, self, vars) + builder.run(target, sources, user_deps, cache, self, vars) end # Parse dependencies for a given target from a Makefile. diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index 78a340d..77c22ec 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -220,7 +220,7 @@ describe Rscons do it 'allows Ruby classes as custom builders to be used to construct files' do test_dir('custom_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| fh.puts < {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 it "returns true when no condition for false is met" do @@ -114,14 +148,15 @@ module Rscons {fname: "dep.2", checksum: "dep.2.chk"}, {fname: "extra.dep", - checksum: "extra.dep.chk"}]}}} + checksum: "extra.dep.chk"}], + 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.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.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 @@ -132,7 +167,8 @@ module Rscons 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 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.should_not be_nil cached_target[:command].should == "the command" @@ -141,6 +177,9 @@ module Rscons {fname: "dep 1", checksum: "dep 1 checksum"}, {fname: "dep 2", checksum: "dep 2 checksum"}, ] + cached_target[:user_deps].should == [ + {fname: "user.dep", checksum: "user.dep checksum"}, + ] end end diff --git a/spec/rscons/environment_spec.rb b/spec/rscons/environment_spec.rb index 52b94a5..9aa4131 100644 --- a/spec/rscons/environment_spec.rb +++ b/spec/rscons/environment_spec.rb @@ -114,7 +114,7 @@ module Rscons cache = "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) env.process @@ -127,8 +127,8 @@ module Rscons cache = "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, "a.out", ["main.o"], cache, {}).and_return("a.out") + 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") cache.should_receive(:write) env.process @@ -141,7 +141,7 @@ module Rscons cache = "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) expect { env.process }.to raise_error BuildError, /Failed.to.build.main.o/ @@ -241,8 +241,8 @@ module Rscons cache = "cache" env = Environment.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["ABuilder"].should_receive(:run).with("mod2.ab_out", ["mod2.ab_in"], cache, env, anything).and_return("mod2.ab_out") + 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.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {}).should == ["precompiled.o", "mod.o", "mod2.ab_out"] end end @@ -255,14 +255,14 @@ module Rscons build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"] 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 == [] end - 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.run_builder(env.builders["Object"], "build/normal/module.o", ["src/normal/module.c"], [], "cache", {}) + env.builders["Object"].stub(:run) do |target, sources, user_deps, cache, env, vars| vars["CFLAGS"].should == ["-O3", "-DSPECIAL"] 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