module Rscons describe Environment do describe "#initialize" do it "adds the default builders when they are not excluded" do env = Environment.new env.builders.size.should be > 0 env.builders.map {|name, builder| builder.is_a?(Builder)}.all?.should be_true env.builders.find {|name, builder| name == "Object"}.should_not be_nil env.builders.find {|name, builder| name == "Program"}.should_not be_nil env.builders.find {|name, builder| name == "Library"}.should_not be_nil end it "excludes the default builders with exclude_builders: :all" do env = Environment.new(exclude_builders: true) env.builders.size.should == 0 end context "when a block is given" do it "yields self and invokes #process()" do env = Environment.new do |env| env.should_receive(:process) end end end end describe "#clone" do it 'should create unique copies of each construction variable' do env = Environment.new env["CPPPATH"] << "path1" env2 = env.clone env2["CPPPATH"] << "path2" env["CPPPATH"].should == ["path1"] env2["CPPPATH"].should == ["path1", "path2"] end context "when a block is given" do it "yields self and invokes #process()" do env = Environment.new env.clone do |env2| env2.should_receive(:process) end end end end describe "#add_builder" do it "adds the builder to the list of builders" do env = Environment.new(exclude_builders: true) env.builders.keys.should == [] env.add_builder(Rscons::Builders::Object.new) env.builders.keys.should == ["Object"] end end describe "#get_build_fname" do context "with no build directories" do it "returns the name of the source file with suffix changed" do env = Environment.new env.get_build_fname("src/dir/file.c", ".o").should == "src/dir/file.o" env.get_build_fname("src\\dir\\other.d", ".a").should == "src/dir/other.a" env.get_build_fname("source.cc", ".o").should == "source.o" end context "with a build_root" do it "uses the build_root unless the path is absolute" do env = Environment.new env.build_root = "build/proj" env.get_build_fname("src/dir/file.c", ".o").should == "build/proj/src/dir/file.o" env.get_build_fname("/some/lib.c", ".a").should == "/some/lib.a" env.get_build_fname("C:\\abspath\\mod.cc", ".o").should == "C:/abspath/mod.o" env.get_build_fname("build\\proj\\generated.c", ".o").should == "build/proj/generated.o" env.get_build_fname("build/proj.XX", ".yy").should == "build/proj/build/proj.yy" end end end context "with build directories" do it "uses the build directories to create the output file name" do env = Environment.new env.build_dir("src", "bld") env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1') env.get_build_fname("src/input.cc", ".o").should == "bld/input.o" env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o" env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o" env.get_build_fname("other_directory/o.d", ".a").should == "other_directory/o.a" end context "with a build_root" do it "uses the build_root unless a build directory matches or the path is absolute" do env = Environment.new env.build_dir("src", "bld") env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1') env.build_root = "bldit" env.get_build_fname("src/input.cc", ".o").should == "bld/input.o" env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o" env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o" env.get_build_fname("other_directory/o.d", ".a").should == "bldit/other_directory/o.a" env.get_build_fname("bldit/some/mod.d", ".a").should == "bldit/some/mod.a" end end end end describe "#[]" do it "allows reading construction variables" do env = Environment.new env["CFLAGS"] = ["-g", "-Wall"] env["CFLAGS"].should == ["-g", "-Wall"] end end describe "#[]=" do it "allows writing construction variables" do env = Environment.new env["CFLAGS"] = ["-g", "-Wall"] env["CFLAGS"] -= ["-g"] env["CFLAGS"] += ["-O3"] env["CFLAGS"].should == ["-Wall", "-O3"] env["other_var"] = "val33" env["other_var"].should == "val33" end end describe "#append" do it "allows adding many construction variables at once" do env = Environment.new env["CFLAGS"] = ["-g"] env["CPPPATH"] = ["inc"] env.append("CFLAGS" => ["-Wall"], "CPPPATH" => ["include"]) env["CFLAGS"].should == ["-Wall"] env["CPPPATH"].should == ["include"] end end describe "#process" do it "runs builders for all of the targets specified" do env = Environment.new env.Program("a.out", "main.c") cache = "cache" Cache.should_receive(:new).and_return(cache) env.should_receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true) cache.should_receive(:write) env.process end it "builds dependent targets first" do env = Environment.new env.Program("a.out", "main.o") env.Object("main.o", "other.cc") 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") cache.should_receive(:write) env.process end it "raises a BuildError when building fails" do env = Environment.new env.Program("a.out", "main.o") env.Object("main.o", "other.cc") cache = "cache" Cache.should_receive(:new).and_return(cache) 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/ 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 env["path"] = ["dir1", "dir2"] env["flags"] = ["-x", "-y", "${specialflag}"] env["specialflag"] = "-z" template = ["cmd", "-I${path}", "${flags}", "${_source}", "${_dest}"] cmd = env.build_command(template, "_source" => "infile", "_dest" => "outfile") cmd.should == ["cmd", "-Idir1", "-Idir2", "-x", "-y", "-z", "infile", "outfile"] end end describe "#expand_varref" do it "returns the fully expanded variable reference" do env = Environment.new env["path"] = ["dir1", "dir2"] env["flags"] = ["-x", "-y", "${specialflag}"] env["specialflag"] = "-z" env.expand_varref(["-p${path}", "${flags}"]).should == ["-pdir1", "-pdir2", "-x", "-y", "-z"] env.expand_varref("foo").should == "foo" expect {env.expand_varref("${foo}")}.to raise_error /expand.a.variable.reference/ env.expand_varref("${specialflag}").should == "-z" env.expand_varref("${path}").should == ["dir1", "dir2"] end end describe "#execute" do context "with echo: :short" do context "with no errors" do it "prints the short description and executes the command" do env = Environment.new(echo: :short) env.should_receive(:puts).with("short desc") env.should_receive(:system).with("a", "command").and_return(true) env.execute("short desc", ["a", "command"]) end end context "with errors" do it "prints the short description, executes the command, and prints the failed command line" do env = Environment.new(echo: :short) env.should_receive(:puts).with("short desc") env.should_receive(:system).with("a", "command").and_return(false) $stdout.should_receive(:write).with("Failed command was: ") env.should_receive(:puts).with("a command") env.execute("short desc", ["a", "command"]) end end end context "with echo: :command" do it "prints the command executed and executes the command" do env = Environment.new(echo: :command) env.should_receive(:puts).with("a command '--arg=val with spaces'") env.should_receive(:system).with({modified: :environment}, "a", "command", "--arg=val with spaces", {opt: :val}).and_return(false) env.execute("short desc", ["a", "command", "--arg=val with spaces"], env: {modified: :environment}, options: {opt: :val}) end end end describe "#method_missing" do it "calls the original method missing when the target method is not a known builder" do env = Environment.new env.should_receive(:orig_method_missing).with(:foobar) env.foobar end it "records the target when the target method is a known builder" do env = Environment.new env.instance_variable_get(:@targets).should == {} env.Program("target", ["src1", "src2"], var: "val") target = env.instance_variable_get(:@targets)["target"] target.should_not be_nil target[:builder].is_a?(Builder).should be_true target[:source].should == ["src1", "src2"] target[:vars].should == {var: "val"} target[:args].should == [] 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/ end end describe "#depends" do it "records the given dependencies in @user_deps" do env = Environment.new env.depends("foo", "bar", "baz") env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]} end it "records user dependencies only once" do env = Environment.new env.instance_variable_set(:@user_deps, {"foo" => ["bar"]}) env.depends("foo", "bar", "baz") env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]} end end describe "#build_sources" do class ABuilder < Builder def produces?(target, source, env) target =~ /\.ab_out$/ and source =~ /\.ab_in$/ end end it "finds and invokes a builder to produce output files with the requested suffixes" do 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.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {}).should == ["precompiled.o", "mod.o", "mod2.ab_out"] end end describe "#run_builder" do it "modifies the construction variables using given build hooks and invokes the builder" do env = Environment.new env.add_build_hook do |build_op| if build_op[:sources].first =~ %r{src/special} build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"] end end env.builders["Object"].stub(:run) do |target, sources, 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| vars["CFLAGS"].should == ["-O3", "-DSPECIAL"] end env.run_builder(env.builders["Object"], "build/special/module.o", ["src/special/module.c"], "cache", {}) end end describe ".parse_makefile_deps" do it 'handles dependencies on one line' do File.should_receive(:read).with('makefile').and_return(<<EOS) module.o: source.cc EOS Environment.parse_makefile_deps('makefile', 'module.o').should == ['source.cc'] end it 'handles dependencies split across many lines' do File.should_receive(:read).with('makefile').and_return(<<EOS) module.o: module.c \\ module.h \\ other.h EOS Environment.parse_makefile_deps('makefile', 'module.o').should == [ 'module.c', 'module.h', 'other.h'] end end end end