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
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'arduino_ci/arduino_cmd'
|
2
|
+
require 'arduino_ci/display_manager'
|
3
|
+
|
4
|
+
module ArduinoCI
|
5
|
+
|
6
|
+
# Implementation of Arduino linux IDE commands
|
7
|
+
class ArduinoCmdLinux < ArduinoCmd
|
8
|
+
|
9
|
+
attr_reader :prefs_response_time
|
10
|
+
|
11
|
+
flag :get_pref, "--get-pref"
|
12
|
+
flag :set_pref, "--pref"
|
13
|
+
flag :save_prefs, "--save-prefs"
|
14
|
+
flag :use_board, "--board"
|
15
|
+
flag :install_boards, "--install-boards"
|
16
|
+
flag :install_library, "--install-library"
|
17
|
+
flag :verify, "--verify"
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
super
|
21
|
+
@prefs_response_time = nil
|
22
|
+
@display_mgr = DisplayManager::instance
|
23
|
+
end
|
24
|
+
|
25
|
+
# fetch preferences in their raw form
|
26
|
+
# @return [String] Preferences as a set of lines
|
27
|
+
def _prefs_raw
|
28
|
+
start = Time.now
|
29
|
+
resp = run_and_capture(flag_get_pref)
|
30
|
+
@prefs_response_time = Time.now - start
|
31
|
+
return nil unless resp[:success]
|
32
|
+
resp[:out]
|
33
|
+
end
|
34
|
+
|
35
|
+
# implementation for Arduino library dir location
|
36
|
+
# @return [String] the path to the Arduino libraries directory
|
37
|
+
def _lib_dir
|
38
|
+
File.join(get_pref("sketchbook.path"), "libraries")
|
39
|
+
end
|
40
|
+
|
41
|
+
# run the arduino command
|
42
|
+
def _run(*args, **kwargs)
|
43
|
+
@display_mgr.run(*args, **kwargs)
|
44
|
+
end
|
45
|
+
|
46
|
+
def run_with_gui_guess(message, *args, **kwargs)
|
47
|
+
# On Travis CI, we get an error message in the GUI instead of on STDERR
|
48
|
+
# so, assume that if we don't get a rapid reply that things are not installed
|
49
|
+
|
50
|
+
prefs if @prefs_response_time.nil?
|
51
|
+
x3 = @prefs_response_time * 3
|
52
|
+
Timeout.timeout(x3) do
|
53
|
+
result = run_and_capture(*args, **kwargs)
|
54
|
+
result[:success]
|
55
|
+
end
|
56
|
+
rescue Timeout::Error
|
57
|
+
puts "No response in #{x3} seconds. Assuming graphical modal error message#{message}."
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# underlying preference-setter.
|
62
|
+
# @param key [String] the preference key
|
63
|
+
# @param value [String] the preference value
|
64
|
+
# @return [bool] whether the command succeeded
|
65
|
+
def _set_pref(key, value)
|
66
|
+
run_with_gui_guess(" about preferences", flag_set_pref, "#{key}=#{value}", flag_save_prefs)
|
67
|
+
end
|
68
|
+
|
69
|
+
# check whether a board is installed
|
70
|
+
# we do this by just selecting a board.
|
71
|
+
# the arduino binary will error if unrecognized and do a successful no-op if it's installed
|
72
|
+
# @param boardname [String] The name of the board
|
73
|
+
# @return [bool]
|
74
|
+
def board_installed?(boardname)
|
75
|
+
run_with_gui_guess(" about board not installed", flag_use_board, boardname)
|
76
|
+
end
|
77
|
+
|
78
|
+
# use a particular board for compilation
|
79
|
+
# @param boardname [String] The name of the board
|
80
|
+
def use_board(boardname)
|
81
|
+
run_with_gui_guess(" about board not installed", flag_use_board, boardname, flag_save_prefs)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "arduino_ci/host"
|
2
|
+
require 'arduino_ci/arduino_cmd'
|
3
|
+
|
4
|
+
module ArduinoCI
|
5
|
+
|
6
|
+
# Implementation of Arduino linux CLI commands
|
7
|
+
class ArduinoCmdLinuxBuilder < ArduinoCmd
|
8
|
+
|
9
|
+
flag :get_pref, "--get-pref" # apparently doesn't exist
|
10
|
+
flag :set_pref, "--pref" # apparently doesn't exist
|
11
|
+
flag :save_prefs, "--save-prefs" # apparently doesn't exist
|
12
|
+
flag :use_board, "-fqbn"
|
13
|
+
flag :install_boards, "--install-boards" # apparently doesn't exist
|
14
|
+
flag :install_library, "--install-library" # apparently doesn't exist
|
15
|
+
flag :verify, "-compile"
|
16
|
+
|
17
|
+
# linux-specific implementation
|
18
|
+
# @return [String] The path to the library dir
|
19
|
+
def _lib_dir
|
20
|
+
File.join(get_pref("sketchbook.path"), "libraries")
|
21
|
+
end
|
22
|
+
|
23
|
+
# run the arduino command
|
24
|
+
# @param [Array<String>] Arguments for the run command
|
25
|
+
# @return [bool] Whether the command succeeded
|
26
|
+
def _run(*args, **kwargs)
|
27
|
+
Host.run(*args, **kwargs)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "arduino_ci/host"
|
2
|
+
require 'arduino_ci/arduino_cmd'
|
3
|
+
|
4
|
+
module ArduinoCI
|
5
|
+
|
6
|
+
# Implementation of OSX commands
|
7
|
+
class ArduinoCmdOSX < ArduinoCmd
|
8
|
+
flag :get_pref, "--get-pref"
|
9
|
+
flag :set_pref, "--pref"
|
10
|
+
flag :save_prefs, "--save-prefs"
|
11
|
+
flag :use_board, "--board"
|
12
|
+
flag :install_boards, "--install-boards"
|
13
|
+
flag :install_library, "--install-library"
|
14
|
+
flag :verify, "--verify"
|
15
|
+
|
16
|
+
# run the arduino command
|
17
|
+
def _run(*args, **kwargs)
|
18
|
+
Host.run(*args, **kwargs)
|
19
|
+
end
|
20
|
+
|
21
|
+
def _lib_dir
|
22
|
+
File.join(ENV['HOME'], "Documents", "Arduino", "libraries")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "arduino_ci/host"
|
2
|
+
require "arduino_ci/arduino_cmd_osx"
|
3
|
+
require "arduino_ci/arduino_cmd_linux"
|
4
|
+
require "arduino_ci/arduino_cmd_linux_builder"
|
5
|
+
|
6
|
+
DESIRED_ARDUINO_IDE_VERSION = "1.8.5".freeze
|
7
|
+
USE_BUILDER = false
|
8
|
+
|
9
|
+
module ArduinoCI
|
10
|
+
|
11
|
+
# Manage the OS-specific install location of Arduino
|
12
|
+
class ArduinoInstallation
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# @return [String] The location where a forced install will go
|
16
|
+
def force_install_location
|
17
|
+
File.join(ENV['HOME'], 'arduino_ci_ide')
|
18
|
+
end
|
19
|
+
|
20
|
+
# attempt to find a workable Arduino executable across platforms
|
21
|
+
# @return [ArduinoCI::ArduinoCmd] an instance of the command
|
22
|
+
def autolocate
|
23
|
+
case Host.os
|
24
|
+
when :osx then autolocate_osx
|
25
|
+
when :linux then autolocate_linux
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [ArduinoCI::ArduinoCmdOSX] an instance of a command
|
30
|
+
def autolocate_osx
|
31
|
+
osx_root = "/Applications/Arduino.app/Contents"
|
32
|
+
old_way = false
|
33
|
+
return nil unless File.exist? osx_root
|
34
|
+
|
35
|
+
ret = ArduinoCmdOSX.new
|
36
|
+
osx_place = "#{osx_root}/MacOS"
|
37
|
+
|
38
|
+
if old_way
|
39
|
+
ret.base_cmd = [File.join(osx_place, "Arduino")]
|
40
|
+
else
|
41
|
+
jvm_runtime = `/usr/libexec/java_home`
|
42
|
+
ret.base_cmd = [
|
43
|
+
"java",
|
44
|
+
"-cp", "#{osx_root}/Java/*",
|
45
|
+
"-DAPP_DIR=#{osx_root}/Java",
|
46
|
+
"-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:#{jvm_runtime}/Contents/Home/jre/lib/ext/",
|
47
|
+
"-Dfile.encoding=UTF-8",
|
48
|
+
"-Dapple.awt.UIElement=true",
|
49
|
+
"-Xms128M",
|
50
|
+
"-Xmx512M",
|
51
|
+
"processing.app.Base",
|
52
|
+
]
|
53
|
+
end
|
54
|
+
ret
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [ArduinoCI::ArduinoCmdLinux] an instance of a command
|
58
|
+
def autolocate_linux
|
59
|
+
if USE_BUILDER
|
60
|
+
builder_name = "arduino-builder"
|
61
|
+
cli_place = Host.which(builder_name)
|
62
|
+
unless cli_place.nil?
|
63
|
+
ret = ArduinoCmdLinuxBuilder.new
|
64
|
+
ret.base_cmd = [cli_place]
|
65
|
+
return ret
|
66
|
+
end
|
67
|
+
|
68
|
+
forced_builder = File.join(force_install_location, builder_name)
|
69
|
+
if File.exist?(forced_builder)
|
70
|
+
ret = ArduinoCmdLinuxBuilder.new
|
71
|
+
ret.base_cmd = [forced_builder]
|
72
|
+
return ret
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
gui_name = "arduino"
|
77
|
+
gui_place = Host.which(gui_name)
|
78
|
+
unless gui_place.nil?
|
79
|
+
ret = ArduinoCmdLinux.new
|
80
|
+
ret.base_cmd = [gui_place]
|
81
|
+
return ret
|
82
|
+
end
|
83
|
+
|
84
|
+
forced_arduino = File.join(force_install_location, gui_name)
|
85
|
+
if File.exist?(forced_arduino)
|
86
|
+
ret = ArduinoCmdLinux.new
|
87
|
+
ret.base_cmd = [forced_arduino]
|
88
|
+
return ret
|
89
|
+
end
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Attempt to find a workable Arduino executable across platforms, and install it if we don't
|
94
|
+
# @return [ArduinoCI::ArduinoCmd] an instance of a command
|
95
|
+
def autolocate!
|
96
|
+
candidate = autolocate
|
97
|
+
return candidate unless candidate.nil?
|
98
|
+
|
99
|
+
# force the install
|
100
|
+
force_install
|
101
|
+
autolocate
|
102
|
+
end
|
103
|
+
|
104
|
+
# Forcibly install Arduino from the web
|
105
|
+
# @return [bool] Whether the command succeeded
|
106
|
+
def force_install
|
107
|
+
case Host.os
|
108
|
+
when :linux
|
109
|
+
pkgname = "arduino-#{DESIRED_ARDUINO_IDE_VERSION}"
|
110
|
+
tarfile = "#{pkgname}-linux64.tar.xz"
|
111
|
+
if File.exist? tarfile
|
112
|
+
puts "Arduino tarfile seems to have been downloaded already"
|
113
|
+
else
|
114
|
+
puts "Downloading Arduino binary with wget"
|
115
|
+
system("wget", "https://downloads.arduino.cc/#{tarfile}")
|
116
|
+
end
|
117
|
+
|
118
|
+
if File.exist? pkgname
|
119
|
+
puts "Tarfile seems to have been extracted already"
|
120
|
+
else
|
121
|
+
puts "Extracting archive with tar"
|
122
|
+
system("tar", "xf", tarfile)
|
123
|
+
end
|
124
|
+
|
125
|
+
if File.exist? force_install_location
|
126
|
+
puts "Arduino binary seems to have already been force-installed"
|
127
|
+
else
|
128
|
+
system("mv", pkgname, force_install_location)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# base config (platforms)
|
4
|
+
# project config - .arduino_ci_platforms.yml
|
5
|
+
# example config - .arduino_ci_plan.yml
|
6
|
+
|
7
|
+
PACKAGE_SCHEMA = {
|
8
|
+
url: String
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
PLATFORM_SCHEMA = {
|
12
|
+
board: String,
|
13
|
+
package: String,
|
14
|
+
gcc: {
|
15
|
+
features: Array,
|
16
|
+
defines: Array,
|
17
|
+
warnings: Array,
|
18
|
+
flags: Array,
|
19
|
+
}
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
COMPILE_SCHEMA = {
|
23
|
+
platforms: Array,
|
24
|
+
libraries: Array,
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
UNITTEST_SCHEMA = {
|
28
|
+
platforms: Array,
|
29
|
+
libraries: Array,
|
30
|
+
}.freeze
|
31
|
+
module ArduinoCI
|
32
|
+
|
33
|
+
# The filename controlling (overriding) the defaults for testing.
|
34
|
+
# Files with this name can be used in the root directory of the Arduino library and in any/all of the example directories
|
35
|
+
CONFIG_FILENAME = ".arduino-ci.yaml".freeze
|
36
|
+
|
37
|
+
# Provide the configuration and CI plan
|
38
|
+
# - Read from a base config with default platforms defined
|
39
|
+
# - Allow project-specific overrides of platforms
|
40
|
+
# - Allow example-specific allowance of platforms to test
|
41
|
+
class CIConfig
|
42
|
+
|
43
|
+
class << self
|
44
|
+
|
45
|
+
# load the default set of platforms
|
46
|
+
# @return [ArudinoCI::CIConfig] The configuration with defaults filled in
|
47
|
+
def default
|
48
|
+
ret = new
|
49
|
+
ret.load_yaml(File.expand_path("../../../misc/default.yaml", __FILE__))
|
50
|
+
ret
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_accessor :package_info
|
55
|
+
attr_accessor :platform_info
|
56
|
+
attr_accessor :compile_info
|
57
|
+
attr_accessor :unittest_info
|
58
|
+
def initialize
|
59
|
+
@package_info = {}
|
60
|
+
@platform_info = {}
|
61
|
+
@compile_info = {}
|
62
|
+
@unittest_info = {}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Deep-clone a hash
|
66
|
+
# @param hash [Hash] the source data
|
67
|
+
# @return [Hash] a copy
|
68
|
+
def deep_clone(hash)
|
69
|
+
Marshal.load(Marshal.dump(hash))
|
70
|
+
end
|
71
|
+
|
72
|
+
# validate a data source according to a schema
|
73
|
+
# print out warnings for bad fields, and return only the good ones
|
74
|
+
# @param rootname [String] the name, for printing
|
75
|
+
# @param source [Hash] source data
|
76
|
+
# @param schema [Hash] a mapping of field names to their expected type
|
77
|
+
# @return [Hash] a copy, containing only expected & valid data
|
78
|
+
def validate_data(rootname, source, schema)
|
79
|
+
return nil if source.nil?
|
80
|
+
good_data = {}
|
81
|
+
source.each do |key, value|
|
82
|
+
ksym = key.to_sym
|
83
|
+
expected_type = schema[ksym].class == Class ? schema[ksym] : Hash
|
84
|
+
if !schema.include?(ksym)
|
85
|
+
puts "Warning: unknown field '#{ksym}' under definition for #{rootname}"
|
86
|
+
elsif value.nil?
|
87
|
+
# unspecificed, that's fine
|
88
|
+
elsif value.class != expected_type
|
89
|
+
puts "Warning: expected field '#{ksym}' of #{rootname} to be '#{expected_type}', got '#{value.class}'"
|
90
|
+
else
|
91
|
+
good_data[ksym] = value.class == Hash ? validate_data(key, value, schema[ksym]) : value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
good_data
|
95
|
+
end
|
96
|
+
|
97
|
+
# Load configuration yaml from a file
|
98
|
+
# @param path [String] the source file
|
99
|
+
# @return [ArduinoCI::CIConfig] a reference to self
|
100
|
+
def load_yaml(path)
|
101
|
+
yml = YAML.load_file(path)
|
102
|
+
if yml.include?("packages")
|
103
|
+
yml["packages"].each do |k, v|
|
104
|
+
valid_data = validate_data("packages", v, PACKAGE_SCHEMA)
|
105
|
+
@package_info[k] = valid_data
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if yml.include?("platforms")
|
110
|
+
yml["platforms"].each do |k, v|
|
111
|
+
valid_data = validate_data("platforms", v, PLATFORM_SCHEMA)
|
112
|
+
@platform_info[k] = valid_data
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if yml.include?("compile")
|
117
|
+
valid_data = validate_data("compile", yml["compile"], COMPILE_SCHEMA)
|
118
|
+
@compile_info = valid_data
|
119
|
+
end
|
120
|
+
|
121
|
+
if yml.include?("unittest")
|
122
|
+
valid_data = validate_data("unittest", yml["unittest"], UNITTEST_SCHEMA)
|
123
|
+
@unittest_info = valid_data
|
124
|
+
end
|
125
|
+
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Override these settings with settings from another file
|
130
|
+
# @param path [String] the path to the settings yaml file
|
131
|
+
# @return [ArduinoCI::CIConfig] the new settings object
|
132
|
+
def with_override(path)
|
133
|
+
overridden_config = self.class.new
|
134
|
+
overridden_config.package_info = deep_clone(@package_info)
|
135
|
+
overridden_config.platform_info = deep_clone(@platform_info)
|
136
|
+
overridden_config.compile_info = deep_clone(@compile_info)
|
137
|
+
overridden_config.unittest_info = deep_clone(@unittest_info)
|
138
|
+
overridden_config.load_yaml(path)
|
139
|
+
overridden_config
|
140
|
+
end
|
141
|
+
|
142
|
+
# Try to override config with a file at a given location (if it exists)
|
143
|
+
# @param path [String] the path to the settings yaml file
|
144
|
+
# @return [ArduinoCI::CIConfig] the new settings object
|
145
|
+
def attempt_override(config_path)
|
146
|
+
return self unless File.exist? config_path
|
147
|
+
with_override(config_path)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Produce a configuration, assuming the CI script runs from the working directory of the base project
|
151
|
+
# @return [ArduinoCI::CIConfig] the new settings object
|
152
|
+
def from_project_library
|
153
|
+
attempt_override(CONFIG_FILENAME)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Produce a configuration override taken from an Arduino library example path
|
157
|
+
# handle either path to example file or example dir
|
158
|
+
# @param path [String] the path to the settings yaml file
|
159
|
+
# @return [ArduinoCI::CIConfig] the new settings object
|
160
|
+
def from_example(example_path)
|
161
|
+
base_dir = File.directory?(example_path) ? example_path : File.dirname(example_path)
|
162
|
+
attempt_override(File.join(base_dir, CONFIG_FILENAME))
|
163
|
+
end
|
164
|
+
|
165
|
+
# get information about a given platform: board name, package name, compiler stuff, etc
|
166
|
+
# @param platform_name [String] The name of the platform as defined in yaml
|
167
|
+
# @return [Hash] the settings object
|
168
|
+
def platform_definition(platform_name)
|
169
|
+
defn = @platform_info[platform_name]
|
170
|
+
return nil if defn.nil?
|
171
|
+
deep_clone(defn)
|
172
|
+
end
|
173
|
+
|
174
|
+
# the URL that gives the download info for a given package (a JSON file).
|
175
|
+
# this is NOT where the package comes from.
|
176
|
+
# @param package [String] the package name (e.g. "arduino:avr")
|
177
|
+
# @return [String] the URL defined for this package
|
178
|
+
def package_url(package)
|
179
|
+
return nil if @package_info[package].nil?
|
180
|
+
@package_info[package][:url]
|
181
|
+
end
|
182
|
+
|
183
|
+
# platforms to build [the examples on]
|
184
|
+
# @return [Array<String>] The platforms to build
|
185
|
+
def platforms_to_build
|
186
|
+
@compile_info[:platforms]
|
187
|
+
end
|
188
|
+
|
189
|
+
# platforms to unit test [the tests on]
|
190
|
+
# @return [Array<String>] The platforms to unit test on
|
191
|
+
def platforms_to_unittest
|
192
|
+
@unittest_info[:platforms]
|
193
|
+
end
|
194
|
+
|
195
|
+
# @return [Array<String>] The aux libraries required for building/verifying
|
196
|
+
def aux_libraries_for_build
|
197
|
+
return [] if @compile_info[:libraries].nil?
|
198
|
+
@compile_info[:libraries]
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [Array<String>] The aux libraries required for unit testing
|
202
|
+
def aux_libraries_for_unittest
|
203
|
+
return [] if @unittest_info[:libraries].nil?
|
204
|
+
@unittest_info[:libraries]
|
205
|
+
end
|
206
|
+
|
207
|
+
# get GCC configuration for a given platform
|
208
|
+
# @param platform_name [String] The name of the platform as defined in yaml
|
209
|
+
# @return [Hash] the settings
|
210
|
+
def gcc_config(platform_name)
|
211
|
+
plat = @platform_info[platform_name]
|
212
|
+
return {} if plat.nil?
|
213
|
+
return {} if plat[:gcc].nil?
|
214
|
+
plat[:gcc]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|