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.
@@ -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