arduino_ci 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -55,6 +55,32 @@ module ArduinoCI
55
55
  @vendor_bundle_cache = nil
56
56
  end
57
57
 
58
+ # The expected path to the library.properties file (i.e. even if it does not exist)
59
+ # @return [Pathname]
60
+ def library_properties_path
61
+ @base_dir + "library.properties"
62
+ end
63
+
64
+ # Whether library.properties definitions for this library exist
65
+ # @return [bool]
66
+ def library_properties?
67
+ lib_props = library_properties_path
68
+ lib_props.exist? && lib_props.file?
69
+ end
70
+
71
+ # Decide whether this is a 1.5-compatible library
72
+ #
73
+ # according to https://arduino.github.io/arduino-cli/latest/library-specification
74
+ #
75
+ # Should match logic from https://github.com/arduino/arduino-cli/blob/master/arduino/libraries/loader.go
76
+ # @return [bool]
77
+ def one_point_five?
78
+ return false unless library_properties?
79
+
80
+ src_dir = (@base_dir + "src")
81
+ src_dir.exist? && src_dir.directory?
82
+ end
83
+
58
84
  # Guess whether a file is part of the vendor bundle (indicating we should ignore it).
59
85
  #
60
86
  # A safe way to do this seems to be to check whether any of the installed gems
@@ -110,6 +136,8 @@ module ArduinoCI
110
136
  # @param path [Pathname] The path to check
111
137
  # @return [bool]
112
138
  def in_tests_dir?(path)
139
+ return false unless tests_dir.exist?
140
+
113
141
  tests_dir_aliases = [tests_dir, tests_dir.realpath]
114
142
  # we could do this but some rubies don't return an enumerator for ascend
115
143
  # path.ascend.any? { |part| tests_dir_aliases.include?(part) }
@@ -150,43 +178,92 @@ module ArduinoCI
150
178
  @has_libasan_cache[gcc_binary]
151
179
  end
152
180
 
181
+ # Library properties
182
+ def library_properties
183
+ return nil unless library_properties?
184
+
185
+ LibraryProperties.new(library_properties_path)
186
+ end
187
+
188
+ # Get a list of all dependencies as defined in library.properties
189
+ # @return [Array<String>] The library names of the dependencies (not the paths)
190
+ def arduino_library_dependencies
191
+ return nil unless library_properties?
192
+
193
+ library_properties.depends
194
+ end
195
+
153
196
  # Get a list of all CPP source files in a directory and its subdirectories
154
197
  # @param some_dir [Pathname] The directory in which to begin the search
198
+ # @param extensions [Array<Sring>] The set of allowable file extensions
155
199
  # @return [Array<Pathname>] The paths of the found files
156
- def cpp_files_in(some_dir)
200
+ def code_files_in(some_dir, extensions)
157
201
  raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
158
202
  return [] unless some_dir.exist? && some_dir.directory?
159
203
 
160
- real = some_dir.realpath
161
- files = Find.find(real).map { |p| Pathname.new(p) }.reject(&:directory?)
162
- cpp = files.select { |path| CPP_EXTENSIONS.include?(path.extname.downcase) }
204
+ files = some_dir.realpath.children.reject(&:directory?)
205
+ cpp = files.select { |path| extensions.include?(path.extname.downcase) }
163
206
  not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
164
207
  not_hidden.sort_by(&:to_s)
165
208
  end
166
209
 
210
+ # Get a list of all CPP source files in a directory and its subdirectories
211
+ # @param some_dir [Pathname] The directory in which to begin the search
212
+ # @param extensions [Array<Sring>] The set of allowable file extensions
213
+ # @return [Array<Pathname>] The paths of the found files
214
+ def code_files_in_recursive(some_dir, extensions)
215
+ raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
216
+ return [] unless some_dir.exist? && some_dir.directory?
217
+
218
+ real = some_dir.realpath
219
+ Find.find(real).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten
220
+ end
221
+
222
+ # Header files that are part of the project library under test
223
+ # @return [Array<Pathname>]
224
+ def header_files
225
+ ret = if one_point_five?
226
+ code_files_in_recursive(@base_dir + "src", HPP_EXTENSIONS)
227
+ else
228
+ [@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, HPP_EXTENSIONS) }.flatten
229
+ end
230
+
231
+ # note to future troubleshooter: some of these tests may not be relevant, but at the moment at
232
+ # least some of them are tied to existing features
233
+ ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
234
+ end
235
+
167
236
  # CPP files that are part of the project library under test
