arduino_ci 0.3.0 → 1.3.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.
- checksums.yaml +5 -5
- data/README.md +125 -69
- data/REFERENCE.md +711 -0
- data/cpp/arduino/Arduino.h +1 -7
- data/cpp/arduino/ArduinoDefines.h +3 -0
- data/cpp/arduino/AvrMath.h +117 -17
- data/cpp/arduino/Client.h +27 -0
- data/cpp/arduino/EEPROM.h +64 -0
- data/cpp/arduino/Godmode.cpp +7 -0
- data/cpp/arduino/Godmode.h +121 -15
- data/cpp/arduino/HardwareSerial.h +4 -4
- data/cpp/arduino/IPAddress.h +59 -0
- data/cpp/arduino/Print.h +9 -12
- data/cpp/arduino/Printable.h +8 -0
- data/cpp/arduino/SPI.h +11 -3
- data/cpp/arduino/Server.h +5 -0
- data/cpp/arduino/Udp.h +27 -0
- data/cpp/arduino/Wire.h +197 -77
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/unittest/ArduinoUnitTests.h +32 -0
- data/cpp/unittest/Assertion.h +54 -26
- data/cpp/unittest/Compare.h +58 -51
- data/cpp/unittest/OstreamHelpers.h +4 -0
- data/exe/arduino_ci.rb +538 -0
- data/exe/arduino_ci_remote.rb +2 -393
- data/exe/arduino_library_location.rb +2 -2
- data/exe/ensure_arduino_installation.rb +7 -1
- data/lib/arduino_ci.rb +1 -0
- data/lib/arduino_ci/arduino_backend.rb +238 -0
- data/lib/arduino_ci/arduino_downloader.rb +43 -73
- data/lib/arduino_ci/arduino_downloader_linux.rb +17 -55
- data/lib/arduino_ci/arduino_downloader_osx.rb +21 -33
- data/lib/arduino_ci/arduino_downloader_windows.rb +11 -53
- data/lib/arduino_ci/arduino_installation.rb +18 -80
- data/lib/arduino_ci/ci_config.rb +8 -11
- data/lib/arduino_ci/cpp_library.rb +250 -59
- data/lib/arduino_ci/host.rb +59 -4
- data/lib/arduino_ci/library_properties.rb +101 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +57 -6
- metadata +19 -87
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/exe/libasan.rb +0 -29
- data/lib/arduino_ci/arduino_cmd.rb +0 -332
- data/lib/arduino_ci/arduino_cmd_linux.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_linux_builder.rb +0 -19
- data/lib/arduino_ci/arduino_cmd_osx.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_windows.rb +0 -17
@@ -1,16 +1,10 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require "arduino_ci/host"
|
3
|
-
require "arduino_ci/
|
4
|
-
require "arduino_ci/arduino_cmd_linux"
|
5
|
-
require "arduino_ci/arduino_cmd_windows"
|
6
|
-
require "arduino_ci/arduino_cmd_linux_builder"
|
3
|
+
require "arduino_ci/arduino_backend"
|
7
4
|
require "arduino_ci/arduino_downloader_osx"
|
8
5
|
require "arduino_ci/arduino_downloader_linux"
|
9
|
-
|
10
6
|
require "arduino_ci/arduino_downloader_windows" if ArduinoCI::Host.os == :windows
|
11
7
|
|
12
|
-
DESIRED_ARDUINO_IDE_VERSION = "1.8.6".freeze
|
13
|
-
|
14
8
|
module ArduinoCI
|
15
9
|
|
16
10
|
class ArduinoInstallationError < StandardError; end
|
@@ -18,85 +12,29 @@ module ArduinoCI
|
|
18
12
|
# Manage the OS-specific install location of Arduino
|
19
13
|
class ArduinoInstallation
|
20
14
|
|
15
|
+
DESIRED_ARDUINO_CLI_VERSION = "0.13.0".freeze
|
16
|
+
|
21
17
|
class << self
|
22
18
|
|
23
19
|
# attempt to find a workable Arduino executable across platforms
|
24
20
|
#
|
25
21
|
# Autolocation assumed to be an expensive operation
|
26
|
-
# @return [ArduinoCI::
|
22
|
+
# @return [ArduinoCI::ArduinoBackend] an instance of the command or nil if it can't be found
|
27
23
|
def autolocate
|
28
|
-
|
29
|
-
|
30
|
-
when :
|
31
|
-
|
32
|
-
when :linux then
|
33
|
-
loc = ArduinoDownloaderLinux.autolocated_executable
|
34
|
-
return nil if loc.nil?
|
35
|
-
|
36
|
-
ret = ArduinoCmdLinux.new
|
37
|
-
ret.base_cmd = [loc]
|
38
|
-
ret.binary_path = Pathname.new(loc)
|
39
|
-
when :windows then
|
40
|
-
loc = ArduinoDownloaderWindows.autolocated_executable
|
41
|
-
return nil if loc.nil?
|
42
|
-
|
43
|
-
ret = ArduinoCmdWindows.new
|
44
|
-
ret.base_cmd = [loc]
|
45
|
-
ret.binary_path = Pathname.new(loc)
|
24
|
+
downloader_class = case Host.os
|
25
|
+
when :osx then ArduinoDownloaderOSX
|
26
|
+
when :linux then ArduinoDownloaderLinux
|
27
|
+
when :windows then ArduinoDownloaderWindows
|
46
28
|
end
|
47
|
-
ret
|
48
|
-
end
|
49
29
|
|
50
|
-
|
51
|
-
|
52
|
-
osx_root = ArduinoDownloaderOSX.autolocated_installation
|
53
|
-
return nil if osx_root.nil?
|
54
|
-
return nil unless File.exist? osx_root
|
30
|
+
loc = downloader_class.autolocated_executable
|
31
|
+
return nil if loc.nil?
|
55
32
|
|
56
|
-
|
57
|
-
# try a hack that skips splash screen
|
58
|
-
# from https://github.com/arduino/Arduino/issues/1970#issuecomment-321975809
|
59
|
-
[
|
60
|
-
"java",
|
61
|
-
"-cp",
|
62
|
-
"#{osx_root}/Contents/Java/*",
|
63
|
-
"-DAPP_DIR=#{osx_root}/Contents/Java",
|
64
|
-
"-Dfile.encoding=UTF-8",
|
65
|
-
"-Dapple.awt.UIElement=true",
|
66
|
-
"-Xms128M",
|
67
|
-
"-Xmx512M",
|
68
|
-
"processing.app.Base",
|
69
|
-
],
|
70
|
-
# failsafe way
|
71
|
-
[File.join(osx_root, "Contents", "MacOS", "Arduino")]
|
72
|
-
]
|
73
|
-
|
74
|
-
# create return and find a command launcher that works
|
75
|
-
ret = ArduinoCmdOSX.new
|
76
|
-
launchers.each do |launcher|
|
77
|
-
# test whether this method successfully launches the IDE
|
78
|
-
# note that "successful launch" involves a command that will fail,
|
79
|
-
# because that's faster than any command which succeeds. what we
|
80
|
-
# don't want to see is a java error.
|
81
|
-
args = launcher + ["--bogus-option"]
|
82
|
-
result = Host.run_and_capture(*args)
|
83
|
-
|
84
|
-
# NOTE: Was originally searching for "Error: unknown option: --bogus-option"
|
85
|
-
# but also need to find "Erreur: option inconnue : --bogus-option"
|
86
|
-
# and who knows how many other languages.
|
87
|
-
# For now, just search for the end of the error and hope that the java-style
|
88
|
-
# launch of this won't include a similar string in it
|
89
|
-
next unless result[:err].include? ": --bogus-option"
|
90
|
-
|
91
|
-
ret.base_cmd = launcher
|
92
|
-
ret.binary_path = Pathname.new(osx_root)
|
93
|
-
return ret
|
94
|
-
end
|
95
|
-
nil
|
33
|
+
ArduinoBackend.new(loc)
|
96
34
|
end
|
97
35
|
|
98
36
|
# Attempt to find a workable Arduino executable across platforms, and install it if we don't
|
99
|
-
# @return [ArduinoCI::
|
37
|
+
# @return [ArduinoCI::ArduinoBackend] an instance of a command
|
100
38
|
def autolocate!(output = $stdout)
|
101
39
|
candidate = autolocate
|
102
40
|
return candidate unless candidate.nil?
|
@@ -109,12 +47,12 @@ module ArduinoCI
|
|
109
47
|
|
110
48
|
# Forcibly install Arduino from the web
|
111
49
|
# @return [bool] Whether the command succeeded
|
112
|
-
def force_install(output = $stdout, version =
|
113
|
-
worker_class =
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
50
|
+
def force_install(output = $stdout, version = DESIRED_ARDUINO_CLI_VERSION)
|
51
|
+
worker_class = case Host.os
|
52
|
+
when :osx then ArduinoDownloaderOSX
|
53
|
+
when :windows then ArduinoDownloaderWindows
|
54
|
+
when :linux then ArduinoDownloaderLinux
|
55
|
+
end
|
118
56
|
worker = worker_class.new(version, output)
|
119
57
|
worker.execute
|
120
58
|
end
|
data/lib/arduino_ci/ci_config.rb
CHANGED
@@ -57,19 +57,23 @@ module ArduinoCI
|
|
57
57
|
# @return [ArudinoCI::CIConfig] The configuration with defaults filled in
|
58
58
|
def default
|
59
59
|
ret = new
|
60
|
+
ret.instance_variable_set("@is_default", true)
|
60
61
|
ret.load_yaml(File.expand_path("../../misc/default.yml", __dir__))
|
61
62
|
ret
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
attr_reader :is_default
|
65
67
|
attr_accessor :package_info
|
66
68
|
attr_accessor :platform_info
|
67
69
|
attr_accessor :compile_info
|
68
70
|
attr_accessor :unittest_info
|
71
|
+
|
69
72
|
def initialize
|
70
|
-
@
|
73
|
+
@is_default = false
|
74
|
+
@package_info = {}
|
71
75
|
@platform_info = {}
|
72
|
-
@compile_info
|
76
|
+
@compile_info = {}
|
73
77
|
@unittest_info = {}
|
74
78
|
end
|
75
79
|
|
@@ -107,7 +111,7 @@ module ArduinoCI
|
|
107
111
|
good_data = {}
|
108
112
|
source.each do |key, value|
|
109
113
|
ksym = key.to_sym
|
110
|
-
expected_type = schema[ksym].
|
114
|
+
expected_type = schema[ksym].instance_of?(Class) ? schema[ksym] : Hash
|
111
115
|
if !schema.include?(ksym)
|
112
116
|
puts "Warning: unknown field '#{ksym}' under definition for #{rootname}"
|
113
117
|
elsif value.nil?
|
@@ -115,7 +119,7 @@ module ArduinoCI
|
|
115
119
|
elsif value.class != expected_type
|
116
120
|
puts "Warning: expected field '#{ksym}' of #{rootname} to be '#{expected_type}', got '#{value.class}'"
|
117
121
|
else
|
118
|
-
good_data[ksym] = value.
|
122
|
+
good_data[ksym] = value.instance_of?(Hash) ? validate_data(key, value, schema[ksym]) : value
|
119
123
|
end
|
120
124
|
end
|
121
125
|
good_data
|
@@ -232,13 +236,6 @@ module ArduinoCI
|
|
232
236
|
deep_clone(defn)
|
233
237
|
end
|
234
238
|
|
235
|
-
# Whether a package is built-in to arduino
|
236
|
-
# @param package [String] the package name (e.g. "arduino:avr")
|
237
|
-
# @return [bool]
|
238
|
-
def package_builtin?(package)
|
239
|
-
package.start_with?("arduino:")
|
240
|
-
end
|
241
|
-
|
242
239
|
# the URL that gives the download info for a given package (a JSON file).
|
243
240
|
# this is NOT where the package comes from.
|
244
241
|
# @param package [String] the package name (e.g. "arduino:avr")
|
@@ -14,15 +14,21 @@ module ArduinoCI
|
|
14
14
|
# Information about an Arduino CPP library, specifically for compilation purposes
|
15
15
|
class CppLibrary
|
16
16
|
|
17
|
-
# @return [
|
18
|
-
|
17
|
+
# @return [String] The official library properties file name
|
18
|
+
LIBRARY_PROPERTIES_FILE = "library.properties".freeze
|
19
19
|
|
20
|
-
# @return [
|
21
|
-
attr_reader :
|
20
|
+
# @return [String] The "official" name of the library, which can include spaces (in a way that the lib dir won't)
|
21
|
+
attr_reader :name
|
22
|
+
|
23
|
+
# @return [ArduinoBackend] The backend support for this library
|
24
|
+
attr_reader :backend
|
22
25
|
|
23
26
|
# @return [Array<Pathname>] The set of artifacts created by this class (note: incomplete!)
|
24
27
|
attr_reader :artifacts
|
25
28
|
|
29
|
+
# @return [Array<Pathname>] The set of directories that should be excluded from compilation
|
30
|
+
attr_reader :exclude_dirs
|
31
|
+
|
26
32
|
# @return [String] STDERR from the last command
|
27
33
|
attr_reader :last_err
|
28
34
|
|
@@ -35,24 +41,138 @@ module ArduinoCI
|
|
35
41
|
# @return [Array<Pathname>] Directories suspected of being vendor-bundle
|
36
42
|
attr_reader :vendor_bundle_cache
|
37
43
|
|
38
|
-
# @param
|
39
|
-
# @param
|
40
|
-
|
41
|
-
|
42
|
-
raise ArgumentError, '
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@base_dir = base_dir
|
48
|
-
@exclude_dirs = exclude_dirs
|
49
|
-
@arduino_lib_dir = arduino_lib_dir.expand_path
|
44
|
+
# @param friendly_name [String] The "official" name of the library, which can contain spaces
|
45
|
+
# @param backend [ArduinoBackend] The support backend
|
46
|
+
def initialize(friendly_name, backend)
|
47
|
+
raise ArgumentError, "friendly_name is not a String (got #{friendly_name.class})" unless friendly_name.is_a? String
|
48
|
+
raise ArgumentError, 'backend is not a ArduinoBackend' unless backend.is_a? ArduinoBackend
|
49
|
+
|
50
|
+
@name = friendly_name
|
51
|
+
@backend = backend
|
52
|
+
@info_cache = nil
|
50
53
|
@artifacts = []
|
51
54
|
@last_err = ""
|
52
55
|
@last_out = ""
|
53
56
|
@last_msg = ""
|
54
57
|
@has_libasan_cache = {}
|
55
58
|
@vendor_bundle_cache = nil
|
59
|
+
@exclude_dirs = []
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate a guess as to the on-disk (coerced character) name of this library
|
63
|
+
#
|
64
|
+
# @TODO: delegate this to the backend in some way? It uses "official" names for install, but dir names in lists :(
|
65
|
+
# @param friendly_name [String] The library name as it might appear in library manager
|
66
|
+
# @return [String] How the path will be stored on disk -- spaces are coerced to underscores
|
67
|
+
def self.library_directory_name(friendly_name)
|
68
|
+
friendly_name.tr(" ", "_")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Generate a guess as to the on-disk (coerced character) name of this library
|
72
|
+
#
|
73
|
+
# @TODO: delegate this to the backend in some way? It uses "official" names for install, but dir names in lists :(
|
74
|
+
# @return [String] How the path will be stored on disk -- spaces are coerced to underscores
|
75
|
+
def name_on_disk
|
76
|
+
self.class.library_directory_name(@name)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get the path to this library, whether or not it exists
|
80
|
+
# @return [Pathname] The fully qualified library path
|
81
|
+
def path
|
82
|
+
@backend.lib_dir + name_on_disk
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [String] The parent directory of all examples
|
86
|
+
def examples_dir
|
87
|
+
path + "examples"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Determine whether a library is present in the lib dir
|
91
|
+
#
|
92
|
+
# Note that `true` doesn't guarantee that the library is valid/installed
|
93
|
+
# and `false` doesn't guarantee that the library isn't built-in
|
94
|
+
#
|
95
|
+
# @return [bool]
|
96
|
+
def installed?
|
97
|
+
path.exist?
|
98
|
+
end
|
99
|
+
|
100
|
+
# install a library by name
|
101
|
+
# @param version [String] the version to install
|
102
|
+
# @param recursive [bool] whether to also install its dependencies
|
103
|
+
# @return [bool] whether the command succeeded
|
104
|
+
def install(version = nil, recursive = false)
|
105
|
+
return true if installed? && !recursive
|
106
|
+
|
107
|
+
fqln = version.nil? ? @name : "#{@name}@#{version}"
|
108
|
+
result = if recursive
|
109
|
+
@backend.run_and_capture("lib", "install", fqln)
|
110
|
+
else
|
111
|
+
@backend.run_and_capture("lib", "install", "--no-deps", fqln)
|
112
|
+
end
|
113
|
+
result[:success]
|
114
|
+
end
|
115
|
+
|
116
|
+
# information about the library as reported by the backend
|
117
|
+
# @return [Hash] the metadata object
|
118
|
+
def info
|
119
|
+
return nil unless installed?
|
120
|
+
|
121
|
+
# note that if the library isn't found, we're going to do a lot of cache attempts...
|
122
|
+
if @info_cache.nil?
|
123
|
+
@info_cache = @backend.installed_libraries.find do |l|
|
124
|
+
lib_info = l["library"]
|
125
|
+
Pathname.new(lib_info["install_dir"]).realpath == path.realpath
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@info_cache
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param installed_library_path [String] The library to query
|
133
|
+
# @return [Array<String>] Example sketch files
|
134
|
+
def example_sketches
|
135
|
+
reported_dirs = info["library"]["examples"].map(&Pathname::method(:new))
|
136
|
+
reported_dirs.map { |e| e + e.basename.sub_ext(".ino") }.select(&:exist?).sort_by(&:to_s)
|
137
|
+
end
|
138
|
+
|
139
|
+
# The expected path to the library.properties file (i.e. even if it does not exist)
|
140
|
+
# @return [Pathname]
|
141
|
+
def library_properties_path
|
142
|
+
path + LIBRARY_PROPERTIES_FILE
|
143
|
+
end
|
144
|
+
|
145
|
+
# Whether library.properties definitions for this library exist
|
146
|
+
# @return [bool]
|
147
|
+
def library_properties?
|
148
|
+
lib_props = library_properties_path
|
149
|
+
lib_props.exist? && lib_props.file?
|
150
|
+
end
|
151
|
+
|
152
|
+
# Library properties
|
153
|
+
# @return [LibraryProperties] The library.properties metadata wrapper for this library
|
154
|
+
def library_properties
|
155
|
+
return nil unless library_properties?
|
156
|
+
|
157
|
+
LibraryProperties.new(library_properties_path)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Set directories that should be excluded from compilation
|
161
|
+
# @param rval [Array] Array of strings or pathnames that will be coerced to pathnames
|
162
|
+
def exclude_dirs=(rval)
|
163
|
+
@exclude_dirs = rval.map { |d| d.is_a?(Pathname) ? d : Pathname.new(d) }
|
164
|
+
end
|
165
|
+
|
166
|
+
# Decide whether this is a 1.5-compatible library
|
167
|
+
#
|
168
|
+
# This should be according to https://arduino.github.io/arduino-cli/latest/library-specification
|
169
|
+
# but we rely on the cli to decide for us
|
170
|
+
# @return [bool]
|
171
|
+
def one_point_five?
|
172
|
+
return false unless library_properties?
|
173
|
+
|
174
|
+
src_dir = path + "src"
|
175
|
+
src_dir.exist? && src_dir.directory?
|
56
176
|
end
|
57
177
|
|
58
178
|
# Guess whether a file is part of the vendor bundle (indicating we should ignore it).
|
@@ -62,9 +182,9 @@ module ArduinoCI
|
|
62
182
|
# That gets us the vendor directory (or multiple directories). We can check
|
63
183
|
# if the given path is contained by any of those.
|
64
184
|
#
|
65
|
-
# @param
|
185
|
+
# @param some_path [Pathname] The path to check
|
66
186
|
# @return [bool]
|
67
|
-
def vendor_bundle?(
|
187
|
+
def vendor_bundle?(some_path)
|
68
188
|
# Cache bundle information, as it is (1) time consuming to fetch and (2) not going to change while we run
|
69
189
|
if @vendor_bundle_cache.nil?
|
70
190
|
bundle_info = Host.run_and_capture("bundle show --paths")
|
@@ -99,7 +219,7 @@ module ArduinoCI
|
|
99
219
|
|
100
220
|
# With vendor bundles located, check this file against those
|
101
221
|
@vendor_bundle_cache.any? do |gem_path|
|
102
|
-
|
222
|
+
some_path.ascend do |part|
|
103
223
|
break true if gem_path == part
|
104
224
|
end
|
105
225
|
end
|
@@ -109,11 +229,13 @@ module ArduinoCI
|
|
109
229
|
#
|
110
230
|
# @param path [Pathname] The path to check
|
111
231
|
# @return [bool]
|
112
|
-
def in_tests_dir?(
|
232
|
+
def in_tests_dir?(sourcefile_path)
|
233
|
+
return false unless tests_dir.exist?
|
234
|
+
|
113
235
|
tests_dir_aliases = [tests_dir, tests_dir.realpath]
|
114
236
|
# we could do this but some rubies don't return an enumerator for ascend
|
115
237
|
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
116
|
-
|
238
|
+
sourcefile_path.ascend do |part|
|
117
239
|
return true if tests_dir_aliases.include?(part)
|
118
240
|
end
|
119
241
|
false
|
@@ -123,11 +245,11 @@ module ArduinoCI
|
|
123
245
|
#
|
124
246
|
# @param path [Pathname] The path to check
|
125
247
|
# @return [bool]
|
126
|
-
def in_exclude_dir?(
|
248
|
+
def in_exclude_dir?(sourcefile_path)
|
127
249
|
# we could do this but some rubies don't return an enumerator for ascend
|
128
250
|
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
129
|
-
|
130
|
-
return true if exclude_dir.any? { |p| p.realpath == part }
|
251
|
+
sourcefile_path.ascend do |part|
|
252
|
+
return true if exclude_dir.any? { |p| p.realpath == part.realpath }
|
131
253
|
end
|
132
254
|
false
|
133
255
|
end
|
@@ -138,13 +260,10 @@ module ArduinoCI
|
|
138
260
|
# @param gcc_binary [String]
|
139
261
|
def libasan?(gcc_binary)
|
140
262
|
unless @has_libasan_cache.key?(gcc_binary)
|
141
|
-
|
142
|
-
begin
|
263
|
+
Tempfile.create(["arduino_ci_libasan_check", ".cpp"]) do |file|
|
143
264
|
file.write "int main(){}"
|
144
265
|
file.close
|
145
266
|
@has_libasan_cache[gcc_binary] = run_gcc(gcc_binary, "-o", "/dev/null", "-fsanitize=address", file.path)
|
146
|
-
ensure
|
147
|
-
file.delete
|
148
267
|
end
|
149
268
|
end
|
150
269
|
@has_libasan_cache[gcc_binary]
|
@@ -152,67 +271,100 @@ module ArduinoCI
|
|
152
271
|
|
153
272
|
# Get a list of all CPP source files in a directory and its subdirectories
|
154
273
|
# @param some_dir [Pathname] The directory in which to begin the search
|
274
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
155
275
|
# @return [Array<Pathname>] The paths of the found files
|
156
|
-
def
|
276
|
+
def code_files_in(some_dir, extensions)
|
157
277
|
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
158
|
-
return [] unless some_dir.exist? && some_dir.directory?
|
159
278
|
|
160
|
-
|
161
|
-
|
162
|
-
|
279
|
+
full_dir = path + some_dir
|
280
|
+
return [] unless full_dir.exist? && full_dir.directory?
|
281
|
+
|
282
|
+
files = full_dir.children.reject(&:directory?)
|
283
|
+
cpp = files.select { |path| extensions.include?(path.extname.downcase) }
|
163
284
|
not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
|
164
285
|
not_hidden.sort_by(&:to_s)
|
165
286
|
end
|
166
287
|
|
288
|
+
# Get a list of all CPP source files in a directory and its subdirectories
|
289
|
+
# @param some_dir [Pathname] The directory in which to begin the search
|
290
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
291
|
+
# @return [Array<Pathname>] The paths of the found files
|
292
|
+
def code_files_in_recursive(some_dir, extensions)
|
293
|
+
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
294
|
+
return [] unless some_dir.exist? && some_dir.directory?
|
295
|
+
|
296
|
+
Find.find(some_dir).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten
|
297
|
+
end
|
298
|
+
|
299
|
+
# Source files that are part of the library under test
|
300
|
+
# @param extensions [Array<String>] the allowed extensions (or, the ones we're looking for)
|
301
|
+
# @return [Array<Pathname>]
|
302
|
+
def source_files(extensions)
|
303
|
+
source_dir = Pathname.new(info["library"]["source_dir"])
|
304
|
+
ret = if one_point_five?
|
305
|
+
code_files_in_recursive(source_dir, extensions)
|
306
|
+
else
|
307
|
+
[source_dir, source_dir + "utility"].map { |d| code_files_in(d, extensions) }.flatten
|
308
|
+
end
|
309
|
+
|
310
|
+
# note to future troubleshooter: some of these tests may not be relevant, but at the moment at
|
311
|
+
# least some of them are tied to existing features
|
312
|
+
ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
|
313
|
+
end
|
314
|
+
|
315
|
+
# Header files that are part of the project library under test
|
316
|
+
# @return [Array<Pathname>]
|
317
|
+
def header_files
|
318
|
+
source_files(HPP_EXTENSIONS)
|
319
|
+
end
|
320
|
+
|
167
321
|
# CPP files that are part of the project library under test
|
168
322
|
# @return [Array<Pathname>]
|
169
323
|
def cpp_files
|
170
|
-
|
324
|
+
source_files(CPP_EXTENSIONS)
|
171
325
|
end
|
172
326
|
|
173
327
|
# CPP files that are part of the arduino mock library we're providing
|
174
328
|
# @return [Array<Pathname>]
|
175
329
|
def cpp_files_arduino
|
176
|
-
|
330
|
+
code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
|
177
331
|
end
|
178
332
|
|
179
333
|
# CPP files that are part of the unit test library we're providing
|
180
334
|
# @return [Array<Pathname>]
|
181
335
|
def cpp_files_unittest
|
182
|
-
|
336
|
+
code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
|
183
337
|
end
|
184
338
|
|
185
339
|
# CPP files that are part of the 3rd-party libraries we're including
|
186
340
|
# @param [Array<String>] aux_libraries
|
187
341
|
# @return [Array<Pathname>]
|
188
342
|
def cpp_files_libraries(aux_libraries)
|
189
|
-
arduino_library_src_dirs(aux_libraries).map { |d|
|
343
|
+
arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq
|
190
344
|
end
|
191
345
|
|
192
346
|
# Returns the Pathnames for all paths to exclude from testing and compilation
|
193
347
|
# @return [Array<Pathname>]
|
194
348
|
def exclude_dir
|
195
|
-
@exclude_dirs.map { |p| Pathname.new(
|
349
|
+
@exclude_dirs.map { |p| Pathname.new(path) + p }.select(&:exist?)
|
196
350
|
end
|
197
351
|
|
198
352
|
# The directory where we expect to find unit test defintions provided by the user
|
199
353
|
# @return [Pathname]
|
200
354
|
def tests_dir
|
201
|
-
Pathname.new(
|
355
|
+
Pathname.new(path) + "test"
|
202
356
|
end
|
203
357
|
|
204
358
|
# The files provided by the user that contain unit tests
|
205
359
|
# @return [Array<Pathname>]
|
206
360
|
def test_files
|
207
|
-
|
361
|
+
code_files_in(tests_dir, CPP_EXTENSIONS)
|
208
362
|
end
|
209
363
|
|
210
364
|
# Find all directories in the project library that include C++ header files
|
211
365
|
# @return [Array<Pathname>]
|
212
366
|
def header_dirs
|
213
|
-
|
214
|
-
all_files = Find.find(real).map { |f| Pathname.new(f) }.reject(&:directory?)
|
215
|
-
unbundled = all_files.reject { |path| vendor_bundle?(path) }
|
367
|
+
unbundled = header_files.reject { |path| vendor_bundle?(path) }
|
216
368
|
unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
|
217
369
|
files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
218
370
|
files.map(&:dirname).uniq
|
@@ -236,23 +388,38 @@ module ArduinoCI
|
|
236
388
|
@last_err
|
237
389
|
end
|
238
390
|
|
239
|
-
#
|
240
|
-
# @return [Array<
|
241
|
-
def
|
391
|
+
# Get a list of all dependencies as defined in library.properties
|
392
|
+
# @return [Array<String>] The library names of the dependencies (not the paths)
|
393
|
+
def arduino_library_dependencies
|
394
|
+
return [] unless library_properties?
|
395
|
+
return [] if library_properties.depends.nil?
|
396
|
+
|
397
|
+
library_properties.depends
|
398
|
+
end
|
399
|
+
|
400
|
+
# Arduino library dependencies all the way down, installing if they are not present
|
401
|
+
# @return [Array<String>] The library names of the dependencies (not the paths)
|
402
|
+
def all_arduino_library_dependencies!(additional_libraries = [])
|
242
403
|
# Pull in all possible places that headers could live, according to the spec:
|
243
404
|
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
405
|
+
recursive = (additional_libraries + arduino_library_dependencies).map do |n|
|
406
|
+
other_lib = self.class.new(n, @backend)
|
407
|
+
other_lib.install unless other_lib.installed?
|
408
|
+
other_lib.all_arduino_library_dependencies!
|
409
|
+
end.flatten
|
410
|
+
(additional_libraries + recursive).uniq
|
411
|
+
end
|
412
|
+
|
413
|
+
# Arduino library directories containing sources -- only those of the dependencies
|
414
|
+
# @return [Array<Pathname>]
|
415
|
+
def arduino_library_src_dirs(aux_libraries)
|
416
|
+
all_arduino_library_dependencies!(aux_libraries).map { |l| self.class.new(l, @backend).header_dirs }.flatten.uniq
|
253
417
|
end
|
254
418
|
|
255
419
|
# GCC command line arguments for including aux libraries
|
420
|
+
#
|
421
|
+
# This function recursively collects the library directores of the dependencies
|
422
|
+
#
|
256
423
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
257
424
|
# @return [Array<String>] The GCC command-line flags necessary to include those libraries
|
258
425
|
def include_args(aux_libraries)
|
@@ -315,6 +482,9 @@ module ArduinoCI
|
|
315
482
|
end
|
316
483
|
|
317
484
|
# build a file for running a test of the given unit test file
|
485
|
+
#
|
486
|
+
# The dependent libraries configuration is appended with data from library.properties internal to the library under test
|
487
|
+
#
|
318
488
|
# @param test_file [Pathname] The path to the file containing the unit tests
|
319
489
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
320
490
|
# @param ci_gcc_config [Hash] The GCC config object
|
@@ -333,8 +503,12 @@ module ArduinoCI
|
|
333
503
|
"-fsanitize=address"
|
334
504
|
]
|
335
505
|
end
|
336
|
-
|
337
|
-
|
506
|
+
|
507
|
+
# combine library.properties defs (if existing) with config file.
|
508
|
+
# 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
|
509
|
+
full_dependencies = all_arduino_library_dependencies!(aux_libraries)
|
510
|
+
arg_sets << test_args(full_dependencies, ci_gcc_config)
|
511
|
+
arg_sets << cpp_files_libraries(full_dependencies).map(&:to_s)
|
338
512
|
arg_sets << [test_file.to_s]
|
339
513
|
args = arg_sets.flatten(1)
|
340
514
|
return nil unless run_gcc(gcc_binary, *args)
|
@@ -343,14 +517,31 @@ module ArduinoCI
|
|
343
517
|
executable
|
344
518
|
end
|
345
519
|
|
520
|
+
# print any found stack dumps
|
521
|
+
# @param executable [Pathname] the path to the test file
|
522
|
+
def print_stack_dump(executable)
|
523
|
+
possible_dumpfiles = [
|
524
|
+
executable.sub_ext("#{executable.extname}.stackdump")
|
525
|
+
]
|
526
|
+
possible_dumpfiles.select(&:exist?).each do |dump|
|
527
|
+
puts "========== Stack dump from #{dump}:"
|
528
|
+
File.foreach(dump) { |line| print " #{line}" }
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
346
532
|
# run a test file
|
347
|
-
# @param [Pathname] the path to the test file
|
533
|
+
# @param executable [Pathname] the path to the test file
|
348
534
|
# @return [bool] whether all tests were successful
|
349
535
|
def run_test_file(executable)
|
350
536
|
@last_cmd = executable
|
351
537
|
@last_out = ""
|
352
538
|
@last_err = ""
|
353
|
-
Host.run_and_output(executable.to_s.shellescape)
|
539
|
+
ret = Host.run_and_output(executable.to_s.shellescape)
|
540
|
+
|
541
|
+
# print any stack traces found during a failure
|
542
|
+
print_stack_dump(executable) unless ret
|
543
|
+
|
544
|
+
ret
|
354
545
|
end
|
355
546
|
|
356
547
|
end
|