arduino_ci 1.4.0 → 1.6.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.
@@ -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