diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 085095d..42fabc5 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -263,11 +263,13 @@ module Rscons # # @return [Array, String] Expansion of the variable reference. def expand_varref(varref, extra_vars = nil) - if extra_vars.nil? - @varset - else - @varset.merge(extra_vars) - end.expand_varref(varref) + vars = if extra_vars.nil? + @varset + else + @varset.merge(extra_vars) + end + lambda_args = [env: self, vars: vars] + vars.expand_varref(varref, lambda_args) end alias_method :build_command, :expand_varref diff --git a/lib/rscons/varset.rb b/lib/rscons/varset.rb index be575d8..0f9eee8 100644 --- a/lib/rscons/varset.rb +++ b/lib/rscons/varset.rb @@ -86,29 +86,38 @@ module Rscons # Replace "$\{var}" variable references in varref with the expanded # variables' values, recursively. # - # @param varref [String, Array] Value containing references to variables. + # @param varref [nil, String, Array, Proc] + # Value containing references to variables. + # @param lambda_args [Array] + # Arguments to pass to any lambda variable values to be expanded. # - # @return [String, Array] + # @return [nil, String, Array] # Expanded value with "$\{var}" variable references replaced. - def expand_varref(varref) - if varref.is_a?(Array) - varref.map do |ent| - expand_varref(ent) - end.flatten - else + def expand_varref(varref, lambda_args) + if varref.is_a?(String) if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/ prefix, varname, suffix = $1, $2, $3 - varval = expand_varref(self[varname]) + varval = expand_varref(self[varname], lambda_args) if varval.is_a?(String) or varval.nil? - expand_varref("#{prefix}#{varval}#{suffix}") + expand_varref("#{prefix}#{varval}#{suffix}", lambda_args) elsif varval.is_a?(Array) - varval.map {|vv| expand_varref("#{prefix}#{vv}#{suffix}")}.flatten + varval.map {|vv| expand_varref("#{prefix}#{vv}#{suffix}", lambda_args)}.flatten else raise "I do not know how to expand a variable reference to a #{varval.class.name} (from #{varname.inspect} => #{self[varname].inspect})" end else varref end + elsif varref.is_a?(Array) + varref.map do |ent| + expand_varref(ent, lambda_args) + end.flatten + elsif varref.is_a?(Proc) + expand_varref(varref[*lambda_args], lambda_args) + elsif varref.nil? + nil + else + raise "Unknown varref type: #{varref.class} (#{varref.inspect})" end end diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index 8042c11..bc1d845 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -543,4 +543,23 @@ EOF expect(File.exists?('inc.h')).to be_truthy expect(`./program`).to eq "The value is 678\n" end + + it "supports lambdas as construction variable values" do + env = Rscons::Environment.new do |env| + env["prefix"] = "-H" + env["suffix"] = "xyz" + env[:cfg] = {val: 44} + env["computed"] = lambda do |args| + "#{args[:env]['prefix']}#{args[:env][:cfg][:val]}#{args[:env]['suffix']}" + end + env["lambda_recurse"] = lambda do |args| + "${prefix}ello" + end + end + e2 = env.clone + e2[:cfg][:val] = 38 + expect(env.expand_varref("${computed}")).to eq("-H44xyz") + expect(e2.expand_varref("${computed}")).to eq("-H38xyz") + expect(env.expand_varref("${lambda_recurse}")).to eq("-Hello") + end end diff --git a/spec/rscons/environment_spec.rb b/spec/rscons/environment_spec.rb index d85b4be..e5033b3 100644 --- a/spec/rscons/environment_spec.rb +++ b/spec/rscons/environment_spec.rb @@ -247,7 +247,7 @@ module Rscons env["foo"] = {} expect(env.expand_varref(["-p${path}", "${flags}"])).to eq ["-pdir1", "-pdir2", "-x", "-y", "-z"] expect(env.expand_varref("foo")).to eq "foo" - expect {env.expand_varref("${foo}")}.to raise_error /expand.a.variable.reference/ + expect {env.expand_varref("${foo}")}.to raise_error /Unknown.varref.type/ expect(env.expand_varref("${specialflag}")).to eq "-z" expect(env.expand_varref("${path}")).to eq ["dir1", "dir2"] end diff --git a/spec/rscons/varset_spec.rb b/spec/rscons/varset_spec.rb index f6651b0..be65cd7 100644 --- a/spec/rscons/varset_spec.rb +++ b/spec/rscons/varset_spec.rb @@ -126,26 +126,26 @@ module Rscons "CPPPATH" => ["dir1", "dir2"], "compiler" => "${CC}", "cmd" => ["${CC}", "-c", "${CFLAGS}", "-I${CPPPATH}"], - "hash" => {}) + "lambda" => lambda {|args| "#{args[:v]}--12"}) it "expands to the string itself if the string is not a variable reference" do - expect(v.expand_varref("CC")).to eq("CC") - expect(v.expand_varref("CPPPATH")).to eq("CPPPATH") - expect(v.expand_varref("str")).to eq("str") + expect(v.expand_varref("CC", :lambda_args)).to eq("CC") + expect(v.expand_varref("CPPPATH", :lambda_args)).to eq("CPPPATH") + expect(v.expand_varref("str", :lambda_args)).to eq("str") end it "expands a single variable reference beginning with a '$'" do - expect(v.expand_varref("${CC}")).to eq("gcc") - expect(v.expand_varref("${CPPPATH}")).to eq(["dir1", "dir2"]) + expect(v.expand_varref("${CC}", :lambda_args)).to eq("gcc") + expect(v.expand_varref("${CPPPATH}", :lambda_args)).to eq(["dir1", "dir2"]) end it "expands a single variable reference in ${arr} notation" do - expect(v.expand_varref("prefix${CFLAGS}suffix")).to eq(["prefix-Wallsuffix", "prefix-O2suffix"]) - expect(v.expand_varref(v["cmd"])).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]) + expect(v.expand_varref("prefix${CFLAGS}suffix", :lambda_args)).to eq(["prefix-Wallsuffix", "prefix-O2suffix"]) + expect(v.expand_varref(v["cmd"], :lambda_args)).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]) end it "expands a variable reference recursively" do - expect(v.expand_varref("${compiler}")).to eq("gcc") - expect(v.expand_varref("${cmd}")).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]) + expect(v.expand_varref("${compiler}", :lambda_args)).to eq("gcc") + expect(v.expand_varref("${cmd}", :lambda_args)).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]) end it "resolves multiple variable references in one element by enumerating all combinations" do - expect(v.expand_varref("cflag: ${CFLAGS}, cpppath: ${CPPPATH}, compiler: ${compiler}")).to eq([ + expect(v.expand_varref("cflag: ${CFLAGS}, cpppath: ${CPPPATH}, compiler: ${compiler}", :lambda_args)).to eq([ "cflag: -Wall, cpppath: dir1, compiler: gcc", "cflag: -O2, cpppath: dir1, compiler: gcc", "cflag: -Wall, cpppath: dir2, compiler: gcc", @@ -153,10 +153,19 @@ module Rscons ]) end it "returns an empty string when a variable reference refers to a non-existent variable" do - expect(v.expand_varref("${not_here}")).to eq("") + expect(v.expand_varref("${not_here}", :lambda_args)).to eq("") end - it "raises an error when a variable reference refers to an unhandled type" do - expect { v.expand_varref("${hash}") }.to raise_error /I do not know how to expand a variable reference to a Hash/ + it "calls a lambda with the given lambda arguments" do + expect(v.expand_varref("${lambda}", [v: "fez"])).to eq("fez--12") + end + it "raises an error when given an invalid argument" do + expect { v.expand_varref({a: :b}, :lambda_args) }.to raise_error /Unknown varref type: Hash/ + end + it "raises an error when an expanded variable is an unexpected type" do + expect(v).to receive(:[]).at_least(1).times.with("bad").and_return("bad_val") + expect(v).to receive(:expand_varref).with("bad_val", :lambda_args).and_return({a: :b}) + expect(v).to receive(:expand_varref).and_call_original + expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /I do not know how to expand a variable reference to a Hash/ end end end