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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38db4872f12c8a48cbdf7033fba9b09ad530e140
4
- data.tar.gz: 558c406c46bc3365bfaecfb9bef40155f0a95514
3
+ metadata.gz: c195767b10bc2c1a3e837b89f087962251fb514c
4
+ data.tar.gz: 2188e52c1fa37664c72067bec73281fc8d5582e6
5
5
  SHA512:
6
- metadata.gz: 68ef58a63df5eb220ba7c5a27833db522bef6f15fa77bc71d5533392d5af5c8bf11337ce88d9c69ba112327db4176a25971c25aaf115c17ddd2be7093753e3ec
7
- data.tar.gz: 32460f132b22f151825590e6dfb845ec36a0a1058ed72b730987b3772db08e01291201c029d473cc2020d6721c55f0d0745fd7e178bb529c2239cbb177c94a2e
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.4.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 Travis or Appveyor. Any OS that can run the Arduino IDE can run `arduino_ci`.
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](http://badges.herokuapp.com/travis/Arduino-CI/arduino_ci?env=BADGE=osx&label=build&branch=master)](https://travis-ci.org/Arduino-CI/arduino_ci)
18
- Linux | [![Linux Build Status](http://badges.herokuapp.com/travis/Arduino-CI/arduino_ci?env=BADGE=linux&label=build&branch=master)](https://travis-ci.org/Arduino-CI/arduino_ci)
19
- Windows | [![Windows Build status](https://ci.appveyor.com/api/projects/status/abynv8xd75m26qo9/branch/master?svg=true)](https://ci.appveyor.com/project/ianfixes/arduino-ci)
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.
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <Stream.h>
4
+ #include <IPAddress.h>
4
5
 
5
6
  class Client : public Stream {
6
7
  public:
@@ -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: #{@arduino_cmd.last_msg}"
67
+ puts "Last message: #{@backend.last_msg}"
72
68
  puts "========== Stdout:"
73
- puts @arduino_cmd.last_out
69
+ puts @backend.last_out
74
70
  puts "========== Stderr:"
75
- puts @arduino_cmd.last_err
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 = pathname.symlink? ? pathname.readlink : pathname
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
- 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 }
179
179
  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
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
- 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
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 perform_compilation_tests(config)
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.reject { |p| config.package_builtin?(p) }.each do |p|
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
- @arduino_cmd.board_manager_urls = all_urls
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
- @arduino_cmd.install_boards(p)
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
- 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|
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
- 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|
349
+ board = example_platform_info[p][:board]
377
350
  example_name = File.basename(example_path)
378
- attempt("Verifying #{example_name}") do
379
- ret = @arduino_cmd.verify_sketch(example_path)
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: #{@arduino_cmd.last_msg}"
383
- puts @arduino_cmd.last_err
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
- @arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
396
- inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s }
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
- perform_compilation_tests(config)
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
- @arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!($stderr)
5
+ @backend = ArduinoCI::ArduinoInstallation.autolocate!($stderr)
6
6
 
7
- puts @arduino_cmd.lib_dir
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