lib/autobuild/subcommand.rb in autobuild-1.17.0 vs lib/autobuild/subcommand.rb in autobuild-1.18.0

- old
+ new

@@ -1,9 +1,10 @@ require 'set' require 'autobuild/exceptions' require 'autobuild/reporting' require 'fcntl' +require 'English' module Autobuild @logfiles = Set.new def self.clear_logfiles @logfiles.clear @@ -22,22 +23,25 @@ end def self.statistics @statistics end + def self.reset_statistics @statistics = Hash.new end + def self.add_stat(package, phase, duration) if !@statistics[package] @statistics[package] = { phase => duration } elsif !@statistics[package][phase] @statistics[package][phase] = duration else @statistics[package][phase] += duration end end + reset_statistics @parallel_build_level = nil @displayed_error_line_count = 10 class << self @@ -50,14 +54,13 @@ # this may be an integer or 'ALL' (which will be translated to -1) # this is not using an attr_accessor to be able to validate the values def displayed_error_line_count=(value) @displayed_error_line_count = validate_displayed_error_line_count(value) end - def displayed_error_line_count - @displayed_error_line_count - end + attr_reader :displayed_error_line_count + # Returns the number of processes that can run in parallel during the # build. This is a system-wide value that can be overriden in a # per-package fashion by using Package#parallel_build_level. # # If not set, defaults to the number of CPUs on the system @@ -76,17 +79,17 @@ end end # Returns the number of CPUs present on this system def self.autodetect_processor_count - if @processor_count - return @processor_count - end + return @processor_count if @processor_count if File.file?('/proc/cpuinfo') cpuinfo = File.readlines('/proc/cpuinfo') - physical_ids, core_count, processor_ids = [], [], [] + physical_ids = [] + core_count = [] + processor_ids = [] cpuinfo.each do |line| case line when /^processor\s+:\s+(\d+)$/ processor_ids << Integer($1) when /^physical id\s+:\s+(\d+)$/ @@ -97,11 +100,14 @@ end # Try to count the number of physical cores, not the number of # logical ones. If the info is not available, fallback to the # logical count - if (physical_ids.size == core_count.size) && (physical_ids.size == processor_ids.size) + has_consistent_info = + (physical_ids.size == core_count.size) && + (physical_ids.size == processor_ids.size) + if has_consistent_info info = Array.new while (id = physical_ids.shift) info[id] = core_count.shift end @processor_count = info.compact.inject(&:+) @@ -110,22 +116,21 @@ end else result = Open3.popen3("sysctl", "-n", "hw.ncpu") do |_, io, _| io.read end - if !result.empty? - @processor_count = Integer(result.chomp.strip) - end + @processor_count = Integer(result.chomp.strip) unless result.empty? end # The format of the cpuinfo file is ... let's say not very standardized. # If the cpuinfo detection fails, inform the user and set it to 1 - if !@processor_count + unless @processor_count # Hug... What kind of system is it ? Autobuild.message "INFO: cannot autodetect the number of CPUs on this sytem" Autobuild.message "INFO: turning parallel builds off" - Autobuild.message "INFO: you can manually set the number of parallel build processes to N" + Autobuild.message "INFO: you can manually set the number of parallel build "\ + "processes to N" Autobuild.message "INFO: (and therefore turn this message off)" Autobuild.message "INFO: with" Autobuild.message " Autobuild.parallel_build_level = N" @processor_count = 1 end @@ -133,24 +138,28 @@ @processor_count end def self.validate_displayed_error_line_count(lines) if lines == 'ALL' - return Float::INFINITY + Float::INFINITY elsif lines.to_i > 0 - return lines.to_i + lines.to_i + else + raise ConfigException.new, 'Autobuild.displayed_error_line_count can only "\ + "be a positive integer or \'ALL\'' end - raise ConfigException.new, 'Autobuild.displayed_error_line_count can only be a positive integer or \'ALL\'' end end - -module Autobuild::Subprocess - class Failed < Exception - def retry?; @retry end +module Autobuild::Subprocess # rubocop:disable Style/ClassAndModuleChildren + class Failed < RuntimeError attr_reader :status + def retry? + @retry + end + def initialize(status, do_retry) @status = status @retry = do_retry end end @@ -204,33 +213,33 @@ # @return [String] the command standard output def self.run(target, phase, *command) STDOUT.sync = true input_streams = [] - options = Hash[retry: false, env: ENV.to_hash, env_inherit: true, encoding: 'BINARY'] + options = { + retry: false, encoding: 'BINARY', + env: ENV.to_hash, env_inherit: true + } + if command.last.kind_of?(Hash) options = command.pop options = Kernel.validate_options options, input: nil, working_directory: nil, retry: false, input_streams: [], env: ENV.to_hash, env_inherit: true, encoding: 'BINARY' - if options[:input] - input_streams << File.open(options[:input]) - end - if options[:input_streams] - input_streams += options[:input_streams] - end + input_streams << File.open(options[:input]) if options[:input] + input_streams.concat(options[:input_streams]) if options[:input_streams] end start_time = Time.now # Filter nil and empty? in command - command.reject! { |o| o.nil? || (o.respond_to?(:empty?) && o.empty?) } - command.collect! { |o| o.to_s } + command.reject! { |o| o.nil? || (o.respond_to?(:empty?) && o.empty?) } + command.collect!(&:to_s) if target.respond_to?(:name) target_name = target.name target_type = target.class else @@ -244,17 +253,19 @@ if target.respond_to?(:working_directory) options[:working_directory] ||= target.working_directory end - logname = File.join(logdir, "#{target_name.gsub(/[:]/,'_')}-#{phase.to_s.gsub(/[:]/,'_')}.log") - if !File.directory?(File.dirname(logname)) + logname = File.join(logdir, "#{target_name.gsub(/[:]/, '_')}-"\ + "#{phase.to_s.gsub(/[:]/, '_')}.log") + unless File.directory?(File.dirname(logname)) FileUtils.mkdir_p File.dirname(logname) end if Autobuild.verbose - Autobuild.message "#{target_name}: running #{command.join(" ")}\n (output goes to #{logname})" + Autobuild.message "#{target_name}: running #{command.join(' ')}\n"\ + " (output goes to #{logname})" end open_flag = if Autobuild.keep_oldlogs then 'a' elsif Autobuild.registered_logfile?(logname) then 'a' else 'w' @@ -265,36 +276,32 @@ subcommand_output = Array.new env = options[:env].dup if options[:env_inherit] ENV.each do |k, v| - if !env.has_key?(k) - env[k] = v - end + env[k] = v unless env.key?(k) end end status = File.open(logname, open_flag) do |logfile| - if Autobuild.keep_oldlogs - logfile.puts - end + logfile.puts if Autobuild.keep_oldlogs logfile.puts logfile.puts "#{Time.now}: running" - logfile.puts " #{command.join(" ")}" + logfile.puts " #{command.join(' ')}" logfile.puts "with environment:" env.keys.sort.each do |key| - if value = env[key] + if (value = env[key]) logfile.puts " '#{key}'='#{value}'" end end logfile.puts logfile.puts "#{Time.now}: running" - logfile.puts " #{command.join(" ")}" + logfile.puts " #{command.join(' ')}" logfile.flush logfile.sync = true - if !input_streams.empty? + unless input_streams.empty? pread, pwrite = IO.pipe # to feed subprocess stdin end outread, outwrite = IO.pipe outread.sync = true @@ -302,63 +309,66 @@ cread, cwrite = IO.pipe # to control that exec goes well if Autobuild.windows? Dir.chdir(options[:working_directory]) do - if !system(*command) - raise Failed.new($?.exitstatus, nil), + unless system(*command) + raise Failed.new($CHILD_STATUS.exitstatus, nil), "'#{command.join(' ')}' returned status #{status.exitstatus}" end end - return + return # rubocop:disable Lint/NonLocalExitFromIterator end cwrite.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) pid = fork do begin - if options[:working_directory] && (options[:working_directory] != Dir.pwd) - Dir.chdir(options[:working_directory]) - end - logfile.puts "in directory #{Dir.pwd}" + logfile.puts "in directory #{options[:working_directory] || Dir.pwd}" cwrite.sync = true if Autobuild.nice Process.setpriority(Process::PRIO_PROCESS, 0, Autobuild.nice) end outread.close $stderr.reopen(outwrite.dup) $stdout.reopen(outwrite.dup) - if !input_streams.empty? + unless input_streams.empty? pwrite.close $stdin.reopen(pread) end - exec(env, *command, close_others: false) + exec(env, *command, + chdir: options[:working_directory] || Dir.pwd, + close_others: false) rescue Errno::ENOENT cwrite.write([CONTROL_COMMAND_NOT_FOUND].pack('I')) exit(100) rescue Interrupt cwrite.write([CONTROL_INTERRUPT].pack('I')) exit(100) - rescue ::Exception + rescue ::Exception => e + STDERR.puts e + STDERR.puts e.backtrace.join("\n ") cwrite.write([CONTROL_UNEXPECTED].pack('I')) exit(100) end end readbuffer = StringIO.new # Feed the input - if !input_streams.empty? + unless input_streams.empty? pread.close begin input_streams.each do |instream| instream.each_line do |line| - readbuffer.write(outread.readpartial(128)) while IO.select([outread], nil, nil, 0) + while IO.select([outread], nil, nil, 0) + readbuffer.write(outread.readpartial(128)) + end pwrite.write(line) end end rescue Errno::ENOENT => e raise Failed.new(nil, false), @@ -383,19 +393,17 @@ "something unexpected happened" end end transparent_prefix = "#{target_name}:#{phase}: " - if target_type - transparent_prefix = "#{target_type}:#{transparent_prefix}" - end + transparent_prefix = "#{target_type}:#{transparent_prefix}" if target_type # If the caller asked for process output, provide it to him # line-by-line. outwrite.close - if !input_streams.empty? + unless input_streams.empty? readbuffer.write(outread.read) readbuffer.seek(0) outread.close outread = readbuffer end @@ -426,10 +434,11 @@ if !status.exitstatus || status.exitstatus > 0 if status.termsig == 2 # SIGINT == 2 raise Interrupt, "subcommand #{command.join(' ')} interrupted" end + if status.termsig raise Failed.new(status.exitstatus, nil), "'#{command.join(' ')}' terminated by signal #{status.termsig}" else raise Failed.new(status.exitstatus, nil), @@ -439,23 +448,21 @@ duration = Time.now - start_time Autobuild.add_stat(target, phase, duration) FileUtils.mkdir_p(Autobuild.logdir) File.open(File.join(Autobuild.logdir, "stats.log"), 'a') do |io| - formatted_time = "#{start_time.strftime('%F %H:%M:%S')}.#{'%.03i' % [start_time.tv_usec / 1000]}" + formatted_msec = format('%.03i', start_time.tv_usec / 1000) + formatted_time = "#{start_time.strftime('%F %H:%M:%S')}.#{formatted_msec}" io.puts "#{formatted_time} #{target_name} #{phase} #{duration}" end - if target.respond_to?(:add_stat) - target.add_stat(phase, duration) - end + target.add_stat(phase, duration) if target.respond_to?(:add_stat) subcommand_output - rescue Failed => e - error = Autobuild::SubcommandFailed.new(target, command.join(" "), logname, e.status, subcommand_output) + error = Autobuild::SubcommandFailed.new(target, command.join(" "), + logname, e.status, subcommand_output) error.retry = if e.retry?.nil? then options[:retry] else e.retry? end error.phase = phase raise error, e.message end - end