autobuild 1.19.0 → 1.22.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +25 -0
- data/.github/workflows/test.yml +30 -0
- data/.rubocop.yml +14 -7
- data/autobuild.gemspec +8 -6
- data/bin/autobuild +1 -1
- data/lib/autobuild/build_logfile.rb +1 -2
- data/lib/autobuild/config.rb +18 -5
- data/lib/autobuild/configurable.rb +3 -1
- data/lib/autobuild/environment.rb +28 -45
- data/lib/autobuild/exceptions.rb +11 -5
- data/lib/autobuild/import/archive.rb +31 -22
- data/lib/autobuild/import/cvs.rb +6 -6
- data/lib/autobuild/import/darcs.rb +4 -4
- data/lib/autobuild/import/git-lfs.rb +4 -4
- data/lib/autobuild/import/git.rb +149 -70
- data/lib/autobuild/import/hg.rb +7 -7
- data/lib/autobuild/import/svn.rb +15 -9
- data/lib/autobuild/importer.rb +38 -38
- data/lib/autobuild/mail_reporter.rb +5 -2
- data/lib/autobuild/package.rb +24 -14
- data/lib/autobuild/packages/autotools.rb +4 -9
- data/lib/autobuild/packages/cmake.rb +16 -7
- data/lib/autobuild/packages/dummy.rb +0 -4
- data/lib/autobuild/packages/gnumake.rb +1 -1
- data/lib/autobuild/packages/orogen.rb +11 -4
- data/lib/autobuild/packages/pkgconfig.rb +2 -2
- data/lib/autobuild/packages/python.rb +1 -2
- data/lib/autobuild/packages/ruby.rb +5 -5
- data/lib/autobuild/parallel.rb +12 -17
- data/lib/autobuild/pkgconfig.rb +1 -0
- data/lib/autobuild/progress_display.rb +130 -49
- data/lib/autobuild/rake_task_extension.rb +6 -0
- data/lib/autobuild/reporting.rb +20 -7
- data/lib/autobuild/subcommand.rb +24 -23
- data/lib/autobuild/test_utility.rb +2 -1
- data/lib/autobuild/timestamps.rb +3 -3
- data/lib/autobuild/utility.rb +29 -9
- data/lib/autobuild/version.rb +1 -1
- data/lib/autobuild.rb +0 -3
- metadata +42 -26
- data/.travis.yml +0 -19
@@ -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
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
13
|
-
attr_accessor :installflags
|
12
|
+
attr_accessor :buildflags, :installflags
|
14
13
|
|
15
14
|
def configurestamp
|
16
15
|
"#{builddir}/configure-autobuild-stamp"
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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 !~
|
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
|
-
|
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,"
|
data/lib/autobuild/parallel.rb
CHANGED
@@ -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
|
-
|
81
|
-
|
82
|
-
|
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
|
data/lib/autobuild/pkgconfig.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 && @
|
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
|
-
|
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
|
-
@
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
if
|
96
|
-
|
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
|
-
#
|
157
|
+
# NOTE: message updates the display already
|
107
158
|
else
|
108
|
-
|
109
|
-
display_progress
|
110
|
-
end
|
159
|
+
refresh_display
|
111
160
|
end
|
112
161
|
true
|
113
162
|
end
|
114
163
|
end
|
115
164
|
|
116
|
-
def
|
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(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
if
|
124
|
-
@io.print
|
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 <<
|
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)
|