diff --git a/build_tests/simple/Rsconsfile b/build_tests/simple/Rsconsfile index 9775f9a..06b9b79 100644 --- a/build_tests/simple/Rsconsfile +++ b/build_tests/simple/Rsconsfile @@ -1,3 +1,3 @@ Rscons::Environment.new do |env| - env.Program('simple', Dir['*.c']) + env.Program('simple.exe', Dir['*.c']) end diff --git a/build_tests/simple/cache_command_change.rb b/build_tests/simple/cache_command_change.rb new file mode 100644 index 0000000..ad6ec2d --- /dev/null +++ b/build_tests/simple/cache_command_change.rb @@ -0,0 +1,4 @@ +Rscons::Environment.new do |env| + env["LIBS"] += ["c"] + env.Program('simple.exe', Dir['*.c']) +end diff --git a/build_tests/simple/cache_dep_checksum_change.rb b/build_tests/simple/cache_dep_checksum_change.rb new file mode 100644 index 0000000..dc12bd5 --- /dev/null +++ b/build_tests/simple/cache_dep_checksum_change.rb @@ -0,0 +1,3 @@ +Rscons::Environment.new do |env| + env.Copy("simple.copy", "simple.c") +end diff --git a/build_tests/simple/cache_new_dep1.rb b/build_tests/simple/cache_new_dep1.rb new file mode 100644 index 0000000..0a2307a --- /dev/null +++ b/build_tests/simple/cache_new_dep1.rb @@ -0,0 +1,6 @@ +Rscons::Environment.new do |env| + env.Object("simple.o", "simple.c") + env.process + env["LDCMD"] = %w[gcc -o ${_TARGET} simple.o] + env.Program('simple.exe', []) +end diff --git a/build_tests/simple/cache_new_dep2.rb b/build_tests/simple/cache_new_dep2.rb new file mode 100644 index 0000000..2308920 --- /dev/null +++ b/build_tests/simple/cache_new_dep2.rb @@ -0,0 +1,5 @@ +Rscons::Environment.new do |env| + env.Object("simple.o", "simple.c") + env["LDCMD"] = %w[gcc -o ${_TARGET} simple.o] + env.Program('simple.exe', ["simple.o"]) +end diff --git a/build_tests/simple/cache_user_dep.rb b/build_tests/simple/cache_user_dep.rb new file mode 100644 index 0000000..db7e028 --- /dev/null +++ b/build_tests/simple/cache_user_dep.rb @@ -0,0 +1,5 @@ +Rscons::Environment.new do |env| + env.Program("simple.exe", "simple.c") + user_deps = File.read("user_deps", mode: "rb").split(" ") + env.depends("simple.exe", *user_deps) +end diff --git a/build_tests/two_sources/cache_strict_deps.rb b/build_tests/two_sources/cache_strict_deps.rb new file mode 100644 index 0000000..a20ad83 --- /dev/null +++ b/build_tests/two_sources/cache_strict_deps.rb @@ -0,0 +1,19 @@ +class StrictBuilder < Rscons::Builder + def run(options) + target, sources, cache, env = options.values_at(:target, :sources, :cache, :env) + command = %W[gcc -o #{target}] + sources.sort + unless cache.up_to_date?(target, command, sources, env, strict_deps: true) + return false unless env.execute("StrictBuilder #{target}", command) + cache.register_build(target, command, sources, env) + end + target + end +end + +Rscons::Environment.new(echo: :command) do |env| + env.add_builder(StrictBuilder.new) + env.Object("one.o", "one.c", "CCFLAGS" => %w[-DONE]) + env.Object("two.o", "two.c") + sources = File.read("sources", mode: "rb").split(" ") + env.StrictBuilder("program.exe", sources) +end diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index 8ab2d4b..6c27976 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -106,7 +106,7 @@ EOF result = run_test expect(result.stderr).to eq "" expect(File.exists?('simple.o')).to be_truthy - expect(`./simple`).to eq "This is a simple C program\n" + expect(`./simple.exe`).to eq "This is a simple C program\n" end it 'prints commands as they are executed' do @@ -766,4 +766,173 @@ EOF end end + context "Cache management" do + it "prints a warning when the cache is corrupt" do + test_dir("simple") + File.open(Rscons::Cache::CACHE_FILE, "w") do |fh| + fh.puts("[1]") + end + result = run_test + expect(result.stderr).to match /Warning.*was corrupt. Contents:/ + end + + it "removes the cache file on a clean operation" do + test_dir("simple") + result = run_test + expect(result.stderr).to eq "" + expect(File.exists?(Rscons::Cache::CACHE_FILE)).to be_truthy + result = run_test(rscons_args: %w[-c]) + expect(result.stderr).to eq "" + expect(File.exists?(Rscons::Cache::CACHE_FILE)).to be_falsey + end + + it "forces a build when the target file does not exist and is not in the cache" do + test_dir("simple") + expect(File.exists?("simple.exe")).to be_falsey + result = run_test + expect(result.stderr).to eq "" + expect(File.exists?("simple.exe")).to be_truthy + end + + it "forces a build when the target file does exist but is not in the cache" do + test_dir("simple") + File.open("simple.exe", "wb") do |fh| + fh.write("hi") + end + result = run_test + expect(result.stderr).to eq "" + expect(File.exists?("simple.exe")).to be_truthy + expect(File.read("simple.exe", mode: "rb")).to_not eq "hi" + end + + it "forces a build when the target file exists and is in the cache but has changed since cached" do + test_dir("simple") + result = run_test + expect(result.stderr).to eq "" + File.open("simple.exe", "wb") do |fh| + fh.write("hi") + end + test_dir("simple") + result = run_test + expect(result.stderr).to eq "" + expect(File.exists?("simple.exe")).to be_truthy + expect(File.read("simple.exe", mode: "rb")).to_not eq "hi" + end + + it "forces a build when the command changes" do + test_dir("simple") + + result = run_test + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "CC simple.o", + "LD simple.exe", + ] + + result = run_test(rsconsfile: "cache_command_change.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "LD simple.exe", + ] + end + + it "forces a build when there is a new dependency" do + test_dir("simple") + + result = run_test(rsconsfile: "cache_new_dep1.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "CC simple.o", + "LD simple.exe", + ] + + result = run_test(rsconsfile: "cache_new_dep2.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "LD simple.exe", + ] + end + + it "forces a build when a dependency's checksum has changed" do + test_dir("simple") + + result = run_test(rsconsfile: "cache_dep_checksum_change.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq ["Copy simple.copy"] + File.open("simple.c", "wb") do |fh| + fh.write("hi") + end + + result = run_test(rsconsfile: "cache_dep_checksum_change.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq ["Copy simple.copy"] + end + + it "forces a rebuild with strict_deps=true when dependency order changes" do + test_dir("two_sources") + + File.open("sources", "wb") do |fh| + fh.write("one.o two.o") + end + result = run_test(rsconsfile: "cache_strict_deps.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to include "gcc -o program.exe one.o two.o" + + result = run_test(rsconsfile: "cache_strict_deps.rb") + expect(result.stderr).to eq "" + expect(result.stdout).to eq "" + + File.open("sources", "wb") do |fh| + fh.write("two.o one.o") + end + result = run_test(rsconsfile: "cache_strict_deps.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to include "gcc -o program.exe one.o two.o" + end + + it "forces a rebuild when there is a new user dependency" do + test_dir("simple") + + File.open("foo", "wb") {|fh| fh.write("hi")} + File.open("user_deps", "wb") {|fh| fh.write("")} + result = run_test(rsconsfile: "cache_user_dep.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "CC simple.o", + "LD simple.exe", + ] + + File.open("user_deps", "wb") {|fh| fh.write("foo")} + result = run_test(rsconsfile: "cache_user_dep.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "LD simple.exe", + ] + end + + it "forces a rebuild when a user dependency file checksum has changed" do + test_dir("simple") + + File.open("foo", "wb") {|fh| fh.write("hi")} + File.open("user_deps", "wb") {|fh| fh.write("foo")} + result = run_test(rsconsfile: "cache_user_dep.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "CC simple.o", + "LD simple.exe", + ] + + result = run_test(rsconsfile: "cache_user_dep.rb") + expect(result.stderr).to eq "" + expect(result.stdout).to eq "" + + File.open("foo", "wb") {|fh| fh.write("hi2")} + result = run_test(rsconsfile: "cache_user_dep.rb") + expect(result.stderr).to eq "" + expect(lines(result.stdout)).to eq [ + "LD simple.exe", + ] + end + end + end diff --git a/spec/rscons/cache_spec.rb b/spec/rscons/cache_spec.rb index 4f5d915..4c15764 100644 --- a/spec/rscons/cache_spec.rb +++ b/spec/rscons/cache_spec.rb @@ -13,194 +13,6 @@ module Rscons end end - describe "#initialize" do - context "when corrupt" do - it "prints a warning and defaults to an empty hash" do - expect(JSON).to receive(:load).and_return("string") - expect($stderr).to receive(:puts).with(/Warning:.*was.corrupt/) - c = Cache.instance - c.send(:initialize!) - expect(c.instance_variable_get(:@cache).is_a?(Hash)).to be_truthy - end - end - end - - describe "#clear" do - it "removes the cache file" do - expect(FileUtils).to receive(:rm_f).with(Cache::CACHE_FILE) - allow(JSON).to receive(:load) {{}} - Cache.instance.clear - end - end - - describe "#write" do - it "fills in 'version' and write to file" do - cache = {} - fh = $stdout - expect(fh).to receive(:puts) - expect(File).to receive(:open).and_yield(fh) - build_from(cache).write - expect(cache["version"]).to eq Rscons::VERSION - end - end - - describe "#up_to_date?" do - empty_env = "env" - before do - allow(empty_env).to receive(:get_user_deps) { nil } - end - - it "returns false when target file does not exist" do - expect(File).to receive(:exists?).with("target").and_return(false) - expect(build_from({}).up_to_date?("target", "command", [], empty_env)).to be_falsey - end - - it "returns false when target is not registered in the cache" do - expect(File).to receive(:exists?).with("target").and_return(true) - expect(build_from({}).up_to_date?("target", "command", [], empty_env)).to be_falsey - end - - it "returns false when the target's checksum does not match" do - _cache = {"targets" => {"target" => {"checksum" => "abc"}}} - cache = build_from(_cache) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("def") - expect(cache.up_to_date?("target", "command", [], empty_env)).to be_falsey - end - - it "returns false when the build command has changed" do - _cache = {"targets" => {"target" => {"checksum" => "abc", "command" => Digest::MD5.hexdigest("old command".inspect)}}} - cache = build_from(_cache) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache.up_to_date?("target", "command", [], empty_env)).to be_falsey - end - - it "returns false when there is a new dependency" do - _cache = {"targets" => {"target" => {"checksum" => "abc", - "command" => Digest::MD5.hexdigest("command".inspect), - "deps" => [{"fname" => "dep.1"}]}}} - cache = build_from(_cache) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_falsey - end - - it "returns false when a dependency's checksum has changed" do - _cache = {"targets" => {"target" => {"checksum" => "abc", - "command" => Digest::MD5.hexdigest("command".inspect), - "deps" => [{"fname" => "dep.1", - "checksum" => "dep.1.chk"}, - {"fname" => "dep.2", - "checksum" => "dep.2.chk"}, - {"fname" => "extra.dep", - "checksum" => "extra.dep.chk"}], - "user_deps" => []}}} - cache = build_from(_cache) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") - expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed") - expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_falsey - end - - it "returns false with strict_deps=true when cache has an extra dependency" do - _cache = {"targets" => {"target" => {"checksum" => "abc", - "command" => Digest::MD5.hexdigest("command".inspect), - "deps" => [{"fname" => "dep.1", - "checksum" => "dep.1.chk"}, - {"fname" => "dep.2", - "checksum" => "dep.2.chk"}, - {"fname" => "extra.dep", - "checksum" => "extra.dep.chk"}], - "user_deps" => []}}} - cache = build_from(_cache) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env, strict_deps: true)).to be_falsey - end - - it "returns false when there is a new user dependency" do - _cache = {"targets" => {"target" => {"checksum" => "abc", - "command" => Digest::MD5.hexdigest("command".inspect), - "deps" => [{"fname" => "dep.1"}], - "user_deps" => []}}} - cache = build_from(_cache) - env = "env" - expect(env).to receive(:get_user_deps).with("target").and_return(["file.ld"]) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache.up_to_date?("target", "command", ["dep.1"], env)).to be_falsey - end - - it "returns false when a user dependency checksum has changed" do - _cache = {"targets" => {"target" => {"checksum" => "abc", - "command" => Digest::MD5.hexdigest("command".inspect), - "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) - env = "env" - expect(env).to receive(:get_user_deps).with("target").and_return(["user.dep"]) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") - expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk") - expect(cache).to receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk") - expect(cache).to receive(:calculate_checksum).with("user.dep").and_return("INCORRECT") - expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], env)).to be_falsey - end - - it "returns true when no condition for false is met" do - _cache = {"targets" => {"target" => {"checksum" => "abc", - "command" => Digest::MD5.hexdigest("command".inspect), - "deps" => [{"fname" => "dep.1", - "checksum" => "dep.1.chk"}, - {"fname" => "dep.2", - "checksum" => "dep.2.chk"}, - {"fname" => "extra.dep", - "checksum" => "extra.dep.chk"}], - "user_deps" => []}}} - cache = build_from(_cache) - expect(File).to receive(:exists?).with("target").and_return(true) - expect(cache).to receive(:calculate_checksum).with("target").and_return("abc") - expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") - expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk") - expect(cache).to receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk") - expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_truthy - end - end - - describe "#register_build" do - it "stores the given information in the cache" do - _cache = {} - cache = build_from(_cache) - env = "env" - expect(env).to receive(:get_user_deps).with("the target").and_return(["user.dep"]) - expect(cache).to receive(:calculate_checksum).with("the target").and_return("the checksum") - expect(cache).to receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum") - expect(cache).to receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum") - expect(cache).to receive(:calculate_checksum).with("user.dep").and_return("user.dep checksum") - cache.register_build("the target", "the command", ["dep 1", "dep 2"], env) - cached_target = cache.instance_variable_get(:@cache)["targets"]["the target"] - expect(cached_target).to_not be_nil - expect(cached_target["command"]).to eq Digest::MD5.hexdigest("the command".inspect) - expect(cached_target["checksum"]).to eq "the checksum" - expect(cached_target["deps"]).to eq [ - {"fname" => "dep 1", "checksum" => "dep 1 checksum"}, - {"fname" => "dep 2", "checksum" => "dep 2 checksum"}, - ] - expect(cached_target["user_deps"]).to eq [ - {"fname" => "user.dep", "checksum" => "user.dep checksum"}, - ] - end - end - describe "#targets" do it "returns a list of targets that are cached" do cache = {"targets" => {"t1" => {}, "t2" => {}, "t3" => {}}}