arduino_ci 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -182,7 +182,10 @@ module ArduinoCI
182
182
  result = if @additional_urls.empty?
183
183
  run_and_capture("core", "install", boardfamily)
184
184
  else
185
- run_and_capture("core", "install", boardfamily, "--additional-urls", @additional_urls.join(","))
185
+ urls = @additional_urls.join(",")
186
+ # update the index, then install. if the update step fails, return that result
187
+ updater = run_and_capture("core", "update-index", "--additional-urls", urls)
188
+ updater[:success] ? run_and_capture("core", "install", boardfamily, "--additional-urls", urls) : updater
186
189
  end
187
190
  result[:success]
188
191
  end
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'pathname'
2
3
 
3
4
  # base config (platforms)
4
5
  # project config - .arduino_ci_platforms.yml
@@ -58,7 +59,7 @@ module ArduinoCI
58
59
  def default
59
60
  ret = new
60
61
  ret.instance_variable_set("@is_default", true)
61
- ret.load_yaml(File.expand_path("../../misc/default.yml", __dir__))
62
+ ret.load_yaml((Pathname.new(__dir__) + "../../misc/default.yml").realpath)
62
63
  ret
63
64
  end
64
65
  end
@@ -195,35 +196,45 @@ module ArduinoCI
195
196
  overridden_config
196
197
  end
197
198
 
198
- # Get the config file at a given path, if it exists, and pass that to a block.
199
- # Many config files may exist, but only the first match is used
199
+ # Get available configuration file, if one exists
200
200
  # @param base_dir [String] The directory in which to search for a config file
201
- # @param val_when_no_match [Object] The value to return if no config files are found
202
- # @yield [path] Process the configuration file at the given path
203
- # @yieldparam [String] The path of an existing config file
204
- # @yieldreturn [ArduinoCI::CIConfig] a settings object
205
- # @return [ArduinoCI::CIConfig]
206
- def with_config(base_dir, val_when_no_match)
207
- CONFIG_FILENAMES.each do |f|
208
- path = base_dir.nil? ? f : File.join(base_dir, f)
209
- return (yield path) if File.exist?(path)
210
- end
211
- val_when_no_match
201
+ # @return [Pathname] the first available config file we could find, or nil
202
+ def available_override_config_path(base_dir = nil)
203
+ CONFIG_FILENAMES.map { |f| base_dir.nil? ? Pathname.new(f) : base_dir + f }.find(&:exist?)
204
+ end
205
+
206
+ # Find an available override file from the project directory
207
+ #
208
+ # @todo this is currently reliant on launching the arduino_ci.rb test runner from
209
+ # the correct working directory
210
+ # @return [Pathname] A file that can override project config, or nil if none was found
211
+ def override_file_from_project_library
212
+ available_override_config_path(nil)
213
+ end
214
+
215
+ # Find an available override file from an example sketch
216
+ #
217
+ # @param path [Pathname] the path to the example or example directory
218
+ # @return [Pathname] A file that can override project config, or nil if none was found
219
+ def override_file_from_example(example_path)
220
+ base_dir = example_path.directory? ? example_path : example_path.dirname
221
+ available_override_config_path(base_dir)
212
222
  end
213
223
 
214
224
  # Produce a configuration, assuming the CI script runs from the working directory of the base project
215
225
  # @return [ArduinoCI::CIConfig] the new settings object
216
226
  def from_project_library
217
- with_config(nil, self) { |path| with_override(path) }
227
+ ovr = override_file_from_project_library
228
+ ovr.nil? ? self : with_override(ovr)
218
229
  end
219
230
 
220
231
  # Produce a configuration override taken from an Arduino library example path
221
232
  # handle either path to example file or example dir
222
- # @param path [String] the path to the settings yaml file
233
+ # @param path [Pathname] the path to the settings yaml file
223
234
  # @return [ArduinoCI::CIConfig] the new settings object
224
235
  def from_example(example_path)
