Skip to content

Commit ba73d16

Browse files
committed
Switch to arduino-cli 0.13.0 backend
1 parent becf990 commit ba73d16

17 files changed

+203
-565
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99
### Added
1010

1111
### Changed
12+
- Arduino backend is now `arduino-cli` version `0.13.0`
1213

1314
### Deprecated
15+
- `arduino_ci_remote.rb` CLI switch `--skip-compilation`
16+
- Deprecated `arduino_ci_remote.rb` in favor of `arduino_ci.rb`
1417

1518
### Removed
19+
- `ARDUINO_CI_SKIP_SPLASH_SCREEN_RSPEC_TESTS` no longer affects any tests because there are no longer splash screens since switching to `arduino-cli`
1620

1721
### Fixed
1822

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: exe/arduino_ci.rb

100644100755
+3-12
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,6 @@ def perform_compilation_tests(config)
262262
return
263263
end
264264

265-
# index the existing libraries
266-
attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed
267-
268265
# initialize library under test
269266
installed_library_path = attempt("Installing library under test") do
270267
@arduino_cmd.install_local_library(Pathname.new("."))
@@ -345,7 +342,6 @@ def perform_compilation_tests(config)
345342

346343
install_arduino_library_dependencies(aux_libraries)
347344

348-
last_board = nil
349345
if config.platforms_to_build.empty?
350346
inform("Skipping builds") { "no platforms were requested" }
351347
return
@@ -356,8 +352,6 @@ def perform_compilation_tests(config)
356352
return
357353
end
358354

359-
attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") }
360-
361355
# switching boards takes time, so iterate board first
362356
# _then_ whichever examples match it
363357
examples_by_platform = library_examples.each_with_object({}) do |example_path, acc|
@@ -370,13 +364,10 @@ def perform_compilation_tests(config)
370364

371365
examples_by_platform.each do |platform, example_paths|
372366
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-
376367
example_paths.each do |example_path|
377368
example_name = File.basename(example_path)
378-
attempt("Verifying #{example_name}") do
379-
ret = @arduino_cmd.verify_sketch(example_path)
369+
attempt("Compiling #{example_name} for #{board}") do
370+
ret = @arduino_cmd.compile_sketch(example_path, board)
380371
unless ret
381372
puts
382373
puts "Last command: #{@arduino_cmd.last_msg}"
@@ -393,7 +384,7 @@ def perform_compilation_tests(config)
393384
config = ArduinoCI::CIConfig.default.from_project_library
394385

395386
@arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
396-
inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s }
387+
inform("Located arduino-cli binary") { @arduino_cmd.binary_path.to_s }
397388

398389
perform_unit_tests(config)
399390
perform_compilation_tests(config)

Diff for: lib/arduino_ci/arduino_cmd.rb

+47-148
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'fileutils'
22
require 'pathname'
3+
require 'json'
34

45
# workaround for https://github.com/arduino/Arduino/issues/3535
56
WORKAROUND_LIB = "USBHost".freeze
@@ -24,17 +25,10 @@ def self.flag(name, text = nil)
2425
self.class_eval("def flag_#{name};\"#{text}\";end", __FILE__, __LINE__)
2526
end
2627

27-
# the array of command components to launch the Arduino executable
28-
# @return [Array<String>]
29-
attr_accessor :base_cmd
30-
3128
# the actual path to the executable on this platform
3229
# @return [Pathname]
3330
attr_accessor :binary_path
3431

35-
# part of a workaround for https://github.com/arduino/Arduino/issues/3535
36-
attr_reader :libraries_indexed
37-
3832
# @return [String] STDOUT of the most recently-run command
3933
attr_reader :last_out
4034

@@ -44,97 +38,29 @@ def self.flag(name, text = nil)
4438
# @return [String] the most recently-run command
4539
attr_reader :last_msg
4640

41+
# @return [Array<String>] Additional URLs for the boards manager
42+
attr_reader :additional_urls
43+
4744
# set the command line flags (undefined for now).
4845
# These vary between gui/cli. Inline comments added for greppability
49-
flag :get_pref # flag_get_pref
50-
flag :set_pref # flag_set_pref
51-
flag :save_prefs # flag_save_prefs
52-
flag :use_board # flag_use_board
5346
flag :install_boards # flag_install_boards
5447
flag :install_library # flag_install_library
5548
flag :verify # flag_verify
5649

57-
def initialize
58-
@prefs_cache = {}
59-
@prefs_fetched = false
60-
@libraries_indexed = false
50+
def initialize(binary_path)
51+
@binary_path = binary_path
52+
@additional_urls = []
6153
@last_out = ""
6254
@last_err = ""
6355
@last_msg = ""
6456
end
6557