168
237
  # @return [Array<Pathname>]
169
238
  def cpp_files
170
- cpp_files_in(@base_dir).reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
239
+ ret = if one_point_five?
240
+ code_files_in_recursive(@base_dir + "src", CPP_EXTENSIONS)
241
+ else
242
+ [@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten
243
+ end
244
+
245
+ # note to future troubleshooter: some of these tests may not be relevant, but at the moment at
246
+ # least some of them are tied to existing features
247
+ ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
171
248
  end
172
249
 
173
250
  # CPP files that are part of the arduino mock library we're providing
174
251
  # @return [Array<Pathname>]
175
252
  def cpp_files_arduino
176
- cpp_files_in(ARDUINO_HEADER_DIR)
253
+ code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
177
254
  end
178
255
 
179
256
  # CPP files that are part of the unit test library we're providing
180
257
  # @return [Array<Pathname>]
181
258
  def cpp_files_unittest
182
- cpp_files_in(UNITTEST_HEADER_DIR)
259
+ code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
183
260
  end
184
261
 
185
262
  # CPP files that are part of the 3rd-party libraries we're including
186
263
  # @param [Array<String>] aux_libraries
187
264
  # @return [Array<Pathname>]
188
265
  def cpp_files_libraries(aux_libraries)
189
- arduino_library_src_dirs(aux_libraries).map { |d| cpp_files_in(d) }.flatten.uniq
266
+ arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq
190
267
  end
191
268
 
192
269
  # Returns the Pathnames for all paths to exclude from testing and compilation
@@ -204,15 +281,13 @@ module ArduinoCI
204
281
  # The files provided by the user that contain unit tests
205
282
  # @return [Array<Pathname>]
206
283
  def test_files
207
- cpp_files_in(tests_dir)
284
+ code_files_in(tests_dir, CPP_EXTENSIONS)
208
285
  end
209
286
 
210
287
  # Find all directories in the project library that include C++ header files
211
288
  # @return [Array<Pathname>]
212
289
  def header_dirs
213
- real = @base_dir.realpath
214
- all_files = Find.find(real).map { |f| Pathname.new(f) }.reject(&:directory?)
215
- unbundled = all_files.reject { |path| vendor_bundle?(path) }
290
+ unbundled = header_files.reject { |path| vendor_bundle?(path) }
216
291
  unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
217
292
  files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
218
293
  files.map(&:dirname).uniq
@@ -236,23 +311,24 @@ module ArduinoCI
236
311
  @last_err
237
312
  end
238
313
 
239
- # Arduino library directories containing sources
314
+ # Arduino library directories containing sources -- only those of the dependencies
240
315
  # @return [Array<Pathname>]
241
316
  def arduino_library_src_dirs(aux_libraries)
242
317
  # Pull in all possible places that headers could live, according to the spec:
243
318
  # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
244
- # TODO: be smart and implement library spec (library.properties, etc)?
245
- subdirs = ["", "src", "utility"]
246
- all_aux_include_dirs_nested = aux_libraries.map do |libdir|
319
+
320
+ aux_libraries.map do |d|
247
321
  # library manager coerces spaces in package names to underscores
248
322
  # see https://github.com/ianfixes/arduino_ci/issues/132#issuecomment-518857059
249
- legal_libdir = libdir.tr(" ", "_")
250
- subdirs.map { |subdir| Pathname.new(@arduino_lib_dir) + legal_libdir + subdir }
251
- end
252
- all_aux_include_dirs_nested.flatten.select(&:exist?).select(&:directory?)
323
+ legal_dir = d.tr(" ", "_")
324
+ self.class.new(@arduino_lib_dir + legal_dir, @arduino_lib_dir, @exclude_dirs).header_dirs
325
+ end.flatten.uniq
253
326
  end
254
327
 
255
328
  # GCC command line arguments for including aux libraries
329
+ #
330
+ # This function recursively collects the library directores of the dependencies
331
+ #
256
332
  # @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
257
333
  # @return [Array<String>] The GCC command-line flags necessary to include those libraries
258
334
  def include_args(aux_libraries)
@@ -315,6 +391,9 @@ module ArduinoCI
315
391
  end
316
392
 
317
393
  # build a file for running a test of the given unit test file
394
+ #
395
+ # The dependent libraries configuration is appended with data from library.properties internal to the library under test
396
+ #
318
397
  # @param test_file [Pathname] The path to the file containing the unit tests
319
398
  # @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
320
399
  # @param ci_gcc_config [Hash] The GCC config object
@@ -333,8 +412,12 @@ module ArduinoCI
333
412
  "-fsanitize=address"
334
413
  ]
