Skip to content

Commit 1acd09b

Browse files
authored
Merge pull request #218 from ianfixes/2020-11-16_arduino-cli
Use `arduino-cli` backend
2 parents becf990 + 1f8ba56 commit 1acd09b

38 files changed

+1043
-1004
lines changed

Diff for: CHANGELOG.md

+17
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99
### Added
10+
- Special handling of attempts to run the `arduino_ci.rb` CI script against the ruby library instead of an actual Arduino project
11+
- Explicit checks for attemping to test `arduino_ci` itself as if it were a library, resolving a minor annoyance to this developer.
12+
- Code coverage tooling
13+
- Explicit check and warning for library directory names that do not match our guess of what the library should/would be called
14+
- Symlink tests for `Host`
1015

1116
### Changed
17+
- Arduino backend is now `arduino-cli` version `0.13.0`
18+
- `ArduinoCmd` is now `ArduinoBackend`
19+
- `CppLibrary` now relies largely on `ArduinoBackend` instead of making its own judgements about libraries (metadata, includes, and examples)
20+
- `ArduinoBackend` functionality related to `CppLibrary` now lives in `CppLibrary`
21+
- `CppLibrary` now works in an installation-first manner for exposure to `arduino-cli`'s logic -- without installation, there is no ability to reason about libraries
22+
- `CppLibrary` forces just-in-time recursive dependency installation in order to work sensibly
23+
- `ArduinoBackend` maintains the central "best guess" logic on what a library (on disk) might be named
1224

1325
### Deprecated
26+
- `arduino_ci_remote.rb` CLI switch `--skip-compilation`
27+
- Deprecated `arduino_ci_remote.rb` in favor of `arduino_ci.rb`
1428

1529
### Removed
30+
- `ARDUINO_CI_SKIP_SPLASH_SCREEN_RSPEC_TESTS` no longer affects any tests because there are no longer splash screens since switching to `arduino-cli`
1631

1732
### Fixed
33+
- Mismatches between library names in `library.properties` and the directory names, which can cause cryptic failures
34+
- `LibraryProperties` skips over parse errors instead of crashing: only lines with non-empty keys and non-nil values are recorded
1835

1936
### Security
2037

