forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
oss-check
executable file
·292 lines (251 loc) · 7.66 KB
/
oss-check
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
#!/usr/bin/env ruby
################################
# Requires
################################
require 'fileutils'
require 'open3'
require 'optparse'
require 'erb'
################################
# Options
################################
@options = {
branch: 'HEAD',
iterations: 5,
skip_clean: false,
verbose: false
}
OptionParser.new do |opts|
opts.on('--branch BRANCH', "compares the performance of BRANCH against 'master'") do |branch|
@options[:branch] = branch
end
opts.on('--iterations N', Integer, 'iterates lint N times on each repositories') do |iterations|
@options[:iterations] = iterations
end
opts.on('--skip-clean', 'skip cleaning on completion') do |skip_clean|
@options[:skip_clean] = skip_clean
end
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
@options[:verbose] = v
end
end.parse!
################################
# Classes
################################
class Repo
attr_accessor :name
attr_accessor :github_location
attr_accessor :commit_hash
attr_accessor :branch_exit_value
attr_accessor :branch_duration
attr_accessor :master_exit_value
attr_accessor :master_duration
def initialize(name, github_location)
@name = name
@github_location = github_location
end
def git_url
"https://github.com/#{github_location}"
end
def to_s
@name
end
def duration_report
percent_change = 100 * (@master_duration - @branch_duration) / @master_duration
faster_slower = nil
if @branch_duration < @master_duration
faster_slower = 'faster'
else
faster_slower = 'slower'
percent_change *= -1
end
"Linting #{self} with this PR took #{@branch_duration}s " \
"vs #{@master_duration}s on master (#{percent_change.to_i}\% #{faster_slower})"
end
end
################################
# Methods
################################
def message(str)
$stderr.puts('Message: ' + str)
end
def warn(str)
$stderr.puts('Warning: ' + str)
end
def fail(str)
$stderr.puts('Error: ' + str)
exit
end
def perform(*args)
commands = args
if @options[:verbose]
commands.each do |x|
puts(x)
system(x)
end
else
commands.each { |x| `#{x}` }
end
end
def validate_state_to_run
if `git symbolic-ref HEAD --short`.strip == 'master' && @options[:branch] == 'HEAD'
fail "can't run osscheck without '--branch' option from 'master' as the script compares " \
"the performance of this branch against 'master'"
end
end
def make_directory_structure
['branch_reports', 'master_reports'].each do |dir|
FileUtils.mkdir_p("#{@working_dir}/#{dir}")
end
end
def convert_to_link(repo, string)
string.sub!("#{Dir.pwd}/#{@working_dir}/#{repo.name}", '')
string.sub!('.swift:', '.swift#L')
string = string.partition(': warning:').first.partition(': error:').first
"#{repo.git_url}/blob/#{repo.commit_hash}#{string}"
end
def non_empty_lines(path)
File.read(path).split(/\n+/).reject(&:empty?)
end
def setup_repos
@repos.each do |repo|
dir = "#{@working_dir}/#{repo.name}"
puts "Cloning #{repo}"
perform("git clone #{repo.git_url} --depth 1 #{dir} 2> /dev/null")
if repo.name == 'Swift'
File.open("#{dir}/.swiftlint.yml", 'w') do |file|
file << 'included: stdlib'
end
end
Dir.chdir(dir) do
repo.commit_hash = `git rev-parse HEAD`.strip
end
end
end
def generate_reports(branch)
@repos.each do |repo|
Dir.chdir("#{@working_dir}/#{repo.name}") do
iterations = @options[:iterations]
print "Linting #{iterations} iterations of #{repo} with #{branch}: 1"
durations = []
start = Time.now
command = '../builds/.build/release/swiftlint lint --no-cache --enable-all-rules --reporter xcode'
File.open("../#{branch}_reports/#{repo}.txt", 'w') do |file|
puts "\n#{command}" if @options[:verbose]
Open3.popen3(command) do |_, stdout, _, wait_thr|
while line = stdout.gets
file.puts line
end
if branch == 'branch'
repo.branch_exit_value = wait_thr.value
else
repo.master_exit_value = wait_thr.value
end
end
end
durations << Time.now - start
for i in 2..iterations
print "..#{i}"
start = Time.now
puts command if @options[:verbose]
Open3.popen3(command) { |_, stdout, _, _| stdout.read }
durations << Time.now - start
end
puts ''
average_duration = (durations.reduce(:+) / iterations).round(2)
if branch == 'branch'
repo.branch_duration = average_duration
else
repo.master_duration = average_duration
end
end
end
end
def build(branch)
puts "Building #{branch}"
dir = "#{@working_dir}/builds"
target = branch == 'master' ? @effective_master_commitish : @options[:branch]
if File.directory?(dir)
perform("cd #{dir}; git checkout #{target}")
else
perform("git fetch && git worktree add --detach #{dir} #{target}")
end
build_command = "cd #{dir}; swift build -c release"
perform(build_command)
return if $?.success?
# Couldn't build, start fresh
Dir.chdir(dir) do
FileUtils.rm_rf %w[Packages .build]
end
return_value = nil
puts build_command if @options[:verbose]
Open3.popen3(build_command) do |_, stdout, _, wait_thr|
puts stdout.read.chomp
return_value = wait_thr.value
end
fail "Could not build #{branch}" unless return_value.success?
end
def diff_and_report_changes_to_danger
@repos.each { |repo| message repo.duration_report }
@repos.each do |repo|
if repo.master_exit_value != repo.branch_exit_value
warn "This PR changed the exit value when running on #{repo.name}: " \
"(#{repo.master_exit_value} to #{repo.branch_exit_value})"
# If the exit value changed, don't show the fixes or regressions for this
# repo because it's likely due to a crash, and all violations would be noisy
next
end
branch = non_empty_lines("#{@working_dir}/branch_reports/#{repo.name}.txt")
master = non_empty_lines("#{@working_dir}/master_reports/#{repo.name}.txt")
(master - branch).each do |fixed|
escaped_message = ERB::Util.html_escape fixed
message "This PR fixed a violation in #{repo.name}: [#{escaped_message}](#{convert_to_link(repo, fixed)})"
end
(branch - master).each do |violation|
escaped_message = ERB::Util.html_escape violation
warn "This PR introduced a violation in #{repo.name}: [#{escaped_message}](#{convert_to_link(repo, violation)})"
end
end
end
def fetch_origin
perform('git fetch origin')
end
def clean_up
FileUtils.rm_rf(@working_dir)
perform('git worktree prune')
end
################################
# Script
################################
# Constants
@working_dir = 'osscheck'
@repos = [
Repo.new('Aerial', 'JohnCoates/Aerial'),
Repo.new('Alamofire', 'Alamofire/Alamofire'),
Repo.new('Firefox', 'mozilla-mobile/firefox-ios'),
Repo.new('Kickstarter', 'kickstarter/ios-oss'),
Repo.new('Moya', 'Moya/Moya'),
Repo.new('Nimble', 'Quick/Nimble'),
Repo.new('Quick', 'Quick/Quick'),
Repo.new('Realm', 'realm/realm-cocoa'),
Repo.new('SourceKitten', 'jpsim/SourceKitten'),
Repo.new('Sourcery', 'krzysztofzablocki/Sourcery'),
Repo.new('Swift', 'apple/swift'),
Repo.new('WordPress', 'wordpress-mobile/WordPress-iOS')
]
# Prep
$stdout.sync = true
validate_state_to_run
setup_repos
make_directory_structure
fetch_origin
@effective_master_commitish = `git merge-base origin/master #{@options[:branch]}`
# Build & generate reports for branch & master
%w[branch master].each do |branch|
build(branch)
generate_reports(branch)
end
# Diff and report changes to Danger
diff_and_report_changes_to_danger
# Clean up
clean_up unless @options[:skip_clean]