arduino_ci 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +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
|