arduino_ci 1.5.0 → 1.6.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/README.md +1 -1
- data/cpp/arduino/ArduinoDefines.h +15 -1
- 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
- data/misc/default.yml +2 -1
- 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)
|