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