autobuild 1.18.1 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +25 -0
  3. data/.github/workflows/test.yml +30 -0
  4. data/.rubocop.yml +14 -7
  5. data/autobuild.gemspec +8 -6
  6. data/bin/autobuild +1 -1
  7. data/lib/autobuild/build_logfile.rb +1 -2
  8. data/lib/autobuild/config.rb +18 -5
  9. data/lib/autobuild/configurable.rb +3 -1
  10. data/lib/autobuild/environment.rb +28 -45
  11. data/lib/autobuild/exceptions.rb +11 -5
  12. data/lib/autobuild/import/archive.rb +31 -22
  13. data/lib/autobuild/import/cvs.rb +6 -6
  14. data/lib/autobuild/import/darcs.rb +4 -4
  15. data/lib/autobuild/import/git-lfs.rb +4 -4
  16. data/lib/autobuild/import/git.rb +153 -68
  17. data/lib/autobuild/import/hg.rb +7 -7
  18. data/lib/autobuild/import/svn.rb +15 -9
  19. data/lib/autobuild/importer.rb +38 -38
  20. data/lib/autobuild/mail_reporter.rb +5 -2
  21. data/lib/autobuild/package.rb +45 -35
  22. data/lib/autobuild/packages/autotools.rb +3 -8
  23. data/lib/autobuild/packages/cmake.rb +16 -7
  24. data/lib/autobuild/packages/dummy.rb +0 -4
  25. data/lib/autobuild/packages/gnumake.rb +1 -1
  26. data/lib/autobuild/packages/orogen.rb +11 -4
  27. data/lib/autobuild/packages/pkgconfig.rb +2 -2
  28. data/lib/autobuild/packages/python.rb +6 -8
  29. data/lib/autobuild/packages/ruby.rb +5 -5
  30. data/lib/autobuild/parallel.rb +20 -21
  31. data/lib/autobuild/pkgconfig.rb +1 -0
  32. data/lib/autobuild/progress_display.rb +130 -49
  33. data/lib/autobuild/rake_task_extension.rb +16 -5
  34. data/lib/autobuild/reporting.rb +20 -7
  35. data/lib/autobuild/subcommand.rb +24 -23
  36. data/lib/autobuild/test_utility.rb +2 -1
  37. data/lib/autobuild/timestamps.rb +3 -3
  38. data/lib/autobuild/utility.rb +54 -8
  39. data/lib/autobuild/version.rb +1 -1
  40. data/lib/autobuild.rb +0 -3
  41. metadata +42 -26
  42. data/.travis.yml +0 -19
@@ -25,13 +25,8 @@ module Autobuild
25
25
  # To override this default behaviour on a per-package basis, use Autotools#use
26
26
  #
27
27
  class Autotools < Configurable
28
- attr_accessor :using
29
- attr_accessor :configureflags
30
- attr_accessor :aclocal_flags
31
- attr_accessor :autoheader_flags
32
- attr_accessor :autoconf_flags
33
- attr_accessor :automake_flags
34
- attr_accessor :bear_flags
28
+ attr_accessor :using, :configureflags, :aclocal_flags, :autoheader_flags,
29
+ :autoconf_flags, :automake_flags, :bear_flags
35
30
 
36
31
  @builddir = 'build'
37
32
  @@enable_bear_globally = false
@@ -278,7 +273,7 @@ module Autobuild
278
273
  file conffile => "#{conffile}#{confext}"
279
274
  elsif using[:autoconf]
280
275
  raise PackageException.new(self, 'prepare'),
281
- "neither configure.ac nor configure.in present in #{srcdir}"
276
+ "neither configure.ac nor configure.in present in #{srcdir}"
282
277
  end
283
278
 
284
279
  file conffile do
@@ -25,6 +25,7 @@ module Autobuild
25
25
  end
26
26
 
27
27
  attr_writer :full_reconfigures
28
+
28
29
  def full_reconfigures?
