diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 81cd63d..57712d6 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -38,7 +38,7 @@ module Rscons # when the block returns, the {#process} method is automatically called. def initialize(options = {}) @varset = VarSet.new - @targets = {} + @job_set = JobSet.new @user_deps = {} @builders = {} @build_dirs = [] @@ -280,40 +280,24 @@ module Rscons # # @return [void] def process - while @targets.size > 0 - expand_paths! - targets = @targets - @targets = {} - cache = Cache.instance - cache.clear_checksum_cache! - targets_processed = Set.new - process_target = proc do |target| - unless targets_processed.include?(target) - targets_processed << target - targets[target].each do |target_params| - target_params[:sources].each do |src| - if targets.include?(src) and not targets_processed.include?(src) - process_target.call(src) - end - end - result = run_builder(target_params[:builder], - target, - target_params[:sources], - cache, - target_params[:vars] || {}) - unless result - raise BuildError.new("Failed to build #{target}") - end - end + cache = Cache.instance + begin + while job = @job_set.get_next_job_to_run + expand_paths(job) + # TODO: have Cache determine when checksums may be invalid based on + # file size and/or timestamp. + cache.clear_checksum_cache! + result = run_builder(job[:builder], + job[:target], + job[:sources], + cache, + job[:vars]) + unless result + raise BuildError.new("Failed to build #{job[:target]}") end end - begin - targets.each_key do |target| - process_target.call(target) - end - ensure - cache.write - end + ensure + cache.write end end @@ -321,7 +305,7 @@ module Rscons # # @return [void] def clear_targets - @targets = {} + @job_set.clear! end # Expand a construction variable reference. @@ -389,7 +373,7 @@ module Rscons sources = Array(sources) builder = @builders[method.to_s] build_target = builder.create_build_target(env: self, target: target, sources: sources) - add_target(build_target.to_s, builder, sources, vars, rest) + add_target(build_target.to_s, builder, sources, vars || {}, rest) build_target else super @@ -402,17 +386,11 @@ module Rscons # @param builder [Builder] The {Builder} to use to build the target. # @param sources [Array] Source file name(s). # @param vars [Hash] Construction variable overrides. - # @param args [Object] Any extra arguments passed to the {Builder}. + # @param args [Object] Deprecated; unused. # # @return [void] def add_target(target, builder, sources, vars, args) - @targets[target] ||= [] - @targets[target] << { - builder: builder, - sources: sources, - vars: vars, - args: args, - } + @job_set.add_job(builder, target, sources, vars) end # Manually record a given target as depending on the specified files. @@ -676,26 +654,20 @@ module Rscons private - # Expand target and source paths before invoking builders. + # Expand target and source paths of a job before invoking the builder. # # This method expand construction variable references in the target and # source file names before passing them to the builder. It also expands # "^/" prefixes to the Environment's build root if a build root is defined. # # @return [void] - def expand_paths! - @targets = @targets.reduce({}) do |result, (target, target_params_list)| - target = expand_path(target) if @build_root - target = expand_varref(target) - result[target] = target_params_list.map do |target_params| - sources = target_params[:sources].map do |source| - source = expand_path(source) if @build_root - expand_varref(source) - end.flatten - target_params.merge(sources: sources) - end - result - end + def expand_paths(job) + job[:target] = expand_path(job[:target]) if @build_root + job[:target] = expand_varref(job[:target]) + job[:sources] = job[:sources].map do |source| + source = expand_path(source) if @build_root + expand_varref(source) + end.flatten end # Parse dependencies for a given target from a Makefile. diff --git a/lib/rscons/job_set.rb b/lib/rscons/job_set.rb index 76de53e..05c6726 100644 --- a/lib/rscons/job_set.rb +++ b/lib/rscons/job_set.rb @@ -20,7 +20,12 @@ module Rscons # @param vars [Hash] # Construction variable overrides. def add_job(builder, target, sources, vars) - @jobs[target] = { + # We allow multiple jobs to be registered per target for cases like: + # env.Directory("dest") + # env.Install("dest", "bin") + # env.Install("dest", "share") + @jobs[target] ||= [] + @jobs[target] << { builder: builder, target: target, sources: sources, @@ -39,18 +44,27 @@ module Rscons evaluated_targets = Set.new attempt = lambda do |target| evaluated_targets << target - @jobs[target][:sources].each do |src| + @jobs[target][0][:sources].each do |src| if @jobs.include?(src) and not evaluated_targets.include?(src) return attempt[src] end end - job = @jobs[target].merge(target: target) - @jobs.delete(target) + job = @jobs[target][0].merge(target: target) + if @jobs[target].size > 1 + @jobs[target].slice!(0) + else + @jobs.delete(target) + end return job end attempt[@jobs.first.first] end end + # Remove all jobs from the JobSet. + def clear! + @jobs.clear + end + end end diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index 98c9918..8baf883 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -966,4 +966,15 @@ EOF end end + context "Environment#clear_targets" do + it "clears registered targets" do + test_dir('header') + env = Rscons::Environment.new do |env| + env.Program('header', Dir['*.c']) + env.clear_targets + end + expect(lines).to eq [] + end + end + end diff --git a/spec/rscons/environment_spec.rb b/spec/rscons/environment_spec.rb index aabab74..802bc5b 100644 --- a/spec/rscons/environment_spec.rb +++ b/spec/rscons/environment_spec.rb @@ -169,7 +169,7 @@ module Rscons cache = "cache" expect(Cache).to receive(:instance).and_return(cache) - expect(cache).to receive(:clear_checksum_cache!) + allow(cache).to receive(:clear_checksum_cache!) expect(env).to receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true) expect(cache).to receive(:write) @@ -183,7 +183,7 @@ module Rscons cache = "cache" expect(Cache).to receive(:instance).and_return(cache) - expect(cache).to receive(:clear_checksum_cache!) + allow(cache).to receive(:clear_checksum_cache!) expect(env).to receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return("main.o") expect(env).to receive(:run_builder).with(anything, "a.out", ["main.o"], cache, {}).and_return("a.out") expect(cache).to receive(:write) @@ -198,7 +198,7 @@ module Rscons cache = "cache" expect(Cache).to receive(:instance).and_return(cache) - expect(cache).to receive(:clear_checksum_cache!) + allow(cache).to receive(:clear_checksum_cache!) expect(env).to receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return(false) expect(cache).to receive(:write) @@ -221,18 +221,6 @@ module Rscons end end - describe "#clear_targets" do - it "resets @targets to an empty hash" do - env = Environment.new - env.Program("a.out", "main.o") - expect(env.instance_variable_get(:@targets).keys).to eq(["a.out"]) - - env.clear_targets - - expect(env.instance_variable_get(:@targets).keys).to eq([]) - end - end - describe "#build_command" do it "returns a command based on the variables in the Environment" do env = Environment.new @@ -299,18 +287,6 @@ module Rscons expect {env.foobar}.to raise_error /undefined method .foobar./ end - it "records the target when the target method is a known builder" do - env = Environment.new - expect(env.instance_variable_get(:@targets)).to eq({}) - env.Object("target.o", ["src1.c", "src2.c"], var: "val") - target = env.instance_variable_get(:@targets)["target.o"] - expect(target).to_not be_nil - expect(target[0][:builder].is_a?(Builder)).to be_truthy - expect(target[0][:sources]).to eq ["src1.c", "src2.c"] - expect(target[0][:vars]).to eq({var: "val"}) - expect(target[0][:args]).to eq [] - end - it "raises an error when vars is not a Hash" do env = Environment.new expect { env.Program("a.out", "main.c", "other") }.to raise_error /Unexpected construction variable set/