arduino_ci 0.0.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c01ddca13fd4bd941dcff9e9faeeae700f184bbf647876d520c7ccfce0c87b3
4
- data.tar.gz: 974ce43ef7eba01113b9470750c78ad4cc5f81f0296f72f18ce43def9ad1c597
3
+ metadata.gz: cad9df4f8ade3fbe801da837c8b6a73d129c9a5497e1d43b4f655de8d9b5b640
4
+ data.tar.gz: b42f9624e6af593c467d0f67a94b0695fb36b584d740f4fc38e256b8f703c075
5
5
  SHA512:
6
- metadata.gz: a044d9ab06badda8a648f189786c14209d7bee59d4ba299dc8790bd29e54adfd107f15a53fcb28d7ad7f286c6daf3e559f6a09b15b3f625c5344c400b0a4c5e8
7
- data.tar.gz: f134b211ce71483ee88794a8a9d9f742deed82ee3d93ccecbbdd745ff53af12d6b44f71e2480c7ea98ce60d62bb28f0553c22b773bd6376ea7854bae29f19c9c
6
+ metadata.gz: 25d40817ac30d531979d197768aad671e8efa9b3e2bc023fde41f3dbcbbb1b829a4f6a9915714a6eec2e1e234f2f70749390802431af81a7b23cf8b847a39750
7
+ data.tar.gz: 9ef54c3d3fc6704ad70d347b85f2fc039d697a81edfafaa3e9fea329cc20c0a0919f42267452e4f62be5d508460f38a3264f04b0c51ea98a3b98062cc4bd18b1
data/README.md CHANGED
@@ -1,32 +1,44 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci)
2
2
  [![Build Status](https://travis-ci.org/ifreecarve/arduino_ci.svg)](https://travis-ci.org/ifreecarve/arduino_ci)
3
- [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/)
3
+ [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/0.1.0)
4
4
 
5
5
  # ArduinoCI Ruby gem (`arduino_ci`)
6
6
 
7
7
  [Arduino CI](https://github.com/ifreecarve/arduino_ci) is a Ruby gem for executing Continuous Integration (CI) tests on an Arduino library -- both locally and as part of a service like Travis CI.
8
8
 
9
9
 
10
- ## Installation
10
+ ## Installation In Your GitHub Project And Using Travis CI
11
11
 
12
- Add this line to your application's Gemfile:
12
+ Add a file called `Gemfile` (no extension) to your Arduino project:
13
13
 
14
14
  ```ruby
15
+ source 'https://rubygems.org'
15
16
  gem 'arduino_ci'
16
17
  ```
17
18
 
18
- And then execute:
19
+ Next, you need this in `.travis.yml`
19
20
 
20
- $ bundle
21
+ ```yaml
22
+ sudo: false
23
+ language: ruby
24
+ script:
25
+ - bundle install
26
+ - bundle exec arduino_ci_remote.rb
27
+ ```
28
+
29
+ That's literally all there is to it on the repository side. 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.
21
30
 
22
- Or install it yourself as:
23
31
 
24
- $ gem install arduino_ci
32
+ ## More Documentation
25
33
 
34
+ This software is in alpha. But [SampleProjects/DoSomething](SampleProjects/DoSomething) has a decent writeup and is a good bare-bones example of all the features.
26
35
 
27
- ## Usage
36
+ ## Known Problems
28
37
 
29
- TODO: Write usage instructions here, based on other TODO of writing the actual gem.
38
+ * The Arduino library is not fully mocked.
39
+ * I don't have preprocessor defines for all the Arduino board flavors
40
+ * Arduino Zero boards don't work in CI. I'm confused.
41
+ * https://github.com/ifreecarve/arduino_ci/issues
30
42
 
31
43
 
32
44
  ## Author
@@ -37,3 +49,5 @@ This gem was written by Ian Katz (ifreecarve@gmail.com) in 2018. It's released
37
49
  ## See Also
38
50
 
39
51
  * [Contributing](CONTRIBUTING.md)
52
+ * [Adafruit/travis-ci-arduino](https://github.com/adafruit/travis-ci-arduino) which inspired this project
53
+ * [mmurdoch/arduinounit](https://github.com/mmurdoch/arduinounit) from which the unit test macros were adopted
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env ruby
2
+ require 'arduino_ci'
3
+ require 'set'
4
+
5
+ WIDTH = 80
6
+
7
+ @failure_count = 0
8
+
9
+ # terminate after printing any debug info. TODO: capture debug info
10
+ def terminate(final = nil)
11
+ puts "Failures: #{@failure_count}"
12
+ unless @failure_count.zero? || final
13
+ puts "Last message: #{@arduino_cmd.last_msg}"
14
+ puts "========== Stdout:"
15
+ puts @arduino_cmd.last_out
16
+ puts "========== Stderr:"
17
+ puts @arduino_cmd.last_err
18
+ end
19
+ retcode = @failure_count.zero? ? 0 : 1
20
+ exit(retcode)
21
+ end
22
+
23
+ # make a nice status line for an action and react to the action
24
+ def perform_action(message, on_fail_msg, abort_on_fail)
25
+ line = "#{message}..."
26
+ print line
27
+ result = yield
28
+ mark = result ? "✓" : "X"
29
+ puts mark.rjust(WIDTH - line.length, " ")
30
+ unless result
31
+ puts on_fail_msg unless on_fail_msg.nil?
32
+ @failure_count += 1
33
+ # print out error messaging here if we've captured it
34
+ terminate if abort_on_fail
35
+ end
36
+ result
37
+ end
38
+
39
+ # Make a nice status for something that defers any failure code until script exit
40
+ def attempt(message, &block)
41
+ perform_action(message, nil, false, &block)
42
+ end
43
+
44
+ # Make a nice status for something that kills the script immediately on failure
45
+ def assure(message, &block)
46
+ perform_action(message, "This may indicate a problem with ArduinoCI!", true, &block)
47
+ end
48
+
49
+ # initialize command and config
50
+ config = ArduinoCI::CIConfig.default.from_project_library
51
+ @arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
52
+
53
+ # initialize library under test
54
+ installed_library_path = assure("Installing library under test") { @arduino_cmd.install_local_library(".") }
55
+ library_examples = @arduino_cmd.library_examples(installed_library_path)
56
+ cpp_library = ArduinoCI::CppLibrary.new(installed_library_path)
57
+ attempt("Library installed at #{installed_library_path}") { true }
58
+
59
+ # gather up all required boards so we can install them up front.
60
+ # start with the "platforms to unittest" and add the examples
61
+ # while we're doing that, get the aux libraries as well
62
+ all_platforms = {}
63
+ aux_libraries = Set.new(config.aux_libraries_for_unittest + config.aux_libraries_for_build)
64
+ config.platforms_to_unittest.each { |p| all_platforms[p] = config.platform_definition(p) }
65
+ library_examples.each do |path|
66
+ ovr_config = config.from_example(path)
67
+ ovr_config.platforms_to_build.each { |p| all_platforms[p] = config.platform_definition(p) }
68
+ aux_libraries.merge(ovr_config.aux_libraries_for_build)
69
+ end
70
+
71
+ # with all platform info, we can extract unique packages and their urls
72
+ # do that, set the URLs, and download the packages
73
+ all_packages = all_platforms.values.map { |v| v[:package] }.uniq.reject(&:nil?)
74
+ all_urls = all_packages.map { |p| config.package_url(p) }.uniq.reject(&:nil?)
75
+ assure("Setting board manager URLs") do
76
+ @arduino_cmd.set_pref("boardsmanager.additional.urls", all_urls.join(","))
77
+ end
78
+
79
+ all_packages.each do |p|
80
+ assure("Installing board package #{p}") do
81
+ @arduino_cmd.install_boards(p)
82
+ end
83
+ end
84
+
85
+ aux_libraries.each do |l|
86
+ assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) }
87
+ end
88
+
89
+ attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") }
90
+
91
+ library_examples.each do |example_path|
92
+ ovr_config = config.from_example(example_path)
93
+ ovr_config.platforms_to_build.each do |p|
94
+ board = all_platforms[p][:board]
95
+ assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) }
96
+ example_name = File.basename(example_path)
97
+ attempt("Verifying #{example_name}") do
98
+ ret = @arduino_cmd.verify_sketch(example_path)
99
+ unless ret
100
+ puts
101
+ puts "Last command: #{@arduino_cmd.last_msg}"
102
+ puts @arduino_cmd.last_err
103
+ end
104
+ ret
105
+ end
106
+ end
107
+ end
108
+
109
+ config.platforms_to_unittest.each do |p|
110
+ board = all_platforms[p][:board]
111
+ assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) }
112
+ cpp_library.test_files.each do |unittest_path|
113
+ unittest_name = File.basename(unittest_path)
114
+ attempt("Unit testing #{unittest_name}") do
115
+ exe = cpp_library.build_for_test_with_configuration(
116
+ unittest_path,
117
+ config.aux_libraries_for_unittest,
118
+ config.gcc_config(p)
119
+ )
120
+ puts
121
+ unless exe
122
+ puts "Last command: #{cpp_library.last_cmd}"
123
+ puts cpp_library.last_out
124
+ puts cpp_library.last_err
125
+ next false
126
+ end
127
+ cpp_library.run_test_file(exe)
128
+ end
129
+ end
130
+ end
131
+
132
+ terminate(true)
data/lib/arduino_ci.rb CHANGED
@@ -1,124 +1,10 @@
1
1
  require "arduino_ci/version"