225
- base_dir = File.directory?(example_path) ? example_path : File.dirname(example_path)
226
- with_config(base_dir, self) { |path| with_override(path) }
236
+ ovr = override_file_from_example(example_path)
237
+ ovr.nil? ? self : with_override(ovr)
227
238
  end
228
239
 
229
240
  # get information about a given platform: board name, package name, compiler stuff, etc
@@ -30,11 +30,31 @@ module ArduinoCI
30
30
  nil
31
31
  end
32
32
 
33
+ # Execute a shell command and capture stdout, stderr, and status
34
+ #
35
+ # @see Process.spawn
36
+ # @see https://docs.ruby-lang.org/en/2.0.0/Process.html#method-c-spawn
37
+ # @return [Hash] with keys "stdout" (String), "stderr" (String), and "success" (bool)
33
38
  def self.run_and_capture(*args, **kwargs)
34
39
  stdout, stderr, status = Open3.capture3(*args, **kwargs)
35
40
  { out: stdout, err: stderr, success: status.exitstatus.zero? }
36
41
  end
37
42
 
43
+ # Merge multiple capture results into one aggregate value
44
+ #
45
+ # @param args [Array] Array of hashes from `run_and_capture`
46
+ # @return [Hash] with keys "stdout" (String), "stderr" (String), and "success" (bool)
47
+ def self.merge_capture_results(*args)
48
+ {
49
+ out: args.map { |a| a[:out] }.join,
50
+ err: args.map { |a| a[:err] }.join,
51
+ success: args.all? { |a| a[:success] }
52
+ }
53
+ end
54
+
55
+ # Execute a shell command
56
+ #
57
+ # @see system
38
58
  def self.run_and_output(*args, **kwargs)
39
59
  system(*args, **kwargs)
40
60
  end
