forked from Homebrew/homebrew-cask
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbrewcask-ci.rb
executable file
·316 lines (251 loc) · 9.75 KB
/
brewcask-ci.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# frozen_string_literal: false
require "utils/github"
require "utils/formatter"
require_relative "lib/capture"
require_relative "lib/check"
require_relative "lib/travis"
module Cask
class Cmd
class Ci < AbstractCommand
option "--only-style", :only_style, false
option "--annotate-style-violations", :annotate_style_violations, false
def run
unless ENV.key?("CI")
raise CaskError, "This command isn’t meant to be run locally."
end
$stdout.sync = true
$stderr.sync = true
unless tap
raise CaskError, "This command must be run from inside a tap directory."
end
@commit_range = begin
commit_range_start = system_command!("git", args: ["rev-parse", "origin/master"]).stdout.chomp
commit_range_end = system_command!("git", args: ["rev-parse", "HEAD"]).stdout.chomp
"#{commit_range_start}...#{commit_range_end}"
end
ruby_files_in_wrong_directory = modified_ruby_files - (modified_cask_files + modified_command_files + modified_github_files)
unless ruby_files_in_wrong_directory.empty?
raise CaskError, "Casks are in the wrong directory:\n" +
ruby_files_in_wrong_directory.join("\n")
end
if modified_cask_files.count > 1 && tap.name != "homebrew/cask-fonts"
raise CaskError, "More than one cask modified; please submit a pull request for each cask separately."
end
overall_success = true
style_status = nil
style_failed_casks = []
style_offenses = []
modified_cask_files.each do |path|
cask = CaskLoader.load(path)
overall_success &= step "brew cask style #{cask.token}", "style" do
begin
Style.run(path)
style_status ||= 'success'
true
rescue => e
style_status = 'action_required'
json = Style.rubocop(path, json: true)
style_offenses +=
json.fetch("files")
.flat_map do |file|
file.fetch("offenses").map do |o|
{
title: o.fetch("cop_name"),
message: o.fetch("message"),
path: Pathname(file.fetch("path")).relative_path_from(tap.path).to_s,
start_line: o.fetch("location").fetch("start_line"),
start_column: o.fetch("location").fetch("start_column"),
end_line: o.fetch("location").fetch("last_line"),
end_column: o.fetch("location").fetch("last_column"),
annotation_level: 'failure',
}
end
end
style_failed_casks << cask.token
false
end
end
next if only_style?
overall_success &= step "brew cask audit #{cask.token}", "audit" do
Auditor.audit(cask, audit_download: true,
audit_appcast: true,
audit_token_conflicts: added_cask_files.include?(path),
commit_range: @commit_range)
end
if (macos_requirement = cask.depends_on.macos) && !macos_requirement.satisfied?
opoo "Skipping installation: #{macos_requirement.message}"
next
end
was_installed = cask.installed?
installer = Installer.new(cask, verbose: true)
cask_and_formula_dependencies = installer.missing_cask_and_formula_dependencies
check = Check.new
overall_success &= step "brew cask install #{cask.token}", "install" do
if was_installed
old_cask = CaskLoader.load(cask.installed_caskfile)
Installer.new(old_cask, verbose: true).zap
end
check.before
installer.install
end
overall_success &= step "brew cask uninstall #{cask.token}", "uninstall" do
success = begin
if manual_installer?(cask)
puts 'Cask has a manual installer, skipping...'
else
installer.uninstall
end
true
rescue => e
$stderr.puts e.message
$stderr.puts e.backtrace
false
ensure
cask_and_formula_dependencies.reverse.each do |cask_or_formula|
next unless cask_or_formula.is_a?(Cask)
Installer.new(cask_or_formula, verbose: true).uninstall if cask_or_formula.any_version_installed?
end
end
check.after
next success if check.success?
$stderr.puts check.message
false
end
if check.success? && !check.success?(ignore_exceptions: false)
overall_success &= step "brew cask zap #{cask.token}", "zap" do
success = begin
Installer.new(cask, verbose: true).zap
true
rescue => e
$stderr.puts e.message
$stderr.puts e.backtrace
false
end
check.after
next success if check.success?(ignore_exceptions: false)
$stderr.puts check.message(stanza: "zap")
false
end
end
end
style_status ||= 'neutral'
if annotate_style_violations?
event = JSON.parse(File.read(ENV.fetch("HOMEBREW_GITHUB_EVENT_PATH")))
case ENV["HOMEBREW_GITHUB_EVENT_NAME"]
when "pull_request"
pr = event.fetch("pull_request")
repo = pr.fetch("base").fetch("repo").fetch("full_name")
head_sha = pr.fetch("head").fetch("sha")
when "check_run"
repo = event.fetch("repository").fetch("full_name")
head_sha = event.fetch("check_run").fetch("head_sha")
else
raise
end
output = case style_status
when 'neutral'
{ title: 'Style check skipped.', summary: 'No matching files changed.' }
when 'success'
{ title: 'Style check succeeded.', summary: 'No style violations found.' }
when 'action_required', 'failure'
{
title: 'Style check failed, run `brew cask style --fix`.',
summary: <<~MARKDOWN,
#{style_offenses.count} style violations were found. Run
```
brew cask style --fix #{style_failed_casks.join(' ')}
```
and fix the remaining violations manually, if any.
MARKDOWN
annotations: style_offenses
}
else
raise
end
GitHub.create_check_run(repo: repo, data: {
name: 'style',
head_sha: head_sha,
conclusion: style_status,
completed_at: Time.now.iso8601,
details_url: "https://github.com/Homebrew/homebrew-cask/blob/master/CONTRIBUTING.md#style-guide",
output: output,
})
end
if overall_success
puts Formatter.success("Build finished successfully.", label: "Success")
return
end
raise CaskError, "Build failed."
end
private
def step(name, travis_id)
unless ENV.key?("TRAVIS_COMMIT_RANGE")
puts Formatter.headline(name, color: :yellow)
return yield != false
end
success = false
output = nil
Travis.fold travis_id do
print Formatter.headline("#{name} ", color: :yellow)
real_stdout = $stdout.dup
travis_wait = Thread.new do
loop do
sleep 595
real_stdout.print "\u200b"
end
end
success, output = capture do
begin
yield != false
rescue => e
$stderr.puts e.message
$stderr.puts e.backtrace
false
end
end
travis_wait.kill
travis_wait.join
if success
puts Formatter.success("✔")
puts output unless output.empty?
else
puts Formatter.error("✘")
end
end
puts output unless success
success
end
def tap
@tap ||= Tap.from_path(Dir.pwd)
end
def modified_files
@modified_files ||= system_command!(
"git", args: ["diff", "--name-only", "--diff-filter=AMR", @commit_range]
).stdout.split("\n").map { |path| Pathname(path) }
end
def added_files
@added_files ||= system_command!(
"git", args: ["diff", "--name-only", "--diff-filter=A", @commit_range]
).stdout.split("\n").map { |path| Pathname(path) }
end
def modified_ruby_files
@modified_ruby_files ||= modified_files.select { |path| path.extname == ".rb" }
end
def modified_command_files
@modified_command_files ||= modified_files.select { |path| path.ascend.to_a.last.to_s == "cmd" }
end
def modified_github_files
@modified_github_files ||= modified_files.select { |path| path.to_s.start_with?(".github/") }
end
def modified_cask_files
@modified_cask_files ||= modified_files.select { |path| tap.cask_file?(path) }
end
def added_cask_files
@added_cask_files ||= added_files.select { |path| tap.cask_file?(path) }
end
def manual_installer?(cask)
cask.artifacts.any? { |artifact| artifact.is_a?(Artifact::Installer::ManualInstaller) }
end
end
end
end