335
414
  end
336
- arg_sets << test_args(aux_libraries, ci_gcc_config)
337
- arg_sets << cpp_files_libraries(aux_libraries).map(&:to_s)
415
+
416
+ # combine library.properties defs (if existing) with config file.
417
+ # TODO: as much as I'd like to rely only on the properties file(s), I think that would prevent testing 1.0-spec libs
418
+ full_aux_libraries = arduino_library_dependencies.nil? ? aux_libraries : aux_libraries + arduino_library_dependencies
419
+ arg_sets << test_args(full_aux_libraries, ci_gcc_config)
420
+ arg_sets << cpp_files_libraries(full_aux_libraries).map(&:to_s)
338
421
  arg_sets << [test_file.to_s]
339
422
  args = arg_sets.flatten(1)
340
423
  return nil unless run_gcc(gcc_binary, *args)
@@ -343,14 +426,31 @@ module ArduinoCI
343
426
  executable
344
427
  end
345
428
 
429
+ # print any found stack dumps
430
+ # @param executable [Pathname] the path to the test file
431
+ def print_stack_dump(executable)
432
+ possible_dumpfiles = [
433
+ executable.sub_ext(executable.extname + ".stackdump")
434
+ ]
435
+ possible_dumpfiles.select(&:exist?).each do |dump|
436
+ puts "========== Stack dump from #{dump}:"
437
+ File.foreach(dump) { |line| print " #{line}" }
438
+ end
439
+ end
440
+
346
441
  # run a test file
347
- # @param [Pathname] the path to the test file
442
+ # @param executable [Pathname] the path to the test file
348
443
  # @return [bool] whether all tests were successful
349
444
  def run_test_file(executable)
350
445
  @last_cmd = executable
351
446
  @last_out = ""
352
447
  @last_err = ""
353
- Host.run_and_output(executable.to_s.shellescape)
448
+ ret = Host.run_and_output(executable.to_s.shellescape)
449
+
450
+ # print any stack traces found during a failure
451
+ print_stack_dump(executable) unless ret
452
+
453
+ ret
354
454
  end
355
455
 
356
456
  end