29
30
  @full_reconfigures
30
31
  end
@@ -36,8 +37,7 @@ module Autobuild
36
37
  # It can be overriden on a per-package basis with CMake.generator=
37
38
  attr_accessor :generator
38
39
 
39
- attr_reader :prefix_path
40
- attr_reader :module_path
40
+ attr_reader :prefix_path, :module_path
41
41
 
42
42
  # Whether files that are not within CMake's install manifest but are
43
43
  # present in the prefix should be deleted. Note that the contents of
@@ -63,6 +63,7 @@ module Autobuild
63
63
 
64
64
  # a key => value association of defines for CMake
65
65
  attr_reader :defines
66
+
66
67
  # The list of all -D options that should be passed on to CMake
67
68
  def all_defines
68
69
  additional_defines = Hash[
@@ -81,6 +82,7 @@ module Autobuild
81
82
  # Sets a generator explicitely for this component. See #generator and
82
83
  # CMake.generator
83
84
  attr_writer :generator
85
+
84
86
  # The CMake generator to use. You must choose one that generates
85
87
  # Makefiles. If not set for this package explicitely, it is using the
86
88
  # global value CMake.generator.
@@ -267,7 +269,7 @@ module Autobuild
267
269
  run('doc', Autobuild.tool(:doxygen), doxyfile)
268
270
  end
269
271
 
270
- def common_utility_handling(utility, target, start_msg, done_msg)
272
+ def common_utility_handling(utility, target, *args, start_msg, done_msg)
271
273
  utility.source_ref_dir = builddir
272
274
  utility.task do
273
275
  progress_start start_msg, :done_message => done_msg do
@@ -277,7 +279,7 @@ module Autobuild
277
279
  run(utility.name,
278
280
  Autobuild.tool(:make),
279
281
  "-j#{parallel_build_level}",
280
- target,
282
+ target, *args,
281
283
  working_directory: builddir)
282
284
  end
283
285
  yield if block_given?
@@ -295,7 +297,7 @@ module Autobuild
295
297
 
296
298
  def with_tests(target = 'test', &block)
297
299
  common_utility_handling(
298
- test_utility, target,
300
+ test_utility, target, "ARGS=-V",
299
301
  "running tests for %s",
300
302
  "successfully ran tests for %s", &block)
301
303
  end
@@ -320,7 +322,7 @@ module Autobuild
320
322
  end
321
323
  end
322
324
 
323
- def import(options = Hash.new)
325
+ def import(**options)
324
326
  super
325
327
 
326
328
  Dir.glob(File.join(srcdir, "*.pc.in")) do |file|
@@ -410,7 +412,7 @@ module Autobuild
410
412
  in_dir(builddir) do
411
413
  unless File.file?(File.join(srcdir, 'CMakeLists.txt'))
412
414
  raise ConfigException.new(self, 'configure'),
413
- "#{srcdir} contains no CMakeLists.txt file"
415
+ "#{srcdir} contains no CMakeLists.txt file"
414
416
  end
415
417
 
416
418
  command = ["cmake"]
@@ -542,5 +544,12 @@ module Autobuild
542
544
  message "%s: removed #{counter} obsolete files from prefix (cmake)"
543
545
  end
544
546
  end
547
+
548
+ def self_fingerprint
549
+ return unless (base = super)
550
+
551
+ all_defines = self.class.defines.merge(defines).sort_by(&:first)
552
+ Digest::SHA1.hexdigest(base + all_defines.join(""))
553
+ end
545
554
  end
546
555
  end
@@ -8,10 +8,6 @@ module Autobuild
8
8
  "#{srcdir}/#{STAMPFILE}"
9
9
  end
10
10
 
11
- def initialize(*args)
12
- super
13
- end
14
-
15
11
  def import(options = Hash.new); end
16
12
 
17
13
  def prepare
@@ -95,7 +95,7 @@ module Autobuild
95
95
  def self.make_subcommand(pkg, phase, *options, &block)
96
96
  invoke_make_parallel(pkg, Autobuild.tool(:make)) do |*make_parallel_options|
97
97
  pkg.run(phase, Autobuild.tool(:make),
98
- *make_parallel_options, *options, &block)
98
+ *make_parallel_options, *options, &block)
99
99
  end
