autobuild 1.17.0 → 1.18.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +107 -0
- data/Gemfile +2 -1
- data/Rakefile +1 -4
- data/autobuild.gemspec +14 -11
- data/bin/autobuild +4 -3
- data/lib/autobuild.rb +4 -5
- data/lib/autobuild/build_logfile.rb +6 -4
- data/lib/autobuild/config.rb +90 -40
- data/lib/autobuild/configurable.rb +30 -18
- data/lib/autobuild/environment.rb +126 -120
- data/lib/autobuild/exceptions.rb +48 -31
- data/lib/autobuild/import/archive.rb +134 -82
- data/lib/autobuild/import/cvs.rb +28 -24
- data/lib/autobuild/import/darcs.rb +13 -16
- data/lib/autobuild/import/git-lfs.rb +37 -30
- data/lib/autobuild/import/git.rb +231 -179
- data/lib/autobuild/import/hg.rb +23 -18
- data/lib/autobuild/import/svn.rb +48 -29
- data/lib/autobuild/importer.rb +530 -499
- data/lib/autobuild/mail_reporter.rb +77 -77
- data/lib/autobuild/package.rb +171 -101
- data/lib/autobuild/packages/autotools.rb +47 -42
- data/lib/autobuild/packages/cmake.rb +71 -65
- data/lib/autobuild/packages/dummy.rb +9 -8
- data/lib/autobuild/packages/genom.rb +1 -1
- data/lib/autobuild/packages/gnumake.rb +19 -13
- data/lib/autobuild/packages/import.rb +2 -6
- data/lib/autobuild/packages/orogen.rb +32 -31
- data/lib/autobuild/packages/pkgconfig.rb +2 -2
- data/lib/autobuild/packages/python.rb +7 -2
- data/lib/autobuild/packages/ruby.rb +22 -17
- data/lib/autobuild/parallel.rb +35 -39
- data/lib/autobuild/pkgconfig.rb +25 -13
- data/lib/autobuild/progress_display.rb +23 -23
- data/lib/autobuild/rake_task_extension.rb +6 -6
- data/lib/autobuild/reporting.rb +38 -26
- data/lib/autobuild/subcommand.rb +72 -65
- data/lib/autobuild/test.rb +8 -7
- data/lib/autobuild/test_utility.rb +10 -9
- data/lib/autobuild/timestamps.rb +28 -23
- data/lib/autobuild/tools.rb +17 -16
- data/lib/autobuild/utility.rb +16 -18
- data/lib/autobuild/version.rb +1 -1
- metadata +39 -38
data/lib/autobuild/pkgconfig.rb
CHANGED
@@ -2,10 +2,16 @@
|
|
2
2
|
class PkgConfig
|
3
3
|
class NotFound < RuntimeError
|
4
4
|
attr_reader :name
|
5
|
-
|
6
|
-
def
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"#{name} is not available to pkg-config"
|
12
|
+
end
|
7
13
|
end
|
8
|
-
|
14
|
+
|
9
15
|
# The module name
|
10
16
|
attr_reader :name
|
11
17
|
# The module version
|
@@ -14,30 +20,36 @@ def to_s; "#{name} is not available to pkg-config" end
|
|
14
20
|
# Create a PkgConfig object for the package +name+
|
15
21
|
# Raises PkgConfig::NotFound if the module does not exist
|
16
22
|
def initialize(name)
|
17
|
-
|
18
|
-
raise NotFound.new(name)
|
23
|
+
unless system("pkg-config --exists #{name}")
|
24
|
+
raise NotFound.new(name), "pkg-config package '#{name}' not found"
|
19
25
|
end
|
20
|
-
|
26
|
+
|
21
27
|
@name = name
|
22
28
|
@version = `pkg-config --modversion #{name}`.chomp.strip
|
23
29
|
@actions = Hash.new
|
24
30
|
@variables = Hash.new
|
25
31
|
end
|
26
32
|
|
27
|
-
ACTIONS = %w
|
28
|
-
|
33
|
+
ACTIONS = %w[cflags cflags-only-I cflags-only-other
|
34
|
+
libs libs-only-L libs-only-l libs-only-other static].freeze
|
29
35
|
ACTIONS.each do |action|
|
30
|
-
define_method(action.
|
36
|
+
define_method(action.tr('-', '_')) do
|
31
37
|
@actions[action] ||= `pkg-config --#{action} #{name}`.chomp.strip
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
41
|
+
def respond_to_missing?(varname, _include_all)
|
42
|
+
varname =~ /^\w+$/
|
43
|
+
end
|
44
|
+
|
35
45
|
def method_missing(varname, *args, &proc)
|
36
46
|
if args.empty?
|
37
|
-
@variables[varname]
|
38
|
-
|
39
|
-
|
47
|
+
unless (value = @variables[varname])
|
48
|
+
value = `pkg-config --variable=#{varname} #{name}`.chomp.strip
|
49
|
+
@variables[varname] = value
|
50
|
+
end
|
51
|
+
return value
|
40
52
|
end
|
53
|
+
super
|
41
54
|
end
|
42
55
|
end
|
43
|
-
|
@@ -3,7 +3,6 @@ module Autobuild
|
|
3
3
|
class ProgressDisplay
|
4
4
|
def initialize(io, color: ::Autobuild.method(:color))
|
5
5
|
@io = io
|
6
|
-
#@cursor = Blank.new
|
7
6
|
@cursor = TTY::Cursor
|
8
7
|
@last_formatted_progress = []
|
9
8
|
@progress_messages = []
|
@@ -21,7 +20,8 @@ def silent?
|
|
21
20
|
end
|
22
21
|
|
23
22
|
def silent
|
24
|
-
@silent
|
23
|
+
@silent = true
|
24
|
+
silent = @silent
|
25
25
|
yield
|
26
26
|
ensure
|
27
27
|
@silent = silent
|
@@ -36,12 +36,11 @@ def progress_enabled?
|
|
36
36
|
def message(message, *args, io: @io, force: false)
|
37
37
|
return if silent? && !force
|
38
38
|
|
39
|
-
if args.last.respond_to?(:to_io)
|
40
|
-
io = args.pop
|
41
|
-
end
|
39
|
+
io = args.pop if args.last.respond_to?(:to_io)
|
42
40
|
|
43
41
|
@display_lock.synchronize do
|
44
|
-
io.print "#{@cursor.column(1)}#{@cursor.clear_screen_down}
|
42
|
+
io.print "#{@cursor.column(1)}#{@cursor.clear_screen_down}"\
|
43
|
+
"#{@color.call(message, *args)}\n"
|
45
44
|
io.flush if @io != io
|
46
45
|
display_progress
|
47
46
|
@io.flush
|
@@ -94,9 +93,7 @@ def progress_done(key, display_last = true, message: nil)
|
|
94
93
|
current_size = @progress_messages.size
|
95
94
|
@progress_messages.delete_if do |msg_key, msg|
|
96
95
|
if msg_key == key
|
97
|
-
if display_last && !message
|
98
|
-
message = msg
|
99
|
-
end
|
96
|
+
message = msg if display_last && !message
|
100
97
|
true
|
101
98
|
end
|
102
99
|
end
|
@@ -116,11 +113,11 @@ def progress_done(key, display_last = true, message: nil)
|
|
116
113
|
end
|
117
114
|
end
|
118
115
|
|
119
|
-
|
120
116
|
def display_progress
|
121
117
|
return unless progress_enabled?
|
122
118
|
|
123
|
-
formatted = format_grouped_messages(@progress_messages.map(&:last),
|
119
|
+
formatted = format_grouped_messages(@progress_messages.map(&:last),
|
120
|
+
indent: " ")
|
124
121
|
@io.print @cursor.clear_screen_down
|
125
122
|
@io.print formatted.join("\n")
|
126
123
|
if formatted.size > 1
|
@@ -137,13 +134,11 @@ def find_common_prefix(msg, other_msg)
|
|
137
134
|
msg.each_with_index do |token, idx|
|
138
135
|
if other_msg[idx] != token
|
139
136
|
prefix = msg[0..(idx - 1)].join(" ")
|
140
|
-
|
141
|
-
prefix << " "
|
142
|
-
end
|
137
|
+
prefix << " " unless prefix.empty?
|
143
138
|
return prefix
|
144
139
|
end
|
145
140
|
end
|
146
|
-
|
141
|
+
msg.join(" ")
|
147
142
|
end
|
148
143
|
|
149
144
|
def group_messages(messages)
|
@@ -152,19 +147,22 @@ def group_messages(messages)
|
|
152
147
|
groups = Array.new
|
153
148
|
groups << ["", (0...messages.size)]
|
154
149
|
messages.each_with_index do |msg, idx|
|
155
|
-
prefix
|
150
|
+
prefix = nil
|
151
|
+
grouping = false
|
156
152
|
messages[(idx + 1)..-1].each_with_index do |other_msg, other_idx|
|
157
153
|
other_idx += idx + 1
|
158
154
|
prefix ||= find_common_prefix(msg, other_msg)
|
159
|
-
break
|
155
|
+
break unless other_msg.start_with?(prefix)
|
160
156
|
|
161
157
|
if grouping
|
162
158
|
break if prefix != groups.last[0]
|
159
|
+
|
163
160
|
groups.last[1] << other_idx
|
164
161
|
else
|
165
162
|
current_prefix, current_group = groups.last
|
166
|
-
if prefix.size > current_prefix.size # create a new group
|
167
|
-
|
163
|
+
if prefix.size > current_prefix.size # create a new group
|
164
|
+
group_end_index = [idx - 1, current_group.last].min
|
165
|
+
groups.last[1] = (current_group.first..group_end_index)
|
168
166
|
groups << [prefix, [idx, other_idx]]
|
169
167
|
grouping = true
|
170
168
|
else break
|
@@ -179,13 +177,14 @@ def group_messages(messages)
|
|
179
177
|
groups.map do |prefix, indexes|
|
180
178
|
indexes = indexes.to_a
|
181
179
|
next if indexes.empty?
|
180
|
+
|
182
181
|
range = (prefix.size)..-1
|
183
182
|
[prefix, indexes.map { |i| messages[i][range] }]
|
184
183
|
end.compact
|
185
184
|
end
|
186
185
|
|
187
|
-
def format_grouped_messages(
|
188
|
-
groups = group_messages(
|
186
|
+
def format_grouped_messages(raw_messages, indent: " ", width: TTY::Screen.width)
|
187
|
+
groups = group_messages(raw_messages)
|
189
188
|
groups.each_with_object([]) do |(prefix, messages), lines|
|
190
189
|
if prefix.empty?
|
191
190
|
lines.concat(messages.map { |m| "#{indent}#{m.strip}" })
|
@@ -197,10 +196,11 @@ def format_grouped_messages(messages, indent: " ", width: TTY::Screen.width)
|
|
197
196
|
msg = messages.shift.strip
|
198
197
|
margin = messages.empty? ? 1 : 2
|
199
198
|
if lines.last.size + margin + msg.size > width
|
200
|
-
lines << ""
|
199
|
+
lines.last << ","
|
200
|
+
lines << +""
|
201
201
|
lines.last << indent << indent << msg
|
202
202
|
else
|
203
|
-
lines.last << " " << msg
|
203
|
+
lines.last << ", " << msg
|
204
204
|
end
|
205
205
|
end
|
206
206
|
lines.last << "," unless messages.empty?
|
@@ -1,19 +1,19 @@
|
|
1
1
|
module Autobuild
|
2
2
|
module RakeTaskExtension
|
3
3
|
def already_invoked?
|
4
|
-
|
4
|
+
@already_invoked
|
5
5
|
end
|
6
6
|
|
7
|
-
|
8
|
-
@already_invoked = value
|
9
|
-
end
|
7
|
+
attr_writer :already_invoked
|
10
8
|
|
11
9
|
def disable!
|
12
10
|
@already_invoked = true
|
13
|
-
|
11
|
+
singleton_class.class_eval do
|
12
|
+
define_method(:needed?) { false }
|
13
|
+
end
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
-
class Rake::Task
|
17
|
+
class Rake::Task # rubocop:disable Style/ClassAndModuleChildren
|
18
18
|
include Autobuild::RakeTaskExtension
|
19
19
|
end
|
data/lib/autobuild/reporting.rb
CHANGED
@@ -16,6 +16,7 @@ def color=(flag)
|
|
16
16
|
def color?
|
17
17
|
@colorizer.enabled?
|
18
18
|
end
|
19
|
+
|
19
20
|
def color(message, *style)
|
20
21
|
@colorizer.decorate(message, *style)
|
21
22
|
end
|
@@ -31,6 +32,7 @@ class << self
|
|
31
32
|
def silent?
|
32
33
|
@display.silent?
|
33
34
|
end
|
35
|
+
|
34
36
|
def silent=(flag)
|
35
37
|
@display.silent = flag
|
36
38
|
end
|
@@ -80,7 +82,7 @@ def self.progress_done(key, display_last = true, message: nil)
|
|
80
82
|
#
|
81
83
|
# It does not use a logging framework like Log4r, but it should ;-)
|
82
84
|
module Reporting
|
83
|
-
|
85
|
+
@reporters = Array.new
|
84
86
|
|
85
87
|
## Run a block and report known exception
|
86
88
|
# If an exception is fatal, the program is terminated using exit()
|
@@ -89,18 +91,22 @@ def self.report(on_package_failures: default_report_on_package_failures)
|
|
89
91
|
rescue Interrupt => e
|
90
92
|
interrupted = e
|
91
93
|
rescue Autobuild::Exception => e
|
92
|
-
return report_finish_on_error([e],
|
94
|
+
return report_finish_on_error([e],
|
95
|
+
on_package_failures: on_package_failures,
|
96
|
+
interrupted_by: interrupted)
|
93
97
|
end
|
94
98
|
|
95
99
|
# If ignore_erorrs is true, check if some packages have failed
|
96
100
|
# on the way. If so, raise an exception to inform the user about
|
97
101
|
# it
|
98
102
|
errors = []
|
99
|
-
Autobuild::Package.each do |
|
103
|
+
Autobuild::Package.each do |_name, pkg|
|
100
104
|
errors.concat(pkg.failures)
|
101
105
|
end
|
102
106
|
|
103
|
-
report_finish_on_error(errors,
|
107
|
+
report_finish_on_error(errors,
|
108
|
+
on_package_failures: on_package_failures,
|
109
|
+
interrupted_by: interrupted)
|
104
110
|
end
|
105
111
|
|
106
112
|
# @api private
|
@@ -121,15 +127,18 @@ def self.default_report_on_package_failures
|
|
121
127
|
#
|
122
128
|
# @param [Symbol] on_package_failures how does the reporting should behave.
|
123
129
|
#
|
124
|
-
def self.report_finish_on_error(errors,
|
125
|
-
|
130
|
+
def self.report_finish_on_error(errors,
|
131
|
+
on_package_failures: default_report_on_package_failures, interrupted_by: nil)
|
132
|
+
if (not_package_error = errors.find { |e| !e.respond_to?(:fatal?) })
|
126
133
|
raise not_package_error
|
127
134
|
end
|
128
|
-
|
135
|
+
|
136
|
+
unless %i[raise report_silent exit_silent].include?(on_package_failures)
|
129
137
|
errors.each { |e| error(e) }
|
130
138
|
end
|
139
|
+
|
131
140
|
fatal = errors.any?(&:fatal?)
|
132
|
-
|
141
|
+
unless fatal
|
133
142
|
if interrupted_by
|
134
143
|
raise interrupted_by
|
135
144
|
else
|
@@ -138,30 +147,29 @@ def self.report_finish_on_error(errors, on_package_failures: default_report_on_p
|
|
138
147
|
end
|
139
148
|
|
140
149
|
if on_package_failures == :raise
|
141
|
-
if interrupted_by
|
142
|
-
raise interrupted_by
|
143
|
-
end
|
150
|
+
raise interrupted_by if interrupted_by
|
144
151
|
|
145
152
|
e = if errors.size == 1 then errors.first
|
146
|
-
|
147
|
-
|
153
|
+
else CompositeException.new(errors)
|
154
|
+
end
|
148
155
|
raise e
|
149
|
-
elsif [
|
156
|
+
elsif %i[report_silent report].include?(on_package_failures)
|
150
157
|
if interrupted_by
|
151
158
|
raise interrupted_by
|
152
159
|
else
|
153
160
|
return errors
|
154
161
|
end
|
155
|
-
elsif [
|
162
|
+
elsif %i[exit exit_silent].include?(on_package_failures)
|
156
163
|
exit 1
|
157
164
|
else
|
158
|
-
raise ArgumentError, "unexpected value for on_package_failures:
|
165
|
+
raise ArgumentError, "unexpected value for on_package_failures: "\
|
166
|
+
"#{on_package_failures}"
|
159
167
|
end
|
160
168
|
end
|
161
169
|
|
162
170
|
## Reports a successful build to the user
|
163
171
|
def self.success
|
164
|
-
each_reporter
|
172
|
+
each_reporter(&:success)
|
165
173
|
end
|
166
174
|
|
167
175
|
## Reports that the build failed to the user
|
@@ -171,19 +179,19 @@ def self.error(error)
|
|
171
179
|
|
172
180
|
## Add a new reporter
|
173
181
|
def self.<<(reporter)
|
174
|
-
|
182
|
+
@reporters << reporter
|
175
183
|
end
|
176
184
|
|
177
185
|
def self.remove(reporter)
|
178
|
-
|
186
|
+
@reporters.delete(reporter)
|
179
187
|
end
|
180
188
|
|
181
189
|
def self.clear_reporters
|
182
|
-
|
190
|
+
@reporters.clear
|
183
191
|
end
|
184
192
|
|
185
193
|
def self.each_reporter(&iter)
|
186
|
-
|
194
|
+
@reporters.each(&iter)
|
187
195
|
end
|
188
196
|
|
189
197
|
## Iterate on all log files
|
@@ -195,6 +203,7 @@ def self.each_log(&block)
|
|
195
203
|
## Base class for reporters
|
196
204
|
class Reporter
|
197
205
|
def error(error); end
|
206
|
+
|
198
207
|
def success; end
|
199
208
|
end
|
200
209
|
|
@@ -203,11 +212,10 @@ class StdoutReporter < Reporter
|
|
203
212
|
def error(error)
|
204
213
|
STDERR.puts "Build failed: #{error}"
|
205
214
|
end
|
215
|
+
|
206
216
|
def success
|
207
217
|
puts "Build finished successfully at #{Time.now}"
|
208
|
-
if Autobuild.post_success_message
|
209
|
-
puts Autobuild.post_success_message
|
210
|
-
end
|
218
|
+
puts Autobuild.post_success_message if Autobuild.post_success_message
|
211
219
|
end
|
212
220
|
end
|
213
221
|
|
@@ -216,12 +224,16 @@ def success
|
|
216
224
|
[1_000_000.0, "M"],
|
217
225
|
[1_000.0, "k"],
|
218
226
|
[1.0, ""]
|
219
|
-
]
|
227
|
+
].freeze
|
220
228
|
|
221
229
|
def self.human_readable_size(size)
|
222
230
|
HUMAN_READABLE_SIZES.each do |scale, name|
|
223
231
|
scaled_size = (size / scale)
|
224
|
-
|
232
|
+
if scaled_size > 1
|
233
|
+
return format("%3.1<scaled>f%<scale_name>s",
|
234
|
+
scaled: scaled_size,
|
235
|
+
scale_name: name)
|
236
|
+
end
|
225
237
|
end
|
226
238
|
end
|
227
239
|
end
|
data/lib/autobuild/subcommand.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'autobuild/exceptions'
|
3
3
|
require 'autobuild/reporting'
|
4
4
|
require 'fcntl'
|
5
|
+
require 'English'
|
5
6
|
|
6
7
|
module Autobuild
|
7
8
|
@logfiles = Set.new
|
@@ -24,9 +25,11 @@ def self.registered_logfile?(logfile)
|
|
24
25
|
def self.statistics
|
25
26
|
@statistics
|
26
27
|
end
|
28
|
+
|
27
29
|
def self.reset_statistics
|
28
30
|
@statistics = Hash.new
|
29
31
|
end
|
32
|
+
|
30
33
|
def self.add_stat(package, phase, duration)
|
31
34
|
if !@statistics[package]
|
32
35
|
@statistics[package] = { phase => duration }
|
@@ -36,6 +39,7 @@ def self.add_stat(package, phase, duration)
|
|
36
39
|
@statistics[package][phase] += duration
|
37
40
|
end
|
38
41
|
end
|
42
|
+
|
39
43
|
reset_statistics
|
40
44
|
|
41
45
|
@parallel_build_level = nil
|
@@ -52,9 +56,8 @@ class << self
|
|
52
56
|
def displayed_error_line_count=(value)
|
53
57
|
@displayed_error_line_count = validate_displayed_error_line_count(value)
|
54
58
|
end
|
55
|
-
|
56
|
-
|
57
|
-
end
|
59
|
+
|
60
|
+
attr_reader :displayed_error_line_count
|
58
61
|
|
59
62
|
# Returns the number of processes that can run in parallel during the
|
60
63
|
# build. This is a system-wide value that can be overriden in a
|
@@ -78,13 +81,13 @@ def parallel_build_level
|
|
78
81
|
|
79
82
|
# Returns the number of CPUs present on this system
|
80
83
|
def self.autodetect_processor_count
|
81
|
-
if @processor_count
|
82
|
-
return @processor_count
|
83
|
-
end
|
84
|
+
return @processor_count if @processor_count
|
84
85
|
|
85
86
|
if File.file?('/proc/cpuinfo')
|
86
87
|
cpuinfo = File.readlines('/proc/cpuinfo')
|
87
|
-
physical_ids
|
88
|
+
physical_ids = []
|
89
|
+
core_count = []
|
90
|
+
processor_ids = []
|
88
91
|
cpuinfo.each do |line|
|
89
92
|
case line
|
90
93
|
when /^processor\s+:\s+(\d+)$/
|
@@ -99,7 +102,10 @@ def self.autodetect_processor_count
|
|
99
102
|
# Try to count the number of physical cores, not the number of
|
100
103
|
# logical ones. If the info is not available, fallback to the
|
101
104
|
# logical count
|
102
|
-
|
105
|
+
has_consistent_info =
|
106
|
+
(physical_ids.size == core_count.size) &&
|
107
|
+
(physical_ids.size == processor_ids.size)
|
108
|
+
if has_consistent_info
|
103
109
|
info = Array.new
|
104
110
|
while (id = physical_ids.shift)
|
105
111
|
info[id] = core_count.shift
|
@@ -112,18 +118,17 @@ def self.autodetect_processor_count
|
|
112
118
|
result = Open3.popen3("sysctl", "-n", "hw.ncpu") do |_, io, _|
|
113
119
|
io.read
|
114
120
|
end
|
115
|
-
|
116
|
-
@processor_count = Integer(result.chomp.strip)
|
117
|
-
end
|
121
|
+
@processor_count = Integer(result.chomp.strip) unless result.empty?
|
118
122
|
end
|
119
123
|
|
120
124
|
# The format of the cpuinfo file is ... let's say not very standardized.
|
121
125
|
# If the cpuinfo detection fails, inform the user and set it to 1
|
122
|
-
|
126
|
+
unless @processor_count
|
123
127
|
# Hug... What kind of system is it ?
|
124
128
|
Autobuild.message "INFO: cannot autodetect the number of CPUs on this sytem"
|
125
129
|
Autobuild.message "INFO: turning parallel builds off"
|
126
|
-
Autobuild.message "INFO: you can manually set the number of parallel build
|
130
|
+
Autobuild.message "INFO: you can manually set the number of parallel build "\
|
131
|
+
"processes to N"
|
127
132
|
Autobuild.message "INFO: (and therefore turn this message off)"
|
128
133
|
Autobuild.message "INFO: with"
|
129
134
|
Autobuild.message " Autobuild.parallel_build_level = N"
|
@@ -135,20 +140,24 @@ def self.autodetect_processor_count
|
|
135
140
|
|
136
141
|
def self.validate_displayed_error_line_count(lines)
|
137
142
|
if lines == 'ALL'
|
138
|
-
|
143
|
+
Float::INFINITY
|
139
144
|
elsif lines.to_i > 0
|
140
|
-
|
145
|
+
lines.to_i
|
146
|
+
else
|
147
|
+
raise ConfigException.new, 'Autobuild.displayed_error_line_count can only "\
|
148
|
+
"be a positive integer or \'ALL\''
|
141
149
|
end
|
142
|
-
raise ConfigException.new, 'Autobuild.displayed_error_line_count can only be a positive integer or \'ALL\''
|
143
150
|
end
|
144
151
|
end
|
145
152
|
|
146
|
-
|
147
|
-
|
148
|
-
class Failed < Exception
|
149
|
-
def retry?; @retry end
|
153
|
+
module Autobuild::Subprocess # rubocop:disable Style/ClassAndModuleChildren
|
154
|
+
class Failed < RuntimeError
|
150
155
|
attr_reader :status
|
151
156
|
|
157
|
+
def retry?
|
158
|
+
@retry
|
159
|
+
end
|
160
|
+
|
152
161
|
def initialize(status, do_retry)
|
153
162
|
@status = status
|
154
163
|
@retry = do_retry
|
@@ -206,7 +215,11 @@ def self.run(target, phase, *command)
|
|
206
215
|
STDOUT.sync = true
|
207
216
|
|
208
217
|
input_streams = []
|
209
|
-
options =
|
218
|
+
options = {
|
219
|
+
retry: false, encoding: 'BINARY',
|
220
|
+
env: ENV.to_hash, env_inherit: true
|
221
|
+
}
|
222
|
+
|
210
223
|
if command.last.kind_of?(Hash)
|
211
224
|
options = command.pop
|
212
225
|
options = Kernel.validate_options options,
|
@@ -216,19 +229,15 @@ def self.run(target, phase, *command)
|
|
216
229
|
env_inherit: true,
|
217
230
|
encoding: 'BINARY'
|
218
231
|
|
219
|
-
if options[:input]
|
220
|
-
|
221
|
-
end
|
222
|
-
if options[:input_streams]
|
223
|
-
input_streams += options[:input_streams]
|
224
|
-
end
|
232
|
+
input_streams << File.open(options[:input]) if options[:input]
|
233
|
+
input_streams.concat(options[:input_streams]) if options[:input_streams]
|
225
234
|
end
|
226
235
|
|
227
236
|
start_time = Time.now
|
228
237
|
|
229
238
|
# Filter nil and empty? in command
|
230
|
-
command.reject!
|
231
|
-
command.collect!
|
239
|
+
command.reject! { |o| o.nil? || (o.respond_to?(:empty?) && o.empty?) }
|
240
|
+
command.collect!(&:to_s)
|
232
241
|
|
233
242
|
if target.respond_to?(:name)
|
234
243
|
target_name = target.name
|
@@ -246,13 +255,15 @@ def self.run(target, phase, *command)
|
|
246
255
|
options[:working_directory] ||= target.working_directory
|
247
256
|
end
|
248
257
|
|
249
|
-
logname = File.join(logdir, "#{target_name.gsub(/[:]/,'_')}
|
250
|
-
|
258
|
+
logname = File.join(logdir, "#{target_name.gsub(/[:]/, '_')}-"\
|
259
|
+
"#{phase.to_s.gsub(/[:]/, '_')}.log")
|
260
|
+
unless File.directory?(File.dirname(logname))
|
251
261
|
FileUtils.mkdir_p File.dirname(logname)
|
252
262
|
end
|
253
263
|
|
254
264
|
if Autobuild.verbose
|
255
|
-
Autobuild.message "#{target_name}: running #{command.join(
|
265
|
+
Autobuild.message "#{target_name}: running #{command.join(' ')}\n"\
|
266
|
+
" (output goes to #{logname})"
|
256
267
|
end
|
257
268
|
|
258
269
|
open_flag = if Autobuild.keep_oldlogs then 'a'
|
@@ -267,32 +278,28 @@ def self.run(target, phase, *command)
|
|
267
278
|
env = options[:env].dup
|
268
279
|
if options[:env_inherit]
|
269
280
|
ENV.each do |k, v|
|
270
|
-
|
271
|
-
env[k] = v
|
272
|
-
end
|
281
|
+
env[k] = v unless env.key?(k)
|
273
282
|
end
|
274
283
|
end
|
275
284
|
|
276
285
|
status = File.open(logname, open_flag) do |logfile|
|
277
|
-
if Autobuild.keep_oldlogs
|
278
|
-
logfile.puts
|
279
|
-
end
|
286
|
+
logfile.puts if Autobuild.keep_oldlogs
|
280
287
|
logfile.puts
|
281
288
|
logfile.puts "#{Time.now}: running"
|
282
|
-
logfile.puts " #{command.join(
|
289
|
+
logfile.puts " #{command.join(' ')}"
|
283
290
|
logfile.puts "with environment:"
|
284
291
|
env.keys.sort.each do |key|
|
285
|
-
if value = env[key]
|
292
|
+
if (value = env[key])
|
286
293
|
logfile.puts " '#{key}'='#{value}'"
|
287
294
|
end
|
288
295
|
end
|
289
296
|
logfile.puts
|
290
297
|
logfile.puts "#{Time.now}: running"
|
291
|
-
logfile.puts " #{command.join(
|
298
|
+
logfile.puts " #{command.join(' ')}"
|
292
299
|
logfile.flush
|
293
300
|
logfile.sync = true
|
294
301
|
|
295
|
-
|
302
|
+
unless input_streams.empty?
|
296
303
|
pread, pwrite = IO.pipe # to feed subprocess stdin
|
297
304
|
end
|
298
305
|
|
@@ -304,22 +311,19 @@ def self.run(target, phase, *command)
|
|
304
311
|
|
305
312
|
if Autobuild.windows?
|
306
313
|
Dir.chdir(options[:working_directory]) do
|
307
|
-
|
308
|
-
raise Failed.new(
|
314
|
+
unless system(*command)
|
315
|
+
raise Failed.new($CHILD_STATUS.exitstatus, nil),
|
309
316
|
"'#{command.join(' ')}' returned status #{status.exitstatus}"
|
310
317
|
end
|
311
318
|
end
|
312
|
-
return
|
319
|
+
return # rubocop:disable Lint/NonLocalExitFromIterator
|
313
320
|
end
|
314
321
|
|
315
322
|
cwrite.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
316
323
|
|
317
324
|
pid = fork do
|
318
325
|
begin
|
319
|
-
|
320
|
-
Dir.chdir(options[:working_directory])
|
321
|
-
end
|
322
|
-
logfile.puts "in directory #{Dir.pwd}"
|
326
|
+
logfile.puts "in directory #{options[:working_directory] || Dir.pwd}"
|
323
327
|
|
324
328
|
cwrite.sync = true
|
325
329
|
if Autobuild.nice
|
@@ -330,19 +334,23 @@ def self.run(target, phase, *command)
|
|
330
334
|
$stderr.reopen(outwrite.dup)
|
331
335
|
$stdout.reopen(outwrite.dup)
|
332
336
|
|
333
|
-
|
337
|
+
unless input_streams.empty?
|
334
338
|
pwrite.close
|
335
339
|
$stdin.reopen(pread)
|
336
340
|
end
|
337
341
|
|
338
|
-
exec(env, *command,
|
342
|
+
exec(env, *command,
|
343
|
+
chdir: options[:working_directory] || Dir.pwd,
|
344
|
+
close_others: false)
|
339
345
|
rescue Errno::ENOENT
|
340
346
|
cwrite.write([CONTROL_COMMAND_NOT_FOUND].pack('I'))
|
341
347
|
exit(100)
|
342
348
|
rescue Interrupt
|
343
349
|
cwrite.write([CONTROL_INTERRUPT].pack('I'))
|
344
350
|
exit(100)
|
345
|
-
rescue ::Exception
|
351
|
+
rescue ::Exception => e
|
352
|
+
STDERR.puts e
|
353
|
+
STDERR.puts e.backtrace.join("\n ")
|
346
354
|
cwrite.write([CONTROL_UNEXPECTED].pack('I'))
|
347
355
|
exit(100)
|
348
356
|
end
|
@@ -351,12 +359,14 @@ def self.run(target, phase, *command)
|
|
351
359
|
readbuffer = StringIO.new
|
352
360
|
|
353
361
|
# Feed the input
|
354
|
-
|
362
|
+
unless input_streams.empty?
|
355
363
|
pread.close
|
356
364
|
begin
|
357
365
|
input_streams.each do |instream|
|
358
366
|
instream.each_line do |line|
|
359
|
-
|
367
|
+
while IO.select([outread], nil, nil, 0)
|
368
|
+
readbuffer.write(outread.readpartial(128))
|
369
|
+
end
|
360
370
|
pwrite.write(line)
|
361
371
|
end
|
362
372
|
end
|
@@ -385,15 +395,13 @@ def self.run(target, phase, *command)
|
|
385
395
|
end
|
386
396
|
|
387
397
|
transparent_prefix = "#{target_name}:#{phase}: "
|
388
|
-
if target_type
|
389
|
-
transparent_prefix = "#{target_type}:#{transparent_prefix}"
|
390
|
-
end
|
398
|
+
transparent_prefix = "#{target_type}:#{transparent_prefix}" if target_type
|
391
399
|
|
392
400
|
# If the caller asked for process output, provide it to him
|
393
401
|
# line-by-line.
|
394
402
|
outwrite.close
|
395
403
|
|
396
|
-
|
404
|
+
unless input_streams.empty?
|
397
405
|
readbuffer.write(outread.read)
|
398
406
|
readbuffer.seek(0)
|
399
407
|
outread.close
|
@@ -428,6 +436,7 @@ def self.run(target, phase, *command)
|
|
428
436
|
if status.termsig == 2 # SIGINT == 2
|
429
437
|
raise Interrupt, "subcommand #{command.join(' ')} interrupted"
|
430
438
|
end
|
439
|
+
|
431
440
|
if status.termsig
|
432
441
|
raise Failed.new(status.exitstatus, nil),
|
433
442
|
"'#{command.join(' ')}' terminated by signal #{status.termsig}"
|
@@ -441,21 +450,19 @@ def self.run(target, phase, *command)
|
|
441
450
|
Autobuild.add_stat(target, phase, duration)
|
442
451
|
FileUtils.mkdir_p(Autobuild.logdir)
|
443
452
|
File.open(File.join(Autobuild.logdir, "stats.log"), 'a') do |io|
|
444
|
-
|
453
|
+
formatted_msec = format('%.03i', start_time.tv_usec / 1000)
|
454
|
+
formatted_time = "#{start_time.strftime('%F %H:%M:%S')}.#{formatted_msec}"
|
445
455
|
io.puts "#{formatted_time} #{target_name} #{phase} #{duration}"
|
446
456
|
end
|
447
|
-
if target.respond_to?(:add_stat)
|
448
|
-
target.add_stat(phase, duration)
|
449
|
-
end
|
457
|
+
target.add_stat(phase, duration) if target.respond_to?(:add_stat)
|
450
458
|
subcommand_output
|
451
|
-
|
452
459
|
rescue Failed => e
|
453
|
-
error = Autobuild::SubcommandFailed.new(target, command.join(" "),
|
460
|
+
error = Autobuild::SubcommandFailed.new(target, command.join(" "),
|
461
|
+
logname, e.status, subcommand_output)
|
454
462
|
error.retry = if e.retry?.nil? then options[:retry]
|
455
463
|
else e.retry?
|
456
464
|
end
|
457
465
|
error.phase = phase
|
458
466
|
raise error, e.message
|
459
467
|
end
|
460
|
-
|
461
468
|
end
|