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.
@@ -1,394 +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 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"
@@ -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 "Arduino package seems to have been downloaded already" if attempts.zero?
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 Arduino package with #{downloader}"
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 "Arduino package seems to have been extracted already"
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 "Arduino package seems to have been installed already"
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 = case Host.os
114
- when :osx then ArduinoDownloaderOSX
115
- when :windows then ArduinoDownloaderWindows
116
- when :linux then ArduinoDownloaderLinux
117
- end
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