arduino_ci 0.1.5 → 0.1.6

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