100
100
  end
101
101
  end
@@ -97,19 +97,19 @@ module Autobuild
97
97
  end
98
98
  end
99
99
 
100
- attr_writer :corba
100
+ attr_writer :corba, :orogen_file
101
+
101
102
  def corba
102
103
  @corba || (@corba.nil? && Orogen.corba)
103
104
  end
104
105
 
105
106
  # Overrides the global Orocos.extended_states for this particular package
106
107
  attr_writer :extended_states
108
+
107
109
  def extended_states
108
110
  @extended_states || (@extended_states.nil? && Orogen.extended_states)
109
111
  end
110
112
 
111
- attr_writer :orogen_file
112
-
113
113
  # Path to the orogen file used for this package
114
114
  #
115
115
  # If not set, the class will look for a .orogen file in the package
@@ -128,7 +128,7 @@ module Autobuild
128
128
  return File.basename(path)
129
129
  end
130
130
  raise ArgumentError,
131
- "cannot find an oroGen specification file in #{srcdir}"
131
+ "cannot find an oroGen specification file in #{srcdir}"
132
132
  end
133
133
  end
134
134
 
@@ -235,6 +235,13 @@ module Autobuild
235
235
  cmdline << "--type-export-policy=#{Orogen.default_type_export_policy}"
236
236
  cmdline << "--transports=#{Orogen.transports.sort.uniq.join(',')}"
237
237
  end
238
+ if version >= "1.2"
239
+ cmdline << "--parallel-codegen=#{parallel_build_level}"
240
+ if (job_server = Autobuild.parallel_task_manager&.job_server)
241
+ fds = "#{job_server.rio.fileno},#{job_server.wio.fileno}"
242
+ cmdline << "--jobserver-auth=#{fds}"
243
+ end
244
+ end
238
245
 
239
246
  # Now, add raw options
240
247
  #
@@ -2,8 +2,7 @@ require 'autobuild/pkgconfig'
2
2
 
3
3
  module Autobuild
4
4
  class InstalledPkgConfig < Package
5
- attr_reader :pkgconfig
6
- attr_reader :prefix
5
+ attr_reader :pkgconfig, :prefix
7
6
 
8
7
  def initialize(name)
9
8
  @pkgconfig = PkgConfig.new(name)
@@ -23,6 +22,7 @@ module Autobuild
23
22
  pcfile
24
23
  end
25
24
  end
25
+
26
26
  def self.installed_pkgconfig(name, &block)
27
27
  InstalledPkgConfig.new(name, &block)
28
28
  end
@@ -9,8 +9,7 @@ module Autobuild
9
9
 
10
10
  # Handler class to build python-based packages
11
11
  class Python < Configurable
12
- attr_accessor :buildflags
13
- attr_accessor :installflags
12
+ attr_accessor :buildflags, :installflags
14
13
 
15
14
  def configurestamp
16
15
  "#{builddir}/configure-autobuild-stamp"
@@ -22,9 +21,8 @@ module Autobuild
22
21
  super
23
22
  end
24
23
 
25
- def prepare
26
- super
27
- @install_mode = File.file?(File.join(srcdir, 'setup.py'))
24
+ def install_mode?
25
+ File.file?(File.join(srcdir, 'setup.py'))
28
26
  end
29
27
 
30
28
  def prepare_for_forced_build
@@ -65,7 +63,7 @@ module Autobuild
65
63
 
66
64
  # Do the build in builddir
67
65
  def build
68
- return unless @install_mode
66
+ return unless install_mode?
69
67
 
70
68
  command = generate_build_command
