arduino_ci 0.1.5 → 0.1.6

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.
@@ -21,11 +21,18 @@ def terminate(final = nil)
21
21
  end
22
22
 
23
23
  # make a nice status line for an action and react to the action
24
- def perform_action(message, on_fail_msg, abort_on_fail)
25
- line = "#{message}..."
26
- print line
24
+ def perform_action(message, multiline, on_fail_msg, abort_on_fail)
25
+ line = "#{message}... "
26
+ endline = "...#{message} "
27
+ if multiline
28
+ puts line
29
+ else
30
+ print line
31
+ end
27
32
  result = yield
28
33
  mark = result ? "✓" : "✗"
34
+ # if multline, put checkmark at full width
35
+ print endline if multiline
29
36
  puts mark.rjust(WIDTH - line.length, " ")
30
37
  unless result
31
38
  puts on_fail_msg unless on_fail_msg.nil?
@@ -38,12 +45,17 @@ end
38
45
 
39
46
  # Make a nice status for something that defers any failure code until script exit
40
47
  def attempt(message, &block)
41
- perform_action(message, nil, false, &block)
48
+ perform_action(message, false, nil, false, &block)
49
+ end
50
+
51
+ # Make a nice status for something that defers any failure code until script exit
52
+ def attempt_multiline(message, &block)
53
+ perform_action(message, true, nil, false, &block)
42
54
  end
43
55
 
44
56
  # Make a nice status for something that kills the script immediately on failure
45
57
  def assure(message, &block)
46
- perform_action(message, "This may indicate a problem with ArduinoCI!", true, &block)
58
+ perform_action(message, false, "This may indicate a problem with ArduinoCI!", true, &block)
47
59
  end
48
60
 
49
61
  # initialize command and config
@@ -56,6 +68,14 @@ library_examples = @arduino_cmd.library_examples(installed_library_path)
56
68
  cpp_library = ArduinoCI::CppLibrary.new(installed_library_path)
57
69
  attempt("Library installed at #{installed_library_path}") { true }
58
70
 
71
+ # check GCC
72
+ attempt_multiline("Checking GCC version") do
73
+ version = cpp_library.gcc_version
74
+ next nil unless version
75
+ puts version.split("\n").map { |l| " #{l}" }.join("\n")
76
+ version
77
+ end
78
+
59
79
  # gather up all required boards so we can install them up front.
60
80
  # start with the "platforms to unittest" and add the examples
61
81
  # while we're doing that, get the aux libraries as well
@@ -97,11 +117,11 @@ elsif config.platforms_to_unittest.empty?
97
117
  else
98
118
  config.platforms_to_unittest.each do |p|
99
119
  board = all_platforms[p][:board]
100
- # assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board
120
+ assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board
101
121
  last_board = board
102
122
  cpp_library.test_files.each do |unittest_path|
103
123
  unittest_name = File.basename(unittest_path)
