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 +4 -4
- data/README.md +23 -9
- data/exe/arduino_ci_remote.rb +132 -0
- data/lib/arduino_ci.rb +3 -117
- data/lib/arduino_ci/arduino_cmd.rb +296 -0
- data/lib/arduino_ci/arduino_cmd_linux.rb +86 -0
- data/lib/arduino_ci/arduino_cmd_linux_builder.rb +32 -0
- data/lib/arduino_ci/arduino_cmd_osx.rb +27 -0
- data/lib/arduino_ci/arduino_installation.rb +135 -0
- data/lib/arduino_ci/ci_config.rb +218 -0
- data/lib/arduino_ci/cpp_library.rb +186 -0
- data/lib/arduino_ci/display_manager.rb +187 -0
- data/lib/arduino_ci/host.rb +36 -0
- data/lib/arduino_ci/version.rb +1 -1
- metadata +32 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cad9df4f8ade3fbe801da837c8b6a73d129c9a5497e1d43b4f655de8d9b5b640
|
4
|
+
data.tar.gz: b42f9624e6af593c467d0f67a94b0695fb36b584d740f4fc38e256b8f703c075
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25d40817ac30d531979d197768aad671e8efa9b3e2bc023fde41f3dbcbbb1b829a4f6a9915714a6eec2e1e234f2f70749390802431af81a7b23cf8b847a39750
|
7
|
+
data.tar.gz: 9ef54c3d3fc6704ad70d347b85f2fc039d697a81edfafaa3e9fea329cc20c0a0919f42267452e4f62be5d508460f38a3264f04b0c51ea98a3b98062cc4bd18b1
|
data/README.md
CHANGED
@@ -1,32 +1,44 @@
|
|
1
1
|
[](https://rubygems.org/gems/arduino_ci)
|
2
2
|
[](https://travis-ci.org/ifreecarve/arduino_ci)
|
3
|
-
[](http://www.rubydoc.info/gems/arduino_ci/)
|
3
|
+
[](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
|
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
|
-
|
19
|
+
Next, you need this in `.travis.yml`
|
19
20
|
|
20
|
-
|
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
|
-
|
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
|
-
##
|
36
|
+
## Known Problems
|
28
37
|
|
29
|
-
|
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
|
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
|