arduino_ci 0.1.21 → 1.0.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -20
  3. data/REFERENCE.md +625 -0
  4. data/cpp/arduino/Arduino.h +1 -1
  5. data/cpp/arduino/AvrMath.h +117 -17
  6. data/cpp/arduino/Client.h +27 -0
  7. data/cpp/arduino/EEPROM.h +64 -0
  8. data/cpp/arduino/Godmode.cpp +38 -19
  9. data/cpp/arduino/Godmode.h +88 -22
  10. data/cpp/arduino/HardwareSerial.h +9 -28
  11. data/cpp/arduino/IPAddress.h +59 -0
  12. data/cpp/arduino/MockEventQueue.h +86 -0
  13. data/cpp/arduino/PinHistory.h +64 -24
  14. data/cpp/arduino/Print.h +9 -12
  15. data/cpp/arduino/Printable.h +8 -0
  16. data/cpp/arduino/SPI.h +11 -3
  17. data/cpp/arduino/Server.h +5 -0
  18. data/cpp/arduino/Udp.h +27 -0
  19. data/cpp/arduino/Wire.h +234 -0
  20. data/cpp/arduino/avr/io.h +10 -1
  21. data/cpp/arduino/avr/pgmspace.h +76 -46
  22. data/cpp/arduino/ci/StreamTape.h +36 -0
  23. data/cpp/unittest/OstreamHelpers.h +4 -0
  24. data/exe/arduino_ci.rb +400 -0
  25. data/exe/arduino_ci_remote.rb +2 -385
  26. data/exe/arduino_library_location.rb +2 -2
  27. data/lib/arduino_ci.rb +1 -0
  28. data/lib/arduino_ci/arduino_backend.rb +218 -0
  29. data/lib/arduino_ci/arduino_downloader.rb +42 -72
  30. data/lib/arduino_ci/arduino_downloader_linux.rb +17 -55
  31. data/lib/arduino_ci/arduino_downloader_osx.rb +21 -33
  32. data/lib/arduino_ci/arduino_downloader_windows.rb +11 -53
  33. data/lib/arduino_ci/arduino_installation.rb +18 -80
  34. data/lib/arduino_ci/ci_config.rb +12 -7
  35. data/lib/arduino_ci/cpp_library.rb +262 -48
  36. data/lib/arduino_ci/host.rb +59 -4
  37. data/lib/arduino_ci/library_properties.rb +96 -0
  38. data/lib/arduino_ci/version.rb +1 -1
  39. data/misc/default.yml +55 -4
  40. metadata +18 -83
  41. data/cpp/arduino/Arduino.h.orig +0 -143
  42. data/cpp/arduino/ci/Queue.h +0 -73
  43. data/exe/libasan.rb +0 -29
  44. data/lib/arduino_ci/arduino_cmd.rb +0 -328
  45. data/lib/arduino_ci/arduino_cmd_linux.rb +0 -17
  46. data/lib/arduino_ci/arduino_cmd_linux_builder.rb +0 -19
  47. data/lib/arduino_ci/arduino_cmd_osx.rb +0 -17
  48. data/lib/arduino_ci/arduino_cmd_windows.rb +0 -17
@@ -0,0 +1,36 @@
1
+ #pragma once
2
+
3
+ #include "../Stream.h"
4
+
5
+ /**
6
+ * Stream with godmode-controlled input and godmode-persisted output
7
+ */
8
+ class StreamTape : public Stream, public ObservableDataStream
9
+ {
10
+ protected:
11
+ String* mGodmodeDataOut;
12
+ // mGodmodeDataIn is provided by Stream
13
+
14
+ public:
15
+ StreamTape(String* dataIn, String* dataOut, unsigned long* delay): Stream(), ObservableDataStream() {
16
+ mGodmodeDataIn = dataIn;
17
+ mGodmodeDataOut = dataOut;
18
+ mGodmodeMicrosDelay = delay;
19
+ }
20
+
21
+ // virtual int available(void);
22
+ // virtual int peek(void);
23
+ // virtual int read(void);
24
+ // virtual int availableForWrite(void);
25
+ // virtual void flush(void);
26
+ virtual size_t write(uint8_t aChar) {
27
+ mGodmodeDataOut->append(String((char)aChar));
28
+ advertiseByte((unsigned char)aChar);
29
+ return 1;
30
+ }
31
+
32
+ // https://stackoverflow.com/a/4271276
33
+ using Print::write; // pull in write(str) and write(buf, size) from Print
34
+
35
+ };
36
+
@@ -2,4 +2,8 @@
2
2
 
