mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

1. Use the maximum timeout possible for GitHub-hosted macOS runners 2. When using a short timeout, use an even shorter timeout on ARM runners. Our ARM runners are typically at least twice as fast as our Intel runners on any given job. So, if it takes an ARM runner over half the timeout to complete a job, it's almost certain that the Intel runner will not complete the job within the timeout. Setting an even shorter timeout on the ARM runners will help us abandon jobs that are unlikely to be completed within the timeout well before we hit the requested timeout.
228 lines
7.5 KiB
Ruby
228 lines
7.5 KiB
Ruby
# 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
|
|
MaybeStringArray = T.type_alias { T.nilable(T::Array[String]) }
|
|
private_constant :MaybeStringArray
|
|
|
|
RunnerSpec = T.type_alias { T.any(LinuxRunnerSpec, MacOSRunnerSpec) }
|
|
private_constant :RunnerSpec
|
|
|
|
MacOSRunnerSpecHash = T.type_alias { { name: String, runner: String, timeout: Integer, cleanup: T::Boolean } }
|
|
private_constant :MacOSRunnerSpecHash
|
|
|
|
LinuxRunnerSpecHash = T.type_alias do
|
|
{
|
|
name: String,
|
|
runner: String,
|
|
container: T::Hash[Symbol, String],
|
|
workdir: String,
|
|
timeout: Integer,
|
|
cleanup: T::Boolean,
|
|
}
|
|
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: MaybeStringArray,
|
|
dependent_matrix: T::Boolean,
|
|
).void
|
|
}
|
|
def initialize(testing_formulae, deleted_formulae, dependent_matrix:)
|
|
@testing_formulae = T.let(testing_formulae, T::Array[TestRunnerFormula])
|
|
@deleted_formulae = T.let(deleted_formulae, MaybeStringArray)
|
|
@dependent_matrix = T.let(dependent_matrix, T::Boolean)
|
|
|
|
@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"
|
|
GITHUB_ACTIONS_LONG_TIMEOUT = 4320
|
|
|
|
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(OS::Mac::Version),
|
|
).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: platform, arch: arch, spec: spec, macos_version: macos_version)
|
|
runner.active = active_runner?(runner)
|
|
runner.freeze
|
|
end
|
|
|
|
NEWEST_GITHUB_ACTIONS_MACOS_RUNNER = :ventura
|
|
GITHUB_ACTIONS_RUNNER_TIMEOUT = 360
|
|
|
|
sig { void }
|
|
def generate_runners!
|
|
return if @runners.present?
|
|
|
|
@runners << create_runner(:linux, :x86_64, linux_runner_spec)
|
|
|
|
github_run_id = ENV.fetch("GITHUB_RUN_ID")
|
|
github_run_attempt = ENV.fetch("GITHUB_RUN_ATTEMPT")
|
|
timeout = ENV.fetch("HOMEBREW_MACOS_TIMEOUT").to_i
|
|
use_github_runner = ENV.fetch("HOMEBREW_MACOS_BUILD_ON_GITHUB_RUNNER", "false") == "true"
|
|
|
|
ephemeral_suffix = +"-#{github_run_id}-#{github_run_attempt}"
|
|
ephemeral_suffix << "-deps" if @dependent_matrix
|
|
ephemeral_suffix.freeze
|
|
|
|
MacOSVersions::SYMBOLS.each_value do |version|
|
|
macos_version = OS::Mac::Version.new(version)
|
|
next if macos_version.unsupported_release?
|
|
|
|
# Intel Big Sur is a bit slower than the other runners,
|
|
# so give it a little bit more time. The comparison below
|
|
# should be `==`, but it returns typecheck errors.
|
|
runner_timeout = timeout
|
|
runner_timeout += 30 if macos_version <= :big_sur
|
|
|
|
# Use GitHub Actions macOS Runner for testing dependents if compatible with timeout.
|
|
runner, runner_timeout = if (@dependent_matrix || use_github_runner) &&
|
|
macos_version <= NEWEST_GITHUB_ACTIONS_MACOS_RUNNER &&
|
|
runner_timeout <= GITHUB_ACTIONS_RUNNER_TIMEOUT
|
|
["macos-#{version}", GITHUB_ACTIONS_RUNNER_TIMEOUT]
|
|
else
|
|
["#{version}#{ephemeral_suffix}", runner_timeout]
|
|
end
|
|
|
|
spec = MacOSRunnerSpec.new(
|
|
name: "macOS #{version}-x86_64",
|
|
runner: runner,
|
|
timeout: runner_timeout,
|
|
cleanup: !runner.end_with?(ephemeral_suffix),
|
|
)
|
|
@runners << create_runner(:macos, :x86_64, spec, macos_version)
|
|
|
|
next if macos_version < :big_sur
|
|
|
|
runner = +"#{version}-arm64"
|
|
|
|
# Use bare metal runner when testing dependents on ARM64 Monterey.
|
|
use_ephemeral = macos_version >= (@dependent_matrix ? :ventura : :monterey)
|
|
runner << ephemeral_suffix if use_ephemeral
|
|
|
|
runner.freeze
|
|
|
|
# The ARM runners are typically over twice as fast as the Intel runners.
|
|
runner_timeout = timeout
|
|
runner_timeout /= 2 if timeout < GITHUB_ACTIONS_LONG_TIMEOUT
|
|
spec = MacOSRunnerSpec.new(
|
|
name: "macOS #{version}-arm64",
|
|
runner: runner,
|
|
timeout: runner_timeout,
|
|
cleanup: !runner.end_with?(ephemeral_suffix),
|
|
)
|
|
@runners << create_runner(:macos, :arm64, spec, macos_version)
|
|
end
|
|
|
|
@runners.freeze
|
|
end
|
|
|
|
sig { params(runner: GitHubRunner).returns(T::Boolean) }
|
|
def active_runner?(runner)
|
|
if @dependent_matrix
|
|
formulae_have_untested_dependents?(runner)
|
|
else
|
|
return true if @deleted_formulae.present?
|
|
|
|
compatible_formulae = @testing_formulae.dup
|
|
|
|
platform = runner.platform
|
|
arch = runner.arch
|
|
macos_version = runner.macos_version
|
|
|
|
compatible_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
|
|
|
|
compatible_formulae.present?
|
|
end
|
|
end
|
|
|
|
sig { params(runner: GitHubRunner).returns(T::Boolean) }
|
|
def formulae_have_untested_dependents?(runner)
|
|
platform = runner.platform
|
|
arch = runner.arch
|
|
macos_version = runner.macos_version
|
|
|
|
@testing_formulae.any? do |formula|
|
|
# If the formula has a platform/arch/macOS version requirement, then its
|
|
# dependents don't need to be tested if these requirements are not satisfied.
|
|
next false unless formula.public_send(:"#{platform}_compatible?")
|
|
next false unless formula.public_send(:"#{arch}_compatible?")
|
|
next false if macos_version.present? && !formula.compatible_with?(macos_version)
|
|
|
|
compatible_dependents = formula.dependents(platform: platform, arch: arch, macos_version: macos_version&.to_sym)
|
|
.dup
|
|
|
|
compatible_dependents.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
|