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