2
-
3
- require 'singleton'
4
-
5
- # Cross-platform way of finding an executable in the $PATH.
6
- # via https://stackoverflow.com/a/5471032/2063546
7
- # which('ruby') #=> /usr/bin/ruby
8
- def which(cmd)
9
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
10
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
11
- exts.each do |ext|
12
- exe = File.join(path, "#{cmd}#{ext}")
13
- return exe if File.executable?(exe) && !File.directory?(exe)
14
- end
15
- end
16
- nil
17
- end
2
+ require "arduino_ci/arduino_installation"
3
+ require "arduino_ci/cpp_library"
4
+ require "arduino_ci/ci_config"
18
5
 
19
6
  # ArduinoCI contains classes for automated testing of Arduino code on the command line
20
7
  # @author Ian Katz <ifreecarve@gmail.com>
21
8
  module ArduinoCI
22
9
 
23
- # Wrap the Arduino executable. This requires, in some cases, a faked display.
24
- class ArduinoCmd
25
-
26
- # create as many ArduinoCmds as you like, but we need one and only one display manager
27
- class DisplayMgr
28
- include Singleton
29
- attr_reader :enabled
30
-
31
- def initialize
32
- @existing = existing_display?
33
- @enabled = false
34
- @pid = nil
35
- end
36
-
37
- # attempt to determine if the machine is running a graphical display (i.e. not Travis)
38
- def existing_display?
39
- return true if RUBY_PLATFORM.include? "darwin"
40
- return true if ENV["DISPLAY"].nil?
41
- return true if ENV["DISPLAY"].include? ":"
42
- false
43
- end
44
-
45
- # enable a virtual display
46
- def enable
47
- return @enabled = true if @existing # silent no-op if built in display
48
- return unless @pid.nil?
49
-
50
- @enabled = true
51
- @pid = fork do
52
- puts "Forking Xvfb"
53
- system("Xvfb", ":1", "-ac", "-screen", "0", "1280x1024x16")
54
- puts "Xvfb unexpectedly quit!"
55
- end
56
- sleep(3) # TODO: test a connection to the X server?
57
- end
58
-
59
- # disable the virtual display
60
- def disable
61
- return @enabled = false if @existing # silent no-op if built in display
62
- return if @pid.nil?
63
-
64
- begin
65
- Process.kill 9, @pid
66
- ensure
67
- Process.wait @pid
68
- @pid = nil
69
- end
70
- puts "Xvfb killed"
71
- end
72
-
73
- # Enable a virtual display for the duration of the given block
74
- def with_display
75
- enable
76
- begin
77
- yield environment
78
- ensure
79
- disable
80
- end
81
- end
82
-
83
- def environment
84
- return nil unless @existing || @enabled
85
- return {} if @existing
86
- { DISPLAY => ":1.0" }
87
- end
88
-
89
- # On finalize, ensure child process is ended
90
- def self.finalize
91
- disable
92
- end
93
- end
94
-
95
- class << self
96
- protected :new
97
-
98
- # attempt to find a workable Arduino executable across platforms
99
- def guess_executable_location
100
- osx_place = "/Applications/Arduino.app/Contents/MacOS/Arduino"
101
- places = {
102
- "arduino" => !which("arduino").nil?,
103
- osx_place => (File.exist? osx_place),
104
- }
105
- places.each { |k, v| return k if v }
106
- nil
107
- end
108
-
109
- def autolocate
110
- ret = new
111
- ret.path = guess_executable_location
112
- ret
113
- end
114
- end
115
-
116
- attr_accessor :path
117
-
118
- def initialize
119
- @display_mgr = DisplayMgr::instance
120
- end
121
-
122
- end
123
-
124
10
  end