71
69
  command << '--force' if @forced
@@ -78,7 +76,7 @@ module Autobuild
78
76
 
79
77
  # Install the result in prefix
80
78
  def install
81
- return unless @install_mode
79
+ return unless install_mode?
82
80
 
83
81
  command = generate_install_command
84
82
  command << '--force' if @forced
@@ -91,7 +89,7 @@ module Autobuild
91
89
 
92
90
  def update_environment
93
91
  super
94
- path = @install_mode ? python_path : srcdir
92
+ path = install_mode? ? python_path : srcdir
95
93
  env_add_path 'PYTHONPATH', path
96
94
  end
97
95
  end
@@ -40,7 +40,7 @@ module Autobuild
40
40
  done_message: 'generated documentation for %s' do
41
41
  run 'doc',
42
42
  Autobuild.tool_in_path('ruby'), '-S',
43
- Autobuild.tool('rake'), rake_doc_task,
43
+ Autobuild.tool('rake'), rake_doc_task,
44
44
  working_directory: srcdir
45
45
  end
46
46
  end
@@ -52,7 +52,7 @@ module Autobuild
52
52
  done_message: 'tests passed for %s' do
53
53
  run 'test',
54
54
  Autobuild.tool_in_path('ruby'), '-S',
55
- Autobuild.tool('rake'), rake_test_task, *rake_test_options,
55
+ Autobuild.tool('rake'), rake_test_task, *rake_test_options,
56
56
  working_directory: srcdir
57
57
  end
58
58
  end
@@ -62,7 +62,7 @@ module Autobuild
62
62
  if setup_task && File.file?(File.join(srcdir, 'Rakefile'))
63
63
  run 'post-install',
64
64
  Autobuild.tool_in_path('ruby'), '-S',
65
- Autobuild.tool('rake'), setup_task,
65
+ Autobuild.tool('rake'), setup_task,
66
66
  working_directory: srcdir
67
67
  end
68
68
  end
@@ -80,7 +80,7 @@ module Autobuild
80
80
  %w[ext tmp].each do |extdir|
81
81
  if File.directory?(extdir)
82
82
  Find.find(extdir) do |file|
83
- next if file !~ /\<Makefile\>|\<CMakeCache.txt\>$/
83
+ next if file !~ /<Makefile>|<CMakeCache.txt>$/
84
84
 
85
85
  FileUtils.rm_rf file
86
86
  end
@@ -94,7 +94,7 @@ module Autobuild
94
94
  begin
95
95
  run 'clean',
96
96
  Autobuild.tool_in_path('ruby'), '-S',
97
- Autobuild.tool('rake'), rake_clean_task,
97
+ Autobuild.tool('rake'), rake_clean_task,
98
98
  working_directory: srcdir
99
99
  rescue Autobuild::SubcommandFailed => e
100
100
  warn "%s: clean failed. If this package does not need a clean target,"
@@ -4,15 +4,10 @@ module Autobuild
4
4
  # Since autobuild does not use task arguments, we don't support them for
5
5
  # simplicity
6
6
  class RakeTaskParallelism
7
- attr_reader :available_workers
8
- attr_reader :finished_workers
9
- attr_reader :workers
10
-
11
- attr_reader :job_server
7
+ attr_reader :available_workers, :finished_workers, :workers, :job_server
12
8
 
13
9
  class JobServer
14
- attr_reader :rio
15
- attr_reader :wio
10
+ attr_reader :rio, :wio
16
11
 
17
12
  def initialize(level)
18
13
  @rio, @wio = IO.pipe
@@ -76,15 +71,12 @@ module Autobuild
76
71
  end
77
72
 
78
73
  class ProcessingState
