arduino_ci 0.2.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|