lib/autobuild/subcommand.rb in autobuild-1.8.3 vs lib/autobuild/subcommand.rb in autobuild-1.9.0.b1
- old
+ new
@@ -37,15 +37,26 @@
end
end
reset_statistics
@parallel_build_level = nil
+ @displayed_error_line_count = 10
class << self
# Sets the level of parallelism during the build
#
# See #parallel_build_level for detailed information
attr_writer :parallel_build_level
+
+ # set/get a value how much log lines should be displayed on errors
+ # 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
# 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.
#
@@ -119,36 +130,87 @@
@processor_count = 1
end
@processor_count
end
+
+ def self.validate_displayed_error_line_count(lines)
+ if lines == 'ALL'
+ return Float::INFINITY
+ elsif lines.to_i > 0
+ return lines.to_i
+ end
+ raise ConfigError, '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
attr_reader :status
- def initialize(status = nil)
+
+ def initialize(status, do_retry)
@status = status
+ @retry = do_retry
end
end
CONTROL_COMMAND_NOT_FOUND = 1
CONTROL_UNEXPECTED = 2
CONTROL_INTERRUPT = 3
+
+ # Run a subcommand and return its standard output
+ #
+ # The command's standard and error outputs, as well as the full command line
+ # and an environment dump are saved in a log file in either the valure
+ # returned by target#logdir, or Autobuild.logdir if the target does not
+ # respond to #logdir.
+ #
+ # The subprocess priority is controlled by Autobuild.nice
+ #
+ # @param [String,(#name,#logdir,#working_directory)] target the target we
+ # run the subcommand for. In general, it will be a Package object (run from
+ # Package#run)
+ # @param [String] phase in which build phase this subcommand is executed
+ # @param [Array<String>] the command itself
+ # @yieldparam [String] line if a block is given, each output line from the
+ # command's standard output are yield to it. This is meant for progress
+ # display, and is disabled if Autobuild.verbose is set.
+ # @param [Hash] options
+ # @option options [String] :working_directory the directory in which the
+ # command should be started. If nil, runs in the current directory. The
+ # default is to either use the value returned by #working_directory on
+ # {target} if it responds to it, or nil.
+ # @option options [Boolean] :retry (false) controls whether a failure to
+ # execute this command should be retried by autobuild retry mechanisms (i.e.
+ # in the importers) or not. {run} will not retry the command by itself, it
+ # is passed as a hint for error handling clauses about whether the error
+ # should be retried or not
+ # @option options [Array<IO>] :input_streams list of input streams that
+ # should be fed to the command standard input. If a file needs to be given,
+ # the :input argument can be used as well as a shortcut
+ # @option options [String] :input the path to a file whose content should be
+ # fed to the command standard input
+ # @return [String] the command standard output
def self.run(target, phase, *command)
STDOUT.sync = true
input_streams = []
- options = Hash.new
+ options = Hash[retry: false]
if command.last.kind_of?(Hash)
options = command.pop
options = Kernel.validate_options options,
- :input => nil, :working_directory => nil
+ input: nil, working_directory: nil, retry: false,
+ input_streams: []
if options[:input]
- input_streams = [options[:input]]
+ input_streams << File.open(options[:input])
end
+ if options[:input_streams]
+ input_streams += options[:input_streams]
+ end
end
start_time = Time.now
# Filter nil and empty? in command
@@ -184,10 +246,11 @@
if defined? Encoding
open_flag << ":BINARY"
end
Autobuild.register_logfile(logname)
+ subcommand_output = Array.new
status = File.open(logname, open_flag) do |logfile|
if Autobuild.keep_oldlogs
logfile.puts
end
@@ -205,30 +268,24 @@
logfile.sync = true
if !input_streams.empty?
pread, pwrite = IO.pipe # to feed subprocess stdin
end
- if Autobuild.verbose || block_given? # the caller wants the stdout/stderr stream of the process, git it to him
- outread, outwrite = IO.pipe
- outread.sync = true
- outwrite.sync = true
- end
+
+ outread, outwrite = IO.pipe
+ outread.sync = true
+ outwrite.sync = true
+
cread, cwrite = IO.pipe # to control that exec goes well
if Autobuild.windows?
- olddir = Dir.pwd
- if options[:working_directory] && (options[:working_directory] != Dir.pwd)
- Dir.chdir(options[:working_directory])
+ Dir.chdir(options[:working_directory]) do
+ if !system(*command)
+ raise Failed.new($?.exitstatus, nil),
+ "'#{command.join(' ')}' returned status #{status.exitstatus}"
+ end
end
- system(*command)
- result=$?.success?
- if(!result)
- error = Autobuild::SubcommandFailed.new(target, command.join(" "), logname, "Systemcall")
- error.phase = phase
- raise error
- end
- Dir.chdir(olddir)
return
end
cwrite.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
@@ -276,17 +333,16 @@
# Feed the input
if !input_streams.empty?
pread.close
begin
- input_streams.each do |infile|
- File.open(infile) do |instream|
- instream.each_line { |line| pwrite.write(line) }
- end
+ input_streams.each do |instream|
+ instream.each_line { |line| pwrite.write(line) }
end
rescue Errno::ENOENT => e
- raise Failed.new, "cannot open input files: #{e.message}"
+ raise Failed.new(nil, false),
+ "cannot open input files: #{e.message}", retry: false
end
pwrite.close
end
# Get control status
@@ -294,47 +350,49 @@
value = cread.read(4)
if value
# An error occured
value = value.unpack('I').first
if value == CONTROL_COMMAND_NOT_FOUND
- raise Failed.new, "command '#{command.first}' not found"
+ raise Failed.new(nil, false),
+ "command '#{command.first}' not found"
elsif value == CONTROL_INTERRUPT
raise Interrupt, "command '#{command.first}': interrupted by user"
else
- raise Failed.new, "something unexpected happened"
+ raise Failed.new(nil, false),
+ "something unexpected happened"
end
end
# If the caller asked for process output, provide it to him
# line-by-line.
- if outread
- outwrite.close
- outread.each_line do |line|
- if line.respond_to?(:force_encoding)
- line.force_encoding('BINARY')
- end
- if Autobuild.verbose
- STDOUT.print line
- end
- logfile.puts line
- # Do not yield the line if Autobuild.verbose is true, as it
- # would mix the progress output with the actual command
- # output. Assume that if the user wants the command output,
- # the autobuild progress output is unnecessary
- if !Autobuild.verbose && block_given?
- yield(line)
- end
+ outwrite.close
+ outread.each_line do |line|
+ line.force_encoding('BINARY')
+ line = line.chomp
+ subcommand_output << line
+
+ if Autobuild.verbose
+ STDOUT.puts line
end
- outread.close
+ logfile.puts line
+ # Do not yield the line if Autobuild.verbose is true, as it
+ # would mix the progress output with the actual command
+ # output. Assume that if the user wants the command output,
+ # the autobuild progress output is unnecessary
+ if !Autobuild.verbose && block_given?
+ yield(line)
+ end
end
+ outread.close
- childpid, childstatus = Process.wait2(pid)
+ _, childstatus = Process.wait2(pid)
childstatus
end
if !status.exitstatus || status.exitstatus > 0
- raise Failed.new(status.exitstatus), "'#{command.join(' ')}' returned status #{status.exitstatus}"
+ raise Failed.new(status.exitstatus, nil),
+ "'#{command.join(' ')}' returned status #{status.exitstatus}"
end
duration = Time.now - start_time
Autobuild.add_stat(target, phase, duration)
FileUtils.mkdir_p(Autobuild.logdir)
@@ -343,16 +401,18 @@
io.puts "#{formatted_time} #{target_name} #{phase} #{duration}"
end
if target.respond_to?(:add_stat)
target.add_stat(phase, duration)
end
+ subcommand_output
rescue Failed => e
- error = Autobuild::SubcommandFailed.new(target, command.join(" "), logname, e.status)
+ 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
-
-