@@ -0,0 +1,243 @@
1
+ require 'io/console'
2
+
3
+ module ArduinoCI
4
+
5
+ # Provide all text processing functions to aid readability of the test log
6
+ class Logger
7
+
8
+ TAB_WIDTH = 4
9
+ INDENT_CHAR = " ".freeze
10
+
11
+ # @return [Integer] the cardinal number of indents
12
+ attr_reader :tab
13
+
14
+ # @return [Integer] The number of failures reported through the logging mechanism
15
+ attr_reader :failure_count
16
+
17
+ # @param width [int] The desired console width
18
+ def initialize(width = nil)
19
+ @tab = 0
20
+ @width = width.nil? ? 80 : width
21
+ @failure_count = 0
22
+ @passfail = proc { |result| result ? "✓" : "✗" }
23
+ end
24
+
25
+ # create a logger that's automatically sized to the console, between 80 and 132 characters
26
+ def self.auto_width
27
+ width = begin
28
+ [132, [80, IO::console.winsize[1] - 2].max].min
29
+ rescue NoMethodError
30
+ 80
31
+ end
32
+
33
+ self.new(width)
34
+ end
35
+
36
+ # print a nice banner for this project
37
+ def banner
38
+ art = [
39
+ " . __ ___",
40
+ " _, ,_ _| , . * ._ _ / ` | ",
41
+ "(_| [ `(_] (_| | [ ) (_) \\__. _|_ v#{ArduinoCI::VERSION}",
42
+ ]
43
+
44
+ pad = " " * ((@width - art[2].length) / 2)
45
+ art.each { |l| puts "#{pad}#{l}" }
46
+ puts
47
+ end
48
+
49
+ # @return [String] the current line indentation
50
+ def indentation
51
+ (INDENT_CHAR * TAB_WIDTH * @tab)
52
+ end
53
+
54
+ # put an indented string
55
+ #
56
+ # @param str [String] the string to puts
57
+ # @return [void]
58
+ def iputs(str = "")
59
+ print(indentation)
60
+
61
+ # split the lines and interleave with a newline character, then render
62
+ stream_lines = str.to_s.split("\n")
63
+ marked_stream_lines = stream_lines.flat_map { |s| [s, :nl] }.tap(&:pop)
64
+ marked_stream_lines.each { |l| print(l == :nl ? "\n#{indentation}" : l) }
65
+ puts
66
+ end
67
+
68
+ # print an indented string
69
+ #
70
+ # @param str [String] the string to print
71
+ # @return [void]
72
+ def iprint(str)
73
+ print(indentation)
74
+ print(str)
75
+ end
76
+
77
+ # increment an indentation level for the duration of a block's execution
78
+ #
79
+ # @param amount [Integer] the number of tabs to indent
80
+ # @yield [] The code to execute while indented
81
+ # @return [void]
82
+ def indent(amount = 1, &block)
83
+ @tab += amount
84
+ block.call
85
+ ensure
86
+ @tab -= amount
87
+ end
88
+
89
+ # make a nice status line for an action and react to the action
90
+ #
91
+ # TODO / note to self: inform_multiline is tougher to write
92
+ # without altering the signature because it only leaves space
93
+ # for the checkmark _after_ the multiline, it doesn't know how
94
+ # to make that conditionally the body
95
+ #
96
+ # @param message String the text of the progress indicator
97
+ # @param multiline boolean whether multiline output is expected
98
+ # @param mark_fn block (string) -> string that says how to describe the result
99
+ # @param on_fail_msg String custom message for failure
100
+ # @param tally_on_fail boolean whether to increment @failure_count
101
+ # @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
102
+ # @yield [] The action being performed
103
+ # @yieldreturn [Object] whether the action was successful, can be any type but it is evaluated as a boolean
104
+ # @return [Object] The return value of the block
105
+ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
106
+ line = "#{indentation}#{message}... "
107
+ endline = "#{indentation}...#{message} "
108
+ if multiline
109
+ puts line
110
+ @tab += 1
111
+ else
112
+ print line
113
+ end
114
+ $stdout.flush
115
+
116
+ # handle the block and any errors it raises
117
+ caught_error = nil
118
+ begin
119
+ result = yield
120
+ rescue StandardError => e
121
+ caught_error = e
122
+ result = false
123
+ ensure
124
+ @tab -= 1 if multiline
125
+ end
126
+
127
+ # put the trailing mark
128
+ mark = mark_fn.nil? ? "" : mark_fn.call(result)
129
+ # if multiline, put checkmark at full width
130
+ print endline if multiline
131
+ puts mark.to_s.rjust(@width - line.length, " ")
132
+ unless result
133
+ iputs on_fail_msg unless on_fail_msg.nil?
134
+ raise caught_error unless caught_error.nil?
135
+
136
+ @failure_count += 1 if tally_on_fail
137
+ terminate if abort_on_fail
138
+ end
139
+ result
140
+ end
141
+
142
+ # Make a nice status (with checkmark) for something that defers any failure code until script exit
143
+ #
144
+ # @param message the message to print
145
+ # @yield [] The action being performed
146
+ # @yieldreturn [boolean] whether the action was successful
147
+ # @return [Object] The return value of the block
148
+ def attempt(message, &block)
149
+ perform_action(message, false, @passfail, nil, true, false, &block)
150
+ end
151
+
152
+ # Make a nice multiline status (with checkmark) for something that defers any failure code until script exit
153
+ #
154
+ # @param message the message to print
155
+ # @yield [] The action being performed
156
+ # @yieldreturn [boolean] whether the action was successful
157
+ # @return [Object] The return value of the block
158
+ def attempt_multiline(message, &block)
159
+ perform_action(message, true, @passfail, nil, true, false, &block)
160
+ end
161
+
162
+ FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with your configuration; halting here".freeze
163
+ # Make a nice status (with checkmark) for something that kills the script immediately on failure
164
+ #
165
+ # @param message the message to print
166
+ # @yield [] The action being performed
167
+ # @yieldreturn [boolean] whether the action was successful
168
+ # @return [Object] The return value of the block
169
+ def assure(message, &block)
170
+ perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
171
+ end
172
+
173
+ # Make a nice multiline status (with checkmark) for something that kills the script immediately on failure
174
+ #
175
+ # @param message the message to print
176
+ # @yield [] The action being performed
177
+ # @yieldreturn [boolean] whether the action was successful
178
+ # @return [Object] The return value of the block
179
+ def assure_multiline(message, &block)
180
+ perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
181
+ end
182
+
183
+ # print a failure message (with checkmark) but do not tally a failure
184
+ # @param message the message to print
185
+ # @return [Object] The return value of the block
186
+ def warn(message)
187
+ inform("WARNING") { message }
188
+ end
189
+
190
+ # print a failure message (with checkmark) but do not exit
191
+ # @param message the message to print
192
+ # @return [Object] The return value of the block
193
+ def fail(message)
194
+ attempt(message) { false }
195
+ end
196
+
197
+ # print a failure message (with checkmark) and exit immediately afterward
198
+ # @param message the message to print
199
+ # @return [Object] The return value of the block
200
+ def halt(message)
201
+ assure(message) { false }
202
+ end
203
+
204
+ # Print a value as a status line "message... retval"
205
+ #
206
+ # @param message the message to print
207
+ # @yield [] The action being performed
208
+ # @yieldreturn [String] The value to print at the end of the line
209
+ # @return [Object] The return value of the block
210
+ def inform(message, &block)
211
+ perform_action(message, false, proc { |x| x }, nil, false, false, &block)
212
+ end
213
+
214
+ # Print section beginning and end
215
+ #
216
+ # @param message the message to print
217
+ # @yield [] The action being performed
218
+ # @return [Object] The return value of the block
219
+ def inform_multiline(message, &block)
220
+ perform_action(message, true, nil, nil, false, false, &block)
221
+ end
222
+
223
+ # Print a horizontal rule across the console
224
+ # @param char [String] the character to use
225
+ # @return [void]
226
+ def rule(char)
227
+ puts char[0] * @width
228
+ end
229
+
230
+ # Print a section heading to the console to break up the text output
231
+ #
232
+ # @param name [String] the section name
233
+ # @return [void]
234
+ def phase(name)
235
+ puts
236
+ rule("=")
237
+ puts("| #{name}")
238
+ puts("====")
239
+ end
240
+
241
+ end
242
+
243
+ end
@@ -1,3 +1,3 @@
1
1
  module ArduinoCI