104
- attempt("Unit testing #{unittest_name}") do
124
+ attempt_multiline("Unit testing #{unittest_name}") do
105
125
  exe = cpp_library.build_for_test_with_configuration(
106
126
  unittest_path,
107
127
  config.aux_libraries_for_unittest,
@@ -114,12 +114,18 @@ module ArduinoCI
114
114
  end
115
115
 
116
116
  # run the arduino command
117
- def _run(*args, **kwargs)
117
+ # @return [bool] whether the command succeeded
118
+ def _run_and_output(*args, **kwargs)
118
119
  raise "Ian needs to implement this in a subclass #{args} #{kwargs}"
119
120
  end
120
121
 
121
- # build and run the arduino command
122
- def run(*args, **kwargs)
122
+ # run the arduino command
123
+ # @return [Hash] keys for :success, :out, and :err
124
+ def _run_and_capture(*args, **kwargs)
125
+ raise "Ian needs to implement this in a subclass #{args} #{kwargs}"
126
+ end
127
+
128
+ def _wrap_run(work_fn, *args, **kwargs)
123
129
  # do some work to extract & merge environment variables if they exist
124
130
  has_env = !args.empty? && args[0].class == Hash
125
131
  env_vars = has_env ? args[0] : {}
@@ -129,26 +135,21 @@ module ArduinoCI
129
135
 
130
136
  shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ")
131
137
  @last_msg = " $ #{shell_vars} #{full_args.join(' ')}"
132
- _run(*full_cmd, **kwargs)
138
+ work_fn.call(*full_cmd, **kwargs)
139
+ end
140
+
141
+ # build and run the arduino command
142
+ def run_and_output(*args, **kwargs)
143
+ _wrap_run((proc { |*a, **k| _run_and_output(*a, **k) }), *args, **kwargs)
133
144
  end
134
145
 
135
146
  # run a command and capture its output
136
147
  # @return [Hash] {:out => String, :err => String, :success => bool}
137
148
  def run_and_capture(*args, **kwargs)
138
- pipe_out, pipe_out_wr = IO.pipe
139
- pipe_err, pipe_err_wr = IO.pipe
140
- our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
141
- eventual_kwargs = our_kwargs.merge(kwargs)
142
- success = run(*args, **eventual_kwargs)
143
- pipe_out_wr.close
144
- pipe_err_wr.close
145
- str_out = pipe_out.read
146
- str_err = pipe_err.read
147
- pipe_out.close
148
- pipe_err.close
149
- @last_err = str_err
150
- @last_out = str_out
151
- { out: str_out, err: str_err, success: success }
149
+ ret = _wrap_run((proc { |*a, **k| _run_and_capture(*a, **k) }), *args, **kwargs)
150
+ @last_err = ret[:err]
151
+ @last_out = ret[:out]
152
+ ret
152
153
  end
153
154
 
154
155
  # run a command and don't capture its output, but use the same signature
@@ -39,8 +39,15 @@ module ArduinoCI
39
39
  end
40
40
 
41
41
  # run the arduino command
42
- def _run(*args, **kwargs)
43
- @display_mgr.run(*args, **kwargs)
42
+ # @return [bool] whether the command succeeded
43
+ def _run_and_output(*args, **kwargs)
44
+ @display_mgr.run_and_output(*args, **kwargs)
45
+ end
46
+
47
+ # run the arduino command
48
+ # @return [Hash] keys for :success, :out, and :err
49
+ def _run_and_capture(*args, **kwargs)
50
+ @display_mgr.run_and_capture(*args, **kwargs)
44
51
  end
45
52
 
46
53
  def run_with_gui_guess(message, *args, **kwargs)
@@ -21,10 +21,15 @@ module ArduinoCI
21
21
  end
22
22
 
23
23
  # run the arduino command
24
- # @param [Array<String>] Arguments for the run command
25
- # @return [bool] Whether the command succeeded
26
- def _run(*args, **kwargs)
27
- Host.run(*args, **kwargs)
24
+ # @return [bool] whether the command succeeded
25
+ def _run_and_output(*args, **kwargs)
26
+ Host.run_and_output(*args, **kwargs)
27
+ end
28
+
29
+ # run the arduino command
30
+ # @return [Hash] keys for :success, :out, and :err
31
+ def _run_and_capture(*args, **kwargs)
32
+ Host.run_and_capture(*args, **kwargs)
28
33
  end
29
34
 
30
35
  end
@@ -14,8 +14,15 @@ module ArduinoCI
14
14
  flag :verify, "--verify"
15
15
 
16
16
  # run the arduino command
17
- def _run(*args, **kwargs)
18
- Host.run(*args, **kwargs)
17
+ # @return [bool] whether the command succeeded
18
+ def _run_and_output(*args, **kwargs)
19
+ Host.run_and_output(*args, **kwargs)
20
+ end
21
+
22
+ # run the arduino command
23
+ # @return [Hash] keys for :success, :out, and :err
24
+ def _run_and_capture(*args, **kwargs)
25
+ Host.run_and_capture(*args, **kwargs)
19
26
  end
20
27
 
21
28
  def _lib_dir
@@ -200,12 +200,14 @@ module ArduinoCI
200
200
  # platforms to build [the examples on]
201
201
  # @return [Array<String>] The platforms to build
202
202
  def platforms_to_build
203
+ return [] if @compile_info[:platforms].nil?
203
204
  @compile_info[:platforms]
204
205
  end
205
206
 
206
207
  # platforms to unit test [the tests on]
207
208
  # @return [Array<String>] The platforms to unit test on
208
209
  def platforms_to_unittest
210
+ return [] if @unittest_info[:platforms].nil?
209
211
  @unittest_info[:platforms]
210
212
  end
211
213
 
@@ -108,23 +108,20 @@ module ArduinoCI
108
108
 
109
109
  # wrapper for the GCC command
110
110
  def run_gcc(*args, **kwargs)
111
- pipe_out, pipe_out_wr = IO.pipe
112
- pipe_err, pipe_err_wr = IO.pipe
113
111
  full_args = ["g++"] + args
114
112
  @last_cmd = " $ #{full_args.join(' ')}"
115
- our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
116
- eventual_kwargs = our_kwargs.merge(kwargs)
117
- success = Host.run(*full_args, **eventual_kwargs)
118
113
 
119
- pipe_out_wr.close
120
- pipe_err_wr.close
121
- str_out = pipe_out.read
122
- str_err = pipe_err.read
123
- pipe_out.close
124
- pipe_err.close
125
- @last_err = str_err
126
- @last_out = str_out
127
- success
114
+ ret = Host.run_and_capture(*full_args, **kwargs)
115
+ @last_err = ret[:err]
116
+ @last_out = ret[:out]
117
+ ret[:success]
118
+ end
119
+
120
+ # Return the GCC version
121
+ # @return [String] the version reported by `gcc -v`
122
+ def gcc_version
123
+ return nil unless run_gcc("-v")
124
+ @last_err
128
125
  end
129
126
 
130
127
  # GCC command line arguments for including aux libraries
@@ -207,7 +204,7 @@ module ArduinoCI
207
204
  @last_cmd = executable
208
205
  @last_out = ""
209
206
  @last_err = ""
210
- Host.run(executable)
207
+ Host.run_and_output(executable)
211
208
  end
212
209
 
213
210
  end
@@ -0,0 +1,215 @@
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
+ # Guess whether a file is part of the vendor bundle (indicating we should ignore it).
39
+ #
40
+ # This assumes the vendor bundle will be at `vendor/bundle` and not some other location
41
+ # @param path [String] The path to check
42
+ # @return [Array<String>] The paths of the found files
43
+ def vendor_bundle?(path)
44
+ # TODO: look for Gemfile, look for .bundle/config and get BUNDLE_PATH from there?
45
+ base = File.join(@base_dir, "vendor")
46
+ real = File.join(File.realpath(@base_dir), "vendor")
47
+ return true if path.start_with?(base)
48
+ return true if path.start_with?(real)
49
+ false
50
+ end
51
+
52
+ # Get a list of all CPP source files in a directory and its subdirectories
53
+ # @param some_dir [String] The directory in which to begin the search
54
+ # @return [Array<String>] The paths of the found files
55
+ def cpp_files_in(some_dir)
56
+ return [] unless File.exist?(some_dir)
57
+ real = File.realpath(some_dir)
58
+ files = Find.find(real).reject { |path| File.directory?(path) }
59
+ ret = files.select { |path| CPP_EXTENSIONS.include?(File.extname(path)) }
60
+ ret
61
+ end
62
+
63
+ # CPP files that are part of the project library under test
64
+ # @return [Array<String>]
65
+ def cpp_files
66
+ real_tests_dir = File.realpath(tests_dir)
67
+ cpp_files_in(@base_dir).reject do |p|
68
+ next true if File.dirname(p).include?(tests_dir)
69
+ next true if File.dirname(p).include?(real_tests_dir)
70
+ next true if vendor_bundle?(p)
71
+ end
72
+ end
73
+
74
+ # CPP files that are part of the arduino mock library we're providing
75
+ # @return [Array<String>]
76
+ def cpp_files_arduino
77
+ cpp_files_in(ARDUINO_HEADER_DIR)
78
+ end
79
+
80
+ # CPP files that are part of the unit test library we're providing
81
+ # @return [Array<String>]
82
+ def cpp_files_unittest
83
+ cpp_files_in(UNITTEST_HEADER_DIR)
84
+ end
85
+
86
+ # The directory where we expect to find unit test defintions provided by the user
87
+ # @return [String]
88
+ def tests_dir
89
+ File.join(@base_dir, "test")
90
+ end
91
+
92
+ # The files provided by the user that contain unit tests
93
+ # @return [Array<String>]
94
+ def test_files
95
+ cpp_files_in(tests_dir)
96
+ end
97
+
98
+ # Find all directories in the project library that include C++ header files
99
+ # @return [Array<String>]
100
+ def header_dirs
101
+ real = File.realpath(@base_dir)
102
+ all_files = Find.find(real).reject { |path| File.directory?(path) }
103
+ unbundled = all_files.reject { |path| vendor_bundle?(path) }
104
+ files = unbundled.select { |path| HPP_EXTENSIONS.include?(File.extname(path)) }
105
+ ret = files.map { |path| File.dirname(path) }.uniq
106
+ ret
107
+ end
108
+
109
+ # wrapper for the GCC command
110
+ def run_gcc(*args, **kwargs)
111
+ full_args = ["g++-4.9"] + args
112
+ @last_cmd = " $ #{full_args.join(' ')}"
113
+ <<<<<<< Updated upstream
114
+
115
+ =======
116
+ >>>>>>> Stashed changes
117
+ ret = Host.run_and_capture(*full_args, **kwargs)
118
+ @last_err = ret[:err]
119
+ @last_out = ret[:out]
120
+ ret[:success]
121
+ end
122
+
123
+ # Return the GCC version
124
+ # @return [String] the version reported by `gcc -v`
125
+ def gcc_version
126
+ return nil unless run_gcc("-v")
127
+ @last_err
128
+ end
129
+
130
+ # GCC command line arguments for including aux libraries
131
+ # @param aux_libraries [String] The external Arduino libraries required by this project
132
+ # @return [Array<String>] The GCC command-line flags necessary to include those libraries
133
+ def include_args(aux_libraries)
134
+ places = [ARDUINO_HEADER_DIR, UNITTEST_HEADER_DIR] + header_dirs + aux_libraries
135
+ places.map { |d| "-I#{d}" }
136
+ end
137
+
138
+ # GCC command line arguments for features (e.g. -fno-weak)
139
+ # @param ci_gcc_config [Hash] The GCC config object
140
+ # @return [Array<String>] GCC command-line flags
141
+ def feature_args(ci_gcc_config)
142
+ return [] if ci_gcc_config[:features].nil?
143
+ ci_gcc_config[:features].map { |f| "-f#{f}" }
144
+ end
145
+
146
+ # GCC command line arguments for warning (e.g. -Wall)
147
+ # @param ci_gcc_config [Hash] The GCC config object
148
+ # @return [Array<String>] GCC command-line flags
149
+ def warning_args(ci_gcc_config)
150
+ return [] if ci_gcc_config[:warnings].nil?
151
+ ci_gcc_config[:features].map { |w| "-W#{w}" }
152
+ end
153
+
154
+ # GCC command line arguments for defines (e.g. -Dhave_something)
155
+ # @param ci_gcc_config [Hash] The GCC config object
156
+ # @return [Array<String>] GCC command-line flags
157
+ def define_args(ci_gcc_config)
158
+ return [] if ci_gcc_config[:defines].nil?
159
+ ci_gcc_config[:defines].map { |d| "-D#{d}" }
160
+ end
161
+
162
+ # GCC command line arguments as-is
163
+ # @param ci_gcc_config [Hash] The GCC config object
164
+ # @return [Array<String>] GCC command-line flags
165
+ def flag_args(ci_gcc_config)
166
+ return [] if ci_gcc_config[:flags].nil?
167
+ ci_gcc_config[:flags]
168
+ end
169
+
170
+ # All GCC command line args for building any unit test
171
+ # @param aux_libraries [String] The external Arduino libraries required by this project
172
+ # @param ci_gcc_config [Hash] The GCC config object
173
+ # @return [Array<String>] GCC command-line flags
174
+ def test_args(aux_libraries, ci_gcc_config)
175
+ # TODO: something with libraries?
176
+ ret = include_args(aux_libraries) + cpp_files_arduino + cpp_files_unittest + cpp_files
177
+ unless ci_gcc_config.nil?
178
+ cgc = ci_gcc_config
179
+ ret = feature_args(cgc) + warning_args(cgc) + define_args(cgc) + flag_args(cgc) + ret
180
+ end
181
+ ret
182
+ end
183
+
184
+ # build a file for running a test of the given unit test file
185
+ # @param test_file [String] The path to the file containing the unit tests
186
+ # @param aux_libraries [String] The external Arduino libraries required by this project
187
+ # @param ci_gcc_config [Hash] The GCC config object
188
+ # @return [String] path to the compiled test executable
189
+ def build_for_test_with_configuration(test_file, aux_libraries, ci_gcc_config)
190
+ base = File.basename(test_file)
191
+ executable = File.expand_path("unittest_#{base}.bin")
192
+ File.delete(executable) if File.exist?(executable)
193
+ args = [
194
+ ["-std=c++0x", "-o", executable, "-DARDUINO=100"],
195
+ test_args(aux_libraries, ci_gcc_config),
196
+ [test_file],
197
+ ].flatten(1)
198
+ return nil unless run_gcc(*args)
199
+ artifacts << executable
200
+ executable
201
+ end
202
+
203
+ # run a test file
204
+ # @param [String] the path to the test file
205
+ # @return [bool] whether all tests were successful
206
+ def run_test_file(executable)
207
+ @last_cmd = executable
208
+ @last_out = ""
209
+ @last_err = ""
210
+ Host.run_and_output(executable)
211
+ end
212
+
213
+ end
214
+
215
+ end