Diff for: CONTRIBUTING.md

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ See `SampleProjects/TestSomething/test/*.cpp` for the existing tests (run by rsp
3131

3232
To speed up testing by targeting only the files you're working on, you can set several environment variables that `bundle exec rspec` will respond to:
3333

34-
* `ARDUINO_CI_SKIP_SPLASH_SCREEN_RSPEC_TESTS`: if set, this will avoid any rspec test that calls the arduino executable (and as such, causes the splash screen to pop up).
3534
* `ARDUINO_CI_SKIP_RUBY_RSPEC_TESTS`: if set, this will skip all tests against ruby code (useful if you are not changing Ruby code).
3635
* `ARDUINO_CI_SKIP_CPP_RSPEC_TESTS`: if set, this will skip all tests against the `TestSomething` sample project (useful if you are not changing C++ code).
3736
* `ARDUINO_CI_SELECT_CPP_TESTS=<glob>`: if set, this will skip all C++ unit tests whose filenames don't match the provided glob (executed in the tests directory)

Diff for: Gemfile

+7
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
44

55
# Specify your gem's dependencies in arduino_ci.gemspec
66
gemspec
7+
8+
gem "bundler", "> 1.15", require: false, group: :test
9+
gem "keepachangelog_manager", "~> 0.0.2", require: false, group: :test
10+
gem "rspec", "~> 3.0", require: false, group: :test
11+
gem 'rubocop', '~>0.59.0', require: false, group: :test
12+
gem 'simplecov', require: false, group: :test
13+
gem 'yard', '~>0.9.11', require: false, group: :test

Diff for: README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
# ArduinoCI Ruby gem (`arduino_ci`)
3-
[![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci)
2+
# ArduinoCI Ruby gem (`arduino_ci`)
3+
[![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci)
44
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/0.4.0)
55
[![Gitter](https://badges.gitter.im/Arduino-CI/arduino_ci.svg)](https://gitter.im/Arduino-CI/arduino_ci?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
66

@@ -36,6 +36,9 @@ For a bare-bones example that you can copy from, see [SampleProjects/DoSomething
3636

3737
The complete set of C++ unit tests for the `arduino_ci` library itself are in the [SampleProjects/TestSomething](SampleProjects/TestSomething) project. The [test files](SampleProjects/TestSomething/test/) are named after the type of feature being tested.
3838

39+
> Arduino expects all libraries to be in a specific `Arduino/libraries` directory on your system. If your library is elsewhere, `arduino_ci` will _automatically_ create a symbolic link in the `libraries` directory that points to the directory of the project being tested. This simplifieds working with project dependencies, but **it can have unintended consequences on Windows systems** because [in some cases deleting a folder that contains a symbolic link to another folder can cause the _entire linked folder_ to be removed instead of just the link itself](https://superuser.com/a/306618).
40+
>
41+
> If you use a Windows system **it is recommended that you only run `arduino_ci` from project directories that are already inside the `libraries` directory**
3942
4043
### You Need Ruby and Bundler
4144

Diff for: SampleProjects/ExcludeSomething/library.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name=TestSomething
1+
name=ExcludeSomething
22
version=0.1.0
33
author=Ian Katz <[email protected]>
44
maintainer=Ian Katz <[email protected]>

Diff for: SampleProjects/NetworkLib/library.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name=Ethernet
1+
name=NetworkLib
22
version=0.1.0
33
author=James Foster <[email protected]>
44
maintainer=James Foster <[email protected]>

Diff for: arduino_ci.gemspec

-6
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,4 @@ Gem::Specification.new do |spec|
2727

2828
spec.add_dependency "os", "~> 1.0"
2929
spec.add_dependency "rubyzip", "~> 1.2"
30-
31-
spec.add_development_dependency "bundler", "> 1.15"
32-
spec.add_development_dependency "keepachangelog_manager", "~> 0.0.2"
33-
spec.add_development_dependency "rspec", "~> 3.0"
34-
spec.add_development_dependency 'rubocop', '~>0.59.0'
35-
spec.add_development_dependency 'yard', '~>0.9.11'
3630
end

Diff for: exe/arduino_ci.rb

100644100755
+90-84
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
@failure_count = 0
1111
@passfail = proc { |result| result ? "✓" : "✗" }
12+
@backend = nil
1213

1314
# Use some basic parsing to allow command-line overrides of config
1415
class Parser
@@ -29,11 +30,6 @@ def self.parse(options)
2930
output_options[:skip_unittests] = p
3031
end
3132

32-
opts.on("--skip-compilation", "Don't compile example sketches (deprecated)") do |p|
33-
puts "The option --skip-compilation has been deprecated in favor of --skip-examples-compilation"
34-
output_options[:skip_compilation] = p
35-
end
36-
3733
opts.on("--skip-examples-compilation", "Don't compile example sketches") do |p|
3834
output_options[:skip_compilation] = p
3935
end
@@ -68,11 +64,11 @@ def self.parse(options)
6864
def terminate(final = nil)
6965
puts "Failures: #{@failure_count}"
7066
unless @failure_count.zero? || final
71-
puts "Last message: #{@arduino_cmd.last_msg}"
67+
puts "Last message: #{@backend.last_msg}"
7268
puts "========== Stdout:"
73-
puts @arduino_cmd.last_out
69+
puts @backend.last_out
7470
puts "========== Stderr:"
75-
puts @arduino_cmd.last_err
71+
puts @backend.last_err
7672
end
7773
retcode = @failure_count.zero? ? 0 : 1
7874
exit(retcode)
@@ -161,7 +157,7 @@ def file_is_hidden_somewhere?(path)
161157
# print out some files
162158
def display_files(pathname)
163159
# `find` doesn't follow symlinks, so we should instead
164-
realpath = pathname.symlink? ? pathname.readlink : pathname
160+
realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname
165161

166162
# suppress directories and dotfile-based things
167163
all_files = realpath.find.select(&:file?)
@@ -172,25 +168,33 @@ def display_files(pathname)
172168
non_hidden.each { |p| puts "#{margin}#{p}" }
173169
end
174170

175-
def install_arduino_library_dependencies(aux_libraries)
176-
aux_libraries.each do |l|
177-
if @arduino_cmd.library_present?(l)
178-
inform("Using pre-existing library") { l.to_s }
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 }
179179
else
180-
assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) }
180+
assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
181+
next nil unless l.install
182+
183+
l.name
184+
end
181185
end
186+
installed << l.name
187+
installed += install_arduino_library_dependencies(l.arduino_library_dependencies, l.name, installed)
182188
end
189+
installed
183190
end
184191

185-
def perform_unit_tests(file_config)
192+
def perform_unit_tests(cpp_library, file_config)
186193
if @cli_options[:skip_unittests]
187194
inform("Skipping unit tests") { "as requested via command line" }
188195
return
189196
end
190197
config = file_config.with_override_config(@cli_options[:ci_config])
191-
cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."),
192-
@arduino_cmd.lib_dir,
193-
config.exclude_dirs.map(&Pathname.method(:new)))
194198

195199
# check GCC
196200
compilers = config.compilers_to_use
@@ -214,10 +218,25 @@ def perform_unit_tests(file_config)
214218

215219
# iterate boards / tests
216220
if !cpp_library.tests_dir.exist?
217-
inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do
218-
puts " In case that's an error, this is what was found in the library:"
219-
display_files(cpp_library.tests_dir.parent)
220-
true
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
221240
end
222241
elsif cpp_library.test_files.empty?
223242
inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do
@@ -228,7 +247,7 @@ def perform_unit_tests(file_config)
228247
elsif config.platforms_to_unittest.empty?
229248
inform("Skipping unit tests") { "no platforms were requested" }
230249
else
231-
install_arduino_library_dependencies(config.aux_libraries_for_unittest)
250+
install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
232251

233252
config.platforms_to_unittest.each do |p|
234253
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
@@ -256,39 +275,12 @@ def perform_unit_tests(file_config)
256275
end
257276
end
258277

259-
def perform_compilation_tests(config)
278+
def perform_example_compilation_tests(cpp_library, config)
260279
if @cli_options[:skip_compilation]
261280
inform("Skipping compilation of examples") { "as requested via command line" }
262281
return
263282
end
264283

265-
# index the existing libraries
266-
attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed
267-
268-
# initialize library under test
269-
installed_library_path = attempt("Installing library under test") do
270-
@arduino_cmd.install_local_library(Pathname.new("."))
271-
end
272-
273-
if !installed_library_path.nil? && installed_library_path.exist?
274-
inform("Library installed at") { installed_library_path.to_s }
275-
else
276-
assure_multiline("Library installed successfully") do
277-
if installed_library_path.nil?
278-
puts @arduino_cmd.last_msg
279-
else
280-
# print out the contents of the deepest directory we actually find
281-
@arduino_cmd.lib_dir.ascend do |path_part|
282-
next unless path_part.exist?
283-
284-
break display_files(path_part)
285-
end
286-
false
287-
end
288-
end
289-
end
290-
library_examples = @arduino_cmd.library_examples(installed_library_path)
291-
292284
# gather up all required boards for compilation so we can install them up front.
293285
# start with the "platforms to unittest" and add the examples
294286
# while we're doing that, get the aux libraries as well
@@ -297,6 +289,7 @@ def perform_compilation_tests(config)
297289
aux_libraries = Set.new(config.aux_libraries_for_build)
298290
# while collecting the platforms, ensure they're defined
299291

292+
library_examples = cpp_library.example_sketches
300293
library_examples.each do |path|
301294
ovr_config = config.from_example(path)
302295
ovr_config.platforms_to_build.each do |platform|
@@ -317,35 +310,36 @@ def perform_compilation_tests(config)
317310
# do that, set the URLs, and download the packages
318311
all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
319312

313+
builtin_packages, external_packages = all_packages.partition { |p| config.package_builtin?(p) }
314+
320315
# inform about builtin packages
321-
all_packages.select { |p| config.package_builtin?(p) }.each do |p|
316+
builtin_packages.each do |p|
322317
inform("Using built-in board package") { p }
323318
end
324319

325320
# make sure any non-builtin package has a URL defined
326-
all_packages.reject { |p| config.package_builtin?(p) }.each do |p|
321+
external_packages.each do |p|
327322
assure("Board package #{p} has a defined URL") { board_package_url[p] }
328323
end
329324

330325
# set up all the board manager URLs.
331326
# we can safely reject nils now, they would be for the builtins
332-
all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
327+
all_urls = external_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
333328

334329
unless all_urls.empty?
335330
assure("Setting board manager URLs") do
336-
@arduino_cmd.board_manager_urls = all_urls
331+
@backend.board_manager_urls = all_urls
337332
end
338333
end
339334

340-
all_packages.each do |p|
335+
external_packages.each do |p|
341336
assure("Installing board package #{p}") do
342-
@arduino_cmd.install_boards(p)
337+
@backend.install_boards(p)
343338
end
344339
end
345340

346-
install_arduino_library_dependencies(aux_libraries)
341+
install_arduino_library_dependencies(aux_libraries, "<compile/libraries>")
347342

348-
last_board = nil
349343
if config.platforms_to_build.empty?
350344
inform("Skipping builds") { "no platforms were requested" }
351345
return
@@ -356,46 +350,58 @@ def perform_compilation_tests(config)
356350
return
357351
end
358352

359-
attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") }
360-
361-
# switching boards takes time, so iterate board first
362-
# _then_ whichever examples match it
363-
examples_by_platform = library_examples.each_with_object({}) do |example_path, acc|
353+
library_examples.each do |example_path|
364354
ovr_config = config.from_example(example_path)
365355
ovr_config.platforms_to_build.each do |p|
366-
acc[p] = [] unless acc.key?(p)
367-
acc[p] << example_path
368-
end
369-
end
370-
371-
examples_by_platform.each do |platform, example_paths|
372-
board = example_platform_info[platform][:board]
373-
assure("Switching to board for #{platform} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board
374-
last_board = board
375-
376-
example_paths.each do |example_path|
356+
board = example_platform_info[p][:board]
377357
example_name = File.basename(example_path)
378-
attempt("Verifying #{example_name}") do
379-
ret = @arduino_cmd.verify_sketch(example_path)
358+
attempt("Compiling #{example_name} for #{board}") do
359+
ret = @backend.compile_sketch(example_path, board)
380360
unless ret
381361
puts
382-
puts "Last command: #{@arduino_cmd.last_msg}"
383-
puts @arduino_cmd.last_err
362+
puts "Last command: #{@backend.last_msg}"
363+
puts @backend.last_err
384364
end
385365
ret
386366
end
387367
end
388368
end
389-
390369
end
391370

392371
# initialize command and config
393372
config = ArduinoCI::CIConfig.default.from_project_library
394373

395-
@arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
396-
inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s }
374+
@backend = ArduinoCI::ArduinoInstallation.autolocate!
375+
inform("Located arduino-cli binary") { @backend.binary_path.to_s }
376+
377+
# initialize library under test
378+
cpp_library_path = Pathname.new(".")
379+
cpp_library = assure("Installing library under test") do
380+
@backend.install_local_library(cpp_library_path)
381+
end
382+
383+
assumed_name = @backend.name_of_library(cpp_library_path)
384+
ondisk_name = cpp_library_path.realpath.basename
385+
if assumed_name != ondisk_name
386+
inform("WARNING") { "Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'" }
387+
end
388+
389+
if !cpp_library.nil?
390+
inform("Library installed at") { cpp_library.path.to_s }
391+
else
392+
# this is a longwinded way of failing, we aren't really "assuring" anything at this point
393+
assure_multiline("Library installed successfully") do
394+
puts @backend.last_msg
395+
false
396+
end
397+
end
398+
399+
install_arduino_library_dependencies(
400+
cpp_library.arduino_library_dependencies,
401+
"<#{ArduinoCI::CppLibrary::LIBRARY_PROPERTIES_FILE}>"
402+
)
397403

398-
perform_unit_tests(config)
399-
perform_compilation_tests(config)
404+
perform_unit_tests(cpp_library, config)
405+
perform_example_compilation_tests(cpp_library, config)
400406

401407
terminate(true)

0 commit comments

Comments
 (0)