arduino_ci 0.3.0 → 1.3.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.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +125 -69
  3. data/REFERENCE.md +711 -0
  4. data/cpp/arduino/Arduino.h +1 -7
  5. data/cpp/arduino/ArduinoDefines.h +3 -0
  6. data/cpp/arduino/AvrMath.h +117 -17
  7. data/cpp/arduino/Client.h +27 -0
  8. data/cpp/arduino/EEPROM.h +64 -0
  9. data/cpp/arduino/Godmode.cpp +7 -0
  10. data/cpp/arduino/Godmode.h +121 -15
  11. data/cpp/arduino/HardwareSerial.h +4 -4
  12. data/cpp/arduino/IPAddress.h +59 -0
  13. data/cpp/arduino/Print.h +9 -12
  14. data/cpp/arduino/Printable.h +8 -0
  15. data/cpp/arduino/SPI.h +11 -3
  16. data/cpp/arduino/Server.h +5 -0
  17. data/cpp/arduino/Udp.h +27 -0
  18. data/cpp/arduino/Wire.h +197 -77
  19. data/cpp/arduino/avr/io.h +10 -1
  20. data/cpp/arduino/avr/pgmspace.h +76 -46
  21. data/cpp/unittest/ArduinoUnitTests.h +32 -0
  22. data/cpp/unittest/Assertion.h +54 -26
  23. data/cpp/unittest/Compare.h +58 -51
  24. data/cpp/unittest/OstreamHelpers.h +4 -0
  25. data/exe/arduino_ci.rb +538 -0
  26. data/exe/arduino_ci_remote.rb +2 -393
  27. data/exe/arduino_library_location.rb +2 -2
  28. data/exe/ensure_arduino_installation.rb +7 -1
  29. data/lib/arduino_ci.rb +1 -0
  30. data/lib/arduino_ci/arduino_backend.rb +238 -0
  31. data/lib/arduino_ci/arduino_downloader.rb +43 -73
  32. data/lib/arduino_ci/arduino_downloader_linux.rb +17 -55
  33. data/lib/arduino_ci/arduino_downloader_osx.rb +21 -33
  34. data/lib/arduino_ci/arduino_downloader_windows.rb +11 -53
  35. data/lib/arduino_ci/arduino_installation.rb +18 -80
  36. data/lib/arduino_ci/ci_config.rb +8 -11
  37. data/lib/arduino_ci/cpp_library.rb +250 -59
  38. data/lib/arduino_ci/host.rb +59 -4
  39. data/lib/arduino_ci/library_properties.rb +101 -0
  40. data/lib/arduino_ci/version.rb +1 -1
  41. data/misc/default.yml +57 -6
  42. metadata +19 -87
  43. data/cpp/arduino/Arduino.h.orig +0 -143
  44. data/exe/libasan.rb +0 -29
  45. data/lib/arduino_ci/arduino_cmd.rb +0 -332
  46. data/lib/arduino_ci/arduino_cmd_linux.rb +0 -17
  47. data/lib/arduino_ci/arduino_cmd_linux_builder.rb +0 -19
  48. data/lib/arduino_ci/arduino_cmd_osx.rb +0 -17
  49. data/lib/arduino_ci/arduino_cmd_windows.rb +0 -17
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env ruby
2
+ require 'arduino_ci'
3
+ require 'set'
4
+ require 'pathname'
5
+ require 'optparse'
6
+
7
+ WIDTH = 80
8
+ VAR_CUSTOM_INIT_SCRIPT = "CUSTOM_INIT_SCRIPT".freeze
9
+ VAR_USE_SUBDIR = "USE_SUBDIR".freeze
10
+ VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze
11
+ VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze
12
+
13
+ @failure_count = 0
14
+ @passfail = proc { |result| result ? "✓" : "✗" }
15
+ @backend = nil
16
+
17
+ # Use some basic parsing to allow command-line overrides of config
18
+ class Parser
19
+ def self.parse(options)
20
+ unit_config = {}
21
+ output_options = {
22
+ skip_unittests: false,
23
+ skip_compilation: false,
24
+ ci_config: {
25
+ "unittest" => unit_config
26
+ },
27
+ }
28
+
29
+ opt_parser = OptionParser.new do |opts|
30
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
31
+
32
+ opts.on("--skip-unittests", "Don't run unit tests") do |p|
33
+ output_options[:skip_unittests] = p
34
+ end
35
+
36
+ opts.on("--skip-examples-compilation", "Don't compile example sketches") do |p|
37
+ output_options[:skip_compilation] = p
38
+ end
39
+
40
+ opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p|
41
+ unit_config["testfiles"] ||= {}
42
+ unit_config["testfiles"]["select"] ||= []
43
+ unit_config["testfiles"]["select"] << p
44
+ end
45
+
46
+ opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p|
47
+ unit_config["testfiles"] ||= {}
48
+ unit_config["testfiles"]["reject"] ||= []
49
+ unit_config["testfiles"]["reject"] << p
50
+ end
51
+
52
+ opts.on("-h", "--help", "Prints this help") do
53
+ puts opts
54
+ puts
55
+ puts "Additionally, the following environment variables control the script:"
56
+ puts " - #{VAR_CUSTOM_INIT_SCRIPT} - if set, this script will be run from the Arduino/libraries directory"
57
+ puts " prior to any automated library installation or testing (e.g. to install unoffical libraries)"
58
+ puts " - #{VAR_USE_SUBDIR} - if set, the script will install the library from this subdirectory of the cwd"
59
+ puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present"
60
+ puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present"
61
+ puts " - #{VAR_SKIP_LIBPROPS} - if set, testing will skip [experimental] library.properties validation"
62
+ exit
63
+ end
64
+ end
65
+
66
+ opt_parser.parse!(options)
67
+ output_options
68
+ end
69
+ end
70
+
71
+ # Read in command line options and make them read-only
72
+ @cli_options = (Parser.parse ARGV).freeze
73
+
74
+ # terminate after printing any debug info. TODO: capture debug info
75
+ def terminate(final = nil)
76
+ puts "Failures: #{@failure_count}"
77
+ unless @failure_count.zero? || final || @backend.nil?
78
+ puts "========== Last backend command (if relevant):"
79
+ puts @backend.last_msg.to_s
80
+ puts "========== Backend Stdout:"
81
+ puts @backend.last_out
82
+ puts "========== Backend Stderr:"
83
+ puts @backend.last_err
84
+ end
85
+ retcode = @failure_count.zero? ? 0 : 1
86
+ exit(retcode)
87
+ end
88
+
89
+ # make a nice status line for an action and react to the action
90
+ # TODO / note to self: inform_multline is tougher to write
91
+ # without altering the signature because it only leaves space
92
+ # for the checkmark _after_ the multiline, it doesn't know how
93
+ # to make that conditionally the body
94
+ # @param message String the text of the progress indicator
95
+ # @param multiline boolean whether multiline output is expected
96
+ # @param mark_fn block (string) -> string that says how to describe the result
97
+ # @param on_fail_msg String custom message for failure
98
+ # @param tally_on_fail boolean whether to increment @failure_count
99
+ # @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
100
+ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
101
+ line = "#{message}... "
102
+ endline = "...#{message} "
103
+ if multiline
104
+ puts line
105
+ else
106
+ print line
107
+ end
108
+ $stdout.flush
109
+ result = yield
110
+ mark = mark_fn.nil? ? "" : mark_fn.call(result)
111
+ # if multline, put checkmark at full width
112
+ print endline if multiline
113
+ puts mark.to_s.rjust(WIDTH - line.length, " ")
114
+ unless result
115
+ puts on_fail_msg unless on_fail_msg.nil?
116
+ @failure_count += 1 if tally_on_fail
117
+ # print out error messaging here if we've captured it
118
+ terminate if abort_on_fail
119
+ end
120
+ result
121
+ end
122
+
123
+ # Make a nice status for something that defers any failure code until script exit
124
+ def attempt(message, &block)
125
+ perform_action(message, false, @passfail, nil, true, false, &block)
126
+ end
127
+
128
+ # Make a nice status for something that defers any failure code until script exit
129
+ def attempt_multiline(message, &block)
130
+ perform_action(message, true, @passfail, nil, true, false, &block)
131
+ end
132
+
133
+ # Make a nice status for something that kills the script immediately on failure
134
+ FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with your configuration; halting here".freeze
135
+ def assure(message, &block)
136
+ perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
137
+ end
138
+
139
+ def assure_multiline(message, &block)
140
+ perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
141
+ end
142
+
143
+ def inform(message, &block)
144
+ perform_action(message, false, proc { |x| x }, nil, false, false, &block)
145
+ end
146
+
147
+ def inform_multiline(message, &block)
148
+ perform_action(message, true, nil, nil, false, false, &block)
149
+ end
150
+
151
+ def rule(char)
152
+ puts char[0] * WIDTH
153
+ end
154
+
155
+ def warn(message)
156
+ inform("WARNING") { message }
157
+ end
158
+
159
+ def phase(name)
160
+ puts
161
+ rule("=")
162
+ inform("Beginning the next phase of testing") { name }
163
+ end
164
+
165
+ def banner
166
+ art = [
167
+ " . __ ___",
168
+ " _, ,_ _| , . * ._ _ / ` | ",
169
+ "(_| [ `(_] (_| | [ ) (_) \\__. _|_ v#{ArduinoCI::VERSION}",
170
+ ]
171
+
172
+ pad = " " * ((WIDTH - art[2].length) / 2)
173
+ art.each { |l| puts "#{pad}#{l}" }
174
+ puts
175
+ end
176
+
177
+ # Assure that a platform exists and return its definition
178
+ def assured_platform(purpose, name, config)
179
+ platform_definition = config.platform_definition(name)
180
+ assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") { !platform_definition.nil? }
181
+ platform_definition
182
+ end
183
+
184
+ # Return true if the file (or one of the dirs containing it) is hidden
185
+ def file_is_hidden_somewhere?(path)
186
+ # this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator
187
+ path.ascend do |part|
188
+ return true if part.basename.to_s.start_with? "."
189
+ end
190
+ false
191
+ end
192
+
193
+ # print out some files
194
+ def display_files(pathname)
195
+ # `find` doesn't follow symlinks, so we should instead
196
+ realpath = ArduinoCI::Host.symlink?(pathname) ? ArduinoCI::Host.readlink(pathname) : pathname
197
+
198
+ # suppress directories and dotfile-based things
199
+ all_files = realpath.find.select(&:file?)
200
+ non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
201
+
202
+ # print files with an indent
203
+ puts " Files (excluding hidden files): #{non_hidden.size}"
204
+ non_hidden.each { |p| puts " #{p}" }
205
+ end
206
+
207
+ # @return [Array<String>] The list of installed libraries
208
+ def install_arduino_library_dependencies(library_names, on_behalf_of, already_installed = [])
209
+ installed = already_installed.clone
210
+ (library_names.map { |n| @backend.library_of_name(n) } - installed).each do |l|
211
+ if l.installed?
212
+ inform("Using pre-existing dependency of #{on_behalf_of}") { l.name }
213
+ else
214
+ assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
215
+ next nil unless l.install
216
+
217
+ l.name
218
+ end
219
+ end
220
+ installed << l.name
221
+ installed += install_arduino_library_dependencies(l.arduino_library_dependencies, l.name, installed)
222
+ end
223
+ installed
224
+ end
225
+
226
+ # @param platforms [Array<String>] list of platforms to consider
227
+ # @param specific_config [CIConfig] configuration to use
228
+ def install_all_packages(platforms, specific_config)
229
+
230
+ # get packages from platforms
231
+ all_packages = specific_config.platform_info.select { |p, _| platforms.include?(p) }.values.map { |v| v[:package] }.compact.uniq
232
+
233
+ all_packages.each do |pkg|
234
+ next if @backend.boards_installed?(pkg)
235
+
236
+ url = assure("Board package #{pkg} has a defined URL") { specific_config.package_url(pkg) }
237
+ @backend.board_manager_urls = [url]
238
+ assure("Installing board package #{pkg}") { @backend.install_boards(pkg) }
239
+ end
240
+ end
241
+
242
+ # @param expectation_envvar [String] the name of the env var to check
243
+ # @param operation [String] a description of what operation we might be skipping
244
+ # @param filegroup_name [String] a description of the set of files without which we effectively skip the operation
245
+ # @param dir_description [String] a description of the directory where we looked for the files
246
+ # @param dir [Pathname] the directory where we looked for the files
247
+ def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir_path)
248
+ # alert future me about running the script from the wrong directory, instead of doing the huge file dump
249
+ # otherwise, assume that the user might be running the script on a library with no actual unit tests
250
+ if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd)
251
+ inform_multiline("arduino_ci seems to be trying to test itself") do
252
+ [
253
+ "arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against",
254
+ "the core library isn't really a valid thing to do... but it's easy for a developer (including the",
255
+ "owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of",
256
+ "the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
257
+ ].each { |l| puts " #{l}" }
258
+ false
259
+ end
260
+ exit(1)
261
+ end
262
+
263
+ # either the directory is empty, or it doesn't exist at all. message accordingly.
264
+ (problem, dir_desc, dir) = if dir_path.exist?
265
+ ["No #{filegroup_name} were found in", dir_description, dir_path]
266
+ else
267
+ ["No #{dir_description} at", "base directory", dir_path.parent]
268
+ end
269
+
270
+ inform(problem) { dir_path }
271
+ explain_and_exercise_envvar(expectation_envvar, operation, "contents of #{dir_desc}") { display_files(dir) }
272
+ end
273
+
274
+ # @param expectation_envvar [String] the name of the env var to check
275
+ # @param operation [String] a description of what operation we might be skipping
276
+ # @param block_desc [String] a description of what information will be dumped to assist the user
277
+ # @param block [Proc] a function that dumps information
278
+ def explain_and_exercise_envvar(expectation_envvar, operation, block_desc, &block)
279
+ inform("Environment variable #{expectation_envvar} is") { "(#{ENV[expectation_envvar].class}) #{ENV[expectation_envvar]}" }
280
+ if ENV[expectation_envvar].nil?
281
+ inform_multiline("Skipping #{operation}") do
282
+ puts " In case that's an error, displaying #{block_desc}:"
283
+ block.call
284
+ puts " To force an error in this case, set the environment variable #{expectation_envvar}"
285
+ true
286
+ end
287
+ else
288
+ assure_multiline("Displaying #{block_desc} before exit") do
289
+ block.call
290
+ false
291
+ end
292
+ end
293
+ end
294
+
295
+ # report and return the set of compilers
296
+ def get_annotated_compilers(config, cpp_library)
297
+ # check GCC
298
+ compilers = config.compilers_to_use
299
+ assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
300
+ compilers.each do |gcc_binary|
301
+ attempt_multiline("Checking #{gcc_binary} version") do
302
+ version = cpp_library.gcc_version(gcc_binary)
303
+ next nil unless version
304
+
305
+ puts version.split("\n").map { |l| " #{l}" }.join("\n")
306
+ version
307
+ end
308
+ inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
309
+ end
310
+ compilers
311
+ end
312
+
313
+ # Handle existence or nonexistence of custom initialization script -- run it if you have it
314
+ #
315
+ # This feature is to drive GitHub actions / docker image installation where the container is
316
+ # in a clean-slate state but needs some way to have custom library versions injected into it.
317
+ # In this case, the user provided script would fetch a git repo or some other method
318
+ def perform_custom_initialization(_config)
319
+ script_path = ENV[VAR_CUSTOM_INIT_SCRIPT]
320
+ inform("Environment variable #{VAR_CUSTOM_INIT_SCRIPT}") { "'#{script_path}'" }
321
+ return if script_path.nil?
322
+ return if script_path.empty?
323
+
324
+ script_pathname = Pathname.getwd + script_path
325
+ assure("Script at #{VAR_CUSTOM_INIT_SCRIPT} exists") { script_pathname.exist? }
326
+
327
+ assure_multiline("Running #{script_pathname} with sh in libraries working dir") do
328
+ Dir.chdir(@backend.lib_dir) do
329
+ IO.popen(["/bin/sh", script_pathname.to_s], err: [:child, :out]) do |io|
330
+ io.each_line { |line| puts " #{line}" }
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ # Auto-select some platforms to test based on the information available
337
+ #
338
+ # Top choice is always library.properties -- otherwise use the default.
339
+ # But filter that through any non-default config
340
+ #
341
+ # @param config [CIConfig] the overridden config object
342
+ # @param reason [String] description of why we might use this platform (i.e. unittest or compilation)
343
+ # @param desired_platforms [Array<String>] the platform names specified
344
+ # @param library_properties [Hash] the library properties defined by the library
345
+ # @return [Array<String>] platforms to use
346
+ def choose_platform_set(config, reason, desired_platforms, library_properties)
347
+
348
+ # if there are no properties or no architectures, defer entirely to desired platforms
349
+ if library_properties.nil? || library_properties.architectures.nil? || library_properties.architectures.empty?
350
+ # verify that all platforms exist
351
+ desired_platforms.each { |p| assured_platform(reason, p, config) }
352
+ return inform_multiline("No architectures listed in library.properties, using configured platforms") do
353
+ desired_platforms.each { |p| puts " #{p}" } # this returns desired_platforms
354
+ end
355
+ end
356
+
357
+ if library_properties.architectures.include?("*")
358
+ return inform_multiline("Wildcard architecture in library.properties, using configured platforms") do
359
+ desired_platforms.each { |p| puts " #{p}" } # this returns desired_platforms
360
+ end
361
+ end
362
+
363
+ platform_architecture = config.platform_info.transform_values { |v| v[:board].split(":")[1] }
364
+ supported_platforms = platform_architecture.select { |_, a| library_properties.architectures.include?(a) }
365
+
366
+ if config.is_default
367
+ # completely ignore default config, opting for brute-force library matches
368
+ # OTOH, we don't need to assure platforms because we defined them
369
+ return inform_multiline("Default config, platforms matching architectures in library.properties") do
370
+ supported_platforms.keys.each do |p| # rubocop:disable Style/HashEachMethods
371
+ puts " #{p}"
372
+ end # this returns supported_platforms
373
+ end
374
+ end
375
+
376
+ desired_supported_platforms = supported_platforms.select { |p, _| desired_platforms.include?(p) }.keys
377
+ desired_supported_platforms.each { |p| assured_platform(reason, p, config) }
378
+ inform_multiline("Configured platforms that match architectures in library.properties") do
379
+ desired_supported_platforms.each do |p|
380
+ puts " #{p}"
381
+ end # this returns supported_platforms
382
+ end
383
+ end
384
+
385
+ # Unit test procedure
386
+ def perform_unit_tests(cpp_library, file_config)
387
+ phase("Unit testing")
388
+ if @cli_options[:skip_unittests]
389
+ inform("Skipping unit tests") { "as requested via command line" }
390
+ return
391
+ end
392
+
393
+ config = file_config.with_override_config(@cli_options[:ci_config])
394
+ compilers = get_annotated_compilers(config, cpp_library)
395
+
396
+ inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
397
+
398
+ # Handle lack of test files
399
+ if cpp_library.test_files.empty?
400
+ handle_expectation_of_files(VAR_EXPECT_UNITTESTS, "unit tests", "test files", "tests directory", cpp_library.tests_dir)
401
+ return
402
+ end
403
+
404
+ # Get platforms, handle lack of them
405
+ platforms = choose_platform_set(config, "unittest", config.platforms_to_unittest, cpp_library.library_properties)
406
+ if platforms.empty?
407
+ explain_and_exercise_envvar(VAR_EXPECT_UNITTESTS, "unit tests", "platforms and architectures") do
408
+ puts " Configured platforms: #{config.platforms_to_unittest}"
409
+ puts " Configuration is default: #{config.is_default}"
410
+ arches = cpp_library.library_properties.nil? ? nil : cpp_library.library_properties.architectures
411
+ puts " Architectures in library.properties: #{arches}"
412
+ end
413
+ end
414
+
415
+ install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
416
+
417
+ platforms.each do |p|
418
+ puts
419
+ config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
420
+ unittest_name = unittest_path.basename.to_s
421
+ compilers.each do |gcc_binary|
422
+ attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
423
+ exe = cpp_library.build_for_test_with_configuration(
424
+ unittest_path,
425
+ config.aux_libraries_for_unittest,
426
+ gcc_binary,
427
+ config.gcc_config(p)
428
+ )
429
+ puts
430
+ unless exe
431
+ puts "Last command: #{cpp_library.last_cmd}"
432
+ puts cpp_library.last_out
433
+ puts cpp_library.last_err
434
+ next false
435
+ end
436
+ cpp_library.run_test_file(exe)
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end
442
+
443
+ def perform_example_compilation_tests(cpp_library, config)
444
+ phase("Compilation of example sketches")
445
+ if @cli_options[:skip_compilation]
446
+ inform("Skipping compilation of examples") { "as requested via command line" }
447
+ return
448
+ end
449
+
450
+ library_examples = cpp_library.example_sketches
451
+
452
+ if library_examples.empty?
453
+ handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the examples directory", cpp_library.examples_dir)
454
+ return
455
+ end
456
+
457
+ library_examples.each do |example_path|
458
+ example_name = File.basename(example_path)
459
+ puts
460
+ inform("Discovered example sketch") { example_name }
461
+
462
+ ovr_config = config.from_example(example_path)
463
+ platforms = choose_platform_set(ovr_config, "library example", ovr_config.platforms_to_build, cpp_library.library_properties)
464
+
465
+ if platforms.empty?
466
+ explain_and_exercise_envvar(VAR_EXPECT_EXAMPLES, "examples compilation", "platforms and architectures") do
467
+ puts " Configured platforms: #{ovr_config.platforms_to_build}"
468
+ puts " Configuration is default: #{ovr_config.is_default}"
469
+ arches = cpp_library.library_properties.nil? ? nil : cpp_library.library_properties.architectures
470
+ puts " Architectures in library.properties: #{arches}"
471
+ end
472
+ end
473
+
474
+ install_all_packages(platforms, ovr_config)
475
+ install_arduino_library_dependencies(ovr_config.aux_libraries_for_build, "<compile/libraries>")
476
+
477
+ platforms.each do |p|
478
+ board = ovr_config.platform_info[p][:board]
479
+ attempt("Compiling #{example_name} for #{board}") do
480
+ ret = @backend.compile_sketch(example_path, board)
481
+ unless ret
482
+ puts
483
+ puts "Last command: #{@backend.last_msg}"
484
+ puts @backend.last_err
485
+ end
486
+ ret
487
+ end
488
+ end
489
+ end
490
+ end
491
+
492
+ banner
493
+ inform("Host OS") { ArduinoCI::Host.os }
494
+
495
+ # initialize command and config
496
+ config = ArduinoCI::CIConfig.default.from_project_library
497
+ @backend = ArduinoCI::ArduinoInstallation.autolocate!
498
+ inform("Located arduino-cli binary") { @backend.binary_path.to_s }
499
+ if @backend.lib_dir.exist?
500
+ inform("Found libraries directory") { @backend.lib_dir }
501
+ else
502
+ assure("Creating libraries directory") { @backend.lib_dir.mkpath || true }
503
+ end
504
+
505
+ # run any library init scripts from the library itself.
506
+ perform_custom_initialization(config)
507
+
508
+ # initialize library under test
509
+ inform("Environment variable #{VAR_USE_SUBDIR}") { "'#{ENV[VAR_USE_SUBDIR]}'" }
510
+ cpp_library_path = Pathname.new(ENV[VAR_USE_SUBDIR].nil? ? "." : ENV[VAR_USE_SUBDIR])
511
+ cpp_library = assure("Installing library under test") do
512
+ @backend.install_local_library(cpp_library_path)
513
+ end
514
+
515
+ # Warn if the library name isn't obvious
516
+ assumed_name = @backend.name_of_library(cpp_library_path)
517
+ ondisk_name = cpp_library_path.realpath.basename.to_s
518
+ warn("Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'") if assumed_name != ondisk_name
519
+
520
+ if !cpp_library.nil?
521
+ inform("Library installed at") { cpp_library.path.to_s }
522
+ else
523
+ # this is a longwinded way of failing, we aren't really "assuring" anything at this point
524
+ assure_multiline("Library installed successfully") do
525
+ puts @backend.last_msg
526
+ false
527
+ end
528
+ end
529
+
530
+ install_arduino_library_dependencies(
531
+ cpp_library.arduino_library_dependencies,
532
+ "<#{ArduinoCI::CppLibrary::LIBRARY_PROPERTIES_FILE}>"
533
+ )
534
+
535
+ perform_unit_tests(cpp_library, config)
536
+ perform_example_compilation_tests(cpp_library, config)
537
+
538
+ terminate(true)