66-
# Convert a preferences dump into a flat hash
67-
# @param arduino_output [String] The raw Arduino executable output
68-
# @return [Hash] preferences as a hash
69-
def parse_pref_string(arduino_output)
70-
lines = arduino_output.split("\n").select { |l| l.include? "=" }
71-
ret = lines.each_with_object({}) do |e, acc|
72-
parts = e.split("=", 2)
73-
acc[parts[0]] = parts[1]
74-
acc
75-
end
76-
ret
77-
end
78-
79-
# @return [String] the path to the Arduino libraries directory
80-
def lib_dir
81-
Pathname.new(get_pref("sketchbook.path")) + "libraries"
82-
end
83-
84-
# fetch preferences in their raw form
85-
# @return [String] Preferences as a set of lines
86-
def _prefs_raw
87-
resp = run_and_capture(flag_get_pref)
88-
fail_msg = "Arduino binary failed to operate as expected; you will have to troubleshoot it manually"
89-
raise ArduinoExecutionError, "#{fail_msg}. The command was #{@last_msg}" unless resp[:success]
90-
91-
@prefs_fetched = true
92-
resp[:out]
93-
end
94-
95-
# Get the Arduino preferences, from cache if possible
96-
# @return [Hash] The full set of preferences
97-
def prefs
98-
prefs_raw = _prefs_raw unless @prefs_fetched
99-
return nil if prefs_raw.nil?
100-
101-
@prefs_cache = parse_pref_string(prefs_raw)
102-
@prefs_cache.clone
103-
end
104-
105-
# get a preference key
106-
# @param key [String] The preferences key to look up
107-
# @return [String] The preference value
108-
def get_pref(key)
109-
data = @prefs_fetched ? @prefs_cache : prefs
110-
data[key]
111-
end
112-
113-
# underlying preference-setter.
114-
# @param key [String] The preference name
115-
# @param value [String] The value to set to
116-
# @return [bool] whether the command succeeded
117-
def _set_pref(key, value)
118-
run_and_capture(flag_set_pref, "#{key}=#{value}", flag_save_prefs)[:success]
119-
end
120-
121-
# set a preference key/value pair, and update the cache.
122-
# @param key [String] the preference key
123-
# @param value [String] the preference value
124-
# @return [bool] whether the command succeeded
125-
def set_pref(key, value)
126-
prefs unless @prefs_fetched # update cache first
127-
success = _set_pref(key, value)
128-
@prefs_cache[key] = value if success
129-
success
130-
end
131-
13258
def _wrap_run(work_fn, *args, **kwargs)
13359
# do some work to extract & merge environment variables if they exist
13460
has_env = !args.empty? && args[0].class == Hash
13561
env_vars = has_env ? args[0] : {}
13662
actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args
137-
full_args = @base_cmd + actual_args
63+
full_args = [binary_path.to_s, "--format", "json"] + actual_args
13864
full_cmd = env_vars.empty? ? full_args : [env_vars] + full_args
13965

14066
shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ")
@@ -156,19 +82,34 @@ def run_and_capture(*args, **kwargs)
15682
ret
15783
end
15884

85+
def capture_json(*args, **kwargs)
86+
ret = run_and_capture(*args, **kwargs)
87+
ret[:json] = JSON.parse(ret[:out])
88+
end
89+
90+
# Get a dump of the entire config
91+
# @return [Hash] The configuration
92+
def config_dump
93+
capture_json("config", "dump")
94+
end
95+
96+
# @return [String] the path to the Arduino libraries directory
97+
def lib_dir
98+
Pathname.new(config_dump["directories"]["user"]) + "libraries"
99+
end
100+
159101
# Board manager URLs
160102
# @return [Array<String>] The additional URLs used by the board manager
161103
def board_manager_urls
162-
url_list = get_pref("boardsmanager.additional.urls")
163-
return [] if url_list.nil?
164-
165-
url_list.split(",")
104+
config_dump["board_manager"]["additional_urls"] + @additional_urls
166105
end
167106

168107
# Set board manager URLs
169108
# @return [Array<String>] The additional URLs used by the board manager
170109
def board_manager_urls=(all_urls)
171-
set_pref("boardsmanager.additional.urls", all_urls.join(","))
110+
raise ArgumentError("all_urls should be an array, got #{all_urls.class}") unless all_urls.is_a? Array
111+
112+
@additional_urls = all_urls
172113
end
173114

174115
# check whether a board is installed
@@ -177,48 +118,33 @@ def board_manager_urls=(all_urls)
177118
# @param boardname [String] The board to test
178119
# @return [bool] Whether the board is installed
179120
def board_installed?(boardname)
180-
run_and_capture(flag_use_board, boardname)[:success]
121+
# capture_json("core", "list")[:json].find { |b| b["ID"] == boardname } # nope, this is for the family
122+
run_and_capture("board", "details", "--fqbn", boardname)[:success]
181123
end
182124

