arduino_ci 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/cpp/arduino/ArduinoDefines.h +14 -0
- data/cpp/arduino/avr/interrupt.h +7 -0
- data/cpp/arduino/util/atomic.h +313 -0
- data/exe/arduino_ci.rb +255 -247
- data/lib/arduino_ci/logger.rb +243 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/lib/arduino_ci.rb +1 -0
- metadata +5 -2
data/exe/arduino_ci.rb
CHANGED
@@ -3,25 +3,34 @@ require 'arduino_ci'
|
|
3
3
|
require 'set'
|
4
4
|
require 'pathname'
|
5
5
|
require 'optparse'
|
6
|
-
require 'io/console'
|
7
6
|
|
8
|
-
# be flexible between 80 and 132 cols of output
|
9
|
-
WIDTH = begin
|
10
|
-
[132, [80, IO::console.winsize[1] - 2].max].min
|
11
|
-
rescue NoMethodError
|
12
|
-
80
|
13
|
-
end
|
14
7
|
VAR_CUSTOM_INIT_SCRIPT = "CUSTOM_INIT_SCRIPT".freeze
|
15
8
|
VAR_USE_SUBDIR = "USE_SUBDIR".freeze
|
16
9
|
VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze
|
17
10
|
VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
CLI_SKIP_EXAMPLES_COMPILATION = "--skip-examples-compilation".freeze
|
13
|
+
CLI_SKIP_UNITTESTS = "--skip-unittests".freeze
|
14
|
+
|
15
|
+
# script-level variables we'll use
|
16
|
+
@log = nil
|
17
|
+
@backend = nil
|
18
|
+
@cli_options = nil
|
22
19
|
|
23
20
|
# Use some basic parsing to allow command-line overrides of config
|
24
21
|
class Parser
|
22
|
+
|
23
|
+
def self.show_help(opts)
|
24
|
+
puts opts
|
25
|
+
puts
|
26
|
+
puts "Additionally, the following environment variables control the script:"
|
27
|
+
puts " - #{VAR_CUSTOM_INIT_SCRIPT} - if set, this script will be run from the Arduino/libraries directory"
|
28
|
+
puts " prior to any automated library installation or testing (e.g. to install unofficial libraries)"
|
29
|
+
puts " - #{VAR_USE_SUBDIR} - if set, the script will install the library from this subdirectory of the cwd"
|
30
|
+
puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present"
|
31
|
+
puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present"
|
32
|
+
end
|
33
|
+
|
25
34
|
def self.parse(options)
|
26
35
|
unit_config = {}
|
27
36
|
output_options = {
|
@@ -36,11 +45,11 @@ class Parser
|
|
36
45
|
opt_parser = OptionParser.new do |opts|
|
37
46
|
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
38
47
|
|
39
|
-
opts.on(
|
48
|
+
opts.on(CLI_SKIP_UNITTESTS, "Don't run unit tests") do |p|
|
40
49
|
output_options[:skip_unittests] = p
|
41
50
|
end
|
42
51
|
|
43
|
-
opts.on(
|
52
|
+
opts.on(CLI_SKIP_EXAMPLES_COMPILATION, "Don't compile example sketches") do |p|
|
44
53
|
output_options[:skip_compilation] = p
|
45
54
|
end
|
46
55
|
|
@@ -61,140 +70,60 @@ class Parser
|
|
61
70
|
end
|
62
71
|
|
63
72
|
opts.on("-h", "--help", "Prints this help") do
|
64
|
-
|
65
|
-
puts
|
66
|
-
puts "Additionally, the following environment variables control the script:"
|
67
|
-
puts " - #{VAR_CUSTOM_INIT_SCRIPT} - if set, this script will be run from the Arduino/libraries directory"
|
68
|
-
puts " prior to any automated library installation or testing (e.g. to install unofficial libraries)"
|
69
|
-
puts " - #{VAR_USE_SUBDIR} - if set, the script will install the library from this subdirectory of the cwd"
|
70
|
-
puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present"
|
71
|
-
puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present"
|
73
|
+
show_help(opts)
|
72
74
|
exit
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
|
-
|
78
|
+
begin
|
79
|
+
opt_parser.parse!(options)
|
80
|
+
rescue OptionParser::InvalidOption => e
|
81
|
+
puts e
|
82
|
+
puts
|
83
|
+
show_help(opt_parser)
|
84
|
+
exit 1
|
85
|
+
end
|
77
86
|
output_options
|
78
87
|
end
|
79
88
|
end
|
80
89
|
|
81
|
-
#
|
82
|
-
@cli_options = (Parser.parse ARGV).freeze
|
83
|
-
|
90
|
+
# print debugging information from the backend, to be used when things don't go as expected
|
84
91
|
def print_backend_logs
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
@log.iputs "========== Last backend command (if relevant):"
|
93
|
+
@log.iputs @backend.last_msg.to_s
|
94
|
+
@log.iputs "========== Backend Stdout:"
|
95
|
+
@log.iputs @backend.last_out
|
96
|
+
@log.iputs "========== Backend Stderr:"
|
97
|
+
@log.iputs @backend.last_err
|
98
|
+
end
|
99
|
+
|
100
|
+
# describe the last command, to help troubleshoot a failure
|
101
|
+
#
|
102
|
+
# @param cpp_library [CppLibrary]
|
103
|
+
def describe_last_command(cpp_library)
|
104
|
+
@log.iputs "Last command: #{cpp_library.last_cmd}"
|
105
|
+
@log.iputs cpp_library.last_out
|
106
|
+
@log.iputs cpp_library.last_err
|
91
107
|
end
|
92
108
|
|
93
109
|
# terminate after printing any debug info. TODO: capture debug info
|
94
110
|
def terminate(final = nil)
|
95
|
-
puts "Failures: #{@failure_count}"
|
96
|
-
print_backend_logs unless @failure_count.zero? || final || @backend.nil?
|
97
|
-
retcode = @failure_count.zero? ? 0 : 1
|
111
|
+
puts "Failures: #{@log.failure_count}"
|
112
|
+
print_backend_logs unless @log.failure_count.zero? || final || @backend.nil?
|
113
|
+
retcode = @log.failure_count.zero? ? 0 : 1
|
98
114
|
exit(retcode)
|
99
115
|
end
|
100
116
|
|
101
|
-
# make a nice status line for an action and react to the action
|
102
|
-
# TODO / note to self: inform_multiline is tougher to write
|
103
|
-
# without altering the signature because it only leaves space
|
104
|
-
# for the checkmark _after_ the multiline, it doesn't know how
|
105
|
-
# to make that conditionally the body
|
106
|
-
# @param message String the text of the progress indicator
|
107
|
-
# @param multiline boolean whether multiline output is expected
|
108
|
-
# @param mark_fn block (string) -> string that says how to describe the result
|
109
|
-
# @param on_fail_msg String custom message for failure
|
110
|
-
# @param tally_on_fail boolean whether to increment @failure_count
|
111
|
-
# @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
|
112
|
-
def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
|
113
|
-
line = "#{message}... "
|
114
|
-
endline = "...#{message} "
|
115
|
-
if multiline
|
116
|
-
puts line
|
117
|
-
else
|
118
|
-
print line
|
119
|
-
end
|
120
|
-
$stdout.flush
|
121
|
-
result = yield
|
122
|
-
mark = mark_fn.nil? ? "" : mark_fn.call(result)
|
123
|
-
# if multiline, put checkmark at full width
|
124
|
-
print endline if multiline
|
125
|
-
puts mark.to_s.rjust(WIDTH - line.length, " ")
|
126
|
-
unless result
|
127
|
-
puts on_fail_msg unless on_fail_msg.nil?
|
128
|
-
@failure_count += 1 if tally_on_fail
|
129
|
-
# print out error messaging here if we've captured it
|
130
|
-
terminate if abort_on_fail
|
131
|
-
end
|
132
|
-
result
|
133
|
-
end
|
134
|
-
|
135
|
-
# Make a nice status for something that defers any failure code until script exit
|
136
|
-
def attempt(message, &block)
|
137
|
-
perform_action(message, false, @passfail, nil, true, false, &block)
|
138
|
-
end
|
139
|
-
|
140
|
-
# Make a nice status for something that defers any failure code until script exit
|
141
|
-
def attempt_multiline(message, &block)
|
142
|
-
perform_action(message, true, @passfail, nil, true, false, &block)
|
143
|
-
end
|
144
|
-
|
145
|
-
# Make a nice status for something that kills the script immediately on failure
|
146
|
-
FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with your configuration; halting here".freeze
|
147
|
-
def assure(message, &block)
|
148
|
-
perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
|
149
|
-
end
|
150
|
-
|
151
|
-
def assure_multiline(message, &block)
|
152
|
-
perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
|
153
|
-
end
|
154
|
-
|
155
|
-
def inform(message, &block)
|
156
|
-
perform_action(message, false, proc { |x| x }, nil, false, false, &block)
|
157
|
-
end
|
158
|
-
|
159
|
-
def inform_multiline(message, &block)
|
160
|
-
perform_action(message, true, nil, nil, false, false, &block)
|
161
|
-
end
|
162
|
-
|
163
|
-
def rule(char)
|
164
|
-
puts char[0] * WIDTH
|
165
|
-
end
|
166
|
-
|
167
|
-
def warn(message)
|
168
|
-
inform("WARNING") { message }
|
169
|
-
end
|
170
|
-
|
171
|
-
def phase(name)
|
172
|
-
puts
|
173
|
-
rule("=")
|
174
|
-
inform("Beginning the next phase of testing") { name }
|
175
|
-
end
|
176
|
-
|
177
|
-
def banner
|
178
|
-
art = [
|
179
|
-
" . __ ___",
|
180
|
-
" _, ,_ _| , . * ._ _ / ` | ",
|
181
|
-
"(_| [ `(_] (_| | [ ) (_) \\__. _|_ v#{ArduinoCI::VERSION}",
|
182
|
-
]
|
183
|
-
|
184
|
-
pad = " " * ((WIDTH - art[2].length) / 2)
|
185
|
-
art.each { |l| puts "#{pad}#{l}" }
|
186
|
-
puts
|
187
|
-
end
|
188
|
-
|
189
117
|
# Assure that a platform exists and return its definition
|
190
118
|
def assured_platform(purpose, name, config)
|
191
119
|
platform_definition = config.platform_definition(name)
|
192
|
-
assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") { !platform_definition.nil? }
|
120
|
+
@log.assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") { !platform_definition.nil? }
|
193
121
|
platform_definition
|
194
122
|
end
|
195
123
|
|
124
|
+
# Perform a config override while explaining it to the user
|
196
125
|
def inform_override(from_where, &block)
|
197
|
-
inform("Using configuration override from #{from_where}") do
|
126
|
+
@log.inform("Using configuration override from #{from_where}") do
|
198
127
|
file = block.call
|
199
128
|
file.nil? ? "<none>" : file
|
200
129
|
end
|
@@ -219,29 +148,50 @@ def display_files(pathname)
|
|
219
148
|
non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
|
220
149
|
|
221
150
|
# print files with an indent
|
222
|
-
|
223
|
-
|
151
|
+
@log.iputs "Files (excluding hidden files): #{non_hidden.size}"
|
152
|
+
@log.indent { non_hidden.each(&@log.method(:iputs)) }
|
224
153
|
end
|
225
154
|
|
155
|
+
# helper recursive function for library installation
|
156
|
+
#
|
157
|
+
# This recursively descends the dependency tree starting from an initial list,
|
158
|
+
# and either uses existing installations (based on directory naming only) or
|
159
|
+
# forcibly installs the dependency. Each child dependency logs which parent requested it
|
160
|
+
#
|
161
|
+
# @param library_names [Array<String>] the list of libraries to install
|
162
|
+
# @param on_behalf_of [String] the requestor of a given dependency
|
163
|
+
# @param already_installed [Array<String>] the set of dependencies installed by previous steps
|
226
164
|
# @return [Array<String>] The list of installed libraries
|
227
|
-
def
|
165
|
+
def install_arduino_library_dependencies_h(library_names, on_behalf_of, already_installed)
|
228
166
|
installed = already_installed.clone
|
229
167
|
(library_names.map { |n| @backend.library_of_name(n) } - installed).each do |l|
|
230
168
|
if l.installed?
|
231
|
-
inform("Using pre-existing dependency of #{on_behalf_of}") { l.name }
|
169
|
+
@log.inform("Using pre-existing dependency of #{on_behalf_of}") { l.name }
|
232
170
|
else
|
233
|
-
assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
|
171
|
+
@log.assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
|
234
172
|
next nil unless l.install
|
235
173
|
|
236
174
|
l.name
|
237
175
|
end
|
238
176
|
end
|
239
177
|
installed << l.name
|
240
|
-
installed +=
|
178
|
+
installed += install_arduino_library_dependencies_h(l.arduino_library_dependencies, l.name, installed)
|
241
179
|
end
|
242
180
|
installed
|
243
181
|
end
|
244
182
|
|
183
|
+
# @return [Array<String>] The list of installed libraries
|
184
|
+
def install_arduino_library_dependencies(library_names, on_behalf_of)
|
185
|
+
if library_names.empty?
|
186
|
+
@log.inform("Arduino library dependencies (configured in #{on_behalf_of}) to resolve") { library_names.length }
|
187
|
+
return []
|
188
|
+
end
|
189
|
+
|
190
|
+
@log.inform_multiline("Resolving #{library_names.length} Arduino library dependencies configured in #{on_behalf_of})") do
|
191
|
+
install_arduino_library_dependencies_h(library_names, on_behalf_of, [])
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
245
195
|
# @param platforms [Array<String>] list of platforms to consider
|
246
196
|
# @param specific_config [CIConfig] configuration to use
|
247
197
|
def install_all_packages(platforms, specific_config)
|
@@ -252,28 +202,29 @@ def install_all_packages(platforms, specific_config)
|
|
252
202
|
all_packages.each do |pkg|
|
253
203
|
next if @backend.boards_installed?(pkg)
|
254
204
|
|
255
|
-
url = assure("Board package #{pkg} has a defined URL") { specific_config.package_url(pkg) }
|
205
|
+
url = @log.assure("Board package #{pkg} has a defined URL") { specific_config.package_url(pkg) }
|
256
206
|
@backend.board_manager_urls = [url]
|
257
|
-
assure("Installing board package #{pkg}") { @backend.install_boards(pkg) }
|
207
|
+
@log.assure("Installing board package #{pkg}") { @backend.install_boards(pkg) }
|
258
208
|
end
|
259
209
|
end
|
260
210
|
|
261
211
|
# @param expectation_envvar [String] the name of the env var to check
|
262
212
|
# @param operation [String] a description of what operation we might be skipping
|
213
|
+
# @param howto_skip [String] a description of how the runner can skip this
|
263
214
|
# @param filegroup_name [String] a description of the set of files without which we effectively skip the operation
|
264
215
|
# @param dir_description [String] a description of the directory where we looked for the files
|
265
216
|
# @param dir [Pathname] the directory where we looked for the files
|
266
|
-
def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir_path)
|
217
|
+
def handle_expectation_of_files(expectation_envvar, operation, howto_skip, filegroup_name, dir_description, dir_path)
|
267
218
|
# alert future me about running the script from the wrong directory, instead of doing the huge file dump
|
268
219
|
# otherwise, assume that the user might be running the script on a library with no actual unit tests
|
269
220
|
if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd)
|
270
|
-
inform_multiline("arduino_ci seems to be trying to test itself") do
|
221
|
+
@log.inform_multiline("arduino_ci seems to be trying to test itself") do
|
271
222
|
[
|
272
223
|
"arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against",
|
273
224
|
"the core library isn't really a valid thing to do... but it's easy for a developer (including the",
|
274
225
|
"owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of",
|
275
226
|
"the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
|
276
|
-
].each
|
227
|
+
].each(&@log.method(:iputs))
|
277
228
|
false
|
278
229
|
end
|
279
230
|
exit(1)
|
@@ -286,25 +237,27 @@ def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, d
|
|
286
237
|
["No #{dir_description} at", "base directory", dir_path.parent]
|
287
238
|
end
|
288
239
|
|
289
|
-
inform(problem) { dir_path }
|
290
|
-
explain_and_exercise_envvar(expectation_envvar, operation, "contents of #{dir_desc}") { display_files(dir) }
|
240
|
+
@log.inform(problem) { dir_path }
|
241
|
+
explain_and_exercise_envvar(expectation_envvar, operation, howto_skip, "contents of #{dir_desc}") { display_files(dir) }
|
291
242
|
end
|
292
243
|
|
293
244
|
# @param expectation_envvar [String] the name of the env var to check
|
294
245
|
# @param operation [String] a description of what operation we might be skipping
|
246
|
+
# @param howto_skip [String] a description of how the runner can skip this
|
295
247
|
# @param block_desc [String] a description of what information will be dumped to assist the user
|
296
248
|
# @param block [Proc] a function that dumps information
|
297
|
-
def explain_and_exercise_envvar(expectation_envvar, operation, block_desc, &block)
|
298
|
-
inform("Environment variable #{expectation_envvar} is") { "(#{ENV[expectation_envvar].class}) #{ENV[expectation_envvar]}" }
|
249
|
+
def explain_and_exercise_envvar(expectation_envvar, operation, howto_skip, block_desc, &block)
|
250
|
+
@log.inform("Environment variable #{expectation_envvar} is") { "(#{ENV[expectation_envvar].class}) #{ENV[expectation_envvar]}" }
|
299
251
|
if ENV[expectation_envvar].nil?
|
300
|
-
inform_multiline("Skipping #{operation}") do
|
301
|
-
|
252
|
+
@log.inform_multiline("Skipping #{operation}") do
|
253
|
+
@log.iputs "In case that's an error, displaying #{block_desc}:"
|
302
254
|
block.call
|
303
|
-
|
255
|
+
@log.iputs "To force an error in this case, set the environment variable #{expectation_envvar}"
|
256
|
+
@log.iputs "To explicitly skip this check, use #{howto_skip}"
|
304
257
|
true
|
305
258
|
end
|
306
259
|
else
|
307
|
-
assure_multiline("Displaying #{block_desc} before exit") do
|
260
|
+
@log.assure_multiline("Displaying #{block_desc} before exit") do
|
308
261
|
block.call
|
309
262
|
false
|
310
263
|
end
|
@@ -315,16 +268,16 @@ end
|
|
315
268
|
def get_annotated_compilers(config, cpp_library)
|
316
269
|
# check GCC
|
317
270
|
compilers = config.compilers_to_use
|
318
|
-
assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
|
271
|
+
@log.assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
|
319
272
|
compilers.each do |gcc_binary|
|
320
|
-
attempt_multiline("Checking #{gcc_binary} version") do
|
273
|
+
@log.attempt_multiline("Checking #{gcc_binary} version") do
|
321
274
|
version = cpp_library.gcc_version(gcc_binary)
|
322
275
|
next nil unless version
|
323
276
|
|
324
|
-
|
277
|
+
@log.iputs(version)
|
325
278
|
version
|
326
279
|
end
|
327
|
-
inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
|
280
|
+
@log.inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
|
328
281
|
end
|
329
282
|
compilers
|
330
283
|
end
|
@@ -336,22 +289,81 @@ end
|
|
336
289
|
# In this case, the user provided script would fetch a git repo or some other method
|
337
290
|
def perform_custom_initialization(_config)
|
338
291
|
script_path = ENV[VAR_CUSTOM_INIT_SCRIPT]
|
339
|
-
inform("Environment variable #{VAR_CUSTOM_INIT_SCRIPT}") { "'#{script_path}'" }
|
292
|
+
@log.inform("Environment variable #{VAR_CUSTOM_INIT_SCRIPT}") { "'#{script_path}'" }
|
340
293
|
return if script_path.nil?
|
341
294
|
return if script_path.empty?
|
342
295
|
|
343
296
|
script_pathname = Pathname.getwd + script_path
|
344
|
-
assure("Script at #{VAR_CUSTOM_INIT_SCRIPT} exists") { script_pathname.exist? }
|
297
|
+
@log.assure("Script at #{VAR_CUSTOM_INIT_SCRIPT} exists") { script_pathname.exist? }
|
345
298
|
|
346
|
-
assure_multiline("Running #{script_pathname} with sh in libraries working dir") do
|
299
|
+
@log.assure_multiline("Running #{script_pathname} with sh in libraries working dir") do
|
347
300
|
Dir.chdir(@backend.lib_dir) do
|
348
301
|
IO.popen(["/bin/sh", script_pathname.to_s], err: [:child, :out]) do |io|
|
349
|
-
|
302
|
+
@log.indent { io.each_line(&@log.method(:iputs)) }
|
350
303
|
end
|
351
304
|
end
|
352
305
|
end
|
353
306
|
end
|
354
307
|
|
308
|
+
# Kick off the arduino_ci test process by explaining and adjusting the environment
|
309
|
+
#
|
310
|
+
# @return Hash of things needed for later steps
|
311
|
+
def perform_bootstrap
|
312
|
+
@log.inform("Host OS") { ArduinoCI::Host.os }
|
313
|
+
@log.inform("Working directory") { Dir.pwd }
|
314
|
+
|
315
|
+
# initialize command and config
|
316
|
+
default_config = ArduinoCI::CIConfig.default
|
317
|
+
inform_override("project") { default_config.override_file_from_project_library }
|
318
|
+
config = default_config.from_project_library
|
319
|
+
|
320
|
+
backend = ArduinoCI::ArduinoInstallation.autolocate!
|
321
|
+
@log.inform("Located arduino-cli binary") { backend.binary_path.to_s }
|
322
|
+
@log.inform("Using arduino-cli version") { backend.version.to_s }
|
323
|
+
if backend.lib_dir.exist?
|
324
|
+
@log.inform("Found libraries directory") { backend.lib_dir }
|
325
|
+
else
|
326
|
+
@log.assure("Creating libraries directory") { backend.lib_dir.mkpath || true }
|
327
|
+
end
|
328
|
+
|
329
|
+
# run any library init scripts from the library itself.
|
330
|
+
perform_custom_initialization(config)
|
331
|
+
|
332
|
+
# initialize library under test
|
333
|
+
@log.inform("Environment variable #{VAR_USE_SUBDIR}") { "'#{ENV[VAR_USE_SUBDIR]}'" }
|
334
|
+
cpp_library_path = Pathname.new(ENV[VAR_USE_SUBDIR].nil? ? "." : ENV[VAR_USE_SUBDIR])
|
335
|
+
cpp_library = @log.assure("Installing library under test") do
|
336
|
+
backend.install_local_library(cpp_library_path)
|
337
|
+
end
|
338
|
+
|
339
|
+
# Warn if the library name isn't obvious
|
340
|
+
assumed_name = backend.name_of_library(cpp_library_path)
|
341
|
+
ondisk_name = cpp_library_path.realpath.basename.to_s
|
342
|
+
@log.warn("Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'") if assumed_name != ondisk_name
|
343
|
+
|
344
|
+
if !cpp_library.nil?
|
345
|
+
@log.inform("Library installed at") { cpp_library.path.to_s }
|
346
|
+
else
|
347
|
+
# this is a longwinded way of failing, we aren't really "assuring" anything at this point
|
348
|
+
@log.assure_multiline("Library installed successfully") do
|
349
|
+
@log.iputs backend.last_msg
|
350
|
+
false
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
install_arduino_library_dependencies(
|
355
|
+
cpp_library.arduino_library_dependencies,
|
356
|
+
"<#{ArduinoCI::CppLibrary::LIBRARY_PROPERTIES_FILE}>"
|
357
|
+
)
|
358
|
+
|
359
|
+
# return all objects needed by other steps
|
360
|
+
{
|
361
|
+
backend: backend,
|
362
|
+
cpp_library: cpp_library,
|
363
|
+
config: config,
|
364
|
+
}
|
365
|
+
end
|
366
|
+
|
355
367
|
# Auto-select some platforms to test based on the information available
|
356
368
|
#
|
357
369
|
# Top choice is always library.properties -- otherwise use the default.
|
@@ -368,14 +380,14 @@ def choose_platform_set(config, reason, desired_platforms, library_properties)
|
|
368
380
|
if library_properties.nil? || library_properties.architectures.nil? || library_properties.architectures.empty?
|
369
381
|
# verify that all platforms exist
|
370
382
|
desired_platforms.each { |p| assured_platform(reason, p, config) }
|
371
|
-
return inform_multiline("No architectures listed in library.properties, using configured platforms") do
|
372
|
-
desired_platforms.each
|
383
|
+
return @log.inform_multiline("No architectures listed in library.properties, using configured platforms") do
|
384
|
+
desired_platforms.each(&@log.method(:iputs)) # this returns desired_platforms
|
373
385
|
end
|
374
386
|
end
|
375
387
|
|
376
388
|
if library_properties.architectures.include?("*")
|
377
|
-
return inform_multiline("Wildcard architecture in library.properties, using configured platforms") do
|
378
|
-
desired_platforms.each
|
389
|
+
return @log.inform_multiline("Wildcard architecture in library.properties, using configured platforms") do
|
390
|
+
desired_platforms.each(&@log.method(:iputs)) # this returns desired_platforms
|
379
391
|
end
|
380
392
|
end
|
381
393
|
|
@@ -385,76 +397,81 @@ def choose_platform_set(config, reason, desired_platforms, library_properties)
|
|
385
397
|
if config.is_default
|
386
398
|
# completely ignore default config, opting for brute-force library matches
|
387
399
|
# OTOH, we don't need to assure platforms because we defined them
|
388
|
-
return inform_multiline("Default config, platforms matching architectures in library.properties") do
|
400
|
+
return @log.inform_multiline("Default config, platforms matching architectures in library.properties") do
|
389
401
|
supported_platforms.keys.each do |p| # rubocop:disable Style/HashEachMethods
|
390
|
-
|
402
|
+
@log.iputs(p)
|
391
403
|
end # this returns supported_platforms
|
392
404
|
end
|
393
405
|
end
|
394
406
|
|
395
407
|
desired_supported_platforms = supported_platforms.select { |p, _| desired_platforms.include?(p) }.keys
|
396
408
|
desired_supported_platforms.each { |p| assured_platform(reason, p, config) }
|
397
|
-
inform_multiline("Configured platforms that match architectures in library.properties") do
|
409
|
+
@log.inform_multiline("Configured platforms that match architectures in library.properties") do
|
398
410
|
desired_supported_platforms.each do |p|
|
399
|
-
|
411
|
+
@log.iputs(p)
|
400
412
|
end # this returns supported_platforms
|
401
413
|
end
|
402
414
|
end
|
403
415
|
|
404
416
|
# Unit test procedure
|
405
417
|
def perform_unit_tests(cpp_library, file_config)
|
406
|
-
phase("Unit testing")
|
418
|
+
@log.phase("Unit testing")
|
407
419
|
if @cli_options[:skip_unittests]
|
408
|
-
inform("Skipping unit tests") { "as requested via command line" }
|
420
|
+
@log.inform("Skipping unit tests") { "as requested via command line" }
|
409
421
|
return
|
410
422
|
end
|
411
423
|
|
412
424
|
config = file_config.with_override_config(@cli_options[:ci_config])
|
413
425
|
compilers = get_annotated_compilers(config, cpp_library)
|
414
426
|
|
415
|
-
inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
|
427
|
+
@log.inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
|
416
428
|
|
417
429
|
# Handle lack of test files
|
418
430
|
if cpp_library.test_files.empty?
|
419
|
-
handle_expectation_of_files(
|
431
|
+
handle_expectation_of_files(
|
432
|
+
VAR_EXPECT_UNITTESTS,
|
433
|
+
"unit tests",
|
434
|
+
CLI_SKIP_UNITTESTS,
|
435
|
+
"test files",
|
436
|
+
"tests directory",
|
437
|
+
cpp_library.tests_dir
|
438
|
+
)
|
420
439
|
return
|
421
440
|
end
|
422
441
|
|
423
442
|
# Get platforms, handle lack of them
|
424
443
|
platforms = choose_platform_set(config, "unittest", config.platforms_to_unittest, cpp_library.library_properties)
|
425
444
|
if platforms.empty?
|
426
|
-
explain_and_exercise_envvar(VAR_EXPECT_UNITTESTS, "unit tests", "platforms and architectures") do
|
427
|
-
|
428
|
-
|
445
|
+
explain_and_exercise_envvar(VAR_EXPECT_UNITTESTS, "unit tests", CLI_SKIP_UNITTESTS, "platforms and architectures") do
|
446
|
+
@log.iputs "Configured platforms: #{config.platforms_to_unittest}"
|
447
|
+
@log.iputs "Configuration is default: #{config.is_default}"
|
429
448
|
arches = cpp_library.library_properties.nil? ? nil : cpp_library.library_properties.architectures
|
430
|
-
|
449
|
+
@log.iputs "Architectures in library.properties: #{arches}"
|
431
450
|
end
|
432
451
|
end
|
433
452
|
|
434
453
|
# having undefined platforms is a config error
|
435
454
|
platforms.select { |p| config.platform_info[p].nil? }.each do |p|
|
436
|
-
assure("Platform '#{p}' is defined in configuration files") { false }
|
455
|
+
@log.assure("Platform '#{p}' is defined in configuration files") { false }
|
437
456
|
end
|
438
457
|
|
439
458
|
install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
|
440
459
|
|
441
460
|
platforms.each do |p|
|
442
|
-
|
461
|
+
@log.iputs
|
443
462
|
compilers.each do |gcc_binary|
|
444
463
|
# before compiling the tests, build a shared library of everything except the test code
|
445
|
-
next @failure_count += 1 unless build_shared_library(gcc_binary, p, config, cpp_library)
|
464
|
+
next @log.failure_count += 1 unless build_shared_library(gcc_binary, p, config, cpp_library)
|
446
465
|
|
447
466
|
# now build and run each test using the shared library build above
|
448
467
|
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
|
449
468
|
unittest_name = unittest_path.basename.to_s
|
450
|
-
|
451
|
-
attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
|
469
|
+
@log.rule "-"
|
470
|
+
@log.attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
|
452
471
|
exe = cpp_library.build_for_test(unittest_path, gcc_binary)
|
453
|
-
|
472
|
+
@log.iputs
|
454
473
|
unless exe
|
455
|
-
|
456
|
-
puts cpp_library.last_out
|
457
|
-
puts cpp_library.last_err
|
474
|
+
describe_last_command(cpp_library)
|
458
475
|
next false
|
459
476
|
end
|
460
477
|
cpp_library.run_test_file(exe)
|
@@ -465,33 +482,36 @@ def perform_unit_tests(cpp_library, file_config)
|
|
465
482
|
end
|
466
483
|
|
467
484
|
def build_shared_library(gcc_binary, platform, config, cpp_library)
|
468
|
-
attempt_multiline("Build shared library with #{gcc_binary} for #{platform}") do
|
485
|
+
@log.attempt_multiline("Build shared library with #{gcc_binary} for #{platform}") do
|
469
486
|
exe = cpp_library.build_shared_library(
|
470
487
|
config.aux_libraries_for_unittest,
|
471
488
|
gcc_binary,
|
472
489
|
config.gcc_config(platform)
|
473
490
|
)
|
474
|
-
|
475
|
-
unless exe
|
476
|
-
puts "Last command: #{cpp_library.last_cmd}"
|
477
|
-
puts cpp_library.last_out
|
478
|
-
puts cpp_library.last_err
|
479
|
-
end
|
491
|
+
@log.iputs
|
492
|
+
describe_last_command(cpp_library) unless exe
|
480
493
|
exe
|
481
494
|
end
|
482
495
|
end
|
483
496
|
|
484
497
|
def perform_example_compilation_tests(cpp_library, config)
|
485
|
-
phase("Compilation of example sketches")
|
498
|
+
@log.phase("Compilation of example sketches")
|
486
499
|
if @cli_options[:skip_compilation]
|
487
|
-
inform("Skipping compilation of examples") { "as requested via command line" }
|
500
|
+
@log.inform("Skipping compilation of examples") { "as requested via command line" }
|
488
501
|
return
|
489
502
|
end
|
490
503
|
|
491
504
|
library_examples = cpp_library.example_sketches
|
492
505
|
|
493
506
|
if library_examples.empty?
|
494
|
-
handle_expectation_of_files(
|
507
|
+
handle_expectation_of_files(
|
508
|
+
VAR_EXPECT_EXAMPLES,
|
509
|
+
"builds",
|
510
|
+
CLI_SKIP_EXAMPLES_COMPILATION,
|
511
|
+
"examples",
|
512
|
+
"the examples directory",
|
513
|
+
cpp_library.examples_dir
|
514
|
+
)
|
495
515
|
return
|
496
516
|
end
|
497
517
|
|
@@ -500,8 +520,8 @@ def perform_example_compilation_tests(cpp_library, config)
|
|
500
520
|
|
501
521
|
library_examples.each do |example_path|
|
502
522
|
example_name = File.basename(example_path)
|
503
|
-
|
504
|
-
inform("Discovered example sketch") { example_name }
|
523
|
+
@log.iputs
|
524
|
+
@log.inform("Discovered example sketch") { example_name }
|
505
525
|
|
506
526
|
inform_override("example") { ex_config.override_file_from_example(example_path) }
|
507
527
|
ovr_config = ex_config.from_example(example_path)
|
@@ -510,17 +530,22 @@ def perform_example_compilation_tests(cpp_library, config)
|
|
510
530
|
|
511
531
|
# having no platforms defined is probably an error
|
512
532
|
if platforms.empty?
|
513
|
-
explain_and_exercise_envvar(
|
514
|
-
|
515
|
-
|
533
|
+
explain_and_exercise_envvar(
|
534
|
+
VAR_EXPECT_EXAMPLES,
|
535
|
+
"examples compilation",
|
536
|
+
CLI_SKIP_EXAMPLES_COMPILATION,
|
537
|
+
"platforms and architectures"
|
538
|
+
) do
|
539
|
+
@log.iputs "Configured platforms: #{ovr_config.platforms_to_build}"
|
540
|
+
@log.iputs "Configuration is default: #{ovr_config.is_default}"
|
516
541
|
arches = cpp_library.library_properties.nil? ? nil : cpp_library.library_properties.architectures
|
517
|
-
|
542
|
+
@log.iputs "Architectures in library.properties: #{arches}"
|
518
543
|
end
|
519
544
|
end
|
520
545
|
|
521
546
|
# having undefined platforms is a config error
|
522
547
|
platforms.select { |p| ovr_config.platform_info[p].nil? }.each do |p|
|
523
|
-
assure("Platform '#{p}' is defined in configuration files") { false }
|
548
|
+
@log.assure("Platform '#{p}' is defined in configuration files") { false }
|
524
549
|
end
|
525
550
|
|
526
551
|
install_all_packages(platforms, ovr_config)
|
@@ -528,75 +553,58 @@ def perform_example_compilation_tests(cpp_library, config)
|
|
528
553
|
|
529
554
|
platforms.each do |p|
|
530
555
|
board = ovr_config.platform_info[p][:board] # assured to exist, above
|
531
|
-
attempt("Compiling #{example_name} for #{board}") do
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
556
|
+
compiled_ok = @log.attempt("Compiling #{example_name} for #{board}") do
|
557
|
+
@backend.compile_sketch(example_path, board)
|
558
|
+
end
|
559
|
+
|
560
|
+
# decode the JSON output of the compiler a little bit
|
561
|
+
unless compiled_ok
|
562
|
+
@log.inform_multiline("Compilation failure details") do
|
563
|
+
begin
|
564
|
+
# parse the JSON, and print out only the nonempty keys. indent them with 4 spaces in their own labelled sections
|
565
|
+
msg_json = JSON.parse(@backend.last_msg)
|
566
|
+
msg_json.each do |k, v|
|
567
|
+
val = if v.is_a?(Hash) || v.is_a?(Array)
|
568
|
+
JSON.pretty_generate(v)
|
569
|
+
else
|
570
|
+
v.to_s
|
571
|
+
end
|
572
|
+
@log.inform_multiline(k) { @log.iputs(val) } unless val.strip.empty?
|
573
|
+
end
|
574
|
+
rescue JSON::ParserError
|
575
|
+
# worst case: dump it
|
576
|
+
@log.iputs "Last command: #{@backend.last_msg}"
|
577
|
+
end
|
578
|
+
@log.iputs @backend.last_err
|
536
579
|
end
|
537
|
-
ret
|
538
580
|
end
|
539
581
|
|
582
|
+
# reporting or enforcing of free space
|
583
|
+
usage = @backend.last_bytes_usage
|
584
|
+
@log.inform("Free space (bytes) after compilation") { usage[:free] }
|
540
585
|
next if @cli_options[:min_free_space].nil?
|
541
586
|
|
542
|
-
usage = @backend.last_bytes_usage
|
543
587
|
min_free_space = @cli_options[:min_free_space]
|
544
|
-
attempt("
|
588
|
+
@log.attempt("Free space exceeds desired minimum #{min_free_space}") do
|
545
589
|
min_free_space <= usage[:free]
|
546
590
|
end
|
547
591
|
end
|
548
592
|
end
|
549
593
|
end
|
550
594
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
# initialize command and config
|
556
|
-
default_config = ArduinoCI::CIConfig.default
|
557
|
-
inform_override("project") { default_config.override_file_from_project_library }
|
558
|
-
config = default_config.from_project_library
|
559
|
-
|
560
|
-
@backend = ArduinoCI::ArduinoInstallation.autolocate!
|
561
|
-
inform("Located arduino-cli binary") { @backend.binary_path.to_s }
|
562
|
-
inform("Using arduino-cli version") { @backend.version.to_s }
|
563
|
-
if @backend.lib_dir.exist?
|
564
|
-
inform("Found libraries directory") { @backend.lib_dir }
|
565
|
-
else
|
566
|
-
assure("Creating libraries directory") { @backend.lib_dir.mkpath || true }
|
567
|
-
end
|
568
|
-
|
569
|
-
# run any library init scripts from the library itself.
|
570
|
-
perform_custom_initialization(config)
|
571
|
-
|
572
|
-
# initialize library under test
|
573
|
-
inform("Environment variable #{VAR_USE_SUBDIR}") { "'#{ENV[VAR_USE_SUBDIR]}'" }
|
574
|
-
cpp_library_path = Pathname.new(ENV[VAR_USE_SUBDIR].nil? ? "." : ENV[VAR_USE_SUBDIR])
|
575
|
-
cpp_library = assure("Installing library under test") do
|
576
|
-
@backend.install_local_library(cpp_library_path)
|
577
|
-
end
|
595
|
+
###############################################################
|
596
|
+
# script execution
|
597
|
+
#
|
578
598
|
|
579
|
-
#
|
580
|
-
|
581
|
-
ondisk_name = cpp_library_path.realpath.basename.to_s
|
582
|
-
warn("Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'") if assumed_name != ondisk_name
|
583
|
-
|
584
|
-
if !cpp_library.nil?
|
585
|
-
inform("Library installed at") { cpp_library.path.to_s }
|
586
|
-
else
|
587
|
-
# this is a longwinded way of failing, we aren't really "assuring" anything at this point
|
588
|
-
assure_multiline("Library installed successfully") do
|
589
|
-
puts @backend.last_msg
|
590
|
-
false
|
591
|
-
end
|
592
|
-
end
|
599
|
+
# Read in command line options and make them read-only
|
600
|
+
@cli_options = Parser.parse(ARGV).freeze
|
593
601
|
|
594
|
-
|
595
|
-
|
596
|
-
"<#{ArduinoCI::CppLibrary::LIBRARY_PROPERTIES_FILE}>"
|
597
|
-
)
|
602
|
+
@log = ArduinoCI::Logger.auto_width
|
603
|
+
@log.banner
|
598
604
|
|
599
|
-
|
600
|
-
|
605
|
+
strap = perform_bootstrap
|
606
|
+
@backend = strap[:backend]
|
607
|
+
perform_unit_tests(strap[:cpp_library], strap[:config])
|
608
|
+
perform_example_compilation_tests(strap[:cpp_library], strap[:config])
|
601
609
|
|
602
610
|
terminate(true)
|