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

We give our ARM runners half the timeout of the Intel runners because the ARM runners are faster. However, this is no longer true for dependent testing because we test recursive dependents on ARM but skip them on Intel. This means that we can often hit the timeout on ARM but have all jobs finish on Intel. We can re-run these with the long build label, but that's a bit wasteful of our limited long build slots. Instead, let's just use the same timeout value across ARM and Intel runners when testing dependents: 2 hours.
267 lines
9.4 KiB
Ruby
267 lines
9.4 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
require "test_runner_formula"
|
|
require "github_runner"
|
|
|
|
class GitHubRunnerMatrix
|
|
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
|
|
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 = :sequoia
|
|
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
|
|
|
|
# We test recursive dependents on ARM macOS, so they can be slower than our Intel runners.
|
|
timeout *= 2 if @dependent_matrix && timeout < GITHUB_ACTIONS_RUNNER_TIMEOUT
|
|
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 !@all_supported && 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
|