local_ci_plus 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +30 -8
- data/lib/local_ci_plus/continuous_integration.rb +105 -37
- data/lib/local_ci_plus/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e29292c8c4b406b43f0e1729ffbfbf52bb7931fa8f62dedab7e9631baab45a5f
|
|
4
|
+
data.tar.gz: 685c98aadeb7b563c077a5ebf99da60880403e3a9bcbcc15e646fac6c70f841d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c40a186742fa94b71a099820f73a03feb79f8b347a00bc8d86d2835dec8070c8acd0f9c03ae30cd37d44f1b3a59bdebb3d897c4d8d0844430e75bd6f5fda1088
|
|
7
|
+
data.tar.gz: ac0a1b4b6adf8ca08457571a8f38fb2259e202c8bf5f4f2a47033495afeb5eb1439bebc9eab031d01083bdbbea9913d7fb05e97ce90e0a5e5d4948bc248434ee
|
data/README.md
CHANGED
|
@@ -28,6 +28,23 @@ continues to work without changes. In plain/non-TTY mode, output is ASCII-only.
|
|
|
28
28
|
bin/ci
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Use `parallel_steps` when you want a parallel phase before sequential follow-up steps.
|
|
32
|
+
If any step in `parallel_steps` fails, later steps are skipped and listed in the summary.
|
|
33
|
+
If no `parallel_steps` block is present, all steps run in parallel (default behavior).
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
CI.run do
|
|
37
|
+
parallel_steps do
|
|
38
|
+
step "Style: Ruby", "bin/standardrb"
|
|
39
|
+
step "JavaScript: lint", "npm run lint"
|
|
40
|
+
step "Security: Gem audit", "bin/bundler-audit"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
step "Tests: Rails", "bin/rspec"
|
|
44
|
+
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
31
48
|
If your app does not already require the gem in `bin/ci`, run the installer generator:
|
|
32
49
|
|
|
33
50
|
```bash
|
|
@@ -51,16 +68,15 @@ require_relative "../config/ci"
|
|
|
51
68
|
|
|
52
69
|
### Options
|
|
53
70
|
|
|
71
|
+
Runs all CI steps in parallel by default.
|
|
72
|
+
|
|
54
73
|
```
|
|
55
|
-
-f, --fail-fast Stop immediately when a step fails
|
|
56
|
-
-c, --continue Resume from the last failed step
|
|
74
|
+
-f, --fail-fast Stop immediately when a step fails (runs inline)
|
|
75
|
+
-c, --continue Resume from the last failed step (runs inline)
|
|
57
76
|
-fc, -cf Combine fail-fast and continue
|
|
58
|
-
-
|
|
77
|
+
-i, --inline Run steps sequentially instead of parallel
|
|
59
78
|
--plain Disable ANSI cursor updates/colors (also used for non-TTY)
|
|
60
79
|
-h, --help Show this help
|
|
61
|
-
|
|
62
|
-
Compatibility:
|
|
63
|
-
--parallel cannot be combined with --fail-fast or --continue
|
|
64
80
|
```
|
|
65
81
|
|
|
66
82
|
### State file
|
|
@@ -83,8 +99,8 @@ bundle exec standardrb
|
|
|
83
99
|
|
|
84
100
|
Or run them both:
|
|
85
101
|
|
|
86
|
-
```
|
|
87
|
-
bin/ci
|
|
102
|
+
```bash
|
|
103
|
+
bin/ci
|
|
88
104
|
```
|
|
89
105
|
|
|
90
106
|
### Publishing Updates
|
|
@@ -96,3 +112,9 @@ bin/ci --parallel
|
|
|
96
112
|
gem build local_ci_plus.gemspec
|
|
97
113
|
gem push local_ci_plus-X.Y.Z.gem
|
|
98
114
|
```
|
|
115
|
+
|
|
116
|
+
## Prior Work
|
|
117
|
+
|
|
118
|
+
This project is based off of ActiveSupport::ContinuousIntegration.
|
|
119
|
+
|
|
120
|
+
Many thanks to the Rails team for shipping the original work.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# - bin/ci -f (--fail-fast): Stop immediately when a step fails
|
|
5
5
|
# - bin/ci -c (--continue): Resume from the last failed step
|
|
6
6
|
# - bin/ci -fc: Both options combined
|
|
7
|
-
# - bin/ci -
|
|
7
|
+
# - bin/ci -i (--inline): Run steps sequentially (default is parallel)
|
|
8
8
|
|
|
9
9
|
require "fileutils"
|
|
10
10
|
require "tempfile"
|
|
@@ -24,7 +24,7 @@ module LocalCiPlus
|
|
|
24
24
|
STATE_FILE = File.join("tmp", "ci_state")
|
|
25
25
|
STATE_FILE_ENV = "CI_STATE_FILE"
|
|
26
26
|
|
|
27
|
-
attr_reader :results
|
|
27
|
+
attr_reader :results, :skipped_steps
|
|
28
28
|
|
|
29
29
|
def self.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block)
|
|
30
30
|
new.tap do |ci|
|
|
@@ -45,17 +45,17 @@ module LocalCiPlus
|
|
|
45
45
|
@results = []
|
|
46
46
|
@step_names = []
|
|
47
47
|
@parallel_steps = []
|
|
48
|
+
@using_explicit_parallel_steps = false
|
|
49
|
+
@inside_parallel_steps_block = false
|
|
50
|
+
@skip_remaining_steps = false
|
|
51
|
+
@skip_remaining_reason = nil
|
|
52
|
+
@skipped_steps = []
|
|
48
53
|
@skip_until = continue_mode? ? load_failed_step : nil
|
|
49
54
|
@skipping = !!@skip_until
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
def validate_mode_compatibility!
|
|
53
|
-
|
|
54
|
-
abort colorize("#{status_marker(:error)} Cannot combine --parallel with --fail-fast", :error)
|
|
55
|
-
end
|
|
56
|
-
if parallel? && continue_mode?
|
|
57
|
-
abort colorize("#{status_marker(:error)} Cannot combine --parallel with --continue", :error)
|
|
58
|
-
end
|
|
58
|
+
# No incompatible combinations now that fail-fast/continue auto-disable parallel
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def help?
|
|
@@ -66,16 +66,15 @@ module LocalCiPlus
|
|
|
66
66
|
$stdout.puts <<~HELP
|
|
67
67
|
Usage: bin/ci [options]
|
|
68
68
|
|
|
69
|
+
Runs all CI steps in parallel by default.
|
|
70
|
+
|
|
69
71
|
Options:
|
|
70
|
-
-f, --fail-fast Stop immediately when a step fails
|
|
71
|
-
-c, --continue Resume from the last failed step
|
|
72
|
+
-f, --fail-fast Stop immediately when a step fails (runs inline)
|
|
73
|
+
-c, --continue Resume from the last failed step (runs inline)
|
|
72
74
|
-fc, -cf Combine fail-fast and continue
|
|
73
|
-
-
|
|
75
|
+
-i, --inline Run steps sequentially instead of parallel
|
|
74
76
|
--plain Disable ANSI cursor updates/colors (also used for non-TTY)
|
|
75
77
|
-h, --help Show this help
|
|
76
|
-
|
|
77
|
-
Compatibility:
|
|
78
|
-
--parallel cannot be combined with --fail-fast or --continue
|
|
79
78
|
HELP
|
|
80
79
|
end
|
|
81
80
|
|
|
@@ -86,31 +85,48 @@ module LocalCiPlus
|
|
|
86
85
|
if title == @skip_until
|
|
87
86
|
@skipping = false
|
|
88
87
|
else
|
|
89
|
-
|
|
90
|
-
results << [true, title]
|
|
88
|
+
mark_step_skipped(title, "resuming from: #{@skip_until}")
|
|
91
89
|
return
|
|
92
90
|
end
|
|
93
91
|
end
|
|
94
92
|
|
|
95
|
-
if
|
|
93
|
+
if @skip_remaining_steps
|
|
94
|
+
mark_step_skipped(title, @skip_remaining_reason || "parallel steps failed")
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if queue_parallel_step?
|
|
96
99
|
@parallel_steps << {title: title, command: command}
|
|
97
100
|
return
|
|
98
101
|
end
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
success = system(*command)
|
|
103
|
-
results << [success, title]
|
|
103
|
+
run_inline_step(title, command)
|
|
104
|
+
end
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
def parallel_steps(&block)
|
|
107
|
+
return unless block
|
|
108
|
+
|
|
109
|
+
@using_explicit_parallel_steps = true
|
|
110
|
+
|
|
111
|
+
if parallel?
|
|
112
|
+
if @parallel_steps.any?
|
|
113
|
+
completed = run_parallel_steps!(@parallel_steps)
|
|
114
|
+
@parallel_steps.clear
|
|
115
|
+
mark_remaining_steps_skipped!("parallel_steps failed") if completed.any? { |job| !job[:success] }
|
|
112
116
|
end
|
|
117
|
+
|
|
118
|
+
@inside_parallel_steps_block = true
|
|
119
|
+
queue_start = @parallel_steps.length
|
|
120
|
+
instance_eval(&block)
|
|
121
|
+
batch = @parallel_steps.slice!(queue_start, @parallel_steps.length - queue_start) || []
|
|
122
|
+
completed = run_parallel_steps!(batch)
|
|
123
|
+
mark_remaining_steps_skipped!("parallel_steps failed") if completed.any? { |job| !job[:success] }
|
|
124
|
+
else
|
|
125
|
+
@inside_parallel_steps_block = true
|
|
126
|
+
instance_eval(&block)
|
|
113
127
|
end
|
|
128
|
+
ensure
|
|
129
|
+
@inside_parallel_steps_block = false
|
|
114
130
|
end
|
|
115
131
|
|
|
116
132
|
def success?
|
|
@@ -154,7 +170,7 @@ module LocalCiPlus
|
|
|
154
170
|
|
|
155
171
|
elapsed = timing do
|
|
156
172
|
ci.instance_eval(&block)
|
|
157
|
-
ci.run_parallel_steps! if ci.parallel? && ci.
|
|
173
|
+
ci.run_parallel_steps!(ci.queued_parallel_steps) if ci.parallel? && ci.queued_parallel_steps.any?
|
|
158
174
|
end
|
|
159
175
|
|
|
160
176
|
@skip_until = ci.instance_variable_get(:@skip_until)
|
|
@@ -171,6 +187,12 @@ module LocalCiPlus
|
|
|
171
187
|
echo " #{failure_bullet} #{step_title} failed", type: :error
|
|
172
188
|
end
|
|
173
189
|
end
|
|
190
|
+
|
|
191
|
+
if ci.skipped_steps.any?
|
|
192
|
+
ci.skipped_steps.each do |step_title|
|
|
193
|
+
echo " #{failure_bullet} #{step_title} skipped", type: :skip
|
|
194
|
+
end
|
|
195
|
+
end
|
|
174
196
|
end
|
|
175
197
|
|
|
176
198
|
results.concat ci.results
|
|
@@ -198,7 +220,10 @@ module LocalCiPlus
|
|
|
198
220
|
end
|
|
199
221
|
|
|
200
222
|
def parallel?
|
|
201
|
-
ARGV.include?("-
|
|
223
|
+
return false if ARGV.include?("-i") || ARGV.include?("--inline")
|
|
224
|
+
return false if fail_fast? || continue_mode?
|
|
225
|
+
|
|
226
|
+
true
|
|
202
227
|
end
|
|
203
228
|
|
|
204
229
|
def plain?
|
|
@@ -211,24 +236,29 @@ module LocalCiPlus
|
|
|
211
236
|
false
|
|
212
237
|
end
|
|
213
238
|
|
|
214
|
-
|
|
239
|
+
def queued_parallel_steps
|
|
240
|
+
@parallel_steps
|
|
241
|
+
end
|
|
215
242
|
|
|
216
243
|
MAX_OUTPUT_BYTES = 100 * 1024 # 100KB max per output stream
|
|
217
244
|
|
|
218
|
-
def run_parallel_steps!
|
|
219
|
-
|
|
245
|
+
def run_parallel_steps!(steps = @parallel_steps)
|
|
246
|
+
return [] if steps.empty?
|
|
247
|
+
|
|
248
|
+
total = steps.size
|
|
249
|
+
@active_parallel_size = total
|
|
220
250
|
@running_jobs = []
|
|
221
251
|
completed = []
|
|
222
252
|
completed_by_index = {}
|
|
223
253
|
|
|
224
254
|
echo "\n#{status_marker(:pending)} Running #{total} steps in parallel:", type: :subtitle
|
|
225
255
|
unless plain?
|
|
226
|
-
|
|
256
|
+
steps.each do |step|
|
|
227
257
|
echo format_parallel_line(step[:title], :pending), type: :pending
|
|
228
258
|
end
|
|
229
259
|
end
|
|
230
260
|
|
|
231
|
-
|
|
261
|
+
steps.each_with_index do |step_info, idx|
|
|
232
262
|
title = step_info[:title]
|
|
233
263
|
command = step_info[:command]
|
|
234
264
|
|
|
@@ -280,7 +310,7 @@ module LocalCiPlus
|
|
|
280
310
|
end
|
|
281
311
|
|
|
282
312
|
if plain?
|
|
283
|
-
|
|
313
|
+
steps.each_with_index do |step, idx|
|
|
284
314
|
job = completed_by_index[idx]
|
|
285
315
|
type = job[:success] ? :success : :error
|
|
286
316
|
echo format_parallel_line(step[:title], type, duration: job[:duration]), type: type
|
|
@@ -288,7 +318,9 @@ module LocalCiPlus
|
|
|
288
318
|
end
|
|
289
319
|
|
|
290
320
|
print_parallel_summary(completed)
|
|
321
|
+
completed
|
|
291
322
|
ensure
|
|
323
|
+
@active_parallel_size = nil
|
|
292
324
|
cleanup_all_jobs!
|
|
293
325
|
end
|
|
294
326
|
|
|
@@ -340,6 +372,42 @@ module LocalCiPlus
|
|
|
340
372
|
|
|
341
373
|
private
|
|
342
374
|
|
|
375
|
+
def run_inline_step(title, command)
|
|
376
|
+
heading title, command.join(" "), type: :title
|
|
377
|
+
report(title) do
|
|
378
|
+
success = system(*command)
|
|
379
|
+
results << [success, title]
|
|
380
|
+
|
|
381
|
+
if success
|
|
382
|
+
clear_state if recorded_failed_step == title
|
|
383
|
+
else
|
|
384
|
+
record_failed_step(title)
|
|
385
|
+
if fail_fast?
|
|
386
|
+
abort colorize("\n#{status_marker(:error)} #{title} failed (fail-fast enabled)", :error)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def mark_step_skipped(title, reason)
|
|
393
|
+
heading title, "skipped (#{reason})", type: :skip
|
|
394
|
+
@skipped_steps << title if reason == @skip_remaining_reason
|
|
395
|
+
results << [true, title]
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def queue_parallel_step?
|
|
399
|
+
return false unless parallel?
|
|
400
|
+
return true if @inside_parallel_steps_block
|
|
401
|
+
return false if @using_explicit_parallel_steps
|
|
402
|
+
|
|
403
|
+
true
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def mark_remaining_steps_skipped!(reason)
|
|
407
|
+
@skip_remaining_steps = true
|
|
408
|
+
@skip_remaining_reason = reason
|
|
409
|
+
end
|
|
410
|
+
|
|
343
411
|
def print_parallel_summary(completed)
|
|
344
412
|
failed_jobs = completed.reject { |j| j[:success] }
|
|
345
413
|
return if failed_jobs.empty?
|
|
@@ -449,7 +517,7 @@ module LocalCiPlus
|
|
|
449
517
|
return
|
|
450
518
|
end
|
|
451
519
|
|
|
452
|
-
lines_up = @parallel_steps.size - index
|
|
520
|
+
lines_up = (@active_parallel_size || @parallel_steps.size) - index
|
|
453
521
|
print "\033[s"
|
|
454
522
|
print "\033[#{lines_up}A" if lines_up > 0
|
|
455
523
|
print "\r\033[2K"
|