79
- attr_reader :reverse_dependencies
80
- attr_reader :processed
81
- attr_reader :started_packages
82
- attr_reader :active_tasks
83
- attr_reader :queue
84
- attr_reader :priorities
85
-
86
- def initialize(reverse_dependencies)
74
+ attr_reader :reverse_dependencies, :processed, :started_packages,
75
+ :active_tasks, :queue, :priorities
76
+
77
+ def initialize(reverse_dependencies, completion_callback: proc {})
87
78
  @reverse_dependencies = reverse_dependencies
79
+ @completion_callback = completion_callback
88
80
  @processed = Set.new
89
81
  @active_tasks = Set.new
90
82
  @priorities = Hash.new
@@ -143,6 +135,8 @@ module Autobuild
143
135
  push(candidate, priorities[task])
144
136
  end
145
137
  end
138
+
139
+ @completion_callback.call(task)
146
140
  end
147
141
 
148
142
  def trivial_task?(task)
@@ -153,7 +147,7 @@ module Autobuild
153
147
 
154
148
  # Invokes the provided tasks. Unlike the rake code, this is a toplevel
155
149
  # algorithm that does not use recursion
156
- def invoke_parallel(required_tasks)
150
+ def invoke_parallel(required_tasks, completion_callback: proc {})
157
151
  tasks = Set.new
158
152
  reverse_dependencies = Hash.new { |h, k| h[k] = Set.new }
159
153
  required_tasks.each do |t|
@@ -162,7 +156,8 @@ module Autobuild
162
156
  # The queue is the set of tasks for which all prerequisites have
163
157
  # been successfully executed (or where not needed). I.e. it is the
164
158
  # set of tasks that can be queued for execution.
165
- state = ProcessingState.new(reverse_dependencies)
159
+ state = ProcessingState.new(reverse_dependencies,
160
+ completion_callback: completion_callback)
166
161
  tasks.each do |t|
167
162
  state.push(t) if state.ready?(t)
168
163
  end
@@ -188,12 +183,16 @@ module Autobuild
188
183
  break if !pending_task && available_workers.size == workers.size
189
184
  end
190
185
 
191
- if state.trivial_task?(pending_task)
192
- Worker.execute_task(pending_task)
186
+ bypass_task = pending_task.disabled? ||
187
+ pending_task.already_invoked? ||
188
+ !pending_task.needed?
189
+
190
+ if bypass_task
191
+ pending_task.already_invoked = true
193
192
  state.process_finished_task(pending_task)
194
193
  next
195
- elsif pending_task.already_invoked? || !pending_task.needed?
196
- pending_task.already_invoked = true
194
+ elsif state.trivial_task?(pending_task)
195
+ Worker.execute_task(pending_task)
197
196
  state.process_finished_task(pending_task)
198
197
  next
199
198
  end
@@ -5,6 +5,7 @@ class PkgConfig
5
5
 
6
6
  def initialize(name)
7
7
  @name = name
8
+ super()
8
9
  end
9
10
 
10
11
  def to_s
@@ -1,3 +1,6 @@
1
+ require "concurrent/atomic/atomic_boolean"
2
+ require "concurrent/array"
3
+
1
4
  module Autobuild
2
5
  # Management of the progress display
3
6
  class ProgressDisplay
@@ -5,20 +8,72 @@ module Autobuild
5
8
  @io = io
6
9
  @cursor = TTY::Cursor
7
10
  @last_formatted_progress = []
8
- @progress_messages = []
11
+ @progress_messages = Concurrent::Array.new
9
12
 
10
13
  @silent = false
11
14
  @color = color
12
- @progress_enabled = true
13
15
  @display_lock = Mutex.new
16
+
17
+ @next_progress_display = Time.at(0)
18
+ @progress_mode = :single_line
19
+ @progress_period = 0.1
20
+
21
+ @message_queue = Queue.new
22
+ @forced_progress_display = Concurrent::AtomicBoolean.new(false)
14
23
  end
15
24
 