@@ -0,0 +1,86 @@
1
+ module ArduinoCI
2
+
3
+ # Information about an Arduino library package, as specified by the library.properties file
4
+ #
5
+ # See https://arduino.github.io/arduino-cli/library-specification/#libraryproperties-file-format
6
+ class LibraryProperties
7
+
8
+ # @return [Hash] The properties file parsed as a hash
9
+ attr_reader :fields
10
+
11
+ # @param path [Pathname] The path to the library.properties file
12
+ def initialize(path)
13
+ @fields = {}
14
+ File.foreach(path) do |line|
15
+ parts = line.split("=", 2)
16
+ @fields[parts[0]] = parts[1].chomp unless parts.empty?
17
+ end
18
+ end
19
+
20
+ # Enable a shortcut syntax for library property accessors, in the style of `attr_accessor` metaprogramming.
21
+ # This is used to create a named field pointing to a specific property in the file, optionally applying
22
+ # a specific formatting function.
23
+ #
24
+ # The formatting function MUST be a static method on this class. This is a limitation caused by the desire
25
+ # to both (1) expose the formatters outside this class, and (2) use them for metaprogramming without the
26
+ # having to name the entire function. field_reader is a static method, so if not for the fact that
27
+ # `self.class.methods.include? formatter` fails to work for class methods in this context (unlike
28
+ # `self.methods.include?`, which properly finds instance methods), I would allow either one and just
29
+ # conditionally `define_method` the proper definition
30
+ #
31
+ # @param name [String] What the accessor will be called
32
+ # @param field_num [Integer] The name of the key of the property
33
+ # @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional)
34
+ # @return [void]
35
+ # @macro [attach] field_reader
36
+ # @!attribute [r] $1
37
+ # @return property $2 of the library.properties file, formatted with the function {$3}
38
+ def self.field_reader(name, formatter = nil)
39
+ key = name.to_s
40
+ if formatter.nil?
41
+ define_method(name) { @fields[key] }
42
+ else
43
+ define_method(name) { @fields.key?(key) ? self.class.send(formatter.to_sym, @fields[key]) : nil }
44
+ end
45
+ end
46
+
47
+ # Parse a value as a comma-separated array
48
+ # @param input [String]
49
+ # @return [Array<String>] The individual values
50
+ def self._csv(input)
51
+ input.split(",").map(&:strip)
52
+ end
53
+
54
+ # Parse a value as a boolean
55
+ # @param input [String]
56
+ # @return [Array<String>] The individual values
57
+ def self._bool(input)
58
+ input == "true" # no indication given in the docs that anything but lowercase "true" indicates boolean true.
59
+ end
60
+
61
+ field_reader :name
62
+ field_reader :version
63
+ field_reader :author, :_csv
64
+ field_reader :maintainer
65
+ field_reader :sentence
66
+ field_reader :paragraph
67
+ field_reader :category
68
+ field_reader :url
69
+ field_reader :architectures, :_csv
70
+ field_reader :depends, :_csv
71
+ field_reader :dot_a_linkage, :_bool
72
+ field_reader :includes, :_csv
73
+ field_reader :precompiled, :_bool
74
+ field_reader :ldflags, :_csv
75
+
76
+ # The value of sentence always will be prepended, so you should start by writing the second sentence here
77
+ #
78
+ # (according to the docs)
79
+ # @return [String] the sentence and paragraph together
80
+ def full_paragraph
81
+ [sentence, paragraph].join(" ")
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -1,3 +1,3 @@
1
1
  module ArduinoCI
2
- VERSION = "0.3.0".freeze
2
+ VERSION = "0.4.0".freeze
3
3
  end
@@ -4,6 +4,7 @@
4
4
 
5
5
  packages:
6
6
  # arduino:xxx are builtin, we don't need to include them here
7
+ # but if we did, it would be url: https://downloads.arduino.cc/packages/package_index.json
7
8
  esp8266:esp8266:
8
9
  url: http://arduino.esp8266.com/stable/package_esp8266com_index.json
9
10
  adafruit:avr:
@@ -21,7 +22,10 @@ platforms:
21
22
  gcc:
22
23
  features:
23
24
  defines:
25
+ - __AVR__
24
26
  - __AVR_ATmega328P__
27
+ - ARDUINO_ARCH_AVR
28
+ - ARDUINO_AVR_UNO
25
29
  warnings:
26
30
  flags:
27
31
  due:
@@ -30,7 +34,10 @@ platforms:
30
34
  gcc:
31
35
  features:
32
36
  defines:
33
- - __AVR_ATmega328__
37
+ - __SAM3X8E__
38
+ - ARDUINO_ARCH_SAM
39
+ - ARDUINO_SAM_DUE
40
+ - NUM_SERIAL_PORTS=4
34
41
  warnings:
35
42
  flags:
36
43
  zero:
@@ -39,8 +46,11 @@ platforms:
39
46
  gcc:
40
47
  features:
41
48
  defines:
