arduino_ci 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +23 -9
- data/exe/arduino_ci_remote.rb +132 -0
- data/lib/arduino_ci.rb +3 -117
- data/lib/arduino_ci/arduino_cmd.rb +296 -0
- data/lib/arduino_ci/arduino_cmd_linux.rb +86 -0
- data/lib/arduino_ci/arduino_cmd_linux_builder.rb +32 -0
- data/lib/arduino_ci/arduino_cmd_osx.rb +27 -0
- data/lib/arduino_ci/arduino_installation.rb +135 -0
- data/lib/arduino_ci/ci_config.rb +218 -0
- data/lib/arduino_ci/cpp_library.rb +186 -0
- data/lib/arduino_ci/display_manager.rb +187 -0
- data/lib/arduino_ci/host.rb +36 -0
- data/lib/arduino_ci/version.rb +1 -1
- metadata +32 -19
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'find'
|
2
|
+
require "arduino_ci/host"
|
3
|
+
|
4
|
+
HPP_EXTENSIONS = [".hpp", ".hh", ".h", ".hxx", ".h++"].freeze
|
5
|
+
CPP_EXTENSIONS = [".cpp", ".cc", ".c", ".cxx", ".c++"].freeze
|
6
|
+
ARDUINO_HEADER_DIR = File.expand_path("../../../cpp/arduino", __FILE__)
|
7
|
+
UNITTEST_HEADER_DIR = File.expand_path("../../../cpp/unittest", __FILE__)
|
8
|
+
|
9
|
+
module ArduinoCI
|
10
|
+
|
11
|
+
# Information about an Arduino CPP library, specifically for compilation purposes
|
12
|
+
class CppLibrary
|
13
|
+
|
14
|
+
# @return [String] The path to the library being tested
|
15
|
+
attr_reader :base_dir
|
16
|
+
|
17
|
+
# @return [Array<String>] The set of artifacts created by this class (note: incomplete!)
|
18
|
+
attr_reader :artifacts
|
19
|
+
|
20
|
+
# @return [String] STDERR from the last command
|
21
|
+
attr_reader :last_err
|
22
|
+
|
23
|
+
# @return [String] STDOUT from the last command
|
24
|
+
attr_reader :last_out
|
25
|
+
|
26
|
+
# @return [String] the last command
|
27
|
+
attr_reader :last_cmd
|
28
|
+
|
29
|
+
# @param base_dir [String] The path to the library being tested
|
30
|
+
def initialize(base_dir)
|
31
|
+
@base_dir = File.expand_path(base_dir)
|
32
|
+
@artifacts = []
|
33
|
+
@last_err = ""
|
34
|
+
@last_out = ""
|
35
|
+
@last_msg = ""
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get a list of all CPP source files in a directory and its subdirectories
|
39
|
+
# @param some_dir [String] The directory in which to begin the search
|
40
|
+
# @return [Array<String>] The paths of the found files
|
41
|
+
def cpp_files_in(some_dir)
|
42
|
+
real = File.realpath(some_dir)
|
43
|
+
Find.find(real).select { |path| CPP_EXTENSIONS.include?(File.extname(path)) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# CPP files that are part of the project library under test
|
47
|
+
# @return [Array<String>]
|
48
|
+
def cpp_files
|
49
|
+
real_tests_dir = File.realpath(tests_dir)
|
50
|
+
cpp_files_in(@base_dir).reject do |p|
|
51
|
+
next true if File.dirname(p).include?(tests_dir)
|
52
|
+
next true if File.dirname(p).include?(real_tests_dir)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# CPP files that are part of the arduino mock library we're providing
|
57
|
+
# @return [Array<String>]
|
58
|
+
def cpp_files_arduino
|
59
|
+
cpp_files_in(ARDUINO_HEADER_DIR)
|
60
|
+
end
|
61
|
+
|
62
|
+
# CPP files that are part of the unit test library we're providing
|
63
|
+
# @return [Array<String>]
|
64
|
+
def cpp_files_unittest
|
65
|
+
cpp_files_in(UNITTEST_HEADER_DIR)
|
66
|
+
end
|
67
|
+
|
68
|
+
# The directory where we expect to find unit test defintions provided by the user
|
69
|
+
# @return [String]
|
70
|
+
def tests_dir
|
71
|
+
File.join(@base_dir, "test")
|
72
|
+
end
|
73
|
+
|
74
|
+
# The files provided by the user that contain unit tests
|
75
|
+
# @return [Array<String>]
|
76
|
+
def test_files
|
77
|
+
cpp_files_in(tests_dir)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Find all directories in the project library that include C++ header files
|
81
|
+
# @return [Array<String>]
|
82
|
+
def header_dirs
|
83
|
+
files = Find.find(@base_dir).select { |path| HPP_EXTENSIONS.include?(File.extname(path)) }
|
84
|
+
files.map { |path| File.dirname(path) }.uniq
|
85
|
+
end
|
86
|
+
|
87
|
+
# wrapper for the GCC command
|
88
|
+
def run_gcc(*args, **kwargs)
|
89
|
+
pipe_out, pipe_out_wr = IO.pipe
|
90
|
+
pipe_err, pipe_err_wr = IO.pipe
|
91
|
+
full_args = ["g++"] + args
|
92
|
+
@last_cmd = " $ #{full_args.join(' ')}"
|
93
|
+
our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
|
94
|
+
eventual_kwargs = our_kwargs.merge(kwargs)
|
95
|
+
success = Host.run(*full_args, **eventual_kwargs)
|
96
|
+
|
97
|
+
pipe_out_wr.close
|
98
|
+
pipe_err_wr.close
|
99
|
+
str_out = pipe_out.read
|
100
|
+
str_err = pipe_err.read
|
101
|
+
pipe_out.close
|
102
|
+
pipe_err.close
|
103
|
+
@last_err = str_err
|
104
|
+
@last_out = str_out
|
105
|
+
success
|
106
|
+
end
|
107
|
+
|
108
|
+
# GCC command line arguments for including aux libraries
|
109
|
+
# @param aux_libraries [String] The external Arduino libraries required by this project
|
110
|
+
# @return [Array<String>] The GCC command-line flags necessary to include those libraries
|
111
|
+
def include_args(aux_libraries)
|
112
|
+
places = [ARDUINO_HEADER_DIR, UNITTEST_HEADER_DIR] + header_dirs + aux_libraries
|
113
|
+
places.map { |d| "-I#{d}" }
|
114
|
+
end
|
115
|
+
|
116
|
+
# GCC command line arguments for features (e.g. -fno-weak)
|
117
|
+
# @param ci_gcc_config [Hash] The GCC config object
|
118
|
+
# @return [Array<String>] GCC command-line flags
|
119
|
+
def feature_args(ci_gcc_config)
|
120
|
+
return [] if ci_gcc_config[:features].nil?
|
121
|
+
ci_gcc_config[:features].map { |f| "-f#{f}" }
|
122
|
+
end
|
123
|
+
|
124
|
+
# GCC command line arguments for warning (e.g. -Wall)
|
125
|
+
# @param ci_gcc_config [Hash] The GCC config object
|
126
|
+
# @return [Array<String>] GCC command-line flags
|
127
|
+
def warning_args(ci_gcc_config)
|
128
|
+
return [] if ci_gcc_config[:warnings].nil?
|
129
|
+
ci_gcc_config[:features].map { |w| "-W#{w}" }
|
130
|
+
end
|
131
|
+
|
132
|
+
# GCC command line arguments for defines (e.g. -Dhave_something)
|
133
|
+
# @param ci_gcc_config [Hash] The GCC config object
|
134
|
+
# @return [Array<String>] GCC command-line flags
|
135
|
+
def define_args(ci_gcc_config)
|
136
|
+
return [] if ci_gcc_config[:defines].nil?
|
137
|
+
ci_gcc_config[:defines].map { |d| "-D#{d}" }
|
138
|
+
end
|
139
|
+
|
140
|
+
# GCC command line arguments as-is
|
141
|
+
# @param ci_gcc_config [Hash] The GCC config object
|
142
|
+
# @return [Array<String>] GCC command-line flags
|
143
|
+
def flag_args(ci_gcc_config)
|
144
|
+
return [] if ci_gcc_config[:flags].nil?
|
145
|
+
ci_gcc_config[:flags]
|
146
|
+
end
|
147
|
+
|
148
|
+
# All GCC command line args for building any unit test
|
149
|
+
# @param aux_libraries [String] The external Arduino libraries required by this project
|
150
|
+
# @param ci_gcc_config [Hash] The GCC config object
|
151
|
+
# @return [Array<String>] GCC command-line flags
|
152
|
+
def test_args(aux_libraries, ci_gcc_config)
|
153
|
+
# TODO: something with libraries?
|
154
|
+
ret = include_args(aux_libraries) + cpp_files_arduino + cpp_files_unittest + cpp_files
|
155
|
+
unless ci_gcc_config.nil?
|
156
|
+
cgc = ci_gcc_config
|
157
|
+
ret = feature_args(cgc) + warning_args(cgc) + define_args(cgc) + flag_args(cgc) + ret
|
158
|
+
end
|
159
|
+
ret
|
160
|
+
end
|
161
|
+
|
162
|
+
# build a file for running a test of the given unit test file
|
163
|
+
# @param test_file [String] The path to the file containing the unit tests
|
164
|
+
# @param aux_libraries [String] The external Arduino libraries required by this project
|
165
|
+
# @param ci_gcc_config [Hash] The GCC config object
|
166
|
+
# @return [String] path to the compiled test executable
|
167
|
+
def build_for_test_with_configuration(test_file, aux_libraries, ci_gcc_config)
|
168
|
+
base = File.basename(test_file)
|
169
|
+
executable = File.expand_path("unittest_#{base}.bin")
|
170
|
+
File.delete(executable) if File.exist?(executable)
|
171
|
+
args = ["-o", executable] + test_args(aux_libraries, ci_gcc_config) + [test_file]
|
172
|
+
return nil unless run_gcc(*args)
|
173
|
+
artifacts << executable
|
174
|
+
executable
|
175
|
+
end
|
176
|
+
|
177
|
+
# run a test file
|
178
|
+
# @param [String] the path to the test file
|
179
|
+
# @return [bool] whether all tests were successful
|
180
|
+
def run_test_file(executable)
|
181
|
+
Host.run(executable)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'arduino_ci/host'
|
2
|
+
require 'singleton'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
DESIRED_DISPLAY = ":1.0".freeze
|
6
|
+
|
7
|
+
module ArduinoCI
|
8
|
+
|
9
|
+
# When arduino commands run, they need a graphical display.
|
10
|
+
# This class handles the setup of that display, if needed.
|
11
|
+
class DisplayManager
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
# @return [bool] whether the display manager is currently active
|
15
|
+
attr_reader :enabled
|
16
|
+
|
17
|
+
# @return [bool] whether to log messages to the terminal
|
18
|
+
attr_accessor :debug
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@existing = existing_display?
|
22
|
+
@enabled = false
|
23
|
+
@pid = nil
|
24
|
+
@debug = false
|
25
|
+
|
26
|
+
# pipes for input and output
|
27
|
+
@xv_pipe_out_wr = nil
|
28
|
+
@xv_pipe_err_wr = nil
|
29
|
+
@xv_pipe_out = nil
|
30
|
+
@xv_pipe_err = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# attempt to determine if the machine is running a graphical display (i.e. not Travis)
|
34
|
+
# @return [bool] whether there is already a GUI that can accept windows
|
35
|
+
def existing_display?
|
36
|
+
return true if RUBY_PLATFORM.include? "darwin"
|
37
|
+
return false if ENV["DISPLAY"].nil?
|
38
|
+
return true if ENV["DISPLAY"].include? ":"
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# check whether a process is alive
|
43
|
+
# https://stackoverflow.com/a/32513298/2063546
|
44
|
+
# @param pid [Int] the process ID
|
45
|
+
# @return [bool]
|
46
|
+
def alive?(pid)
|
47
|
+
Process.kill(0, pid)
|
48
|
+
true
|
49
|
+
rescue
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
# check whether an X server is taking connections
|
54
|
+
# @param display [String] the display variable as it would be specified in the environment
|
55
|
+
# @return [bool]
|
56
|
+
def xserver_exist?(display)
|
57
|
+
system({ "DISPLAY" => display }, "xdpyinfo", out: File::NULL, err: File::NULL)
|
58
|
+
end
|
59
|
+
|
60
|
+
# wait for the xvfb command to launch
|
61
|
+
# @param display [String] the value of the DISPLAY env var
|
62
|
+
# @param pid [Int] the process of Xvfb
|
63
|
+
# @param timeout [Int] the timeout in seconds
|
64
|
+
# @return [Bool] whether we detected a launch
|
65
|
+
def xvfb_launched?(display, pid, timeout)
|
66
|
+
Timeout.timeout(timeout) do
|
67
|
+
loop do
|
68
|
+
unless alive? pid
|
69
|
+
puts "Xvfb process has died"
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
x = xserver_exist? display
|
73
|
+
puts "xdpyinfo reports X server status as #{x}" if debug
|
74
|
+
return true if x
|
75
|
+
sleep(0.1)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
rescue Timeout::Error
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
# enable a virtual display
|
83
|
+
def enable
|
84
|
+
if @existing
|
85
|
+
puts "DisplayManager enable: no-op for what appears to be an existing display" if debug
|
86
|
+
@enabled = true
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
return unless @pid.nil? # TODO: disable first?
|
91
|
+
@xv_pipe_out.close unless @xv_pipe_out.nil?
|
92
|
+
@xv_pipe_err.close unless @xv_pipe_err.nil?
|
93
|
+
|
94
|
+
# open Xvfb
|
95
|
+
xvfb_cmd = [
|
96
|
+
"Xvfb",
|
97
|
+
"+extension", "RANDR",
|
98
|
+
":1",
|
99
|
+
"-ac",
|
100
|
+
"-screen", "0",
|
101
|
+
"1280x1024x16",
|
102
|
+
]
|
103
|
+
puts "Xvfb launching" if debug
|
104
|
+
|
105
|
+
@xv_pipe_out, @xv_pipe_out_wr = IO.pipe
|
106
|
+
@xv_pipe_err, @xv_pipe_err_wr = IO.pipe
|
107
|
+
pipe = IO.popen(xvfb_cmd, stdout: @xv_pipe_out_wr, err: @xv_pipe_err_wr)
|
108
|
+
@pid = pipe.pid
|
109
|
+
@enabled = xvfb_launched?(DESIRED_DISPLAY, @pid, 30)
|
110
|
+
end
|
111
|
+
|
112
|
+
# disable the virtual display
|
113
|
+
def disable
|
114
|
+
if @existing
|
115
|
+
puts "DisplayManager disable: no-op for what appears to be an existing display" if debug
|
116
|
+
return @enabled = false
|
117
|
+
end
|
118
|
+
|
119
|
+
return @enabled = false if @pid.nil?
|
120
|
+
|
121
|
+
# https://www.whatastruggle.com/timeout-a-subprocess-in-ruby
|
122
|
+
begin
|
123
|
+
Timeout.timeout(30) do
|
124
|
+
Process.kill("TERM", @pid)
|
125
|
+
puts "Xvfb TERMed" if debug
|
126
|
+
end
|
127
|
+
rescue Timeout::Error
|
128
|
+
Process.kill(9, @pid)
|
129
|
+
puts "Xvfb KILLed" if debug
|
130
|
+
ensure
|
131
|
+
Process.wait @pid
|
132
|
+
@enabled = false
|
133
|
+
@pid = nil
|
134
|
+
|
135
|
+
@xv_pipe_out_wr.close
|
136
|
+
@xv_pipe_err_wr.close
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Enable a virtual display for the duration of the given block
|
141
|
+
# @yield [environment] The code to execute within the display environment
|
142
|
+
# @yieldparam [Hash] the environment variables relating to the display
|
143
|
+
def with_display
|
144
|
+
was_enabled = @enabled
|
145
|
+
enable unless was_enabled
|
146
|
+
begin
|
147
|
+
yield environment
|
148
|
+
ensure
|
149
|
+
disable unless was_enabled
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# run a command in a display
|
154
|
+
# @return [bool]
|
155
|
+
def run(*args, **kwargs)
|
156
|
+
ret = false
|
157
|
+
# do some work to extract & merge environment variables if they exist
|
158
|
+
has_env = !args.empty? && args[0].class == Hash
|
159
|
+
with_display do |env_vars|
|
160
|
+
env_vars = {} if env_vars.nil?
|
161
|
+
env_vars.merge!(args[0]) if has_env
|
162
|
+
actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args
|
163
|
+
full_cmd = env_vars.empty? ? actual_args : [env_vars] + actual_args
|
164
|
+
ret = Host.run(*full_cmd, **kwargs)
|
165
|
+
end
|
166
|
+
ret
|
167
|
+
end
|
168
|
+
|
169
|
+
# run a command in a display with no output
|
170
|
+
# @return [bool]
|
171
|
+
def run_silent(*args)
|
172
|
+
run(*args, out: File::NULL, err: File::NULL)
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Hash] the environment variables for the display
|
176
|
+
def environment
|
177
|
+
return nil unless @existing || @enabled
|
178
|
+
return { "EXISTING_DISPLAY" => "YES" } if @existing
|
179
|
+
{ "DISPLAY" => DESIRED_DISPLAY }
|
180
|
+
end
|
181
|
+
|
182
|
+
# On finalize, ensure child process is ended
|
183
|
+
def self.finalize
|
184
|
+
disable
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'os'
|
2
|
+
|
3
|
+
module ArduinoCI
|
4
|
+
|
5
|
+
# Tools for interacting with the host machine
|
6
|
+
class Host
|
7
|
+
# Cross-platform way of finding an executable in the $PATH.
|
8
|
+
# via https://stackoverflow.com/a/5471032/2063546
|
9
|
+
# which('ruby') #=> /usr/bin/ruby
|
10
|
+
# @param cmd [String] the command to search for
|
11
|
+
# @return [String] the full path to the command if it exists
|
12
|
+
def self.which(cmd)
|
13
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
14
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
15
|
+
exts.each do |ext|
|
16
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
17
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# run a command in a display
|
24
|
+
def self.run(*args, **kwargs)
|
25
|
+
system(*args, **kwargs)
|
26
|
+
end
|
27
|
+
|
28
|
+
# return [Symbol] the operating system of the host
|
29
|
+
def self.os
|
30
|
+
return :osx if OS.osx?
|
31
|
+
return :linux if OS.linux?
|
32
|
+
return :windows if OS.windows?
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/lib/arduino_ci/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arduino_ci
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ian Katz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: os
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,50 +58,49 @@ dependencies:
|
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
-
- - ">="
|
49
|
-
- !ruby/object:Gem::Version
|
50
|
-
version: 0.46.0
|
61
|
+
version: 0.49.0
|
51
62
|
type: :development
|
52
63
|
prerelease: false
|
53
64
|
version_requirements: !ruby/object:Gem::Requirement
|
54
65
|
requirements:
|
55
66
|
- - "~>"
|
56
67
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
58
|
-
- - ">="
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: 0.46.0
|
68
|
+
version: 0.49.0
|
61
69
|
- !ruby/object:Gem::Dependency
|
62
70
|
name: yard
|
63
71
|
requirement: !ruby/object:Gem::Requirement
|
64
72
|
requirements:
|
65
73
|
- - "~>"
|
66
74
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
68
|
-
- - ">="
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: '0.8'
|
75
|
+
version: 0.9.11
|
71
76
|
type: :development
|
72
77
|
prerelease: false
|
73
78
|
version_requirements: !ruby/object:Gem::Requirement
|
74
79
|
requirements:
|
75
80
|
- - "~>"
|
76
81
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
78
|
-
- - ">="
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version: '0.8'
|
82
|
+
version: 0.9.11
|
81
83
|
description: ''
|
82
84
|
email:
|
83
85
|
- ifreecarve@gmail.com
|
84
|
-
executables:
|
86
|
+
executables:
|
87
|
+
- arduino_ci_remote.rb
|
85
88
|
extensions: []
|
86
89
|
extra_rdoc_files: []
|
87
90
|
files:
|
88
91
|
- ".yardopts"
|
89
92
|
- README.md
|
93
|
+
- exe/arduino_ci_remote.rb
|
90
94
|
- lib/arduino_ci.rb
|
95
|
+
- lib/arduino_ci/arduino_cmd.rb
|
96
|
+
- lib/arduino_ci/arduino_cmd_linux.rb
|
97
|
+
- lib/arduino_ci/arduino_cmd_linux_builder.rb
|
98
|
+
- lib/arduino_ci/arduino_cmd_osx.rb
|
99
|
+
- lib/arduino_ci/arduino_installation.rb
|
100
|
+
- lib/arduino_ci/ci_config.rb
|
101
|
+
- lib/arduino_ci/cpp_library.rb
|
102
|
+
- lib/arduino_ci/display_manager.rb
|
103
|
+
- lib/arduino_ci/host.rb
|
91
104
|
- lib/arduino_ci/version.rb
|
92
105
|
homepage: http://github.com/ifreecarve/arduino_ci
|
93
106
|
licenses:
|