16
- attr_writer :silent
25
+ def synchronize(&block)
26
+ result = @display_lock.synchronize(&block)
27
+ refresh_display
28
+ result
29
+ end
30
+
31
+ # Set the minimum time between two progress messages
32
+ #
33
+ # @see period
34
+ def progress_period=(period)
35
+ @progress_period = Float(period)
36
+ end
37
+
38
+ # Minimum time between two progress displays
39
+ #
40
+ # This does not affect normal messages
41
+ #
42
+ # @return [Float]
43
+ attr_reader :progress_period
44
+
45
+ # Valid progress modes
46
+ #
47
+ # @see progress_mode=
48
+ PROGRESS_MODES = %I[single_line newline off].freeze
49
+
50
+ # Sets how progress messages will be displayed
51
+ #
52
+ # @param [String] the new mode. Can be either 'single_line', where a
53
+ # progress message replaces the last one, 'newline' which displays
54
+ # each on a new line or 'off' to disable progress messages altogether
55
+ def progress_mode=(mode)
56
+ mode = mode.to_sym
57
+ unless PROGRESS_MODES.include?(mode)
58
+ raise ArgumentError,
59
+ "#{mode} is not a valid mode, expected one of "\
60
+ "#{PROGRESS_MODES.join(', ')}"
61
+ end
62
+ @progress_mode = mode
63
+ end
64
+
65
+ # Return the current display mode
66
+ #
67
+ # @return [Symbol]
68
+ # @see mode=
69
+ attr_reader :progress_mode
17
70
 
18
71
  def silent?
19
72
  @silent
20
73
  end
21
74
 
75
+ attr_writer :silent
76
+
22
77
  def silent
23
78
  silent = @silent
24
79
  @silent = true
@@ -27,24 +82,23 @@ module Autobuild
27
82
  @silent = silent
28
83
  end
29
84
 
30
- attr_writer :progress_enabled
85
+ # @deprecated use progress_mode= instead
86
+ def progress_enabled=(flag)
87
+ self.progress_mode = flag ? :single_line : :off
88
+ end
31
89
 
90
+ # Whether progress messages will be displayed at all
32
91
  def progress_enabled?
33
- !@silent && @progress_enabled
92
+ !@silent && (@progress_mode != :off)
34
93
  end
35
94
 
36
95
  def message(message, *args, io: @io, force: false)
37
96
  return if silent? && !force
38
97
 
39
98
  io = args.pop if args.last.respond_to?(:to_io)
99
+ @message_queue << [message, args, io]
40
100
 
41
- @display_lock.synchronize do
42
- io.print "#{@cursor.column(1)}#{@cursor.clear_screen_down}"\
43
- "#{@color.call(message, *args)}\n"
44
- io.flush if @io != io
45
- display_progress
46
- @io.flush
47
- end
101
+ refresh_display
48
102
  end
49
103
 
50
104
  def progress_start(key, *args, done_message: nil)
@@ -53,13 +107,13 @@ module Autobuild
53
107
  formatted_message = @color.call(*args)
54
108
  @progress_messages << [key, formatted_message]
55
109
  if progress_enabled?
56
- @display_lock.synchronize do
57
- display_progress
58
- end
110
+ @forced_progress_display.make_true
59
111
  else
60
112
  message " #{formatted_message}"
61
113
  end
62
114
 
115
+ refresh_display
116
+
63
117
  if block_given?
64
118
  begin
65
119
  result = yield
@@ -73,72 +127,99 @@ module Autobuild
73
127
  end
74
128
 
75
129
  def progress(key, *args)
76
- @display_lock.synchronize do
77
- found = false
78
- @progress_messages.map! do |msg_key, msg|
79
- if msg_key == key
80
- found = true
81
- [msg_key, @color.call(*args)]
82
- else
83
- [msg_key, msg]
84
- end
130
+ found = false
131
+ @progress_messages.map! do |msg_key, msg|
132
+ if msg_key == key
133
+ found = true
134
+ [msg_key, @color.call(*args)]
135
+ else
136
+ [msg_key, msg]
85
137
  end
