diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 5e2ef13..1980315 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -1,5 +1,6 @@ -require 'set' -require 'fileutils' +require "fileutils" +require "set" +require "shellwords" module Rscons # The Environment class is the main programmatic interface to Rscons. It @@ -372,6 +373,134 @@ module Rscons end end + # @!method parse_flags(flags) + # @!method parse_flags!(flags) + # + # Parse command-line flags for compilation/linking options into separate + # construction variables. + # + # The parsed construction variables are returned in a Hash instead of + # merging them directly to the Environment. They can be merged with + # {#merge_flags}. The {#parse_flags!} version immediately merges the parsed + # flags as well. + # + # Example: + # # Import FreeType build options + # env.parse_flags!("!freetype-config --cflags --libs") + # + # @param flags [String] + # String containing the flags to parse, or if the flags string begins + # with "!", a shell command to execute using {#shell} to obtain the + # flags to parse. + # + # @return [Hash] Set of construction variables to append. + def parse_flags(flags) + if flags =~ /^!(.*)$/ + flags = shell($1) + end + rv = {} + words = Shellwords.split(flags) + skip = false + words.each_with_index do |word, i| + if skip + skip = false + next + end + append = lambda do |var, val| + rv[var] ||= [] + rv[var] += val + end + handle = lambda do |var, val| + if val.nil? or val.empty? + val = words[i + 1] + skip = true + end + if val and not val.empty? + append[var, [val]] + end + end + if word == "-arch" + if val = words[i + 1] + append["CCFLAGS", ["-arch", val]] + append["LDFLAGS", ["-arch", val]] + end + skip = true + elsif word =~ /^#{self["CPPDEFPREFIX"]}(.*)$/ + handle["CPPDEFINES", $1] + elsif word == "-include" + if val = words[i + 1] + append["CCFLAGS", ["-include", val]] + end + skip = true + elsif word == "-isysroot" + if val = words[i + 1] + append["CCFLAGS", ["-isysroot", val]] + append["LDFLAGS", ["-isysroot", val]] + end + skip = true + elsif word =~ /^#{self["INCPREFIX"]}(.*)$/ + handle["CPPPATH", $1] + elsif word =~ /^#{self["LIBLINKPREFIX"]}(.*)$/ + handle["LIBS", $1] + elsif word =~ /^#{self["LIBDIRPREFIX"]}(.*)$/ + handle["LIBPATH", $1] + elsif word == "-mno-cygwin" + append["CCFLAGS", [word]] + append["LDFLAGS", [word]] + elsif word == "-mwindows" + append["LDFLAGS", [word]] + elsif word == "-pthread" + append["CCFLAGS", [word]] + append["LDFLAGS", [word]] + elsif word =~ /^-std=/ + append["CFLAGS", [word]] + elsif word =~ /^-Wa,(.*)$/ + append["ASFLAGS", $1.split(",")] + elsif word =~ /^-Wl,(.*)$/ + append["LDFLAGS", $1.split(",")] + elsif word =~ /^-Wp,(.*)$/ + append["CPPFLAGS", $1.split(",")] + elsif word.start_with?("-") + append["CCFLAGS", [word]] + elsif word.start_with?("+") + append["CCFLAGS", [word]] + append["LDFLAGS", [word]] + else + append["LIBS", [word]] + end + end + rv + end + + def parse_flags!(flags) + flags = parse_flags(flags) + merge_flags(flags) + flags + end + + # Merge construction variable flags into this Environment's construction + # variables. + # + # This method does the same thing as {#append}, except that Array values in + # +flags+ are appended to the end of Array construction variables instead + # of replacing their contents. + # + # @param flags [Hash] + # Set of construction variables to merge into the current Environment. + # This can be the value (or a modified version) returned by + # {#parse_flags}. + # + # @return [void] + def merge_flags(flags) + flags.each_pair do |key, val| + if self[key].is_a?(Array) and val.is_a?(Array) + self[key] += val + else + self[key] = val + end + end + end + private # Expand target and source paths before invoking builders. diff --git a/spec/rscons/environment_spec.rb b/spec/rscons/environment_spec.rb index 5f05e50..5a91fa3 100644 --- a/spec/rscons/environment_spec.rb +++ b/spec/rscons/environment_spec.rb @@ -379,6 +379,60 @@ module Rscons end end + describe "#parse_flags" do + it "executes the shell command and parses the returned flags when the input argument begins with !" do + env = Environment.new + env["CFLAGS"] = ["-g"] + env.should_receive(:shell).with("my_command").and_return(%[-arch my_arch -Done=two -include ii -isysroot sr -Iincdir -Llibdir -lmy_lib -mno-cygwin -mwindows -pthread -std=c99 -Wa,'asm,args 1 2' -Wl,linker,"args 1 2" -Wp,cpp,args,1,2 -arbitrary +other_arbitrary some_lib /a/b/c/lib]) + rv = env.parse_flags("!my_command") + expect(rv).to eq({ + "CCFLAGS" => %w[-arch my_arch -include ii -isysroot sr -mno-cygwin -pthread -arbitrary +other_arbitrary], + "LDFLAGS" => %w[-arch my_arch -isysroot sr -mno-cygwin -mwindows -pthread] + ["linker", "args 1 2"] + %w[+other_arbitrary], + "CPPPATH" => %w[incdir], + "LIBS" => %w[my_lib some_lib /a/b/c/lib], + "LIBPATH" => %w[libdir], + "CPPDEFINES" => %w[one=two], + "CFLAGS" => %w[-std=c99], + "ASFLAGS" => ["asm", "args 1 2"], + "CPPFLAGS" => %w[cpp args 1 2], + }) + expect(env["CFLAGS"]).to eq(["-g"]) + expect(env["ASFLAGS"]).to eq([]) + env.merge_flags(rv) + expect(env["CFLAGS"]).to eq(["-g", "-std=c99"]) + expect(env["ASFLAGS"]).to eq(["asm", "args 1 2"]) + end + end + + describe "#parse_flags!" do + it "parses the given build flags and merges them into the Environment" do + env = Environment.new + env["CFLAGS"] = ["-g"] + rv = env.parse_flags!("-I incdir -D my_define -L /a/libdir -l /some/lib") + expect(rv).to eq({ + "CPPPATH" => %w[incdir], + "LIBS" => %w[/some/lib], + "LIBPATH" => %w[/a/libdir], + "CPPDEFINES" => %w[my_define], + }) + expect(env["CPPPATH"]).to eq(%w[incdir]) + expect(env["LIBS"]).to eq(%w[/some/lib]) + expect(env["LIBPATH"]).to eq(%w[/a/libdir]) + expect(env["CPPDEFINES"]).to eq(%w[my_define]) + end + end + + describe "#merge_flags" do + it "appends array contents and replaces other variable values" do + env = Environment.new + env["CPPPATH"] = ["incdir"] + env["CSUFFIX"] = ".x" + env.merge_flags("CPPPATH" => ["a"], "CSUFFIX" => ".c") + expect(env["CPPPATH"]).to eq(%w[incdir a]) + expect(env["CSUFFIX"]).to eq(".c") + end + end + describe ".parse_makefile_deps" do it 'handles dependencies on one line' do File.should_receive(:read).with('makefile').and_return(<