183125
# install a board by name
184126
# @param name [String] the board name
185127
# @return [bool] whether the command succeeded
186128
def install_boards(boardfamily)
187-
# TODO: find out why IO.pipe fails but File::NULL succeeds :(
188-
result = run_and_capture(flag_install_boards, boardfamily)
189-
already_installed = result[:err].include?("Platform is already installed!")
190-
result[:success] || already_installed
129+
result = run_and_capture("core", "install", boardfamily)
130+
result[:success]
191131
end
192132

193-
# install a library by name
194-
# @param name [String] the library name
195-
# @return [bool] whether the command succeeded
196-
def _install_library(library_name)
197-
result = run_and_capture(flag_install_library, library_name)
198-
199-
already_installed = result[:err].include?("Library is already installed: #{library_name}")
200-
success = result[:success] || already_installed
201-
202-
@libraries_indexed = (@libraries_indexed || success) if library_name == WORKAROUND_LIB
203-
success
204-
end
205-
206-
# index the set of libraries by installing a dummy library
207-
# related to WORKAROUND_LIB and https://github.com/arduino/Arduino/issues/3535
208-
# TODO: unclear if this is still necessary
209-
def index_libraries
210-
return true if @libraries_indexed
211-
212-
_install_library(WORKAROUND_LIB)
213-
@libraries_indexed
133+
# @return [Hash] information about installed libraries via the CLI
134+
def installed_libraries
135+
capture_json("lib", "list")[:json]
214136
end
215137

216138
# install a library by name
217139
# @param name [String] the library name
140+
# @param version [String] the version to install
218141
# @return [bool] whether the command succeeded
219-
def install_library(library_name)
220-
index_libraries
221-
_install_library(library_name)
142+
def install_library(library_name, version = nil)
143+
return true if library_present?(library_name)
144+
145+
fqln = version.nil? ? library_name : "#{library_name}@#{version}"
146+
result = run_and_capture("lib", "install", fqln)
147+
result[:success]
222148
end
223149

224150
# generate the (very likely) path of a library given its name
@@ -239,47 +165,20 @@ def library_present?(library_name)
239165
library_path(library_name).exist?
240166
end
241167

242-
# update the library index
243-
# @return [bool] Whether the update succeeded
244-
def update_library_index
245-
# install random lib so the arduino IDE grabs a new library index
246-
# see: https://github.com/arduino/Arduino/issues/3535
247-
install_library(WORKAROUND_LIB)
248-
end
249-
250-
# use a particular board for compilation
168+
# @param path [String] The sketch to compile
251169
# @param boardname [String] The board to use
252170
# @return [bool] whether the command succeeded
253-
def use_board(boardname)
254-
run_and_capture(flag_use_board, boardname, flag_save_prefs)[:success]
255-
end
256-
257-
# use a particular board for compilation, installing it if necessary
258-
# @param boardname [String] The board to use
259-
# @return [bool] whether the command succeeded
260-
def use_board!(boardname)
261-
return true if use_board(boardname)
262-
263-
boardfamily = boardname.split(":")[0..1].join(":")
264-
puts "Board '#{boardname}' not found; attempting to install '#{boardfamily}'"
265-
return false unless install_boards(boardfamily) # guess board family from first 2 :-separated fields
266-
267-
use_board(boardname)
268-
end
269-
270-
# @param path [String] The sketch to verify
271-
# @return [bool] whether the command succeeded
272-
def verify_sketch(path)
171+
def compile_sketch(path, boardname)
273172
ext = File.extname path
274173
unless ext.casecmp(".ino").zero?
275-
@last_msg = "Refusing to verify sketch with '#{ext}' extension -- rename it to '.ino'!"
174+
@last_msg = "Refusing to compile sketch with '#{ext}' extension -- rename it to '.ino'!"
276175
return false
277176
end
278177
unless File.exist? path
279-
@last_msg = "Can't verify Sketch at nonexistent path '#{path}'!"
178+
@last_msg = "Can't compile Sketch at nonexistent path '#{path}'!"
280179
return false
281180
end
282-
ret = run_and_capture(flag_verify, path)
181+
ret = run_and_capture("compile", "--fqbn", boardname, "--warnings", "all", "--dry-run", path)
283182
ret[:success]
284183
end
285184

Diff for: lib/arduino_ci/arduino_cmd_linux.rb

-17
This file was deleted.

Diff for: lib/arduino_ci/arduino_cmd_linux_builder.rb

-19
This file was deleted.

0 commit comments

Comments
 (0)