3
3
  #include <ostream>
4
4
 
5
+ #if (defined __apple_build_version__) && (__apple_build_version__ >= 12000000)
6
+ // defined in /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ostream:223:20
7
+ #else
5
8
  inline std::ostream& operator << (std::ostream& out, const std::nullptr_t &np) { return out << "nullptr"; }
9
+ #endif
@@ -0,0 +1,400 @@
1
+ #!/usr/bin/env ruby
2
+ require 'arduino_ci'
3
+ require 'set'
4
+ require 'pathname'
5
+ require 'optparse'
6
+
7
+ WIDTH = 80
8
+ FIND_FILES_INDENT = 4
9
+
10
+ @failure_count = 0
11
+ @passfail = proc { |result| result ? "✓" : "✗" }
12
+ @backend = nil
13
+
14
+ # Use some basic parsing to allow command-line overrides of config
15
+ class Parser
16
+ def self.parse(options)
17
+ unit_config = {}
18
+ output_options = {
19
+ skip_unittests: false,
20
+ skip_compilation: false,
21
+ ci_config: {
22
+ "unittest" => unit_config
23
+ },
24
+ }
25
+
26
+ opt_parser = OptionParser.new do |opts|
27
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
28
+
29
+ opts.on("--skip-unittests", "Don't run unit tests") do |p|
30
+ output_options[:skip_unittests] = p
31
+ end
32
+
33
+ opts.on("--skip-examples-compilation", "Don't compile example sketches") do |p|
34
+ output_options[:skip_compilation] = p
35
+ end
36
+
37
+ opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p|
38
+ unit_config["testfiles"] ||= {}
39
+ unit_config["testfiles"]["select"] ||= []
40
+ unit_config["testfiles"]["select"] << p
41
+ end
42
+
43
+ opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p|
44
+ unit_config["testfiles"] ||= {}
45
+ unit_config["testfiles"]["reject"] ||= []
46
+ unit_config["testfiles"]["reject"] << p
47
+ end
48
+
49
+ opts.on("-h", "--help", "Prints this help") do
50
+ puts opts
51
+ exit
52
+ end
53
+ end
54
+
55
+ opt_parser.parse!(options)
56
+ output_options
57
+ end
58
+ end
59
+
60
+ # Read in command line options and make them read-only
61
+ @cli_options = (Parser.parse ARGV).freeze
62
+
63
+ # terminate after printing any debug info. TODO: capture debug info
64
+ def terminate(final = nil)
65
+ puts "Failures: #{@failure_count}"
66
+ unless @failure_count.zero? || final
67
+ puts "Last message: #{@backend.last_msg}"
68
+ puts "========== Stdout:"
69
+ puts @backend.last_out
70
+ puts "========== Stderr:"
71
+ puts @backend.last_err
72
+ end
73
+ retcode = @failure_count.zero? ? 0 : 1
74
+ exit(retcode)
75
+ end
76
+
77
+ # make a nice status line for an action and react to the action
78
+ # TODO / note to self: inform_multline is tougher to write
79
+ # without altering the signature because it only leaves space
80
+ # for the checkmark _after_ the multiline, it doesn't know how
81
+ # to make that conditionally the body
82
+ # @param message String the text of the progress indicator
83
+ # @param multiline boolean whether multiline output is expected
84
+ # @param mark_fn block (string) -> string that says how to describe the result
85
+ # @param on_fail_msg String custom message for failure
86
+ # @param tally_on_fail boolean whether to increment @failure_count
87
+ # @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
88
+ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
89
+ line = "#{message}... "
90
+ endline = "...#{message} "
91
+ if multiline
92
+ puts line
93
+ else
94
+ print line
95
+ end
96
+ STDOUT.flush
97
+ result = yield
98
+ mark = mark_fn.nil? ? "" : mark_fn.call(result)
99
+ # if multline, put checkmark at full width
100
+ print endline if multiline
101
+ puts mark.to_s.rjust(WIDTH - line.length, " ")
102
+ unless result
103
+ puts on_fail_msg unless on_fail_msg.nil?
104
+ @failure_count += 1 if tally_on_fail
105
+ # print out error messaging here if we've captured it
106
+ terminate if abort_on_fail
107
+ end
108
+ result
109
+ end
110
+
111
+ # Make a nice status for something that defers any failure code until script exit
112
+ def attempt(message, &block)
113
+ perform_action(message, false, @passfail, nil, true, false, &block)
114
+ end
115
+
116
+ # Make a nice status for something that defers any failure code until script exit
117
+ def attempt_multiline(message, &block)
118
+ perform_action(message, true, @passfail, nil, true, false, &block)
119
+ end
120
+
121
+ # Make a nice status for something that kills the script immediately on failure
122
+ FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze
123
+ def assure(message, &block)
124
+ perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
125
+ end
126
+
127
+ def assure_multiline(message, &block)
128
+ perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
129
+ end
130
+
131
+ def inform(message, &block)
132
+ perform_action(message, false, proc { |x| x }, nil, false, false, &block)
133
+ end
134
+
135
+ def inform_multiline(message, &block)
136
+ perform_action(message, true, nil, nil, false, false, &block)
137
+ end
138
+
139
+ # Assure that a platform exists and return its definition
140
+ def assured_platform(purpose, name, config)
141
+ platform_definition = config.platform_definition(name)
142
+ assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do
143
+ !platform_definition.nil?
144
+ end
145
+ platform_definition
146
+ end
147
+
148
+ # Return true if the file (or one of the dirs containing it) is hidden
149
+ def file_is_hidden_somewhere?(path)
150
+ # this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator
151
+ path.ascend do |part|
152
+ return true if part.basename.to_s.start_with? "."
153
+ end
154
+ false
155
+ end
156
+
157
+ # print out some files
158
+ def display_files(pathname)
159
+ # `find` doesn't follow symlinks, so we should instead
160
+ realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname
161
+
162
+ # suppress directories and dotfile-based things
163
+ all_files = realpath.find.select(&:file?)
164
+ non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
165
+
166
+ # print files with an indent
167
+ margin = " " * FIND_FILES_INDENT
168
+ non_hidden.each { |p| puts "#{margin}#{p}" }
169
+ end
170
+
171
+ # @return [Array<String>] The list of installed libraries
172
+ def install_arduino_library_dependencies(library_names, on_behalf_of, already_installed = [])
173
+ installed = already_installed.clone
174
+ library_names.map { |n| @backend.library_of_name(n) }.each do |l|
175
+ if installed.include?(l)
176
+ # do nothing
177
+ elsif l.installed?
178
+ inform("Using pre-existing dependency of #{on_behalf_of}") { l.name }
179
+ else
180
+ assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
181
+ next nil unless l.install
182
+
183
+ l.name
184
+ end
185
+ end
186
+ installed << l.name
187
+ installed += install_arduino_library_dependencies(l.arduino_library_dependencies, l.name, installed)
188
+ end
189
+ installed
190
+ end
191
+
192
+ def perform_unit_tests(cpp_library, file_config)
193
+ if @cli_options[:skip_unittests]
194
+ inform("Skipping unit tests") { "as requested via command line" }
195
+ return
196
+ end
197
+ config = file_config.with_override_config(@cli_options[:ci_config])
198
+
199
+ # check GCC
200
+ compilers = config.compilers_to_use
201
+ assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
202
+ compilers.each do |gcc_binary|
203
+ attempt_multiline("Checking #{gcc_binary} version") do
204
+ version = cpp_library.gcc_version(gcc_binary)
205
+ next nil unless version
206
+
207
+ puts version.split("\n").map { |l| " #{l}" }.join("\n")
208
+ version
209
+ end
210
+ inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
211
+ end
212
+
213
+ # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name
214
+ all_platform_info = {}
215
+ config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) }
216
+
217
+ inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
218
+
219
+ # iterate boards / tests
220
+ if !cpp_library.tests_dir.exist?
221
+ # alert future me about running the script from the wrong directory, instead of doing the huge file dump
222
+ # otherwise, assume that the user might be running the script on a library with no actual unit tests
223
+ if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd)
224
+ inform_multiline("arduino_ci seems to be trying to test itself") do
225
+ [
226
+ "arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against",
227
+ "the core library isn't really a valid thing to do... but it's easy for a developer (including the",
228
+ "owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of",
229
+ "the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
230
+ ].each { |l| puts " #{l}" }
231
+ false
232
+ end
233
+ exit(1)
234
+ else
235
+ inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do
236
+ puts " In case that's an error, this is what was found in the library:"
237
+ display_files(cpp_library.tests_dir.parent)
238
+ true
239
+ end
240
+ end
241
+ elsif cpp_library.test_files.empty?
242
+ inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do
243
+ puts " In case that's an error, this is what was found in the tests directory:"
244
+ display_files(cpp_library.tests_dir)
245
+ true
246
+ end
247
+ elsif config.platforms_to_unittest.empty?
248
+ inform("Skipping unit tests") { "no platforms were requested" }
249
+ else
250
+ install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
251
+
252
+ config.platforms_to_unittest.each do |p|
253
+ config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
254
+ unittest_name = unittest_path.basename.to_s
255
+ compilers.each do |gcc_binary|
256
+ attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
257
+ exe = cpp_library.build_for_test_with_configuration(
258
+ unittest_path,
259
+ config.aux_libraries_for_unittest,
260
+ gcc_binary,
261
+ config.gcc_config(p)
262
+ )
263
+ puts
264
+ unless exe
265
+ puts "Last command: #{cpp_library.last_cmd}"
266
+ puts cpp_library.last_out
267
+ puts cpp_library.last_err
268
+ next false
269
+ end
270
+ cpp_library.run_test_file(exe)
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ def perform_example_compilation_tests(cpp_library, config)
279
+ if @cli_options[:skip_compilation]
280
+ inform("Skipping compilation of examples") { "as requested via command line" }
281
+ return
282
+ end
283
+
284
+ # gather up all required boards for compilation so we can install them up front.
285
+ # start with the "platforms to unittest" and add the examples
286
+ # while we're doing that, get the aux libraries as well
287
+ example_platform_info = {}
288
+ board_package_url = {}
289
+ aux_libraries = Set.new(config.aux_libraries_for_build)
290
+ # while collecting the platforms, ensure they're defined
291
+
292
+ library_examples = cpp_library.example_sketches
293
+ library_examples.each do |path|
294
+ ovr_config = config.from_example(path)
295
+ ovr_config.platforms_to_build.each do |platform|
296
+ # assure the platform if we haven't already
297
+ next if example_platform_info.key?(platform)
298
+
299
+ platform_info = assured_platform("library example", platform, config)
300
+ next if platform_info.nil?
301
+
302
+ example_platform_info[platform] = platform_info
303
+ package = platform_info[:package]
304
+ board_package_url[package] = ovr_config.package_url(package)
305
+ end
306
+ aux_libraries.merge(ovr_config.aux_libraries_for_build)
307
+ end
308
+
309
+ # with all platform info, we can extract unique packages and their urls
310
+ # do that, set the URLs, and download the packages
311
+ all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
312
+
313
+ # make sure any non-builtin package has a URL defined
314
+ all_packages.each do |p|
315
+ assure("Board package #{p} has a defined URL") { board_package_url[p] }
316
+ end
317
+
318
+ # set up all the board manager URLs.
319
+ # we can safely reject nils now, they would be for the builtins
320
+ all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
321
+
322
+ unless all_urls.empty?
323
+ assure("Setting board manager URLs") do
324
+ @backend.board_manager_urls = all_urls
325
+ end
326
+ end
327
+
328
+ all_packages.each do |p|
329
+ assure("Installing board package #{p}") do
330
+ @backend.install_boards(p)
331
+ end
332
+ end
333
+
334
+ install_arduino_library_dependencies(aux_libraries, "<compile/libraries>")
335
+
336
+ if config.platforms_to_build.empty?
337
+ inform("Skipping builds") { "no platforms were requested" }
338
+ return
339
+ elsif library_examples.empty?
340
+ inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do
341
+ display_files(installed_library_path)
342
+ end
343
+ return
344
+ end
345
+
346
+ library_examples.each do |example_path|
347
+ ovr_config = config.from_example(example_path)
348
+ ovr_config.platforms_to_build.each do |p|
349
+ board = example_platform_info[p][:board]
350
+ example_name = File.basename(example_path)
351
+ attempt("Compiling #{example_name} for #{board}") do
352
+ ret = @backend.compile_sketch(example_path, board)
353
+ unless ret
354
+ puts
355
+ puts "Last command: #{@backend.last_msg}"
356
+ puts @backend.last_err
357
+ end
358
+ ret
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ # initialize command and config
365
+ config = ArduinoCI::CIConfig.default.from_project_library
366
+
367
+ @backend = ArduinoCI::ArduinoInstallation.autolocate!
368
+ inform("Located arduino-cli binary") { @backend.binary_path.to_s }
369
+
370
+ # initialize library under test
371
+ cpp_library_path = Pathname.new(".")
372
+ cpp_library = assure("Installing library under test") do
373
+ @backend.install_local_library(cpp_library_path)
374
+ end
375
+
376
+ assumed_name = @backend.name_of_library(cpp_library_path)
377
+ ondisk_name = cpp_library_path.realpath.basename
378
+ if assumed_name != ondisk_name
379
+ inform("WARNING") { "Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'" }
380
+ end
381
+
382
+ if !cpp_library.nil?
383
+ inform("Library installed at") { cpp_library.path.to_s }
384
+ else
385
+ # this is a longwinded way of failing, we aren't really "assuring" anything at this point
386
+ assure_multiline("Library installed successfully") do
387
+ puts @backend.last_msg
388
+ false
389
+ end
390
+ end
391
+
392
+ install_arduino_library_dependencies(
393
+ cpp_library.arduino_library_dependencies,
394
+ "<#{ArduinoCI::CppLibrary::LIBRARY_PROPERTIES_FILE}>"
395
+ )
396
+
397
+ perform_unit_tests(cpp_library, config)
398
+ perform_example_compilation_tests(cpp_library, config)
399
+
400
+ terminate(true)
@@ -1,386 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'arduino_ci'
3
- require 'set'
4
- require 'pathname'
5
- require 'optparse'
6
-
7
- WIDTH = 80
8
- FIND_FILES_INDENT = 4
9
-
10
- @failure_count = 0
11
- @passfail = proc { |result| result ? "✓" : "✗" }
12
-
13
- # Use some basic parsing to allow command-line overrides of config
14
- class Parser
15
- def self.parse(options)
16
- unit_config = {}
17
- output_options = {
18
- skip_unittests: false,
19
- skip_compilation: false,
20
- ci_config: {
21
- "unittest" => unit_config
22
- },
23
- }
24
-
25
- opt_parser = OptionParser.new do |opts|
26
- opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
27
-
28
- opts.on("--skip-unittests", "Don't run unit tests") do |p|
29
- output_options[:skip_unittests] = p
30
- end
31
-
32
- opts.on("--skip-compilation", "Don't compile example sketches") do |p|
33
- output_options[:skip_compilation] = p
34
- end
35
-
36
- opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p|
37
- unit_config["testfiles"] ||= {}
38
- unit_config["testfiles"]["select"] ||= []
39
- unit_config["testfiles"]["select"] << p
40
- end
41
-
42
- opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p|
43
- unit_config["testfiles"] ||= {}
44
- unit_config["testfiles"]["reject"] ||= []
45
- unit_config["testfiles"]["reject"] << p
46
- end
47
-
48
- opts.on("-h", "--help", "Prints this help") do
49
- puts opts
50
- exit
51
- end
52
- end
53
-
54
- opt_parser.parse!(options)
55
- output_options
56
- end
57
- end
58
-
59
- # Read in command line options and make them read-only
60
- @cli_options = (Parser.parse ARGV).freeze
61
-
62
- # terminate after printing any debug info. TODO: capture debug info
63
- def terminate(final = nil)
64
- puts "Failures: #{@failure_count}"
65
- unless @failure_count.zero? || final
66
- puts "Last message: #{@arduino_cmd.last_msg}"
67
- puts "========== Stdout:"
68
- puts @arduino_cmd.last_out
69
- puts "========== Stderr:"
70
- puts @arduino_cmd.last_err
71
- end
72
- retcode = @failure_count.zero? ? 0 : 1
73
- exit(retcode)
74
- end
75
-
76
- # make a nice status line for an action and react to the action
77
- # TODO / note to self: inform_multline is tougher to write
78
- # without altering the signature because it only leaves space
79
- # for the checkmark _after_ the multiline, it doesn't know how
80
- # to make that conditionally the body
81
- # @param message String the text of the progress indicator
82
- # @param multiline boolean whether multiline output is expected
83
- # @param mark_fn block (string) -> string that says how to describe the result
84
- # @param on_fail_msg String custom message for failure
85
- # @param tally_on_fail boolean whether to increment @failure_count
86
- # @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
87
- def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
88
- line = "#{message}... "
89
- endline = "...#{message} "
90
- if multiline
91
- puts line
92
- else
93
- print line
94
- end
95
- STDOUT.flush
96
- result = yield
97
- mark = mark_fn.nil? ? "" : mark_fn.call(result)
98
- # if multline, put checkmark at full width
99
- print endline if multiline
100
- puts mark.to_s.rjust(WIDTH - line.length, " ")
101
- unless result
102
- puts on_fail_msg unless on_fail_msg.nil?
103
- @failure_count += 1 if tally_on_fail
104
- # print out error messaging here if we've captured it
105
- terminate if abort_on_fail
106
- end
107
- result
108
- end
109
-
110
- # Make a nice status for something that defers any failure code until script exit
111
- def attempt(message, &block)
112
- perform_action(message, false, @passfail, nil, true, false, &block)
113
- end
114
-
115
- # Make a nice status for something that defers any failure code until script exit
116
- def attempt_multiline(message, &block)
117
- perform_action(message, true, @passfail, nil, true, false, &block)
118
- end
119
-
120
- # Make a nice status for something that kills the script immediately on failure
121
- FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze
122
- def assure(message, &block)
123
- perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
124
- end
125
-
126
- def assure_multiline(message, &block)
127
- perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
128
- end
129
-
130
- def inform(message, &block)
131
- perform_action(message, false, proc { |x| x }, nil, false, false, &block)
132
- end
133
-
134
- def inform_multiline(message, &block)
135
- perform_action(message, true, nil, nil, false, false, &block)
136
- end
137
-
138
- # Assure that a platform exists and return its definition
139
- def assured_platform(purpose, name, config)
140
- platform_definition = config.platform_definition(name)
141
- assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do
142
- !platform_definition.nil?
143
- end
144
- platform_definition
145
- end
146
-
147
- # Return true if the file (or one of the dirs containing it) is hidden
148
- def file_is_hidden_somewhere?(path)
149
- # this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator
150
- path.ascend do |part|
151
- return true if part.basename.to_s.start_with? "."
152
- end
153
- false
154
- end
155
-
156
- # print out some files
157
- def display_files(pathname)
158
- # `find` doesn't follow symlinks, so we should instead
159
- realpath = pathname.symlink? ? pathname.readlink : pathname
160
-
161
- # suppress directories and dotfile-based things
162
- all_files = realpath.find.select(&:file?)
163
- non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
164
-
165
- # print files with an indent
166
- margin = " " * FIND_FILES_INDENT
167
- non_hidden.each { |p| puts "#{margin}#{p}" }
168
- end
169
-
170
- def perform_unit_tests(file_config)
171
- if @cli_options[:skip_unittests]
172
- inform("Skipping unit tests") { "as requested via command line" }
173
- return
174
- end
175
- config = file_config.with_override_config(@cli_options[:ci_config])
176
- cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."), @arduino_cmd.lib_dir)
177
-
178
- # check GCC
179
- compilers = config.compilers_to_use
180
- assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
181
- compilers.each do |gcc_binary|
182
- attempt_multiline("Checking #{gcc_binary} version") do
183
- version = cpp_library.gcc_version(gcc_binary)
184
- next nil unless version
185
-
186
- puts version.split("\n").map { |l| " #{l}" }.join("\n")
187
- version
188
- end
189
- inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
190
- end
191
-
192
- # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name
193
- all_platform_info = {}
194
- config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) }
195
-
196
- # iterate boards / tests
197
- if !cpp_library.tests_dir.exist?
198
- inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do
199
- puts " In case that's an error, this is what was found in the library:"
200
- display_files(cpp_library.tests_dir.parent)
201
- true
202
- end
203
- elsif cpp_library.test_files.empty?
204
- inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do
205
- puts " In case that's an error, this is what was found in the tests directory:"
206
- display_files(cpp_library.tests_dir)
207
- true
208
- end
209
- elsif config.platforms_to_unittest.empty?
210
- inform("Skipping unit tests") { "no platforms were requested" }
211
- else
212
- config.platforms_to_unittest.each do |p|
213
- config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
214
- unittest_name = unittest_path.basename.to_s
215
- compilers.each do |gcc_binary|
216
- attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary}") do
217
- exe = cpp_library.build_for_test_with_configuration(
218
- unittest_path,
219
- config.aux_libraries_for_unittest,
220
- gcc_binary,
221
- config.gcc_config(p)
222
- )
223
- puts
224
- unless exe
225
- puts "Last command: #{cpp_library.last_cmd}"
226
- puts cpp_library.last_out
227
- puts cpp_library.last_err
228
- next false
229
- end
230
- cpp_library.run_test_file(exe)
231
- end
232
- end
233
- end
234
- end
235
- end
236
- end
237
-
238
- def perform_compilation_tests(config)
239
- if @cli_options[:skip_compilation]
240
- inform("Skipping compilation of examples") { "as requested via command line" }
241
- return
242
- end
243
-
244
- # index the existing libraries
245
- attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed
246
-
247
- # initialize library under test
248
- installed_library_path = attempt("Installing library under test") do
249
- @arduino_cmd.install_local_library(Pathname.new("."))
250
- end
251
-
252
- if !installed_library_path.nil? && installed_library_path.exist?
253
- inform("Library installed at") { installed_library_path.to_s }
254
- else
255
- assure_multiline("Library installed successfully") do
256
- if installed_library_path.nil?
257
- puts @arduino_cmd.last_msg
258
- else
259
- # print out the contents of the deepest directory we actually find
260
- @arduino_cmd.lib_dir.ascend do |path_part|
261
- next unless path_part.exist?
262
-
263
- break display_files(path_part)
264
- end
265
- false
266
- end
267
- end
268
- end
269
- library_examples = @arduino_cmd.library_examples(installed_library_path)
270
-
271
- # gather up all required boards for compilation so we can install them up front.
272
- # start with the "platforms to unittest" and add the examples
273
- # while we're doing that, get the aux libraries as well
274
- example_platform_info = {}
275
- board_package_url = {}
276
- aux_libraries = Set.new(config.aux_libraries_for_unittest + config.aux_libraries_for_build)
277
- # while collecting the platforms, ensure they're defined
278
-
279
- library_examples.each do |path|
280
- ovr_config = config.from_example(path)
281
- ovr_config.platforms_to_build.each do |platform|
282
- # assure the platform if we haven't already
283
- next if example_platform_info.key?(platform)
284
-
285
- platform_info = assured_platform("library example", platform, config)
286
- next if platform_info.nil?
287
-
288
- example_platform_info[platform] = platform_info
289
- package = platform_info[:package]
290
- board_package_url[package] = ovr_config.package_url(package)
291
- end
292
- aux_libraries.merge(ovr_config.aux_libraries_for_build)
293
- end
294
-
295
- # with all platform info, we can extract unique packages and their urls
296
- # do that, set the URLs, and download the packages
297
- all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
298
-
299
- # inform about builtin packages
300
- all_packages.select { |p| config.package_builtin?(p) }.each do |p|
301
- inform("Using built-in board package") { p }
302
- end
303
-
304
- # make sure any non-builtin package has a URL defined
305
- all_packages.reject { |p| config.package_builtin?(p) }.each do |p|
306
- assure("Board package #{p} has a defined URL") { board_package_url[p] }
307
- end
308
-
309
- # set up all the board manager URLs.
310
- # we can safely reject nils now, they would be for the builtins
311
- all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
312
-
313
- unless all_urls.empty?
314
- assure("Setting board manager URLs") do
315
- @arduino_cmd.board_manager_urls = all_urls
316
- end
317
- end
318
-
319
- all_packages.each do |p|
320
- assure("Installing board package #{p}") do
321
- @arduino_cmd.install_boards(p)
322
- end
323
- end
324
-
325
- aux_libraries.each do |l|
326
- if @arduino_cmd.library_present?(l)
327
- inform("Using pre-existing library") { l.to_s }
328
- else
329
- assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) }
330
- end
331
- end
332
-
333
- last_board = nil
334
- if config.platforms_to_build.empty?
335
- inform("Skipping builds") { "no platforms were requested" }
336
- return
337
- elsif library_examples.empty?
338
- inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do
339
- display_files(installed_library_path)
340
- end
341
- return
342
- end
343
-
344
- attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") }
345
-
346
- # switching boards takes time, so iterate board first
347
- # _then_ whichever examples match it
348
- examples_by_platform = library_examples.each_with_object({}) do |example_path, acc|
349
- ovr_config = config.from_example(example_path)
350
- ovr_config.platforms_to_build.each do |p|
351
- acc[p] = [] unless acc.key?(p)
352
- acc[p] << example_path
353
- end
354
- end
355
-
356
- examples_by_platform.each do |platform, example_paths|
357
- board = example_platform_info[platform][:board]
358
- assure("Switching to board for #{platform} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board
359
- last_board = board
360
-
361
- example_paths.each do |example_path|
362
- example_name = File.basename(example_path)
363
- attempt("Verifying #{example_name}") do
364
- ret = @arduino_cmd.verify_sketch(example_path)
365
- unless ret
366
- puts
367
- puts "Last command: #{@arduino_cmd.last_msg}"
368
- puts @arduino_cmd.last_err
369
- end
370
- ret
371
- end
372
- end
373
- end
374
-
375
- end
376
-
377
- # initialize command and config
378
- config = ArduinoCI::CIConfig.default.from_project_library
379
-
380
- @arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
381
- inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s }
382
-
383
- perform_unit_tests(config)
384
- perform_compilation_tests(config)
385
-
386
- terminate(true)
2
+ puts "arduino_ci_remote.rb is deprecated in favor of arduino_ci.rb."
3
+ require_relative "arduino_ci.rb"