2
- VERSION = "1.4.0".freeze
2
+ VERSION = "1.6.0".freeze
3
3
  end
data/lib/arduino_ci.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "arduino_ci/version"
2
+ require "arduino_ci/logger"
2
3
  require "arduino_ci/arduino_installation"
3
4
  require "arduino_ci/cpp_library"
4
5
  require "arduino_ci/ci_config"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arduino_ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.6.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: 2022-12-28 00:00:00.000000000 Z
11
+ date: 2023-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: os
@@ -78,6 +78,7 @@ files:
78
78
  - cpp/arduino/avr/README.md
79
79
  - cpp/arduino/avr/common.h
80
80
  - cpp/arduino/avr/fuse.h
81
+ - cpp/arduino/avr/interrupt.h
81
82
  - cpp/arduino/avr/io.h
82
83
  - cpp/arduino/avr/io1200.h
83
84
  - cpp/arduino/avr/io2313.h
@@ -359,6 +360,7 @@ files:
359
360
  - cpp/arduino/ci/Table.h
360
361
  - cpp/arduino/stdlib.cpp
361
362
  - cpp/arduino/stdlib.h
363
+ - cpp/arduino/util/atomic.h
362
364
  - cpp/unittest/ArduinoUnitTests.cpp
363
365
  - cpp/unittest/ArduinoUnitTests.h
364
366
  - cpp/unittest/Assertion.h
@@ -379,6 +381,7 @@ files:
379
381
  - lib/arduino_ci/cpp_library.rb
380
382
  - lib/arduino_ci/host.rb
381
383
  - lib/arduino_ci/library_properties.rb
384
+ - lib/arduino_ci/logger.rb
382
385
  - lib/arduino_ci/version.rb
383
386
  - misc/default.yml
384
387
  homepage: http://github.com/Arduino-CI/arduino_ci