arduino_ci 0.1.21 → 1.0.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 +4 -4
- data/README.md +49 -20
- data/REFERENCE.md +625 -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 +38 -19
- data/cpp/arduino/Godmode.h +88 -22
- data/cpp/arduino/HardwareSerial.h +9 -28
- data/cpp/arduino/IPAddress.h +59 -0
- data/cpp/arduino/MockEventQueue.h +86 -0
- data/cpp/arduino/PinHistory.h +64 -24
- 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 +400 -0
- data/exe/arduino_ci_remote.rb +2 -385
- data/exe/arduino_library_location.rb +2 -2
- data/lib/arduino_ci.rb +1 -0
- data/lib/arduino_ci/arduino_backend.rb +218 -0
- data/lib/arduino_ci/arduino_downloader.rb +42 -72
- 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 +12 -7
- data/lib/arduino_ci/cpp_library.rb +262 -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 +18 -83
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/cpp/arduino/ci/Queue.h +0 -73
- 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
@@ -8,54 +8,42 @@ module ArduinoCI
|
|
8
8
|
# The local filename of the desired IDE package (zip/tar/etc)
|
9
9
|
# @return [string]
|
10
10
|
def package_file
|
11
|
-
"arduino
|
11
|
+
"arduino-cli_#{@desired_version}_macOS_64bit.tar.gz"
|
12
12
|
end
|
13
13
|
|
14
14
|
# The local file (dir) name of the extracted IDE package (zip/tar/etc)
|
15
15
|
# @return [string]
|
16
|
-
def extracted_file
|
17
|
-
"
|
16
|
+
def self.extracted_file
|
17
|
+
"arduino-cli"
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
21
|
-
def self.force_install_location
|
22
|
-
# include the .app extension
|
23
|
-
File.join(ENV['HOME'], 'Arduino.app')
|
24
|
-
end
|
25
|
-
|
26
|
-
# An existing Arduino directory in one of the given directories, or nil
|
27
|
-
# @param Array<string> a list of places to look
|
20
|
+
# The executable Arduino file in an existing installation, or nil
|
28
21
|
# @return [string]
|
29
|
-
def self.
|
30
|
-
|
22
|
+
def self.existing_executable
|
23
|
+
Host.which("arduino-cli")
|
31
24
|
end
|
32
25
|
|
33
|
-
#
|
34
|
-
# @
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
File.exist? exe
|
26
|
+
# Make any preparations or run any checks prior to making changes
|
27
|
+
# @return [string] Error message, or nil if success
|
28
|
+
def prepare
|
29
|
+
reqs = [self.class.extracter]
|
30
|
+
reqs.each do |req|
|
31
|
+
return "#{req} does not appear to be installed!" unless Host.which(req)
|
40
32
|
end
|
33
|
+
nil
|
41
34
|
end
|
42
35
|
|
43
|
-
# The
|
36
|
+
# The technology that will be used to extract the download
|
37
|
+
# (for logging purposes)
|
44
38
|
# @return [string]
|
45
|
-
def self.
|
46
|
-
|
39
|
+
def self.extracter
|
40
|
+
"tar"
|
47
41
|
end
|
48
42
|
|
49
|
-
#
|
50
|
-
# @return [
|
51
|
-
def self.
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
# The executable Arduino file in a forced installation, or nil
|
56
|
-
# @return [string]
|
57
|
-
def self.force_installed_executable
|
58
|
-
self.find_existing_arduino_exe([self.force_install_location])
|
43
|
+
# Extract the package_file to extracted_file
|
44
|
+
# @return [bool] whether successful
|
45
|
+
def self.extract(package_file)
|
46
|
+
system(extracter, "xf", package_file, extracted_file)
|
59
47
|
end
|
60
48
|
|
61
49
|
end
|
@@ -10,19 +10,6 @@ module ArduinoCI
|
|
10
10
|
# Manage the POSIX download & install of Arduino
|
11
11
|
class ArduinoDownloaderWindows < ArduinoDownloader
|
12
12
|
|
13
|
-
# Make any preparations or run any checks prior to making changes
|
14
|
-
# @return [string] Error message, or nil if success
|
15
|
-
def prepare
|
16
|
-
nil
|
17
|
-
end
|
18
|
-
|
19
|
-
# The technology that will be used to complete the download
|
20
|
-
# (for logging purposes)
|
21
|
-
# @return [string]
|
22
|
-
def downloader
|
23
|
-
"open-uri"
|
24
|
-
end
|
25
|
-
|
26
13
|
# Download the package_url to package_file
|
27
14
|
# @return [bool] whether successful
|
28
15
|
def download
|
@@ -35,29 +22,28 @@ module ArduinoCI
|
|
35
22
|
@output.puts "\nArduino force-install failed downloading #{package_url}: #{e}"
|
36
23
|
end
|
37
24
|
|
38
|
-
# Move the extracted package file from extracted_file to the force_install_location
|
39
|
-
# @return [bool] whether successful
|
40
|
-
def install
|
41
|
-
# Move only the content of the directory
|
42
|
-
FileUtils.mv extracted_file, self.class.force_install_location
|
43
|
-
end
|
44
|
-
|
45
25
|
# The local filename of the desired IDE package (zip/tar/etc)
|
46
26
|
# @return [string]
|
47
27
|
def package_file
|
48
|
-
"#{
|
28
|
+
"arduino-cli_#{@desired_version}_Windows_64bit.zip"
|
29
|
+
end
|
30
|
+
|
31
|
+
# The executable Arduino file in an existing installation, or nil
|
32
|
+
# @return [string]
|
33
|
+
def self.existing_executable
|
34
|
+
Host.which("arduino-cli")
|
49
35
|
end
|
50
36
|
|
51
37
|
# The technology that will be used to extract the download
|
52
38
|
# (for logging purposes)
|
53
39
|
# @return [string]
|
54
|
-
def extracter
|
40
|
+
def self.extracter
|
55
41
|
"Expand-Archive"
|
56
42
|
end
|
57
43
|
|
58
44
|
# Extract the package_file to extracted_file
|
59
45
|
# @return [bool] whether successful
|
60
|
-
def extract
|
46
|
+
def self.extract(package_file)
|
61
47
|
Zip::File.open(package_file) do |zip|
|
62
48
|
zip.each do |file|
|
63
49
|
file.extract(file.name)
|
@@ -67,36 +53,8 @@ module ArduinoCI
|
|
67
53
|
|
68
54
|
# The local file (dir) name of the extracted IDE package (zip/tar/etc)
|
69
55
|
# @return [string]
|
70
|
-
def extracted_file
|
71
|
-
"arduino
|
72
|
-
end
|
73
|
-
|
74
|
-
# The path to the directory of an existing installation, or nil
|
75
|
-
# @return [string]
|
76
|
-
def self.existing_installation
|
77
|
-
exe = self.existing_executable
|
78
|
-
return nil if exe.nil?
|
79
|
-
|
80
|
-
File.dirname(exe)
|
81
|
-
end
|
82
|
-
|
83
|
-
# The executable Arduino file in an existing installation, or nil
|
84
|
-
# @return [string]
|
85
|
-
def self.existing_executable
|
86
|
-
arduino_reg = 'SOFTWARE\WOW6432Node\Arduino'
|
87
|
-
Win32::Registry::HKEY_LOCAL_MACHINE.open(arduino_reg).find do |reg|
|
88
|
-
path = reg.read_s('Install_Dir')
|
89
|
-
exe = File.join(path, "arduino_debug.exe")
|
90
|
-
File.exist? exe
|
91
|
-
end
|
92
|
-
rescue
|
93
|
-
nil
|
94
|
-
end
|
95
|
-
|
96
|
-
# The executable Arduino file in a forced installation, or nil
|
97
|
-
# @return [string]
|
98
|
-
def self.force_installed_executable
|
99
|
-
File.join(self.force_install_location, "arduino_debug.exe")
|
56
|
+
def self.extracted_file
|
57
|
+
"arduino-cli.exe"
|
100
58
|
end
|
101
59
|
|
102
60
|
end
|
@@ -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,
|
@@ -231,13 +232,6 @@ module ArduinoCI
|
|
231
232
|
deep_clone(defn)
|
232
233
|
end
|
233
234
|
|
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
235
|
# the URL that gives the download info for a given package (a JSON file).
|
242
236
|
# this is NOT where the package comes from.
|
243
237
|
# @param package [String] the package name (e.g. "arduino:avr")
|
@@ -256,6 +250,14 @@ module ArduinoCI
|
|
256
250
|
@unittest_info[:compilers]
|
257
251
|
end
|
258
252
|
|
253
|
+
# paths to exclude all files in for building and unitttests
|
254
|
+
# @return [Array<String>] The directories (relative to base dir) to exclude
|
255
|
+
def exclude_dirs
|
256
|
+
return [] if @unittest_info[:exclude_dirs].nil?
|
257
|
+
|
258
|
+
@unittest_info[:exclude_dirs]
|
259
|
+
end
|
260
|
+
|
259
261
|
# platforms to build [the examples on]
|
260
262
|
# @return [Array<String>] The platforms to build
|
261
263
|
def platforms_to_build
|
@@ -293,9 +295,12 @@ module ArduinoCI
|
|
293
295
|
return paths if @unittest_info[:testfiles].nil?
|
294
296
|
|
295
297
|
ret = paths
|
298
|
+
# Check for array emptiness, otherwise nothing will be selected!
|
296
299
|
unless @unittest_info[:testfiles][:select].nil? || @unittest_info[:testfiles][:select].empty?
|
297
300
|
ret.select! { |p| unittest_info[:testfiles][:select].any? { |glob| p.basename.fnmatch(glob) } }
|
298
301
|
end
|
302
|
+
|
303
|
+
# It's OK for the :reject array to be empty, that means nothing will be rejected by default
|
299
304
|
unless @unittest_info[:testfiles][:reject].nil?
|
300
305
|
ret.reject! { |p| unittest_info[:testfiles][:reject].any? { |glob| p.basename.fnmatch(glob) } }
|
301
306
|
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,133 @@ 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
|
+
# Determine whether a library is present in the lib dir
|
86
|
+
#
|
87
|
+
# Note that `true` doesn't guarantee that the library is valid/installed
|
88
|
+
# and `false` doesn't guarantee that the library isn't built-in
|
89
|
+
#
|
90
|
+
# @return [bool]
|
91
|
+
def installed?
|
92
|
+
path.exist?
|
93
|
+
end
|
94
|
+
|
95
|
+
# install a library by name
|
96
|
+
# @param version [String] the version to install
|
97
|
+
# @param recursive [bool] whether to also install its dependencies
|
98
|
+
# @return [bool] whether the command succeeded
|
99
|
+
def install(version = nil, recursive = false)
|
100
|
+
return true if installed? && !recursive
|
101
|
+
|
102
|
+
fqln = version.nil? ? @name : "#{@name}@#{version}"
|
103
|
+
result = if recursive
|
104
|
+
@backend.run_and_capture("lib", "install", fqln)
|
105
|
+
else
|
106
|
+
@backend.run_and_capture("lib", "install", "--no-deps", fqln)
|
107
|
+
end
|
108
|
+
result[:success]
|
109
|
+
end
|
110
|
+
|
111
|
+
# information about the library as reported by the backend
|
112
|
+
# @return [Hash] the metadata object
|
113
|
+
def info
|
114
|
+
return nil unless installed?
|
115
|
+
|
116
|
+
# note that if the library isn't found, we're going to do a lot of cache attempts...
|
117
|
+
if @info_cache.nil?
|
118
|
+
@info_cache = @backend.installed_libraries.find do |l|
|
119
|
+
lib_info = l["library"]
|
120
|
+
Pathname.new(lib_info["install_dir"]).realpath == path.realpath
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
@info_cache
|
125
|
+
end
|
126
|
+
|
127
|
+
# @param installed_library_path [String] The library to query
|
128
|
+
# @return [Array<String>] Example sketch files
|
129
|
+
def example_sketches
|
130
|
+
reported_dirs = info["library"]["examples"].map(&Pathname::method(:new))
|
131
|
+
reported_dirs.map { |e| e + e.basename.sub_ext(".ino") }.select(&:exist?).sort_by(&:to_s)
|
132
|
+
end
|
133
|
+
|
134
|
+
# The expected path to the library.properties file (i.e. even if it does not exist)
|
135
|
+
# @return [Pathname]
|
136
|
+
def library_properties_path
|
137
|
+
path + LIBRARY_PROPERTIES_FILE
|
138
|
+
end
|
139
|
+
|
140
|
+
# Whether library.properties definitions for this library exist
|
141
|
+
# @return [bool]
|
142
|
+
def library_properties?
|
143
|
+
lib_props = library_properties_path
|
144
|
+
lib_props.exist? && lib_props.file?
|
145
|
+
end
|
146
|
+
|
147
|
+
# Library properties
|
148
|
+
# @return [LibraryProperties] The library.properties metadata wrapper for this library
|
149
|
+
def library_properties
|
150
|
+
return nil unless library_properties?
|
151
|
+
|
152
|
+
LibraryProperties.new(library_properties_path)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Set directories that should be excluded from compilation
|
156
|
+
# @param rval [Array] Array of strings or pathnames that will be coerced to pathnames
|
157
|
+
def exclude_dirs=(rval)
|
158
|
+
@exclude_dirs = rval.map { |d| d.is_a?(Pathname) ? d : Pathname.new(d) }
|
159
|
+
end
|
160
|
+
|
161
|
+
# Decide whether this is a 1.5-compatible library
|
162
|
+
#
|
163
|
+
# This should be according to https://arduino.github.io/arduino-cli/latest/library-specification
|
164
|
+
# but we rely on the cli to decide for us
|
165
|
+
# @return [bool]
|
166
|
+
def one_point_five?
|
167
|
+
return false unless library_properties?
|
168
|
+
|
169
|
+
src_dir = path + "src"
|
170
|
+
src_dir.exist? && src_dir.directory?
|
52
171
|
end
|
53
172
|
|
54
173
|
# Guess whether a file is part of the vendor bundle (indicating we should ignore it).
|
@@ -58,9 +177,9 @@ module ArduinoCI
|
|
58
177
|
# That gets us the vendor directory (or multiple directories). We can check
|
59
178
|
# if the given path is contained by any of those.
|
60
179
|
#
|
61
|
-
# @param
|
180
|
+
# @param some_path [Pathname] The path to check
|
62
181
|
# @return [bool]
|
63
|
-
def vendor_bundle?(
|
182
|
+
def vendor_bundle?(some_path)
|
64
183
|
# Cache bundle information, as it is (1) time consuming to fetch and (2) not going to change while we run
|
65
184
|
if @vendor_bundle_cache.nil?
|
66
185
|
bundle_info = Host.run_and_capture("bundle show --paths")
|
@@ -95,7 +214,7 @@ module ArduinoCI
|
|
95
214
|
|
96
215
|
# With vendor bundles located, check this file against those
|
97
216
|
@vendor_bundle_cache.any? do |gem_path|
|
98
|
-
|
217
|
+
some_path.ascend do |part|
|
99
218
|
break true if gem_path == part
|
100
219
|
end
|
101
220
|
end
|
@@ -105,29 +224,41 @@ module ArduinoCI
|
|
105
224
|
#
|
106
225
|
# @param path [Pathname] The path to check
|
107
226
|
# @return [bool]
|
108
|
-
def in_tests_dir?(
|
227
|
+
def in_tests_dir?(sourcefile_path)
|
228
|
+
return false unless tests_dir.exist?
|
229
|
+
|
109
230
|
tests_dir_aliases = [tests_dir, tests_dir.realpath]
|
110
231
|
# we could do this but some rubies don't return an enumerator for ascend
|
111
232
|
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
112
|
-
|
233
|
+
sourcefile_path.ascend do |part|
|
113
234
|
return true if tests_dir_aliases.include?(part)
|
114
235
|
end
|
115
236
|
false
|
116
237
|
end
|
117
238
|
|
239
|
+
# Guess whether a file is part of any @excludes_dir dir (indicating library compilation should ignore it).
|
240
|
+
#
|
241
|
+
# @param path [Pathname] The path to check
|
242
|
+
# @return [bool]
|
243
|
+
def in_exclude_dir?(sourcefile_path)
|
244
|
+
# we could do this but some rubies don't return an enumerator for ascend
|
245
|
+
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
246
|
+
sourcefile_path.ascend do |part|
|
247
|
+
return true if exclude_dir.any? { |p| p.realpath == part.realpath }
|
248
|
+
end
|
249
|
+
false
|
250
|
+
end
|
251
|
+
|
118
252
|
# Check whether libasan (and by extension -fsanitizer=address) is supported
|
119
253
|
#
|
120
254
|
# This requires compilation of a sample program, and will be cached
|
121
255
|
# @param gcc_binary [String]
|
122
256
|
def libasan?(gcc_binary)
|
123
257
|
unless @has_libasan_cache.key?(gcc_binary)
|
124
|
-
|
125
|
-
begin
|
258
|
+
Tempfile.create(["arduino_ci_libasan_check", ".cpp"]) do |file|
|
126
259
|
file.write "int main(){}"
|
127
260
|
file.close
|
128
261
|
@has_libasan_cache[gcc_binary] = run_gcc(gcc_binary, "-o", "/dev/null", "-fsanitize=address", file.path)
|
129
|
-
ensure
|
130
|
-
file.delete
|
131
262
|
end
|
132
263
|
end
|
133
264
|
@has_libasan_cache[gcc_binary]
|
@@ -135,62 +266,102 @@ module ArduinoCI
|
|
135
266
|
|
136
267
|
# Get a list of all CPP source files in a directory and its subdirectories
|
137
268
|
# @param some_dir [Pathname] The directory in which to begin the search
|
269
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
138
270
|
# @return [Array<Pathname>] The paths of the found files
|
139
|
-
def
|
271
|
+
def code_files_in(some_dir, extensions)
|
140
272
|
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
141
|
-
return [] unless some_dir.exist? && some_dir.directory?
|
142
273
|
|
143
|
-
|
144
|
-
|
145
|
-
|
274
|
+
full_dir = path + some_dir
|
275
|
+
return [] unless full_dir.exist? && full_dir.directory?
|
276
|
+
|
277
|
+
files = full_dir.children.reject(&:directory?)
|
278
|
+
cpp = files.select { |path| extensions.include?(path.extname.downcase) }
|
146
279
|
not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
|
147
280
|
not_hidden.sort_by(&:to_s)
|
148
281
|
end
|
149
282
|
|
283
|
+
# Get a list of all CPP source files in a directory and its subdirectories
|
284
|
+
# @param some_dir [Pathname] The directory in which to begin the search
|
285
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
286
|
+
# @return [Array<Pathname>] The paths of the found files
|
287
|
+
def code_files_in_recursive(some_dir, extensions)
|
288
|
+
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
289
|
+
return [] unless some_dir.exist? && some_dir.directory?
|
290
|
+
|
291
|
+
Find.find(some_dir).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten
|
292
|
+
end
|
293
|
+
|
294
|
+
# Source files that are part of the library under test
|
295
|
+
# @param extensions [Array<String>] the allowed extensions (or, the ones we're looking for)
|
296
|
+
# @return [Array<Pathname>]
|
297
|
+
def source_files(extensions)
|
298
|
+
source_dir = Pathname.new(info["library"]["source_dir"])
|
299
|
+
ret = if one_point_five?
|
300
|
+
code_files_in_recursive(source_dir, extensions)
|
301
|
+
else
|
302
|
+
[source_dir, source_dir + "utility"].map { |d| code_files_in(d, extensions) }.flatten
|
303
|
+
end
|
304
|
+
|
305
|
+
# note to future troubleshooter: some of these tests may not be relevant, but at the moment at
|
306
|
+
# least some of them are tied to existing features
|
307
|
+
ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
|
308
|
+
end
|
309
|
+
|
310
|
+
# Header files that are part of the project library under test
|
311
|
+
# @return [Array<Pathname>]
|
312
|
+
def header_files
|
313
|
+
source_files(HPP_EXTENSIONS)
|
314
|
+
end
|
315
|
+
|
150
316
|
# CPP files that are part of the project library under test
|
151
317
|
# @return [Array<Pathname>]
|
152
318
|
def cpp_files
|
153
|
-
|
319
|
+
source_files(CPP_EXTENSIONS)
|
154
320
|
end
|
155
321
|
|
156
322
|
# CPP files that are part of the arduino mock library we're providing
|
157
323
|
# @return [Array<Pathname>]
|
158
324
|
def cpp_files_arduino
|
159
|
-
|
325
|
+
code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
|
160
326
|
end
|
161
327
|
|
162
328
|
# CPP files that are part of the unit test library we're providing
|
163
329
|
# @return [Array<Pathname>]
|
164
330
|
def cpp_files_unittest
|
165
|
-
|
331
|
+
code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
|
166
332
|
end
|
167
333
|
|
168
334
|
# CPP files that are part of the 3rd-party libraries we're including
|
169
335
|
# @param [Array<String>] aux_libraries
|
170
336
|
# @return [Array<Pathname>]
|
171
337
|
def cpp_files_libraries(aux_libraries)
|
172
|
-
arduino_library_src_dirs(aux_libraries).map { |d|
|
338
|
+
arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns the Pathnames for all paths to exclude from testing and compilation
|
342
|
+
# @return [Array<Pathname>]
|
343
|
+
def exclude_dir
|
344
|
+
@exclude_dirs.map { |p| Pathname.new(path) + p }.select(&:exist?)
|
173
345
|
end
|
174
346
|
|
175
347
|
# The directory where we expect to find unit test defintions provided by the user
|
176
348
|
# @return [Pathname]
|
177
349
|
def tests_dir
|
178
|
-
Pathname.new(
|
350
|
+
Pathname.new(path) + "test"
|
179
351
|
end
|
180
352
|
|
181
353
|
# The files provided by the user that contain unit tests
|
182
354
|
# @return [Array<Pathname>]
|
183
355
|
def test_files
|
184
|
-
|
356
|
+
code_files_in(tests_dir, CPP_EXTENSIONS)
|
185
357
|
end
|
186
358
|
|
187
359
|
# Find all directories in the project library that include C++ header files
|
188
360
|
# @return [Array<Pathname>]
|
189
361
|
def header_dirs
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
files = unbundled.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
362
|
+
unbundled = header_files.reject { |path| vendor_bundle?(path) }
|
363
|
+
unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
|
364
|
+
files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
194
365
|
files.map(&:dirname).uniq
|
195
366
|
end
|
196
367
|
|
@@ -212,20 +383,39 @@ module ArduinoCI
|
|
212
383
|
@last_err
|
213
384
|
end
|
214
385
|
|
215
|
-
#
|
216
|
-
# @return [Array<
|
217
|
-
def
|
386
|
+
# Get a list of all dependencies as defined in library.properties
|
387
|
+
# @return [Array<String>] The library names of the dependencies (not the paths)
|
388
|
+
def arduino_library_dependencies
|
389
|
+
return [] unless library_properties?
|
390
|
+
return [] if library_properties.depends.nil?
|
391
|
+
|
392
|
+
library_properties.depends
|
393
|
+
end
|
394
|
+
|
395
|
+
# Arduino library dependencies all the way down, installing if they are not present
|
396
|
+
# @return [Array<String>] The library names of the dependencies (not the paths)
|
397
|
+
def all_arduino_library_dependencies!(additional_libraries = [])
|
218
398
|
# Pull in all possible places that headers could live, according to the spec:
|
219
399
|
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
end
|
225
|
-
|
400
|
+
recursive = (additional_libraries + arduino_library_dependencies).map do |n|
|
401
|
+
other_lib = self.class.new(n, @backend)
|
402
|
+
other_lib.install unless other_lib.installed?
|
403
|
+
other_lib.all_arduino_library_dependencies!
|
404
|
+
end.flatten
|
405
|
+
ret = (additional_libraries + recursive).uniq
|
406
|
+
ret
|
407
|
+
end
|
408
|
+
|
409
|
+
# Arduino library directories containing sources -- only those of the dependencies
|
410
|
+
# @return [Array<Pathname>]
|
411
|
+
def arduino_library_src_dirs(aux_libraries)
|
412
|
+
all_arduino_library_dependencies!(aux_libraries).map { |l| self.class.new(l, @backend).header_dirs }.flatten.uniq
|
226
413
|
end
|
227
414
|
|
228
415
|
# GCC command line arguments for including aux libraries
|
416
|
+
#
|
417
|
+
# This function recursively collects the library directores of the dependencies
|
418
|
+
#
|
229
419
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
230
420
|
# @return [Array<String>] The GCC command-line flags necessary to include those libraries
|
231
421
|
def include_args(aux_libraries)
|
@@ -288,6 +478,9 @@ module ArduinoCI
|
|
288
478
|
end
|
289
479
|
|
290
480
|
# build a file for running a test of the given unit test file
|
481
|
+
#
|
482
|
+
# The dependent libraries configuration is appended with data from library.properties internal to the library under test
|
483
|
+
#
|
291
484
|
# @param test_file [Pathname] The path to the file containing the unit tests
|
292
485
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
293
486
|
# @param ci_gcc_config [Hash] The GCC config object
|
@@ -306,8 +499,12 @@ module ArduinoCI
|
|
306
499
|
"-fsanitize=address"
|
307
500
|
]
|
308
501
|
end
|
309
|
-
|
310
|
-
|
502
|
+
|
503
|
+
# combine library.properties defs (if existing) with config file.
|
504
|
+
# 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
|
505
|
+
full_dependencies = all_arduino_library_dependencies!(aux_libraries)
|
506
|
+
arg_sets << test_args(full_dependencies, ci_gcc_config)
|
507
|
+
arg_sets << cpp_files_libraries(full_dependencies).map(&:to_s)
|
311
508
|
arg_sets << [test_file.to_s]
|
312
509
|
args = arg_sets.flatten(1)
|
313
510
|
return nil unless run_gcc(gcc_binary, *args)
|
@@ -316,14 +513,31 @@ module ArduinoCI
|
|
316
513
|
executable
|
317
514
|
end
|
318
515
|
|
516
|
+
# print any found stack dumps
|
517
|
+
# @param executable [Pathname] the path to the test file
|
518
|
+
def print_stack_dump(executable)
|
519
|
+
possible_dumpfiles = [
|
520
|
+
executable.sub_ext(executable.extname + ".stackdump")
|
521
|
+
]
|
522
|
+
possible_dumpfiles.select(&:exist?).each do |dump|
|
523
|
+
puts "========== Stack dump from #{dump}:"
|
524
|
+
File.foreach(dump) { |line| print " #{line}" }
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
319
528
|
# run a test file
|
320
|
-
# @param [Pathname] the path to the test file
|
529
|
+
# @param executable [Pathname] the path to the test file
|
321
530
|
# @return [bool] whether all tests were successful
|
322
531
|
def run_test_file(executable)
|
323
532
|
@last_cmd = executable
|
324
533
|
@last_out = ""
|
325
534
|
@last_err = ""
|
326
|
-
Host.run_and_output(executable.to_s.shellescape)
|
535
|
+
ret = Host.run_and_output(executable.to_s.shellescape)
|
536
|
+
|
537
|
+
# print any stack traces found during a failure
|
538
|
+
print_stack_dump(executable) unless ret
|
539
|
+
|
540
|
+
ret
|
327
541
|
end
|
328
542
|
|
329
543
|
end
|