arduino_ci 0.2.0 → 1.1.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 +49 -20
- data/REFERENCE.md +636 -0
- data/cpp/arduino/Arduino.h +1 -1
- 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 +11 -0
- data/cpp/arduino/Godmode.h +58 -9
- data/cpp/arduino/HardwareSerial.h +9 -28
- 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 +234 -0
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/arduino/ci/StreamTape.h +36 -0
- data/cpp/unittest/OstreamHelpers.h +4 -0
- data/exe/arduino_ci.rb +427 -0
- data/exe/arduino_ci_remote.rb +2 -385
- 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 +222 -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 +15 -9
- data/lib/arduino_ci/cpp_library.rb +266 -48
- data/lib/arduino_ci/host.rb +59 -4
- data/lib/arduino_ci/library_properties.rb +96 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +55 -4
- metadata +21 -87
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/exe/libasan.rb +0 -29
- data/lib/arduino_ci/arduino_cmd.rb +0 -328
- 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
@@ -28,6 +28,7 @@ UNITTEST_SCHEMA = {
|
|
28
28
|
compilers: Array,
|
29
29
|
platforms: Array,
|
30
30
|
libraries: Array,
|
31
|
+
exclude_dirs: Array,
|
31
32
|
testfiles: {
|
32
33
|
select: Array,
|
33
34
|
reject: Array,
|
@@ -65,6 +66,7 @@ module ArduinoCI
|
|
65
66
|
attr_accessor :platform_info
|
66
67
|
attr_accessor :compile_info
|
67
68
|
attr_accessor :unittest_info
|
69
|
+
|
68
70
|
def initialize
|
69
71
|
@package_info = {}
|
70
72
|
@platform_info = {}
|
@@ -106,7 +108,7 @@ module ArduinoCI
|
|
106
108
|
good_data = {}
|
107
109
|
source.each do |key, value|
|
108
110
|
ksym = key.to_sym
|
109
|
-
expected_type = schema[ksym].
|
111
|
+
expected_type = schema[ksym].instance_of?(Class) ? schema[ksym] : Hash
|
110
112
|
if !schema.include?(ksym)
|
111
113
|
puts "Warning: unknown field '#{ksym}' under definition for #{rootname}"
|
112
114
|
elsif value.nil?
|
@@ -114,7 +116,7 @@ module ArduinoCI
|
|
114
116
|
elsif value.class != expected_type
|
115
117
|
puts "Warning: expected field '#{ksym}' of #{rootname} to be '#{expected_type}', got '#{value.class}'"
|
116
118
|
else
|
117
|
-
good_data[ksym] = value.
|
119
|
+
good_data[ksym] = value.instance_of?(Hash) ? validate_data(key, value, schema[ksym]) : value
|
118
120
|
end
|
119
121
|
end
|
120
122
|
good_data
|
@@ -231,13 +233,6 @@ module ArduinoCI
|
|
231
233
|
deep_clone(defn)
|
232
234
|
end
|
233
235
|
|
234
|
-
# Whether a package is built-in to arduino
|
235
|
-
# @param package [String] the package name (e.g. "arduino:avr")
|
236
|
-
# @return [bool]
|
237
|
-
def package_builtin?(package)
|
238
|
-
package.start_with?("arduino:")
|
239
|
-
end
|
240
|
-
|
241
236
|
# the URL that gives the download info for a given package (a JSON file).
|
242
237
|
# this is NOT where the package comes from.
|
243
238
|
# @param package [String] the package name (e.g. "arduino:avr")
|
@@ -256,6 +251,14 @@ module ArduinoCI
|
|
256
251
|
@unittest_info[:compilers]
|
257
252
|
end
|
258
253
|
|
254
|
+
# paths to exclude all files in for building and unitttests
|
255
|
+
# @return [Array<String>] The directories (relative to base dir) to exclude
|
256
|
+
def exclude_dirs
|
257
|
+
return [] if @unittest_info[:exclude_dirs].nil?
|
258
|
+
|
259
|
+
@unittest_info[:exclude_dirs]
|
260
|
+
end
|
261
|
+
|
259
262
|
# platforms to build [the examples on]
|
260
263
|
# @return [Array<String>] The platforms to build
|
261
264
|
def platforms_to_build
|
@@ -293,9 +296,12 @@ module ArduinoCI
|
|
293
296
|
return paths if @unittest_info[:testfiles].nil?
|
294
297
|
|
295
298
|
ret = paths
|
299
|
+
# Check for array emptiness, otherwise nothing will be selected!
|
296
300
|
unless @unittest_info[:testfiles][:select].nil? || @unittest_info[:testfiles][:select].empty?
|
297
301
|
ret.select! { |p| unittest_info[:testfiles][:select].any? { |glob| p.basename.fnmatch(glob) } }
|
298
302
|
end
|
303
|
+
|
304
|
+
# It's OK for the :reject array to be empty, that means nothing will be rejected by default
|
299
305
|
unless @unittest_info[:testfiles][:reject].nil?
|
300
306
|
ret.reject! { |p| unittest_info[:testfiles][:reject].any? { |glob| p.basename.fnmatch(glob) } }
|
301
307
|
end
|
@@ -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,20 +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
|
-
def initialize(
|
41
|
-
raise ArgumentError,
|
42
|
-
raise ArgumentError, '
|
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
|
43
49
|
|
44
|
-
@
|
45
|
-
@
|
50
|
+
@name = friendly_name
|
51
|
+
@backend = backend
|
52
|
+
@info_cache = nil
|
46
53
|
@artifacts = []
|
47
54
|
@last_err = ""
|
48
55
|
@last_out = ""
|
49
56
|
@last_msg = ""
|
50
57
|
@has_libasan_cache = {}
|
51
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?
|
52
176
|
end
|
53
177
|
|
54
178
|
# Guess whether a file is part of the vendor bundle (indicating we should ignore it).
|
@@ -58,9 +182,9 @@ module ArduinoCI
|
|
58
182
|
# That gets us the vendor directory (or multiple directories). We can check
|
59
183
|
# if the given path is contained by any of those.
|
60
184
|
#
|
61
|
-
# @param
|
185
|
+
# @param some_path [Pathname] The path to check
|
62
186
|
# @return [bool]
|
63
|
-
def vendor_bundle?(
|
187
|
+
def vendor_bundle?(some_path)
|
64
188
|
# Cache bundle information, as it is (1) time consuming to fetch and (2) not going to change while we run
|
65
189
|
if @vendor_bundle_cache.nil?
|
66
190
|
bundle_info = Host.run_and_capture("bundle show --paths")
|
@@ -95,7 +219,7 @@ module ArduinoCI
|
|
95
219
|
|
96
220
|
# With vendor bundles located, check this file against those
|
97
221
|
@vendor_bundle_cache.any? do |gem_path|
|
98
|
-
|
222
|
+
some_path.ascend do |part|
|
99
223
|
break true if gem_path == part
|
100
224
|
end
|
101
225
|
end
|
@@ -105,29 +229,41 @@ module ArduinoCI
|
|
105
229
|
#
|
106
230
|
# @param path [Pathname] The path to check
|
107
231
|
# @return [bool]
|
108
|
-
def in_tests_dir?(
|
232
|
+
def in_tests_dir?(sourcefile_path)
|
233
|
+
return false unless tests_dir.exist?
|
234
|
+
|
109
235
|
tests_dir_aliases = [tests_dir, tests_dir.realpath]
|
110
236
|
# we could do this but some rubies don't return an enumerator for ascend
|
111
237
|
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
112
|
-
|
238
|
+
sourcefile_path.ascend do |part|
|
113
239
|
return true if tests_dir_aliases.include?(part)
|
114
240
|
end
|
115
241
|
false
|
116
242
|
end
|
117
243
|
|
244
|
+
# Guess whether a file is part of any @excludes_dir dir (indicating library compilation should ignore it).
|
245
|
+
#
|
246
|
+
# @param path [Pathname] The path to check
|
247
|
+
# @return [bool]
|
248
|
+
def in_exclude_dir?(sourcefile_path)
|
249
|
+
# we could do this but some rubies don't return an enumerator for ascend
|
250
|
+
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
251
|
+
sourcefile_path.ascend do |part|
|
252
|
+
return true if exclude_dir.any? { |p| p.realpath == part.realpath }
|
253
|
+
end
|
254
|
+
false
|
255
|
+
end
|
256
|
+
|
118
257
|
# Check whether libasan (and by extension -fsanitizer=address) is supported
|
119
258
|
#
|
120
259
|
# This requires compilation of a sample program, and will be cached
|
121
260
|
# @param gcc_binary [String]
|
122
261
|
def libasan?(gcc_binary)
|
123
262
|
unless @has_libasan_cache.key?(gcc_binary)
|
124
|
-
|
125
|
-
begin
|
263
|
+
Tempfile.create(["arduino_ci_libasan_check", ".cpp"]) do |file|
|
126
264
|
file.write "int main(){}"
|
127
265
|
file.close
|
128
266
|
@has_libasan_cache[gcc_binary] = run_gcc(gcc_binary, "-o", "/dev/null", "-fsanitize=address", file.path)
|
129
|
-
ensure
|
130
|
-
file.delete
|
131
267
|
end
|
132
268
|
end
|
133
269
|
@has_libasan_cache[gcc_binary]
|
@@ -135,62 +271,102 @@ module ArduinoCI
|
|
135
271
|
|
136
272
|
# Get a list of all CPP source files in a directory and its subdirectories
|
137
273
|
# @param some_dir [Pathname] The directory in which to begin the search
|
274
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
138
275
|
# @return [Array<Pathname>] The paths of the found files
|
139
|
-
def
|
276
|
+
def code_files_in(some_dir, extensions)
|
140
277
|
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
141
|
-
return [] unless some_dir.exist? && some_dir.directory?
|
142
278
|
|
143
|
-
|
144
|
-
|
145
|
-
|
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) }
|
146
284
|
not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
|
147
285
|
not_hidden.sort_by(&:to_s)
|
148
286
|
end
|
149
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
|
+
|
150
321
|
# CPP files that are part of the project library under test
|
151
322
|
# @return [Array<Pathname>]
|
152
323
|
def cpp_files
|
153
|
-
|
324
|
+
source_files(CPP_EXTENSIONS)
|
154
325
|
end
|
155
326
|
|
156
327
|
# CPP files that are part of the arduino mock library we're providing
|
157
328
|
# @return [Array<Pathname>]
|
158
329
|
def cpp_files_arduino
|
159
|
-
|
330
|
+
code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
|
160
331
|
end
|
161
332
|
|
162
333
|
# CPP files that are part of the unit test library we're providing
|
163
334
|
# @return [Array<Pathname>]
|
164
335
|
def cpp_files_unittest
|
165
|
-
|
336
|
+
code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
|
166
337
|
end
|
167
338
|
|
168
339
|
# CPP files that are part of the 3rd-party libraries we're including
|
169
340
|
# @param [Array<String>] aux_libraries
|
170
341
|
# @return [Array<Pathname>]
|
171
342
|
def cpp_files_libraries(aux_libraries)
|
172
|
-
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
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns the Pathnames for all paths to exclude from testing and compilation
|
347
|
+
# @return [Array<Pathname>]
|
348
|
+
def exclude_dir
|
349
|
+
@exclude_dirs.map { |p| Pathname.new(path) + p }.select(&:exist?)
|
173
350
|
end
|
174
351
|
|
175
352
|
# The directory where we expect to find unit test defintions provided by the user
|
176
353
|
# @return [Pathname]
|
177
354
|
def tests_dir
|
178
|
-
Pathname.new(
|
355
|
+
Pathname.new(path) + "test"
|
179
356
|
end
|
180
357
|
|
181
358
|
# The files provided by the user that contain unit tests
|
182
359
|
# @return [Array<Pathname>]
|
183
360
|
def test_files
|
184
|
-
|
361
|
+
code_files_in(tests_dir, CPP_EXTENSIONS)
|
185
362
|
end
|
186
363
|
|
187
364
|
# Find all directories in the project library that include C++ header files
|
188
365
|
# @return [Array<Pathname>]
|
189
366
|
def header_dirs
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
files = unbundled.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
367
|
+
unbundled = header_files.reject { |path| vendor_bundle?(path) }
|
368
|
+
unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
|
369
|
+
files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
194
370
|
files.map(&:dirname).uniq
|
195
371
|
end
|
196
372
|
|
@@ -212,20 +388,38 @@ module ArduinoCI
|
|
212
388
|
@last_err
|
213
389
|
end
|
214
390
|
|
215
|
-
#
|
216
|
-
# @return [Array<
|
217
|
-
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 = [])
|
218
403
|
# Pull in all possible places that headers could live, according to the spec:
|
219
404
|
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
end
|
225
|
-
|
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
|
226
417
|
end
|
227
418
|
|
228
419
|
# GCC command line arguments for including aux libraries
|
420
|
+
#
|
421
|
+
# This function recursively collects the library directores of the dependencies
|
422
|
+
#
|
229
423
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
230
424
|
# @return [Array<String>] The GCC command-line flags necessary to include those libraries
|
231
425
|
def include_args(aux_libraries)
|
@@ -288,6 +482,9 @@ module ArduinoCI
|
|
288
482
|
end
|
289
483
|
|
290
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
|
+
#
|
291
488
|
# @param test_file [Pathname] The path to the file containing the unit tests
|
292
489
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
293
490
|
# @param ci_gcc_config [Hash] The GCC config object
|
@@ -306,8 +503,12 @@ module ArduinoCI
|
|
306
503
|
"-fsanitize=address"
|
307
504
|
]
|
308
505
|
end
|
309
|
-
|
310
|
-
|
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)
|
311
512
|
arg_sets << [test_file.to_s]
|
312
513
|
args = arg_sets.flatten(1)
|
313
514
|
return nil unless run_gcc(gcc_binary, *args)
|
@@ -316,14 +517,31 @@ module ArduinoCI
|
|
316
517
|
executable
|
317
518
|
end
|
318
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
|
+
|
319
532
|
# run a test file
|
320
|
-
# @param [Pathname] the path to the test file
|
533
|
+
# @param executable [Pathname] the path to the test file
|
321
534
|
# @return [bool] whether all tests were successful
|
322
535
|
def run_test_file(executable)
|
323
536
|
@last_cmd = executable
|
324
537
|
@last_out = ""
|
325
538
|
@last_err = ""
|
326
|
-
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
|
327
545
|
end
|
328
546
|
|
329
547
|
end
|