arduino_ci 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -7
- data/cpp/arduino/Client.h +1 -0
- data/exe/arduino_ci.rb +85 -86
- data/exe/arduino_library_location.rb +2 -2
- data/lib/arduino_ci/arduino_backend.rb +218 -0
- data/lib/arduino_ci/arduino_downloader.rb +42 -73
- data/lib/arduino_ci/arduino_downloader_linux.rb +17 -55
- data/lib/arduino_ci/arduino_downloader_osx.rb +21 -33
- data/lib/arduino_ci/arduino_downloader_windows.rb +11 -53
- data/lib/arduino_ci/arduino_installation.rb +13 -75
- data/lib/arduino_ci/ci_config.rb +0 -7
- data/lib/arduino_ci/cpp_library.rb +166 -79
- data/lib/arduino_ci/host.rb +59 -4
- data/lib/arduino_ci/library_properties.rb +12 -2
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +6 -2
- metadata +3 -78
- data/lib/arduino_ci/arduino_cmd.rb +0 -332
- data/lib/arduino_ci/arduino_cmd_linux.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_linux_builder.rb +0 -19
- data/lib/arduino_ci/arduino_cmd_osx.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_windows.rb +0 -17
- data/lib/arduino_ci/installed_cpp_library.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c195767b10bc2c1a3e837b89f087962251fb514c
|
4
|
+
data.tar.gz: 2188e52c1fa37664c72067bec73281fc8d5582e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef354ff7e588004f7d8394999d3044edd1a40aeb2187fe607a4cd76535f7bd23aabfb916226640dfa3778f084b67f32ffd1a30e22b86e7a9b06094ac021d7130
|
7
|
+
data.tar.gz: 3e53f5fefb40d4fe7593e66118f7e51b73b01921cff1b2489787ccadcca5dadac2be3f9d9b2947ad0018f692747984c092365157d3a22a7cbdc180c51cde0ef4
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
|
-
# ArduinoCI Ruby gem (`arduino_ci`)
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci)
|
4
|
-
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/0.
|
2
|
+
# ArduinoCI Ruby gem (`arduino_ci`)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci)
|
4
|
+
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/1.0.0)
|
5
5
|
[![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)
|
6
6
|
|
7
7
|
You want to run tests on your Arduino library (bonus: without hardware present), but the IDE doesn't support that. Arduino CI provides that ability.
|
@@ -10,13 +10,14 @@ You want to precisely replicate certain software states in your library, but you
|
|
10
10
|
|
11
11
|
You want your Arduino library to be automatically built and tested every time someone contributes code to your project on GitHub, but the Arduino IDE lacks the ability to run unit tests. [Arduino CI](https://github.com/Arduino-CI/arduino_ci) provides that ability.
|
12
12
|
|
13
|
-
`arduino_ci` is a cross-platform build/test system, consisting of a Ruby gem and a series of C++ mocks. It enables tests to be run both locally and as part of a CI service like
|
13
|
+
`arduino_ci` is a cross-platform build/test system, consisting of a Ruby gem and a series of C++ mocks. It enables tests to be run both locally and as part of a CI service like GitHub Actions, TravisCI, Appveyor, etc. Any OS that can run the Arduino IDE can run `arduino_ci`.
|
14
|
+
|
14
15
|
|
15
16
|
Platform | CI Status
|
16
17
|
---------|:---------
|
17
|
-
OSX | [![OSX Build Status](
|
18
|
-
Linux | [![Linux Build Status](
|
19
|
-
Windows | [![Windows Build status](https://
|
18
|
+
OSX | [![OSX Build Status](https://github.com/Arduino-CI/arduino_ci/workflows/macos/badge.svg)](https://github.com/Arduino-CI/arduino_ci/actions?workflow=macos)
|
19
|
+
Linux | [![Linux Build Status](https://github.com/Arduino-CI/arduino_ci/workflows/linux/badge.svg)](https://github.com/Arduino-CI/arduino_ci/actions?workflow=linux)
|
20
|
+
Windows | [![Windows Build status](https://github.com/Arduino-CI/arduino_ci/workflows/windows/badge.svg)](https://github.com/Arduino-CI/arduino_ci/actions?workflow=windows)
|
20
21
|
|
21
22
|
|
22
23
|
## Comparison to Other Arduino Testing Tools
|
@@ -36,6 +37,9 @@ For a bare-bones example that you can copy from, see [SampleProjects/DoSomething
|
|
36
37
|
|
37
38
|
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.
|
38
39
|
|
40
|
+
> 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).
|
41
|
+
>
|
42
|
+
> 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**
|
39
43
|
|
40
44
|
### You Need Ruby and Bundler
|
41
45
|
|
@@ -127,6 +131,28 @@ The following prerequisites must be fulfilled:
|
|
127
131
|
> **Note:** `arduino_ci.rb` expects to be run from the root directory of your Arduino project library.
|
128
132
|
|
129
133
|
|
134
|
+
#### GitHub Actions
|
135
|
+
|
136
|
+
GitHub Actions allows you to automate your workflows directly in GitHub.
|
137
|
+
No additional steps are needed.
|
138
|
+
Just create a YAML file with the information below in your repo under the `.github/workflows/` directory.
|
139
|
+
|
140
|
+
```yaml
|
141
|
+
on: [push, pull_request]
|
142
|
+
jobs:
|
143
|
+
runTest:
|
144
|
+
runs-on: ubuntu-latest
|
145
|
+
steps:
|
146
|
+
- uses: actions/checkout@v2
|
147
|
+
- uses: ruby/setup-ruby@v1
|
148
|
+
with:
|
149
|
+
ruby-version: 2.6
|
150
|
+
- run: |
|
151
|
+
bundle install
|
152
|
+
bundle exec arduino_ci_remote.rb
|
153
|
+
```
|
154
|
+
|
155
|
+
|
130
156
|
#### Travis CI
|
131
157
|
|
132
158
|
You'll need to go to https://travis-ci.org/profile/ and enable testing for your Arduino project. Once that happens, you should be all set. The script will test all example projects of the library and all unit tests.
|
@@ -155,6 +181,7 @@ test_script:
|
|
155
181
|
- bundle exec arduino_ci.rb
|
156
182
|
```
|
157
183
|
|
184
|
+
|
158
185
|
## Known Problems
|
159
186
|
|
160
187
|
* The Arduino library is not fully mocked.
|
data/cpp/arduino/Client.h
CHANGED
data/exe/arduino_ci.rb
CHANGED
@@ -9,6 +9,7 @@ FIND_FILES_INDENT = 4
|
|
9
9
|
|
10
10
|
@failure_count = 0
|
11
11
|
@passfail = proc { |result| result ? "✓" : "✗" }
|
12
|
+
@backend = nil
|
12
13
|
|
13
14
|
# Use some basic parsing to allow command-line overrides of config
|
14
15
|
class Parser
|
@@ -29,11 +30,6 @@ class Parser
|
|
29
30
|
output_options[:skip_unittests] = p
|
30
31
|
end
|
31
32
|
|
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
|
-
|
37
33
|
opts.on("--skip-examples-compilation", "Don't compile example sketches") do |p|
|
38
34
|
output_options[:skip_compilation] = p
|
39
35
|
end
|
@@ -68,11 +64,11 @@ end
|
|
68
64
|
def terminate(final = nil)
|
69
65
|
puts "Failures: #{@failure_count}"
|
70
66
|
unless @failure_count.zero? || final
|
71
|
-
puts "Last message: #{@
|
67
|
+
puts "Last message: #{@backend.last_msg}"
|
72
68
|
puts "========== Stdout:"
|
73
|
-
puts @
|
69
|
+
puts @backend.last_out
|
74
70
|
puts "========== Stderr:"
|
75
|
-
puts @
|
71
|
+
puts @backend.last_err
|
76
72
|
end
|
77
73
|
retcode = @failure_count.zero? ? 0 : 1
|
78
74
|
exit(retcode)
|
@@ -161,7 +157,7 @@ end
|
|
161
157
|
# print out some files
|
162
158
|
def display_files(pathname)
|
163
159
|
# `find` doesn't follow symlinks, so we should instead
|
164
|
-
realpath =
|
160
|
+
realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname
|
165
161
|
|
166
162
|
# suppress directories and dotfile-based things
|
167
163
|
all_files = realpath.find.select(&:file?)
|
@@ -172,25 +168,33 @@ def display_files(pathname)
|
|
172
168
|
non_hidden.each { |p| puts "#{margin}#{p}" }
|
173
169
|
end
|
174
170
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
179
|
else
|
180
|
-
assure("Installing
|
180
|
+
assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
|
181
|
+
next nil unless l.install
|
182
|
+
|
183
|
+
l.name
|
184
|
+
end
|
181
185
|
end
|
186
|
+
installed << l.name
|
187
|
+
installed += install_arduino_library_dependencies(l.arduino_library_dependencies, l.name, installed)
|
182
188
|
end
|
189
|
+
installed
|
183
190
|
end
|
184
191
|
|
185
|
-
def perform_unit_tests(file_config)
|
192
|
+
def perform_unit_tests(cpp_library, file_config)
|
186
193
|
if @cli_options[:skip_unittests]
|
187
194
|
inform("Skipping unit tests") { "as requested via command line" }
|
188
195
|
return
|
189
196
|
end
|
190
197
|
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)))
|
194
198
|
|
195
199
|
# check GCC
|
196
200
|
compilers = config.compilers_to_use
|
@@ -214,10 +218,25 @@ def perform_unit_tests(file_config)
|
|
214
218
|
|
215
219
|
# iterate boards / tests
|
216
220
|
if !cpp_library.tests_dir.exist?
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
221
240
|
end
|
222
241
|
elsif cpp_library.test_files.empty?
|
223
242
|
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)
|
|
228
247
|
elsif config.platforms_to_unittest.empty?
|
229
248
|
inform("Skipping unit tests") { "no platforms were requested" }
|
230
249
|
else
|
231
|
-
install_arduino_library_dependencies(config.aux_libraries_for_unittest)
|
250
|
+
install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
|
232
251
|
|
233
252
|
config.platforms_to_unittest.each do |p|
|
234
253
|
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
|
@@ -256,39 +275,12 @@ def perform_unit_tests(file_config)
|
|
256
275
|
end
|
257
276
|
end
|
258
277
|
|
259
|
-
def
|
278
|
+
def perform_example_compilation_tests(cpp_library, config)
|
260
279
|
if @cli_options[:skip_compilation]
|
261
280
|
inform("Skipping compilation of examples") { "as requested via command line" }
|
262
281
|
return
|
263
282
|
end
|
264
283
|
|
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
|
-
|
292
284
|
# gather up all required boards for compilation so we can install them up front.
|
293
285
|
# start with the "platforms to unittest" and add the examples
|
294
286
|
# while we're doing that, get the aux libraries as well
|
@@ -297,6 +289,7 @@ def perform_compilation_tests(config)
|
|
297
289
|
aux_libraries = Set.new(config.aux_libraries_for_build)
|
298
290
|
# while collecting the platforms, ensure they're defined
|
299
291
|
|
292
|
+
library_examples = cpp_library.example_sketches
|
300
293
|
library_examples.each do |path|
|
301
294
|
ovr_config = config.from_example(path)
|
302
295
|
ovr_config.platforms_to_build.each do |platform|
|
@@ -317,13 +310,8 @@ def perform_compilation_tests(config)
|
|
317
310
|
# do that, set the URLs, and download the packages
|
318
311
|
all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
|
319
312
|
|
320
|
-
# inform about builtin packages
|
321
|
-
all_packages.select { |p| config.package_builtin?(p) }.each do |p|
|
322
|
-
inform("Using built-in board package") { p }
|
323
|
-
end
|
324
|
-
|
325
313
|
# make sure any non-builtin package has a URL defined
|
326
|
-
all_packages.
|
314
|
+
all_packages.each do |p|
|
327
315
|
assure("Board package #{p} has a defined URL") { board_package_url[p] }
|
328
316
|
end
|
329
317
|
|
@@ -333,19 +321,18 @@ def perform_compilation_tests(config)
|
|
333
321
|
|
334
322
|
unless all_urls.empty?
|
335
323
|
assure("Setting board manager URLs") do
|
336
|
-
@
|
324
|
+
@backend.board_manager_urls = all_urls
|
337
325
|
end
|
338
326
|
end
|
339
327
|
|
340
328
|
all_packages.each do |p|
|
341
329
|
assure("Installing board package #{p}") do
|
342
|
-
@
|
330
|
+
@backend.install_boards(p)
|
343
331
|
end
|
344
332
|
end
|
345
333
|
|
346
|
-
install_arduino_library_dependencies(aux_libraries)
|
334
|
+
install_arduino_library_dependencies(aux_libraries, "<compile/libraries>")
|
347
335
|
|
348
|
-
last_board = nil
|
349
336
|
if config.platforms_to_build.empty?
|
350
337
|
inform("Skipping builds") { "no platforms were requested" }
|
351
338
|
return
|
@@ -356,46 +343,58 @@ def perform_compilation_tests(config)
|
|
356
343
|
return
|
357
344
|
end
|
358
345
|
|
359
|
-
|
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|
|
346
|
+
library_examples.each do |example_path|
|
364
347
|
ovr_config = config.from_example(example_path)
|
365
348
|
ovr_config.platforms_to_build.each do |p|
|
366
|
-
|
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|
|
349
|
+
board = example_platform_info[p][:board]
|
377
350
|
example_name = File.basename(example_path)
|
378
|
-
attempt("
|
379
|
-
ret = @
|
351
|
+
attempt("Compiling #{example_name} for #{board}") do
|
352
|
+
ret = @backend.compile_sketch(example_path, board)
|
380
353
|
unless ret
|
381
354
|
puts
|
382
|
-
puts "Last command: #{@
|
383
|
-
puts @
|
355
|
+
puts "Last command: #{@backend.last_msg}"
|
356
|
+
puts @backend.last_err
|
384
357
|
end
|
385
358
|
ret
|
386
359
|
end
|
387
360
|
end
|
388
361
|
end
|
389
|
-
|
390
362
|
end
|
391
363
|
|
392
364
|
# initialize command and config
|
393
365
|
config = ArduinoCI::CIConfig.default.from_project_library
|
394
366
|
|
395
|
-
@
|
396
|
-
inform("Located
|
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
|
+
)
|
397
396
|
|
398
|
-
perform_unit_tests(config)
|
399
|
-
|
397
|
+
perform_unit_tests(cpp_library, config)
|
398
|
+
perform_example_compilation_tests(cpp_library, config)
|
400
399
|
|
401
400
|
terminate(true)
|
@@ -2,6 +2,6 @@
|
|
2
2
|
require 'arduino_ci'
|
3
3
|
|
4
4
|
# locate and/or forcibly install Arduino, keep stdout clean
|
5
|
-
@
|
5
|
+
@backend = ArduinoCI::ArduinoInstallation.autolocate!($stderr)
|
6
6
|
|
7
|
-
puts @
|
7
|
+
puts @backend.lib_dir
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# workaround for https://github.com/arduino/Arduino/issues/3535
|
6
|
+
WORKAROUND_LIB = "USBHost".freeze
|
7
|
+
|
8
|
+
module ArduinoCI
|
9
|
+
|
10
|
+
# To report errors that we can't resolve or possibly even explain
|
11
|
+
class ArduinoExecutionError < StandardError; end
|
12
|
+
|
13
|
+
# Wrap the Arduino executable. This requires, in some cases, a faked display.
|
14
|
+
class ArduinoBackend
|
15
|
+
|
16
|
+
# We never even use this in code, it's just here for reference because the backend is picky about it. Used for testing
|
17
|
+
# @return [String] the only allowable name for the arduino-cli config file.
|
18
|
+
CONFIG_FILE_NAME = "arduino-cli.yaml".freeze
|
19
|
+
|
20
|
+
# the actual path to the executable on this platform
|
21
|
+
# @return [Pathname]
|
22
|
+
attr_accessor :binary_path
|
23
|
+
|
24
|
+
# If a custom config is deired (i.e. for testing), specify it here.
|
25
|
+
# Note https://github.com/arduino/arduino-cli/issues/753 : the --config-file option
|
26
|
+
# is really the director that contains the file
|
27
|
+
# @return [Pathname]
|
28
|
+
attr_accessor :config_dir
|
29
|
+
|
30
|
+
# @return [String] STDOUT of the most recently-run command
|
31
|
+
attr_reader :last_out
|
32
|
+
|
33
|
+
# @return [String] STDERR of the most recently-run command
|
34
|
+
attr_reader :last_err
|
35
|
+
|
36
|
+
# @return [String] the most recently-run command
|
37
|
+
attr_reader :last_msg
|
38
|
+
|
39
|
+
# @return [Array<String>] Additional URLs for the boards manager
|
40
|
+
attr_reader :additional_urls
|
41
|
+
|
42
|
+
def initialize(binary_path)
|
43
|
+
@binary_path = binary_path
|
44
|
+
@config_dir = nil
|
45
|
+
@additional_urls = []
|
46
|
+
@last_out = ""
|
47
|
+
@last_err = ""
|
48
|
+
@last_msg = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
def _wrap_run(work_fn, *args, **kwargs)
|
52
|
+
# do some work to extract & merge environment variables if they exist
|
53
|
+
has_env = !args.empty? && args[0].class == Hash
|
54
|
+
env_vars = has_env ? args[0] : {}
|
55
|
+
actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args
|
56
|
+
custom_config = @config_dir.nil? ? [] : ["--config-file", @config_dir.to_s]
|
57
|
+
full_args = [binary_path.to_s, "--format", "json"] + custom_config + actual_args
|
58
|
+
full_cmd = env_vars.empty? ? full_args : [env_vars] + full_args
|
59
|
+
|
60
|
+
shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ")
|
61
|
+
@last_msg = " $ #{shell_vars} #{full_args.join(' ')}"
|
62
|
+
work_fn.call(*full_cmd, **kwargs)
|
63
|
+
end
|
64
|
+
|
65
|
+
# build and run the arduino command
|
66
|
+
def run_and_output(*args, **kwargs)
|
67
|
+
_wrap_run((proc { |*a, **k| Host.run_and_output(*a, **k) }), *args, **kwargs)
|
68
|
+
end
|
69
|
+
|
70
|
+
# run a command and capture its output
|
71
|
+
# @return [Hash] {:out => String, :err => String, :success => bool}
|
72
|
+
def run_and_capture(*args, **kwargs)
|
73
|
+
ret = _wrap_run((proc { |*a, **k| Host.run_and_capture(*a, **k) }), *args, **kwargs)
|
74
|
+
@last_err = ret[:err]
|
75
|
+
@last_out = ret[:out]
|
76
|
+
ret
|
77
|
+
end
|
78
|
+
|
79
|
+
def capture_json(*args, **kwargs)
|
80
|
+
ret = run_and_capture(*args, **kwargs)
|
81
|
+
ret[:json] = JSON.parse(ret[:out])
|
82
|
+
ret
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get a dump of the entire config
|
86
|
+
# @return [Hash] The configuration
|
87
|
+
def config_dump
|
88
|
+
capture_json("config", "dump")[:json]
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] the path to the Arduino libraries directory
|
92
|
+
def lib_dir
|
93
|
+
Pathname.new(config_dump["directories"]["user"]) + "libraries"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Board manager URLs
|
97
|
+
# @return [Array<String>] The additional URLs used by the board manager
|
98
|
+
def board_manager_urls
|
99
|
+
config_dump["board_manager"]["additional_urls"] + @additional_urls
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set board manager URLs
|
103
|
+
# @return [Array<String>] The additional URLs used by the board manager
|
104
|
+
def board_manager_urls=(all_urls)
|
105
|
+
raise ArgumentError("all_urls should be an array, got #{all_urls.class}") unless all_urls.is_a? Array
|
106
|
+
|
107
|
+
@additional_urls = all_urls
|
108
|
+
end
|
109
|
+
|
110
|
+
# check whether a board is installed
|
111
|
+
# we do this by just selecting a board.
|
112
|
+
# the arduino binary will error if unrecognized and do a successful no-op if it's installed
|
113
|
+
# @param boardname [String] The board to test
|
114
|
+
# @return [bool] Whether the board is installed
|
115
|
+
def board_installed?(boardname)
|
116
|
+
# capture_json("core", "list")[:json].find { |b| b["ID"] == boardname } # nope, this is for the family
|
117
|
+
run_and_capture("board", "details", "--fqbn", boardname)[:success]
|
118
|
+
end
|
119
|
+
|
120
|
+
# install a board by name
|
121
|
+
# @param name [String] the board name
|
122
|
+
# @return [bool] whether the command succeeded
|
123
|
+
def install_boards(boardfamily)
|
124
|
+
result = run_and_capture("core", "install", boardfamily)
|
125
|
+
result[:success]
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Hash] information about installed libraries via the CLI
|
129
|
+
def installed_libraries
|
130
|
+
capture_json("lib", "list")[:json]
|
131
|
+
end
|
132
|
+
|
133
|
+
# @param path [String] The sketch to compile
|
134
|
+
# @param boardname [String] The board to use
|
135
|
+
# @return [bool] whether the command succeeded
|
136
|
+
def compile_sketch(path, boardname)
|
137
|
+
ext = File.extname path
|
138
|
+
unless ext.casecmp(".ino").zero?
|
139
|
+
@last_msg = "Refusing to compile sketch with '#{ext}' extension -- rename it to '.ino'!"
|
140
|
+
return false
|
141
|
+
end
|
142
|
+
unless File.exist? path
|
143
|
+
@last_msg = "Can't compile Sketch at nonexistent path '#{path}'!"
|
144
|
+
return false
|
145
|
+
end
|
146
|
+
ret = run_and_capture("compile", "--fqbn", boardname, "--warnings", "all", "--dry-run", path.to_s)
|
147
|
+
ret[:success]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Guess the name of a library
|
151
|
+
# @param path [Pathname] The path to the library (installed or not)
|
152
|
+
# @return [String] the probable library name
|
153
|
+
def name_of_library(path)
|
154
|
+
src_path = path.realpath
|
155
|
+
properties_file = src_path + CppLibrary::LIBRARY_PROPERTIES_FILE
|
156
|
+
return src_path.basename.to_s unless properties_file.exist?
|
157
|
+
return src_path.basename.to_s if LibraryProperties.new(properties_file).name.nil?
|
158
|
+
|
159
|
+
LibraryProperties.new(properties_file).name
|
160
|
+
end
|
161
|
+
|
162
|
+
# Create a handle to an Arduino library by name
|
163
|
+
# @param name [String] The library "real name"
|
164
|
+
# @return [CppLibrary] The library object
|
165
|
+
def library_of_name(name)
|
166
|
+
raise ArgumentError, "name is not a String (got #{name.class})" unless name.is_a? String
|
167
|
+
|
168
|
+
CppLibrary.new(name, self)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Create a handle to an Arduino library by path
|
172
|
+
# @param path [Pathname] The path to the library
|
173
|
+
# @return [CppLibrary] The library object
|
174
|
+
def library_of_path(path)
|
175
|
+
# the path must exist... and if it does, brute-force search the installed libs for it
|
176
|
+
realpath = path.realpath # should produce error if the path doesn't exist to begin with
|
177
|
+
entry = installed_libraries.find { |l| Pathname.new(l["library"]["install_dir"]).realpath == realpath }
|
178
|
+
probable_name = entry["real_name"].nil? ? realpath.basename.to_s : entry["real_name"]
|
179
|
+
CppLibrary.new(probable_name, self)
|
180
|
+
end
|
181
|
+
|
182
|
+
# install a library from a path on the local machine (not via library manager), by symlink or no-op as appropriate
|
183
|
+
# @param path [Pathname] library to use
|
184
|
+
# @return [CppLibrary] the installed library, or nil
|
185
|
+
def install_local_library(path)
|
186
|
+
src_path = path.realpath
|
187
|
+
library_name = name_of_library(path)
|
188
|
+
cpp_library = library_of_name(library_name)
|
189
|
+
destination_path = cpp_library.path
|
190
|
+
|
191
|
+
# things get weird if the sketchbook contains the library.
|
192
|
+
# check that first
|
193
|
+
if cpp_library.installed?
|
194
|
+
# maybe the project has always lived in the libraries directory, no need to symlink
|
195
|
+
return cpp_library if destination_path == src_path
|
196
|
+
|
197
|
+
uhoh = "There is already a library '#{library_name}' in the library directory (#{destination_path})"
|
198
|
+
# maybe it's a symlink? that would be OK
|
199
|
+
if Host.symlink?(destination_path)
|
200
|
+
current_destination_target = Host.readlink(destination_path)
|
201
|
+
return cpp_library if current_destination_target == src_path
|
202
|
+
|
203
|
+
@last_msg = "#{uhoh} and it's symlinked to #{current_destination_target} (expected #{src_path})"
|
204
|
+
return nil
|
205
|
+
end
|
206
|
+
|
207
|
+
@last_msg = "#{uhoh}. It may need to be removed manually."
|
208
|
+
return nil
|
209
|
+
end
|
210
|
+
|
211
|
+
# install the library
|
212
|
+
libraries_dir = destination_path.parent
|
213
|
+
libraries_dir.mkpath unless libraries_dir.exist?
|
214
|
+
Host.symlink(src_path, destination_path)
|
215
|
+
cpp_library
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|