86
- @progress_messages << [key, @color.call(*args)] unless found
87
- display_progress
88
138
  end
139
+ @progress_messages << [key, @color.call(*args)] unless found
140
+
141
+ refresh_display
89
142
  end
90
143
 
91
144
  def progress_done(key, display_last = true, message: nil)
92
- changed = @display_lock.synchronize do
93
- current_size = @progress_messages.size
94
- @progress_messages.delete_if do |msg_key, msg|
95
- if msg_key == key
96
- message = msg if display_last && !message
97
- true
98
- end
145
+ current_size = @progress_messages.size
146
+ @progress_messages.delete_if do |msg_key, msg|
147
+ if msg_key == key
148
+ message = msg if display_last && !message
149
+ true
99
150
  end
100
- current_size != @progress_messages.size
101
151
  end
152
+ changed = current_size != @progress_messages.size
102
153
 
103
154
  if changed
104
155
  if message
105
156
  message(" #{message}")
106
- # Note: message calls display_progress already
157
+ # NOTE: message updates the display already
107
158
  else
108
- @display_lock.synchronize do
109
- display_progress
110
- end
159
+ refresh_display
111
160
  end
112
161
  true
113
162
  end
114
163
  end
115
164
 
116
- def display_progress
165
+ def refresh_display
166
+ return unless @display_lock.try_lock
167
+
168
+ begin
169
+ refresh_display_under_lock
170
+ ensure
171
+ @display_lock.unlock
172
+ end
173
+ end
174
+
175
+ def refresh_display_under_lock
176
+ # Display queued messages
177
+ until @message_queue.empty?
178
+ message, args, io = @message_queue.pop
179
+ io.print @cursor.clear_screen_down if @progress_mode == :single_line
180
+ io.puts @color.call(message, *args)
181
+
182
+ io.flush if @io != io
183
+ end
184
+
185
+ # And re-display the progress
186
+ display_progress(consider_period: @forced_progress_display.false?)
187
+ @forced_progress_display.make_false
188
+ @io.flush
189
+ end
190
+
191
+ def display_progress(consider_period: true)
117
192
  return unless progress_enabled?
193
+ return if consider_period && (@next_progress_display > Time.now)
118
194
 
119
- formatted = format_grouped_messages(@progress_messages.map(&:last),
120
- indent: " ")
121
- @io.print @cursor.clear_screen_down
122
- @io.print formatted.join("\n")
123
- if formatted.size > 1
124
- @io.print "#{@cursor.up(formatted.size - 1)}#{@cursor.column(0)}"
195
+ formatted = format_grouped_messages(
196
+ @progress_messages.map(&:last),
197
+ indent: " "
198
+ )
199
+ if @progress_mode == :newline
200
+ @io.print formatted.join("\n")
201
+ @io.print "\n"
125
202
  else
203
+ @io.print @cursor.clear_screen_down
204
+ @io.print formatted.join("\n")
205
+ @io.print @cursor.up(formatted.size - 1) if formatted.size > 1
126
206
  @io.print @cursor.column(0)
127
207
  end
128
208
  @io.flush
209
+ @next_progress_display = Time.now + @progress_period
129
210
  end
130
211
 
131
212
  def find_common_prefix(msg, other_msg)
132
- msg = msg.split(" ")
133
- other_msg = other_msg.split(" ")
213
+ msg = msg.split(' ')
214
+ other_msg = other_msg.split(' ')
134
215
  msg.each_with_index do |token, idx|
135
216
  if other_msg[idx] != token
136
217
  prefix = msg[0..(idx - 1)].join(" ")
137
- prefix << " " unless prefix.empty?
218
+ prefix << ' ' unless prefix.empty?
138
219
  return prefix
139
220
  end
140
221
  end
141
- msg.join(" ")
222
+ msg.join(' ')
142
223
  end
143
224
 
144
225
  def group_messages(messages)