arduino_ci 0.3.0 → 0.4.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 +21 -19
- data/REFERENCE.md +625 -0
- data/cpp/arduino/Arduino.h +1 -2
- data/cpp/arduino/AvrMath.h +117 -17
- data/cpp/arduino/Client.h +26 -0
- data/cpp/arduino/EEPROM.h +64 -0
- data/cpp/arduino/Godmode.cpp +7 -0
- data/cpp/arduino/Godmode.h +58 -9
- data/cpp/arduino/HardwareSerial.h +4 -4
- data/cpp/arduino/IPAddress.h +59 -0
- data/cpp/arduino/Print.h +9 -12
- data/cpp/arduino/Printable.h +8 -0
- data/cpp/arduino/SPI.h +11 -3
- data/cpp/arduino/Server.h +5 -0
- data/cpp/arduino/Udp.h +27 -0
- data/cpp/arduino/Wire.h +173 -77
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/unittest/OstreamHelpers.h +4 -0
- data/exe/arduino_ci.rb +401 -0
- data/exe/arduino_ci_remote.rb +2 -393
- data/lib/arduino_ci.rb +1 -0
- data/lib/arduino_ci/arduino_downloader.rb +5 -4
- data/lib/arduino_ci/arduino_installation.rb +5 -5
- data/lib/arduino_ci/cpp_library.rb +124 -24
- data/lib/arduino_ci/installed_cpp_library.rb +0 -0
- data/lib/arduino_ci/library_properties.rb +86 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +50 -3
- metadata +15 -7
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/exe/libasan.rb +0 -29
data/exe/arduino_ci_remote.rb
CHANGED
@@ -1,394 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
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 install_arduino_library_dependencies(aux_libraries)
|
171
|
-
aux_libraries.each do |l|
|
172
|
-
if @arduino_cmd.library_present?(l)
|
173
|
-
inform("Using pre-existing library") { l.to_s }
|
174
|
-
else
|
175
|
-
assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) }
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def perform_unit_tests(file_config)
|
181
|
-
if @cli_options[:skip_unittests]
|
182
|
-
inform("Skipping unit tests") { "as requested via command line" }
|
183
|
-
return
|
184
|
-
end
|
185
|
-
config = file_config.with_override_config(@cli_options[:ci_config])
|
186
|
-
cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."),
|
187
|
-
@arduino_cmd.lib_dir,
|
188
|
-
config.exclude_dirs.map(&Pathname.method(:new)))
|
189
|
-
|
190
|
-
# check GCC
|
191
|
-
compilers = config.compilers_to_use
|
192
|
-
assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
|
193
|
-
compilers.each do |gcc_binary|
|
194
|
-
attempt_multiline("Checking #{gcc_binary} version") do
|
195
|
-
version = cpp_library.gcc_version(gcc_binary)
|
196
|
-
next nil unless version
|
197
|
-
|
198
|
-
puts version.split("\n").map { |l| " #{l}" }.join("\n")
|
199
|
-
version
|
200
|
-
end
|
201
|
-
inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
|
202
|
-
end
|
203
|
-
|
204
|
-
# Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name
|
205
|
-
all_platform_info = {}
|
206
|
-
config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) }
|
207
|
-
|
208
|
-
# iterate boards / tests
|
209
|
-
if !cpp_library.tests_dir.exist?
|
210
|
-
inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do
|
211
|
-
puts " In case that's an error, this is what was found in the library:"
|
212
|
-
display_files(cpp_library.tests_dir.parent)
|
213
|
-
true
|
214
|
-
end
|
215
|
-
elsif cpp_library.test_files.empty?
|
216
|
-
inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do
|
217
|
-
puts " In case that's an error, this is what was found in the tests directory:"
|
218
|
-
display_files(cpp_library.tests_dir)
|
219
|
-
true
|
220
|
-
end
|
221
|
-
elsif config.platforms_to_unittest.empty?
|
222
|
-
inform("Skipping unit tests") { "no platforms were requested" }
|
223
|
-
else
|
224
|
-
install_arduino_library_dependencies(config.aux_libraries_for_unittest)
|
225
|
-
|
226
|
-
config.platforms_to_unittest.each do |p|
|
227
|
-
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
|
228
|
-
unittest_name = unittest_path.basename.to_s
|
229
|
-
compilers.each do |gcc_binary|
|
230
|
-
attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary}") do
|
231
|
-
exe = cpp_library.build_for_test_with_configuration(
|
232
|
-
unittest_path,
|
233
|
-
config.aux_libraries_for_unittest,
|
234
|
-
gcc_binary,
|
235
|
-
config.gcc_config(p)
|
236
|
-
)
|
237
|
-
puts
|
238
|
-
unless exe
|
239
|
-
puts "Last command: #{cpp_library.last_cmd}"
|
240
|
-
puts cpp_library.last_out
|
241
|
-
puts cpp_library.last_err
|
242
|
-
next false
|
243
|
-
end
|
244
|
-
cpp_library.run_test_file(exe)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
def perform_compilation_tests(config)
|
253
|
-
if @cli_options[:skip_compilation]
|
254
|
-
inform("Skipping compilation of examples") { "as requested via command line" }
|
255
|
-
return
|
256
|
-
end
|
257
|
-
|
258
|
-
# index the existing libraries
|
259
|
-
attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed
|
260
|
-
|
261
|
-
# initialize library under test
|
262
|
-
installed_library_path = attempt("Installing library under test") do
|
263
|
-
@arduino_cmd.install_local_library(Pathname.new("."))
|
264
|
-
end
|
265
|
-
|
266
|
-
if !installed_library_path.nil? && installed_library_path.exist?
|
267
|
-
inform("Library installed at") { installed_library_path.to_s }
|
268
|
-
else
|
269
|
-
assure_multiline("Library installed successfully") do
|
270
|
-
if installed_library_path.nil?
|
271
|
-
puts @arduino_cmd.last_msg
|
272
|
-
else
|
273
|
-
# print out the contents of the deepest directory we actually find
|
274
|
-
@arduino_cmd.lib_dir.ascend do |path_part|
|
275
|
-
next unless path_part.exist?
|
276
|
-
|
277
|
-
break display_files(path_part)
|
278
|
-
end
|
279
|
-
false
|
280
|
-
end
|
281
|
-
end
|
282
|
-
end
|
283
|
-
library_examples = @arduino_cmd.library_examples(installed_library_path)
|
284
|
-
|
285
|
-
# gather up all required boards for compilation so we can install them up front.
|
286
|
-
# start with the "platforms to unittest" and add the examples
|
287
|
-
# while we're doing that, get the aux libraries as well
|
288
|
-
example_platform_info = {}
|
289
|
-
board_package_url = {}
|
290
|
-
aux_libraries = Set.new(config.aux_libraries_for_build)
|
291
|
-
# while collecting the platforms, ensure they're defined
|
292
|
-
|
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
|
-
# inform about builtin packages
|
314
|
-
all_packages.select { |p| config.package_builtin?(p) }.each do |p|
|
315
|
-
inform("Using built-in board package") { p }
|
316
|
-
end
|
317
|
-
|
318
|
-
# make sure any non-builtin package has a URL defined
|
319
|
-
all_packages.reject { |p| config.package_builtin?(p) }.each do |p|
|
320
|
-
assure("Board package #{p} has a defined URL") { board_package_url[p] }
|
321
|
-
end
|
322
|
-
|
323
|
-
# set up all the board manager URLs.
|
324
|
-
# we can safely reject nils now, they would be for the builtins
|
325
|
-
all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
|
326
|
-
|
327
|
-
unless all_urls.empty?
|
328
|
-
assure("Setting board manager URLs") do
|
329
|
-
@arduino_cmd.board_manager_urls = all_urls
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
all_packages.each do |p|
|
334
|
-
assure("Installing board package #{p}") do
|
335
|
-
@arduino_cmd.install_boards(p)
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
install_arduino_library_dependencies(aux_libraries)
|
340
|
-
|
341
|
-
last_board = nil
|
342
|
-
if config.platforms_to_build.empty?
|
343
|
-
inform("Skipping builds") { "no platforms were requested" }
|
344
|
-
return
|
345
|
-
elsif library_examples.empty?
|
346
|
-
inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do
|
347
|
-
display_files(installed_library_path)
|
348
|
-
end
|
349
|
-
return
|
350
|
-
end
|
351
|
-
|
352
|
-
attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") }
|
353
|
-
|
354
|
-
# switching boards takes time, so iterate board first
|
355
|
-
# _then_ whichever examples match it
|
356
|
-
examples_by_platform = library_examples.each_with_object({}) do |example_path, acc|
|
357
|
-
ovr_config = config.from_example(example_path)
|
358
|
-
ovr_config.platforms_to_build.each do |p|
|
359
|
-
acc[p] = [] unless acc.key?(p)
|
360
|
-
acc[p] << example_path
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
examples_by_platform.each do |platform, example_paths|
|
365
|
-
board = example_platform_info[platform][:board]
|
366
|
-
assure("Switching to board for #{platform} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board
|
367
|
-
last_board = board
|
368
|
-
|
369
|
-
example_paths.each do |example_path|
|
370
|
-
example_name = File.basename(example_path)
|
371
|
-
attempt("Verifying #{example_name}") do
|
372
|
-
ret = @arduino_cmd.verify_sketch(example_path)
|
373
|
-
unless ret
|
374
|
-
puts
|
375
|
-
puts "Last command: #{@arduino_cmd.last_msg}"
|
376
|
-
puts @arduino_cmd.last_err
|
377
|
-
end
|
378
|
-
ret
|
379
|
-
end
|
380
|
-
end
|
381
|
-
end
|
382
|
-
|
383
|
-
end
|
384
|
-
|
385
|
-
# initialize command and config
|
386
|
-
config = ArduinoCI::CIConfig.default.from_project_library
|
387
|
-
|
388
|
-
@arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
|
389
|
-
inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s }
|
390
|
-
|
391
|
-
perform_unit_tests(config)
|
392
|
-
perform_compilation_tests(config)
|
393
|
-
|
394
|
-
terminate(true)
|
2
|
+
puts "arduino_ci_remote.rb is deprecated in favor of arduino_ci.rb."
|
3
|
+
require_relative "arduino_ci.rb"
|
data/lib/arduino_ci.rb
CHANGED
@@ -2,6 +2,7 @@ require "arduino_ci/version"
|
|
2
2
|
require "arduino_ci/arduino_installation"
|
3
3
|
require "arduino_ci/cpp_library"
|
4
4
|
require "arduino_ci/ci_config"
|
5
|
+
require "arduino_ci/library_properties"
|
5
6
|
|
6
7
|
# ArduinoCI contains classes for automated testing of Arduino code on the command line
|
7
8
|
# @author Ian Katz <ianfixes@gmail.com>
|
@@ -161,16 +161,17 @@ module ArduinoCI
|
|
161
161
|
return false
|
162
162
|
end
|
163
163
|
|
164
|
+
arduino_package = "Arduino #{@desired_ide_version} package"
|
164
165
|
attempts = 0
|
165
166
|
|
166
167
|
loop do
|
167
168
|
if File.exist? package_file
|
168
|
-
@output.puts "
|
169
|
+
@output.puts "#{arduino_package} seems to have been downloaded already" if attempts.zero?
|
169
170
|
break
|
170
171
|
elsif attempts >= DOWNLOAD_ATTEMPTS
|
171
172
|
break @output.puts "After #{DOWNLOAD_ATTEMPTS} attempts, failed to download #{package_url}"
|
172
173
|
else
|
173
|
-
@output.print "Attempting to download
|
174
|
+
@output.print "Attempting to download #{arduino_package} with #{downloader}"
|
174
175
|
download
|
175
176
|
@output.puts
|
176
177
|
end
|
@@ -178,7 +179,7 @@ module ArduinoCI
|
|
178
179
|
end
|
179
180
|
|
180
181
|
if File.exist? extracted_file
|
181
|
-
@output.puts "
|
182
|
+
@output.puts "#{arduino_package} seems to have been extracted already"
|
182
183
|
elsif File.exist? package_file
|
183
184
|
@output.print "Extracting archive with #{extracter}"
|
184
185
|
extract
|
@@ -186,7 +187,7 @@ module ArduinoCI
|
|
186
187
|
end
|
187
188
|
|
188
189
|
if File.exist? self.class.force_install_location
|
189
|
-
@output.puts "
|
190
|
+
@output.puts "#{arduino_package} seems to have been installed already"
|
190
191
|
elsif File.exist? extracted_file
|
191
192
|
install
|
192
193
|
else
|
@@ -110,11 +110,11 @@ module ArduinoCI
|
|
110
110
|
# Forcibly install Arduino from the web
|
111
111
|
# @return [bool] Whether the command succeeded
|
112
112
|
def force_install(output = $stdout, version = DESIRED_ARDUINO_IDE_VERSION)
|
113
|
-
worker_class =
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
worker_class = case Host.os
|
114
|
+
when :osx then ArduinoDownloaderOSX
|
115
|
+
when :windows then ArduinoDownloaderWindows
|
116
|
+
when :linux then ArduinoDownloaderLinux
|
117
|
+
end
|
118
118
|
worker = worker_class.new(version, output)
|
119
119
|
worker.execute
|
120
120
|
end
|