-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
/
Copy pathgithub_runner_matrix.rb
268 lines (221 loc) · 9.33 KB
/
github_runner_matrix.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
# typed: strict
# frozen_string_literal: true
require "test_runner_formula"
require "github_runner"
class GitHubRunnerMatrix
# FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed.
# rubocop:disable Style/MutableConstant
RunnerSpec = T.type_alias { T.any(LinuxRunnerSpec, MacOSRunnerSpec) }
private_constant :RunnerSpec
MacOSRunnerSpecHash = T.type_alias do
{
name: String,
runner: String,
timeout: Integer,
cleanup: T::Boolean,
testing_formulae: String,
}
end
private_constant :MacOSRunnerSpecHash
LinuxRunnerSpecHash = T.type_alias do
{
name: String,
runner: String,
container: T::Hash[Symbol, String],
workdir: String,
timeout: Integer,
cleanup: T::Boolean,
testing_formulae: String,
}
end
private_constant :LinuxRunnerSpecHash
RunnerSpecHash = T.type_alias { T.any(LinuxRunnerSpecHash, MacOSRunnerSpecHash) }
private_constant :RunnerSpecHash
# rubocop:enable Style/MutableConstant
sig { returns(T::Array[GitHubRunner]) }
attr_reader :runners
sig {
params(
testing_formulae: T::Array[TestRunnerFormula],
deleted_formulae: T::Array[String],
all_supported: T::Boolean,
dependent_matrix: T::Boolean,
).void
}
def initialize(testing_formulae, deleted_formulae, all_supported:, dependent_matrix:)
if all_supported && (testing_formulae.present? || deleted_formulae.present? || dependent_matrix)
raise ArgumentError, "all_supported is mutually exclusive to other arguments"
end
@testing_formulae = T.let(testing_formulae, T::Array[TestRunnerFormula])
@deleted_formulae = T.let(deleted_formulae, T::Array[String])
@all_supported = T.let(all_supported, T::Boolean)
@dependent_matrix = T.let(dependent_matrix, T::Boolean)
@compatible_testing_formulae = T.let({}, T::Hash[GitHubRunner, T::Array[TestRunnerFormula]])
@formulae_with_untested_dependents = T.let({}, T::Hash[GitHubRunner, T::Array[TestRunnerFormula]])
@runners = T.let([], T::Array[GitHubRunner])
generate_runners!
freeze
end
sig { returns(T::Array[RunnerSpecHash]) }
def active_runner_specs_hash
runners.select(&:active)
.map(&:spec)
.map(&:to_h)
end
private
SELF_HOSTED_LINUX_RUNNER = "linux-self-hosted-1"
# ARM macOS timeout, keep this under 1/2 of GitHub's job execution time limit for self-hosted runners.
# https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits
GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours
GITHUB_ACTIONS_SHORT_TIMEOUT = 60
sig { returns(LinuxRunnerSpec) }
def linux_runner_spec
linux_runner = ENV.fetch("HOMEBREW_LINUX_RUNNER")
LinuxRunnerSpec.new(
name: "Linux",
runner: linux_runner,
container: {
image: "ghcr.io/homebrew/ubuntu22.04:master",
options: "--user=linuxbrew -e GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED",
},
workdir: "/github/home",
timeout: GITHUB_ACTIONS_LONG_TIMEOUT,
cleanup: linux_runner == SELF_HOSTED_LINUX_RUNNER,
)
end
VALID_PLATFORMS = T.let([:macos, :linux].freeze, T::Array[Symbol])
VALID_ARCHES = T.let([:arm64, :x86_64].freeze, T::Array[Symbol])
sig {
params(
platform: Symbol,
arch: Symbol,
spec: RunnerSpec,
macos_version: T.nilable(MacOSVersion),
).returns(GitHubRunner)
}
def create_runner(platform, arch, spec, macos_version = nil)
raise "Unexpected platform: #{platform}" if VALID_PLATFORMS.exclude?(platform)
raise "Unexpected arch: #{arch}" if VALID_ARCHES.exclude?(arch)
runner = GitHubRunner.new(platform:, arch:, spec:, macos_version:)
runner.spec.testing_formulae += testable_formulae(runner)
runner.active = active_runner?(runner)
runner.freeze
end
NEWEST_HOMEBREW_CORE_MACOS_RUNNER = :sequoia
OLDEST_HOMEBREW_CORE_MACOS_RUNNER = :ventura
NEWEST_HOMEBREW_CORE_INTEL_MACOS_RUNNER = :sonoma
sig { params(macos_version: MacOSVersion).returns(T::Boolean) }
def runner_enabled?(macos_version)
macos_version <= NEWEST_HOMEBREW_CORE_MACOS_RUNNER && macos_version >= OLDEST_HOMEBREW_CORE_MACOS_RUNNER
end
NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura
OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :monterey
NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma
OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma
GITHUB_ACTIONS_RUNNER_TIMEOUT = 360
sig { void }
def generate_runners!
return if @runners.present?
if !@all_supported || ENV.key?("HOMEBREW_LINUX_RUNNER")
@runners << create_runner(:linux, :x86_64, linux_runner_spec)
end
github_run_id = ENV.fetch("GITHUB_RUN_ID")
long_timeout = ENV.fetch("HOMEBREW_MACOS_LONG_TIMEOUT", "false") == "true"
use_github_runner = ENV.fetch("HOMEBREW_MACOS_BUILD_ON_GITHUB_RUNNER", "false") == "true"
runner_timeout = long_timeout ? GITHUB_ACTIONS_LONG_TIMEOUT : GITHUB_ACTIONS_SHORT_TIMEOUT
# Use GitHub Actions macOS Runner for testing dependents if compatible with timeout.
use_github_runner ||= @dependent_matrix
use_github_runner &&= runner_timeout <= GITHUB_ACTIONS_RUNNER_TIMEOUT
ephemeral_suffix = +"-#{github_run_id}"
ephemeral_suffix << "-deps" if @dependent_matrix
ephemeral_suffix << "-long" if runner_timeout == GITHUB_ACTIONS_LONG_TIMEOUT
ephemeral_suffix.freeze
MacOSVersion::SYMBOLS.each_value do |version|
macos_version = MacOSVersion.new(version)
next unless runner_enabled?(macos_version)
github_runner_available = macos_version <= NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER &&
macos_version >= OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER
runner, timeout = if use_github_runner && github_runner_available
["macos-#{version}", GITHUB_ACTIONS_RUNNER_TIMEOUT]
elsif macos_version >= :monterey
["#{version}-arm64#{ephemeral_suffix}", runner_timeout]
else
["#{version}-arm64", runner_timeout]
end
spec = MacOSRunnerSpec.new(
name: "macOS #{version}-arm64",
runner:,
timeout:,
cleanup: !runner.end_with?(ephemeral_suffix),
)
@runners << create_runner(:macos, :arm64, spec, macos_version)
next if macos_version > NEWEST_HOMEBREW_CORE_INTEL_MACOS_RUNNER
github_runner_available = macos_version <= NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER &&
macos_version >= OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER
runner, timeout = if use_github_runner && github_runner_available
["macos-#{version}", GITHUB_ACTIONS_RUNNER_TIMEOUT]
else
["#{version}-x86_64#{ephemeral_suffix}", runner_timeout]
end
# macOS 12-x86_64 is usually slower.
timeout += 30 if macos_version <= :monterey
# The ARM runners are typically over twice as fast as the Intel runners.
timeout *= 2 if !(use_github_runner && github_runner_available) && timeout < GITHUB_ACTIONS_LONG_TIMEOUT
spec = MacOSRunnerSpec.new(
name: "macOS #{version}-x86_64",
runner:,
timeout:,
cleanup: !runner.end_with?(ephemeral_suffix),
)
@runners << create_runner(:macos, :x86_64, spec, macos_version)
end
@runners.freeze
end
sig { params(runner: GitHubRunner).returns(T::Array[String]) }
def testable_formulae(runner)
formulae = if @dependent_matrix
formulae_with_untested_dependents(runner)
else
compatible_testing_formulae(runner)
end
formulae.map(&:name)
end
sig { params(runner: GitHubRunner).returns(T::Boolean) }
def active_runner?(runner)
return true if @all_supported
return true if @deleted_formulae.present? && !@dependent_matrix
runner.spec.testing_formulae.present?
end
sig { params(runner: GitHubRunner).returns(T::Array[TestRunnerFormula]) }
def compatible_testing_formulae(runner)
@compatible_testing_formulae[runner] ||= begin
platform = runner.platform
arch = runner.arch
macos_version = runner.macos_version
@testing_formulae.select do |formula|
next false if macos_version && !formula.compatible_with?(macos_version)
formula.public_send(:"#{platform}_compatible?") &&
formula.public_send(:"#{arch}_compatible?")
end
end
end
sig { params(runner: GitHubRunner).returns(T::Array[TestRunnerFormula]) }
def formulae_with_untested_dependents(runner)
@formulae_with_untested_dependents[runner] ||= begin
platform = runner.platform
arch = runner.arch
macos_version = runner.macos_version
compatible_testing_formulae(runner).select do |formula|
compatible_dependents = formula.dependents(platform:, arch:, macos_version: macos_version&.to_sym)
.select do |dependent_f|
next false if macos_version && !dependent_f.compatible_with?(macos_version)
dependent_f.public_send(:"#{platform}_compatible?") &&
dependent_f.public_send(:"#{arch}_compatible?")
end
# These arrays will generally have been generated by different Formulary caches,
# so we can only compare them by name and not directly.
(compatible_dependents.map(&:name) - @testing_formulae.map(&:name)).present?
end
end
end
end