42
- - __SAMD21G18A__
43
- - ARDUINO_SAMD_ZERO
49
+ - __SAMD21G18A__
50
+ - ARDUINO_ARCH_SAMD
51
+ - ARDUINO_SAMD_ZERO
52
+ # This also has SerialUSB, which is not included here.
53
+ - NUM_SERIAL_PORTS=2
44
54
  warnings:
45
55
  flags:
46
56
  esp32:
@@ -49,6 +59,10 @@ platforms:
49
59
  gcc:
50
60
  features:
51
61
  defines:
62
+ - ESP32
63
+ - ARDUINO_ARCH_ESP32
64
+ - ARDUINO_FEATHER_ESP32
65
+ - NUM_SERIAL_PORTS=3
52
66
  warnings:
53
67
  flags:
54
68
  esp8266:
@@ -57,6 +71,10 @@ platforms:
57
71
  gcc:
58
72
  features:
59
73
  defines:
74
+ - ESP8266
75
+ - ARDUINO_ARCH_ESP8266
76
+ - ARDUINO_ESP8266_ESP12
77
+ - NUM_SERIAL_PORTS=2
60
78
  warnings:
61
79
  flags:
62
80
  leonardo:
@@ -65,7 +83,10 @@ platforms:
65
83
  gcc:
66
84
  features:
67
85
  defines:
86
+ - __AVR__
68
87
  - __AVR_ATmega32U4__
88
+ - ARDUINO_ARCH_AVR
89
+ - ARDUINO_AVR_LEONARDO
69
90
  warnings:
70
91
  flags:
71
92
  trinket:
@@ -74,6 +95,10 @@ platforms:
74
95
  gcc:
75
96
  features:
76
97
  defines:
98
+ - __AVR__
99
+ - __AVR_ATtiny85__
100
+ - ARDUINO_ARCH_AVR
101
+ - ARDUINO_AVR_TRINKET5
77
102
  warnings:
78
103
  flags:
79
104
  gemma:
@@ -82,6 +107,10 @@ platforms:
82
107
  gcc:
83
108
  features:
84
109
  defines:
110
+ - __AVR__
111
+ - __AVR_ATtiny85__
112
+ - ARDUINO_ARCH_AVR
113
+ - ARDUINO_AVR_GEMMA
85
114
  warnings:
86
115
  flags:
87
116
  m4:
@@ -90,6 +119,12 @@ platforms:
90
119
  gcc:
91
120
  features:
92
121
  defines:
122
+ - __SAMD51__
123
+ - __SAMD51J19A__
124
+ - ARDUINO_ARCH_SAMD
125
+ - ARDUINO_METRO_M4
126
+ # Serial is actually USB virtual serial, not HardwareSerial
127
+ - NUM_SERIAL_PORTS=2
93
128
  warnings:
94
129
  flags:
95
130
  mega2560:
@@ -98,7 +133,10 @@ platforms:
98
133
  gcc:
99
134
  features:
100
135
  defines:
136
+ - __AVR__
101
137
  - __AVR_ATmega2560__
138
+ - ARDUINO_ARCH_AVR
139
+ - ARDUINO_AVR_MEGA2560
102
140
  warnings:
103
141
  flags:
104
142
  cplayClassic:
@@ -107,6 +145,10 @@ platforms:
107
145
  gcc:
108
146
  features:
109
147
  defines:
148
+ - __AVR__
149
+ - __AVR_ATmega32U4__
150
+ - ARDUINO_ARCH_AVR
151
+ - ARDUINO_AVR_CIRCUITPLAY
110
152
  warnings:
111
153
  flags:
112
154
  cplayExpress:
@@ -115,6 +157,11 @@ platforms:
115
157
  gcc:
116
158
  features:
117
159
  defines:
160
+ - __SAMD21G18A__
161
+ - ARDUINO_ARCH_SAMD
162
+ - ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS
163
+ # Serial is actually an alias of SerialUSB, not a HardwareSerial
164
+ - NUM_SERIAL_PORTS=2
118
165
  warnings:
119
166
  flags:
120
167