arduino_ci 0.3.0 → 0.4.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.
@@ -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