diff --git a/lib/rscons.rb b/lib/rscons.rb index 609f9cc..fc84d21 100644 --- a/lib/rscons.rb +++ b/lib/rscons.rb @@ -1,3 +1,4 @@ +require_relative "rscons/ansi" require_relative "rscons/build_target" require_relative "rscons/builder" require_relative "rscons/cache" @@ -50,6 +51,10 @@ module Rscons # The number of threads to use when scheduling subprocesses. attr_accessor :n_threads + # @return [Boolean] + # Whether to output ANSI color escape sequences. + attr_accessor :do_ansi_color + # Remove all generated files. # # @return [void] diff --git a/lib/rscons/ansi.rb b/lib/rscons/ansi.rb new file mode 100644 index 0000000..d6e145f --- /dev/null +++ b/lib/rscons/ansi.rb @@ -0,0 +1,52 @@ +module Rscons + module Ansi + class << self + + # Write a message to an IO with ANSI escape codes. + # + # @param io [IO] + # The IO to write to. + # @param message [Array] + # Strings to be printed, with Symbols representing ANSI escape codes. + # + # @return [void] + def write(io, *message) + do_color = Rscons.do_ansi_color + if do_color.nil? + do_color = do_ansi?(io) + end + out = "" + message.each do |m| + if m.is_a?(String) + out += m + elsif do_color + case m + when :red + out += "\e[0;31m" + when :cyan + out += "\e[0;36m" + when :reset + out += "\e[0m" + end + end + end + io.write(out) + end + + private + + # Determine whether to output ANSI color escape codes. + # + # @return [Boolean] + # Whether to output ANSI color escape codes. + def do_ansi?(io) + if RUBY_PLATFORM =~ /mingw/ + (ENV["TERM"] == "xterm") && %w[fifo characterSpecial].include?(io.stat.ftype) + else + io.tty? + end + end + + end + end +end diff --git a/lib/rscons/cli.rb b/lib/rscons/cli.rb index 80bb1a5..1d5f5d5 100644 --- a/lib/rscons/cli.rb +++ b/lib/rscons/cli.rb @@ -39,6 +39,15 @@ module Rscons Rscons.n_threads = n_threads.to_i end + opts.on("-r", "--color MODE", "Set color mode (off, auto, force)") do |color_mode| + case color_mode + when "off" + Rscons.do_ansi_color = false + when "force" + Rscons.do_ansi_color = true + end + end + opts.on_tail("--version", "Show version") do puts "Rscons version #{Rscons::VERSION}" exit 0 diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 3df43ad..fe46ef3 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -331,7 +331,7 @@ module Rscons setup_info: job[:setup_info]) unless result failure = "Failed to build #{job[:target]}" - $stderr.puts failure + Ansi.write($stderr, :red, failure, :reset, "\n") next end end @@ -358,10 +358,10 @@ module Rscons end else unless @echo == :command - $stdout.puts "Failed command was: #{command_to_s(tc.command)}" + print_failed_command(tc.command) end failure = "Failed to build #{tc.build_operation[:target]}" - $stderr.puts failure + Ansi.write($stderr, :red, failure, :reset, "\n") break end end @@ -412,16 +412,12 @@ module Rscons # # @return [true,false,nil] Return value from Kernel.system(). def execute(short_desc, command, options = {}) - if @echo == :command - puts command_to_s(command) - elsif @echo == :short - puts short_desc - end + print_builder_run_message(short_desc, command) env_args = options[:env] ? [options[:env]] : [] options_args = options[:options] ? [options[:options]] : [] system(*env_args, *Rscons.command_executer, *command, *options_args).tap do |result| unless result or @echo == :command - $stdout.puts "Failed command was: #{command_to_s(command)}" + print_failed_command(command) end end end @@ -657,7 +653,7 @@ module Rscons call_build_hooks[:post] else unless @echo == :command - $stdout.puts "Failed command was: #{command_to_s(tc.command)}" + print_failed_command(tc.command) end end end @@ -844,7 +840,7 @@ module Rscons varset_hash = @varset.to_h varset_hash.keys.sort_by(&:to_s).each do |var| var_str = var.is_a?(Symbol) ? var.inspect : var - puts "#{var_str} => #{varset_hash[var].inspect}" + Ansi.write($stdout, :cyan, var_str, :reset, " => #{varset_hash[var].inspect}\n") end end @@ -857,6 +853,34 @@ module Rscons @n_threads || Rscons.n_threads end + # Print the builder run message, depending on the Environment's echo mode. + # + # @param short_description [String] + # Builder short description, printed if the echo mode is :short. + # @param command [Array] + # Builder command, printed if the echo mode is :command. + # + # @return [void] + def print_builder_run_message(short_description, command) + case @echo + when :command + message = command_to_s(command) if command + when :short + message = short_description if short_description + end + Ansi.write($stdout, :cyan, message, :reset, "\n") if message + end + + # Print a failed command. + # + # @param command [Array] + # Builder command. + # + # @return [void] + def print_failed_command(command) + Ansi.write($stdout, :red, "Failed command was: #{command_to_s(command)}", :reset, "\n") + end + private # Add a build target. @@ -889,13 +913,7 @@ module Rscons # # @return [void] def start_threaded_command(tc) - if @echo == :command - puts command_to_s(tc.command) - elsif @echo == :short - if tc.short_description - puts tc.short_description - end - end + print_builder_run_message(tc.short_description, tc.command) env_args = tc.system_env ? [tc.system_env] : [] options_args = tc.system_options ? [tc.system_options] : [] diff --git a/spec/build_tests_spec.rb b/spec/build_tests_spec.rb index e3372de..aa52793 100644 --- a/spec/build_tests_spec.rb +++ b/spec/build_tests_spec.rb @@ -769,6 +769,29 @@ EOF expect(lines(result.stdout)).to eq ["165"] end + context "colored output" do + it "does not output in color with --color=off" do + test_dir("simple") + result = run_test(rscons_args: %w[--color=off]) + expect(result.stderr).to eq "" + expect(result.stdout).to_not match(/\e\[/) + end + + it "displays output in color with --color=force" do + test_dir("simple") + + result = run_test(rscons_args: %w[--color=force]) + expect(result.stderr).to eq "" + expect(result.stdout).to match(/\e\[/) + + File.open("simple.c", "wb") do |fh| + fh.write("foobar") + end + result = run_test(rscons_args: %w[--color=force]) + expect(result.stderr).to match(/\e\[/) + end + end + context "backward compatibility" do it "allows a builder to call Environment#run_builder in a non-threaded manner" do test_dir("simple") @@ -791,6 +814,21 @@ EOF ] end + it "prints the failed build command for a non-threaded builder" do + test_dir("simple") + File.open("simple.c", "wb") do |fh| + fh.write(<