arduino_ci 0.0.1 → 0.1.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 +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:
|