1
1
require 'fileutils'
2
2
require 'pathname'
3
+ require 'json'
3
4
4
5
# workaround for https://github.com/arduino/Arduino/issues/3535
5
6
WORKAROUND_LIB = "USBHost" . freeze
@@ -24,17 +25,10 @@ def self.flag(name, text = nil)
24
25
self . class_eval ( "def flag_#{ name } ;\" #{ text } \" ;end" , __FILE__ , __LINE__ )
25
26
end
26
27
27
- # the array of command components to launch the Arduino executable
28
- # @return [Array<String>]
29
- attr_accessor :base_cmd
30
-
31
28
# the actual path to the executable on this platform
32
29
# @return [Pathname]
33
30
attr_accessor :binary_path
34
31
35
- # part of a workaround for https://github.com/arduino/Arduino/issues/3535
36
- attr_reader :libraries_indexed
37
-
38
32
# @return [String] STDOUT of the most recently-run command
39
33
attr_reader :last_out
40
34
@@ -44,97 +38,29 @@ def self.flag(name, text = nil)
44
38
# @return [String] the most recently-run command
45
39
attr_reader :last_msg
46
40
41
+ # @return [Array<String>] Additional URLs for the boards manager
42
+ attr_reader :additional_urls
43
+
47
44
# set the command line flags (undefined for now).
48
45
# These vary between gui/cli. Inline comments added for greppability
49
- flag :get_pref # flag_get_pref
50
- flag :set_pref # flag_set_pref
51
- flag :save_prefs # flag_save_prefs
52
- flag :use_board # flag_use_board
53
46
flag :install_boards # flag_install_boards
54
47
flag :install_library # flag_install_library
55
48
flag :verify # flag_verify
56
49
57
- def initialize
58
- @prefs_cache = { }
59
- @prefs_fetched = false
60
- @libraries_indexed = false
50
+ def initialize ( binary_path )
51
+ @binary_path = binary_path
52
+ @additional_urls = [ ]
61
53
@last_out = ""
62
54
@last_err = ""
63
55
@last_msg = ""
64
56
end
65
57
66
- # Convert a preferences dump into a flat hash
67
- # @param arduino_output [String] The raw Arduino executable output
68
- # @return [Hash] preferences as a hash
69
- def parse_pref_string ( arduino_output )
70
- lines = arduino_output . split ( "\n " ) . select { |l | l . include? "=" }
71
- ret = lines . each_with_object ( { } ) do |e , acc |
72
- parts = e . split ( "=" , 2 )
73
- acc [ parts [ 0 ] ] = parts [ 1 ]
74
- acc
75
- end
76
- ret
77
- end
78
-
79
- # @return [String] the path to the Arduino libraries directory
80
- def lib_dir
81
- Pathname . new ( get_pref ( "sketchbook.path" ) ) + "libraries"
82
- end
83
-
84
- # fetch preferences in their raw form
85
- # @return [String] Preferences as a set of lines
86
- def _prefs_raw
87
- resp = run_and_capture ( flag_get_pref )
88
- fail_msg = "Arduino binary failed to operate as expected; you will have to troubleshoot it manually"
89
- raise ArduinoExecutionError , "#{ fail_msg } . The command was #{ @last_msg } " unless resp [ :success ]
90
-
91
- @prefs_fetched = true
92
- resp [ :out ]
93
- end
94
-
95
- # Get the Arduino preferences, from cache if possible
96
- # @return [Hash] The full set of preferences
97
- def prefs
98
- prefs_raw = _prefs_raw unless @prefs_fetched
99
- return nil if prefs_raw . nil?
100
-
101
- @prefs_cache = parse_pref_string ( prefs_raw )
102
- @prefs_cache . clone
103
- end
104
-
105
- # get a preference key
106
- # @param key [String] The preferences key to look up
107
- # @return [String] The preference value
108
- def get_pref ( key )
109
- data = @prefs_fetched ? @prefs_cache : prefs
110
- data [ key ]
111
- end
112
-
113
- # underlying preference-setter.
114
- # @param key [String] The preference name
115
- # @param value [String] The value to set to
116
- # @return [bool] whether the command succeeded
117
- def _set_pref ( key , value )
118
- run_and_capture ( flag_set_pref , "#{ key } =#{ value } " , flag_save_prefs ) [ :success ]
119
- end
120
-
121
- # set a preference key/value pair, and update the cache.
122
- # @param key [String] the preference key
123
- # @param value [String] the preference value
124
- # @return [bool] whether the command succeeded
125
- def set_pref ( key , value )
126
- prefs unless @prefs_fetched # update cache first
127
- success = _set_pref ( key , value )
128
- @prefs_cache [ key ] = value if success
129
- success
130
- end
131
-
132
58
def _wrap_run ( work_fn , *args , **kwargs )
133
59
# do some work to extract & merge environment variables if they exist
134
60
has_env = !args . empty? && args [ 0 ] . class == Hash
135
61
env_vars = has_env ? args [ 0 ] : { }
136
62
actual_args = has_env ? args [ 1 ..-1 ] : args # need to shift over if we extracted args
137
- full_args = @base_cmd + actual_args
63
+ full_args = [ binary_path . to_s , "--format" , "json" ] + actual_args
138
64
full_cmd = env_vars . empty? ? full_args : [ env_vars ] + full_args
139
65
140
66
shell_vars = env_vars . map { |k , v | "#{ k } =#{ v } " } . join ( " " )
@@ -156,19 +82,34 @@ def run_and_capture(*args, **kwargs)
156
82
ret
157
83
end
158
84
85
+ def capture_json ( *args , **kwargs )
86
+ ret = run_and_capture ( *args , **kwargs )
87
+ ret [ :json ] = JSON . parse ( ret [ :out ] )
88
+ end
89
+
90
+ # Get a dump of the entire config
91
+ # @return [Hash] The configuration
92
+ def config_dump
93
+ capture_json ( "config" , "dump" )
94
+ end
95
+
96
+ # @return [String] the path to the Arduino libraries directory
97
+ def lib_dir
98
+ Pathname . new ( config_dump [ "directories" ] [ "user" ] ) + "libraries"
99
+ end
100
+
159
101
# Board manager URLs
160
102
# @return [Array<String>] The additional URLs used by the board manager
161
103
def board_manager_urls
162
- url_list = get_pref ( "boardsmanager.additional.urls" )
163
- return [ ] if url_list . nil?
164
-
165
- url_list . split ( "," )
104
+ config_dump [ "board_manager" ] [ "additional_urls" ] + @additional_urls
166
105
end
167
106
168
107
# Set board manager URLs
169
108
# @return [Array<String>] The additional URLs used by the board manager
170
109
def board_manager_urls = ( all_urls )
171
- set_pref ( "boardsmanager.additional.urls" , all_urls . join ( "," ) )
110
+ raise ArgumentError ( "all_urls should be an array, got #{ all_urls . class } " ) unless all_urls . is_a? Array
111
+
112
+ @additional_urls = all_urls
172
113
end
173
114
174
115
# check whether a board is installed
@@ -177,48 +118,33 @@ def board_manager_urls=(all_urls)
177
118
# @param boardname [String] The board to test
178
119
# @return [bool] Whether the board is installed
179
120
def board_installed? ( boardname )
180
- run_and_capture ( flag_use_board , boardname ) [ :success ]
121
+ # capture_json("core", "list")[:json].find { |b| b["ID"] == boardname } # nope, this is for the family
122
+ run_and_capture ( "board" , "details" , "--fqbn" , boardname ) [ :success ]
181
123
end
182
124
183
125
# install a board by name
184
126
# @param name [String] the board name
185
127
# @return [bool] whether the command succeeded
186
128
def install_boards ( boardfamily )
187
- # TODO: find out why IO.pipe fails but File::NULL succeeds :(
188
- result = run_and_capture ( flag_install_boards , boardfamily )
189
- already_installed = result [ :err ] . include? ( "Platform is already installed!" )
190
- result [ :success ] || already_installed
129
+ result = run_and_capture ( "core" , "install" , boardfamily )
130
+ result [ :success ]
191
131
end
192
132
193
- # install a library by name
194
- # @param name [String] the library name
195
- # @return [bool] whether the command succeeded
196
- def _install_library ( library_name )
197
- result = run_and_capture ( flag_install_library , library_name )
198
-
199
- already_installed = result [ :err ] . include? ( "Library is already installed: #{ library_name } " )
200
- success = result [ :success ] || already_installed
201
-
202
- @libraries_indexed = ( @libraries_indexed || success ) if library_name == WORKAROUND_LIB
203
- success
204
- end
205
-
206
- # index the set of libraries by installing a dummy library
207
- # related to WORKAROUND_LIB and https://github.com/arduino/Arduino/issues/3535
208
- # TODO: unclear if this is still necessary
209
- def index_libraries
210
- return true if @libraries_indexed
211
-
212
- _install_library ( WORKAROUND_LIB )
213
- @libraries_indexed
133
+ # @return [Hash] information about installed libraries via the CLI
134
+ def installed_libraries
135
+ capture_json ( "lib" , "list" ) [ :json ]
214
136
end
215
137
216
138
# install a library by name
217
139
# @param name [String] the library name
140
+ # @param version [String] the version to install
218
141
# @return [bool] whether the command succeeded
219
- def install_library ( library_name )
220
- index_libraries
221
- _install_library ( library_name )
142
+ def install_library ( library_name , version = nil )
143
+ return true if library_present? ( library_name )
144
+
145
+ fqln = version . nil? ? library_name : "#{ library_name } @#{ version } "
146
+ result = run_and_capture ( "lib" , "install" , fqln )
147
+ result [ :success ]
222
148
end
223
149
224
150
# generate the (very likely) path of a library given its name
@@ -239,47 +165,20 @@ def library_present?(library_name)
239
165
library_path ( library_name ) . exist?
240
166
end
241
167
242
- # update the library index
243
- # @return [bool] Whether the update succeeded
244
- def update_library_index
245
- # install random lib so the arduino IDE grabs a new library index
246
- # see: https://github.com/arduino/Arduino/issues/3535
247
- install_library ( WORKAROUND_LIB )
248
- end
249
-
250
- # use a particular board for compilation
168
+ # @param path [String] The sketch to compile
251
169
# @param boardname [String] The board to use
252
170
# @return [bool] whether the command succeeded
253
- def use_board ( boardname )
254
- run_and_capture ( flag_use_board , boardname , flag_save_prefs ) [ :success ]
255
- end
256
-
257
- # use a particular board for compilation, installing it if necessary
258
- # @param boardname [String] The board to use
259
- # @return [bool] whether the command succeeded
260
- def use_board! ( boardname )
261
- return true if use_board ( boardname )
262
-
263
- boardfamily = boardname . split ( ":" ) [ 0 ..1 ] . join ( ":" )
264
- puts "Board '#{ boardname } ' not found; attempting to install '#{ boardfamily } '"
265
- return false unless install_boards ( boardfamily ) # guess board family from first 2 :-separated fields
266
-
267
- use_board ( boardname )
268
- end
269
-
270
- # @param path [String] The sketch to verify
271
- # @return [bool] whether the command succeeded
272
- def verify_sketch ( path )
171
+ def compile_sketch ( path , boardname )
273
172
ext = File . extname path
274
173
unless ext . casecmp ( ".ino" ) . zero?
275
- @last_msg = "Refusing to verify sketch with '#{ ext } ' extension -- rename it to '.ino'!"
174
+ @last_msg = "Refusing to compile sketch with '#{ ext } ' extension -- rename it to '.ino'!"
276
175
return false
277
176
end
278
177
unless File . exist? path
279
- @last_msg = "Can't verify Sketch at nonexistent path '#{ path } '!"
178
+ @last_msg = "Can't compile Sketch at nonexistent path '#{ path } '!"
280
179
return false
281
180
end
282
- ret = run_and_capture ( flag_verify , path )
181
+ ret = run_and_capture ( "compile" , "--fqbn" , boardname , "--warnings" , "all" , "--dry-run" , path )
283
182
ret [ :success ]
284
183
end
285
184
0 commit comments