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