@@ -0,0 +1,296 @@
1
+ require 'fileutils'
2
+
3
+ module ArduinoCI
4
+
5
+ # Wrap the Arduino executable. This requires, in some cases, a faked display.
6
+ class ArduinoCmd
7
+
8
+ # Enable a shortcut syntax for command line flags
9
+ # @param name [String] What the flag will be called (prefixed with 'flag_')
10
+ # @return [void]
11
+ # @macro [attach] flag
12
+ # The text of the command line flag for $1
13
+ # @!attribute [r] flag_$1
14
+ # @return [String] the text of the command line flag (`$2` in this case)
15
+ def self.flag(name, text = nil)
16
+ text = "(flag #{name} not defined)" if text.nil?
17
+ self.class_eval("def flag_#{name};\"#{text}\";end")
18
+ end
19
+
20
+ # the path to the Arduino executable
21
+ # @return [String]
22
+ attr_accessor :base_cmd
23
+
24
+ # part of a workaround for https://github.com/arduino/Arduino/issues/3535
25
+ attr_reader :library_is_indexed
26
+
27
+ # @return [String] STDOUT of the most recently-run command
28
+ attr_reader :last_out
29
+
30
+ # @return [String] STDERR of the most recently-run command
31
+ attr_reader :last_err
32
+
33
+ # @return [String] the most recently-run command
34
+ attr_reader :last_msg
35
+
36
+ # set the command line flags (undefined for now).
37
+ # These vary between gui/cli
38
+ flag :get_pref
39
+ flag :set_pref
40
+ flag :save_prefs
41
+ flag :use_board
42
+ flag :install_boards
43
+ flag :install_library
44
+ flag :verify
45
+
46
+ def initialize
47
+ @prefs_cache = {}
48
+ @prefs_fetched = false
49
+ @library_is_indexed = false
50
+ @last_out = ""
51
+ @last_err = ""
52
+ @last_msg = ""
53
+ end
54
+
55
+ # Convert a preferences dump into a flat hash
56
+ # @param arduino_output [String] The raw Arduino executable output
57
+ # @return [Hash] preferences as a hash
58
+ def parse_pref_string(arduino_output)
59
+ lines = arduino_output.split("\n").select { |l| l.include? "=" }
60
+ ret = lines.each_with_object({}) do |e, acc|
61
+ parts = e.split("=", 2)
62
+ acc[parts[0]] = parts[1]
63
+ acc
64
+ end
65
+ ret
66
+ end
67
+
68
+ # @return [String] the path to the Arduino libraries directory
69
+ def _lib_dir
70
+ "<lib dir not defined>"
71
+ end
72
+
73
+ # fetch preferences in their raw form
74
+ # @return [String] Preferences as a set of lines
75
+ def _prefs_raw
76
+ resp = run_and_capture(flag_get_pref)
77
+ return nil unless resp[:success]
78
+ resp[:out]
79
+ end
80
+
81
+ # Get the Arduino preferences, from cache if possible
82
+ # @return [Hash] The full set of preferences
83
+ def prefs
84
+ prefs_raw = _prefs_raw unless @prefs_fetched
85
+ return nil if prefs_raw.nil?
86
+ @prefs_cache = parse_pref_string(prefs_raw)
87
+ @prefs_cache.clone
88
+ end
89
+
90
+ # get a preference key
91
+ # @param key [String] The preferences key to look up
92
+ # @return [String] The preference value
93
+ def get_pref(key)
94
+ data = @prefs_fetched ? @prefs_cache : prefs
95
+ data[key]
96
+ end
97
+
98
+ # underlying preference-setter.
99
+ # @param key [String] The preference name
100
+ # @param value [String] The value to set to
101
+ # @return [bool] whether the command succeeded
102
+ def _set_pref(key, value)
103
+ run_and_capture(flag_set_pref, "#{key}=#{value}", flag_save_prefs)[:success]
104
+ end
105
+
106
+ # set a preference key/value pair, and update the cache.
107
+ # @param key [String] the preference key
108
+ # @param value [String] the preference value
109
+ # @return [bool] whether the command succeeded
110
+ def set_pref(key, value)
111
+ success = _set_pref(key, value)
112
+ @prefs_cache[key] = value if success
113
+ success
114
+ end
115
+
116
+ # run the arduino command
117
+ def _run(*args, **kwargs)
118
+ raise "Ian needs to implement this in a subclass #{args} #{kwargs}"
119
+ end
120
+
121
+ # build and run the arduino command
122
+ def run(*args, **kwargs)
123
+ # do some work to extract & merge environment variables if they exist
124
+ has_env = !args.empty? && args[0].class == Hash
125
+ env_vars = has_env ? args[0] : {}
126
+ actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args
127
+ full_args = @base_cmd + actual_args
128
+ full_cmd = env_vars.empty? ? full_args : [env_vars] + full_args
129
+
130
+ shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ")
131
+ @last_msg = " $ #{shell_vars} #{full_args.join(' ')}"
132
+ _run(*full_cmd, **kwargs)
133
+ end
134
+
135
+ # run a command and capture its output
136
+ # @return [Hash] {:out => String, :err => String, :success => bool}
137
+ def run_and_capture(*args, **kwargs)
138
+ pipe_out, pipe_out_wr = IO.pipe
139
+ pipe_err, pipe_err_wr = IO.pipe
140
+ our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
141
+ eventual_kwargs = our_kwargs.merge(kwargs)
142
+ success = run(*args, **eventual_kwargs)
143
+ pipe_out_wr.close
144
+ pipe_err_wr.close
145
+ str_out = pipe_out.read
146
+ str_err = pipe_err.read
147
+ pipe_out.close
148
+ pipe_err.close
149
+ @last_err = str_err
150
+ @last_out = str_out
151
+ { out: str_out, err: str_err, success: success }
152
+ end
153
+
154
+ # run a command and don't capture its output, but use the same signature
155
+ # @return [Hash] {:out => String, :err => String, :success => bool}
156
+ def run_wrap(*args, **kwargs)
157
+ success = run(*args, **kwargs)
158
+ { out: "NOPE, use run_and_capture", err: "NOPE, use run_and_capture", success: success }
159
+ end
160
+
161
+ # check whether a board is installed
162
+ # we do this by just selecting a board.
163
+ # the arduino binary will error if unrecognized and do a successful no-op if it's installed
164
+ # @param boardname [String] The board to test
165
+ # @return [bool] Whether the board is installed
166
+ def board_installed?(boardname)
167
+ run_and_capture(flag_use_board, boardname)[:success]
168
+ end
169
+
170
+ # install a board by name
171
+ # @param name [String] the board name
172
+ # @return [bool] whether the command succeeded
173
+ def install_boards(boardfamily)
174
+ # TODO: find out why IO.pipe fails but File::NULL succeeds :(
175
+ result = run_and_capture(flag_install_boards, boardfamily)
176
+ already_installed = result[:err].include?("Platform is already installed!")
177
+ result[:success] || already_installed
178
+ end
179
+
180
+ # install a library by name
181
+ # @param name [String] the library name
182
+ # @return [bool] whether the command succeeded
183
+ def install_library(library_name)
184
+ # workaround for https://github.com/arduino/Arduino/issues/3535
185
+ # use a dummy library name but keep open the possiblity that said library
186
+ # might be selected by choice for installation
187
+ workaround_lib = "USBHost"
188
+ unless @library_is_indexed || workaround_lib == library_name
189
+ @library_is_indexed = run_and_capture(flag_install_library, workaround_lib)
190
+ end
191
+
192
+ # actual installation
193
+ result = run_and_capture(flag_install_library, library_name)
194
+
195
+ # update flag if necessary
196
+ @library_is_indexed = (@library_is_indexed || result[:success]) if library_name == workaround_lib
197
+ result[:success]
198
+ end
199
+
200
+ # generate the (very likely) path of a library given its name
201
+ # @param library_name [String] The name of the library
202
+ # @return [String] The fully qualified library name
203
+ def library_path(library_name)
204
+ File.join(_lib_dir, library_name)
205
+ end
206
+
207
+ # update the library index
208
+ # @return [bool] Whether the update succeeded
209
+ def update_library_index
210
+ # install random lib so the arduino IDE grabs a new library index
211
+ # see: https://github.com/arduino/Arduino/issues/3535
212
+ install_library("USBHost")
213
+ end
214
+
215
+ # use a particular board for compilation
216
+ # @param boardname [String] The board to use
217
+ # @return [bool] whether the command succeeded
218
+ def use_board(boardname)
219
+ run_and_capture(flag_use_board, boardname, flag_save_prefs)[:success]
220
+ end
221
+
222
+ # use a particular board for compilation, installing it if necessary
223
+ # @param boardname [String] The board to use
224
+ # @return [bool] whether the command succeeded
225
+ def use_board!(boardname)
226
+ return true if use_board(boardname)
227
+ boardfamily = boardname.split(":")[0..1].join(":")
228
+ puts "Board '#{boardname}' not found; attempting to install '#{boardfamily}'"
229
+ return false unless install_boards(boardfamily) # guess board family from first 2 :-separated fields
230
+ use_board(boardname)
231
+ end
232
+
233
+ # @param path [String] The sketch to verify
234
+ # @return [bool] whether the command succeeded
235
+ def verify_sketch(path)
236
+ ext = File.extname path
237
+ unless ext.casecmp(".ino").zero?
238
+ @last_msg = "Refusing to verify sketch with '#{ext}' extension -- rename it to '.ino'!"
239
+ return false
240
+ end
241
+ unless File.exist? path
242
+ @last_msg = "Can't verify Sketch at nonexistent path '#{path}'!"
243
+ return false
244
+ end
245
+ ret = run_and_capture(flag_verify, path)
246
+ puts "=============="
247
+ puts ret[:out]
248
+ puts "--------------"
249
+ puts ret[:err]
250
+ ret[:success]
251
+ end
252
+
253
+ # ensure that the given library is installed, or symlinked as appropriate
254
+ # return the path of the prepared library, or nil
255
+ # @param path [String] library to use
256
+ # @return [String] the path of the installed library
257
+ def install_local_library(path)
258
+ realpath = File.expand_path(path)
259
+ library_name = File.basename(realpath)
260
+ destination_path = library_path(library_name)
261
+
262
+ # things get weird if the sketchbook contains the library.
263
+ # check that first
264
+ if File.exist? destination_path
265
+ uhoh = "There is already a library '#{library_name}' in the library directory"
266
+ return destination_path if destination_path == realpath
267
+
268
+ # maybe it's a symlink? that would be OK
269
+ if File.symlink?(destination_path)
270
+ return destination_path if File.readlink(destination_path) == realpath
271
+ @last_msg = "#{uhoh} and it's not symlinked to #{realpath}"
272
+ return nil
273
+ end
274
+
275
+ @last_msg = "#{uhoh}. It may need to be removed manually."
276
+ return nil
277
+ end
278
+
279
+ # install the library
280
+ FileUtils.ln_s(realpath, destination_path)
281
+ destination_path
282
+ end
283
+
284
+ # @param installed_library_path [String] The library to query
285
+ # @return [Array<String>] Example sketch files
286
+ def library_examples(installed_library_path)
287
+ example_path = File.join(installed_library_path, "examples")
288
+ examples = Pathname.new(example_path).children.select(&:directory?).map(&:to_path).map(&File.method(:basename))
289
+ files = examples.map do |e|
290
+ proj_file = File.join(example_path, e, "#{e}.ino")
291
+ File.exist?(proj_file) ? proj_file : nil
292
+ end
293
+ files.reject(&:nil?)
294
+ end
295
+ end
296
+ end