Extract code to GitHubRunnerMatrix class
The `GitHubRunnerMatrix` takes a list of testing formulae, deleted formulae, and the available runners and constructs the matrix of active runners for any given test job.
This commit is contained in:
parent
49e8d088ae
commit
868f9395b6
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "test_runner_formula"
|
require "test_runner_formula"
|
||||||
|
require "github_runner_matrix"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -29,59 +30,17 @@ module Homebrew
|
|||||||
|
|
||||||
sig {
|
sig {
|
||||||
params(
|
params(
|
||||||
testing_formulae: T::Array[TestRunnerFormula],
|
version: String,
|
||||||
platform: Symbol,
|
|
||||||
arch: Symbol,
|
arch: Symbol,
|
||||||
macos_version: T.nilable(OS::Mac::Version),
|
ephemeral: T::Boolean,
|
||||||
).returns(T::Boolean)
|
ephemeral_suffix: T.nilable(String),
|
||||||
|
).returns(T::Hash[Symbol, T.any(String, T::Boolean)])
|
||||||
}
|
}
|
||||||
def self.formulae_have_untested_dependents?(testing_formulae, platform:, arch:, macos_version:)
|
def self.runner_spec(version, arch:, ephemeral:, ephemeral_suffix: nil)
|
||||||
testing_formulae.any? do |formula|
|
case arch
|
||||||
# If the formula has a platform/arch/macOS version requirement, then its
|
when :arm64 then { runner: "#{version}-arm64#{ephemeral_suffix}", clean: !ephemeral }
|
||||||
# dependents don't need to be tested if these requirements are not satisfied.
|
when :x86_64 then { runner: "#{version}#{ephemeral_suffix}", clean: !ephemeral }
|
||||||
next false unless formula.send(:"#{platform}_compatible?")
|
else raise "Unexpected arch: #{arch}"
|
||||||
next false unless formula.send(:"#{arch}_compatible?")
|
|
||||||
next false if macos_version && !formula.compatible_with?(macos_version)
|
|
||||||
|
|
||||||
compatible_dependents = formula.dependents(platform: platform, arch: arch, macos_version: macos_version&.to_sym)
|
|
||||||
.dup
|
|
||||||
|
|
||||||
compatible_dependents.select! { |dependent_f| dependent_f.send(:"#{platform}_compatible?") }
|
|
||||||
compatible_dependents.select! { |dependent_f| dependent_f.send(:"#{arch}_compatible?") }
|
|
||||||
compatible_dependents.select! { |dependent_f| dependent_f.compatible_with?(macos_version) } if macos_version
|
|
||||||
|
|
||||||
(compatible_dependents - testing_formulae).present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sig {
|
|
||||||
params(
|
|
||||||
formulae: T::Array[TestRunnerFormula],
|
|
||||||
dependents: T::Boolean,
|
|
||||||
deleted_formulae: T.nilable(T::Array[String]),
|
|
||||||
platform: Symbol,
|
|
||||||
arch: Symbol,
|
|
||||||
macos_version: T.nilable(OS::Mac::Version),
|
|
||||||
).returns(T::Boolean)
|
|
||||||
}
|
|
||||||
def self.add_runner?(formulae, dependents:, deleted_formulae:, platform:, arch:, macos_version: nil)
|
|
||||||
if dependents
|
|
||||||
formulae_have_untested_dependents?(
|
|
||||||
formulae,
|
|
||||||
platform: platform,
|
|
||||||
arch: arch,
|
|
||||||
macos_version: macos_version,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
return true if deleted_formulae.present?
|
|
||||||
|
|
||||||
compatible_formulae = formulae.dup
|
|
||||||
|
|
||||||
compatible_formulae.select! { |formula| formula.send(:"#{platform}_compatible?") }
|
|
||||||
compatible_formulae.select! { |formula| formula.send(:"#{arch}_compatible?") }
|
|
||||||
compatible_formulae.select! { |formula| formula.compatible_with?(macos_version) } if macos_version
|
|
||||||
|
|
||||||
compatible_formulae.present?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -96,14 +55,15 @@ module Homebrew
|
|||||||
Formulary.enable_factory_cache!
|
Formulary.enable_factory_cache!
|
||||||
|
|
||||||
testing_formulae = args.named.first.split(",")
|
testing_formulae = args.named.first.split(",")
|
||||||
testing_formulae.map! { |name| TestRunnerFormula.new(Formula[name], eval_all: eval_all) }
|
testing_formulae.map! { |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: eval_all) }
|
||||||
.freeze
|
.freeze
|
||||||
deleted_formulae = args.named.second&.split(",")
|
deleted_formulae = args.named.second&.split(",")
|
||||||
|
|
||||||
runners = []
|
|
||||||
|
|
||||||
linux_runner = ENV.fetch("HOMEBREW_LINUX_RUNNER") { raise "HOMEBREW_LINUX_RUNNER is not defined" }
|
linux_runner = ENV.fetch("HOMEBREW_LINUX_RUNNER") { raise "HOMEBREW_LINUX_RUNNER is not defined" }
|
||||||
linux_cleanup = ENV.fetch("HOMEBREW_LINUX_CLEANUP") { raise "HOMEBREW_LINUX_CLEANUP is not defined" }
|
linux_cleanup = ENV.fetch("HOMEBREW_LINUX_CLEANUP") { raise "HOMEBREW_LINUX_CLEANUP is not defined" }
|
||||||
|
github_run_id = ENV.fetch("GITHUB_RUN_ID") { raise "GITHUB_RUN_ID is not defined" }
|
||||||
|
github_run_attempt = ENV.fetch("GITHUB_RUN_ATTEMPT") { raise "GITHUB_RUN_ATTEMPT is not defined" }
|
||||||
|
github_output = ENV.fetch("GITHUB_OUTPUT") { raise "GITHUB_OUTPUT is not defined" }
|
||||||
|
|
||||||
linux_runner_spec = {
|
linux_runner_spec = {
|
||||||
runner: linux_runner,
|
runner: linux_runner,
|
||||||
@ -115,61 +75,42 @@ module Homebrew
|
|||||||
timeout: 4320,
|
timeout: 4320,
|
||||||
cleanup: linux_cleanup == "true",
|
cleanup: linux_cleanup == "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
if add_runner?(
|
|
||||||
testing_formulae,
|
|
||||||
platform: :linux,
|
|
||||||
arch: :x86_64,
|
|
||||||
deleted_formulae: deleted_formulae,
|
|
||||||
dependents: args.dependents?,
|
|
||||||
)
|
|
||||||
runners << linux_runner_spec
|
|
||||||
end
|
|
||||||
|
|
||||||
github_run_id = ENV.fetch("GITHUB_RUN_ID") { raise "GITHUB_RUN_ID is not defined" }
|
|
||||||
github_run_attempt = ENV.fetch("GITHUB_RUN_ATTEMPT") { raise "GITHUB_RUN_ATTEMPT is not defined" }
|
|
||||||
ephemeral_suffix = "-#{github_run_id}-#{github_run_attempt}"
|
ephemeral_suffix = "-#{github_run_id}-#{github_run_attempt}"
|
||||||
|
|
||||||
|
available_runners = []
|
||||||
|
available_runners << { platform: :linux, arch: :x86_64, runner_spec: linux_runner_spec, macos_version: nil }
|
||||||
|
|
||||||
MacOSVersions::SYMBOLS.each_value do |version|
|
MacOSVersions::SYMBOLS.each_value do |version|
|
||||||
macos_version = OS::Mac::Version.new(version)
|
macos_version = OS::Mac::Version.new(version)
|
||||||
next if macos_version.outdated_release? || macos_version.prerelease?
|
next if macos_version.outdated_release? || macos_version.prerelease?
|
||||||
|
|
||||||
if add_runner?(
|
spec = runner_spec(version, arch: :x86_64, ephemeral: true, ephemeral_suffix: ephemeral_suffix)
|
||||||
testing_formulae,
|
available_runners << { platform: :macos, arch: :x86_64, runner_spec: spec, macos_version: macos_version }
|
||||||
platform: :macos,
|
|
||||||
arch: :x86_64,
|
|
||||||
macos_version: macos_version,
|
|
||||||
deleted_formulae: deleted_formulae,
|
|
||||||
dependents: args.dependents?,
|
|
||||||
)
|
|
||||||
runners << { runner: "#{version}#{ephemeral_suffix}", cleanup: false }
|
|
||||||
end
|
|
||||||
|
|
||||||
next unless add_runner?(
|
# Use bare metal runner when testing dependents on ARM64 Monterey.
|
||||||
testing_formulae,
|
if (macos_version >= :ventura && args.dependents?) || macos_version >= :monterey
|
||||||
platform: :macos,
|
spec = runner_spec(version, arch: :arm64, ephemeral: true, ephemeral_suffix: ephemeral_suffix)
|
||||||
arch: :arm64,
|
available_runners << { platform: :macos, arch: :arm64, runner_spec: spec, macos_version: macos_version }
|
||||||
macos_version: macos_version,
|
|
||||||
deleted_formulae: deleted_formulae,
|
|
||||||
dependents: args.dependents?,
|
|
||||||
)
|
|
||||||
|
|
||||||
runner_name = "#{version}-arm64"
|
|
||||||
# Use bare metal runner when testing dependents on Monterey.
|
|
||||||
if macos_version >= :ventura || (macos_version >= :monterey && !args.dependents?)
|
|
||||||
runners << { runner: "#{runner_name}#{ephemeral_suffix}", cleanup: false }
|
|
||||||
elsif macos_version >= :big_sur
|
elsif macos_version >= :big_sur
|
||||||
runners << { runner: runner_name, cleanup: true }
|
spec = runner_spec(version, arch: :arm64, ephemeral: false)
|
||||||
|
available_runners << { platform: :macos, arch: :arm64, runner_spec: spec, macos_version: macos_version }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
runner_matrix = GitHubRunnerMatrix.new(
|
||||||
|
available_runners,
|
||||||
|
testing_formulae,
|
||||||
|
deleted_formulae,
|
||||||
|
dependent_matrix: args.dependents?,
|
||||||
|
)
|
||||||
|
runners = runner_matrix.active_runners
|
||||||
|
|
||||||
if !args.dependents? && runners.blank?
|
if !args.dependents? && runners.blank?
|
||||||
# If there are no tests to run, add a runner that is meant to do nothing
|
# If there are no tests to run, add a runner that is meant to do nothing
|
||||||
# to support making the `tests` job a required status check.
|
# to support making the `tests` job a required status check.
|
||||||
runners << { runner: "ubuntu-latest", no_op: true }
|
runners << { runner: "ubuntu-latest", no_op: true }
|
||||||
end
|
end
|
||||||
|
|
||||||
github_output = ENV.fetch("GITHUB_OUTPUT") { raise "GITHUB_OUTPUT is not defined" }
|
|
||||||
File.open(github_output, "a") do |f|
|
File.open(github_output, "a") do |f|
|
||||||
f.puts("runners=#{runners.to_json}")
|
f.puts("runners=#{runners.to_json}")
|
||||||
f.puts("runners_present=#{runners.present?}")
|
f.puts("runners_present=#{runners.present?}")
|
||||||
|
|||||||
110
Library/Homebrew/github_runner_matrix.rb
Normal file
110
Library/Homebrew/github_runner_matrix.rb
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "test_runner_formula"
|
||||||
|
|
||||||
|
class GitHubRunnerMatrix
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
# FIXME: sig { returns(T::Array[RunnerSpec]) }
|
||||||
|
sig { returns(T::Array[RunnerHashValue]) }
|
||||||
|
attr_reader :active_runners
|
||||||
|
|
||||||
|
# FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed.
|
||||||
|
# rubocop:disable Style/MutableConstant
|
||||||
|
RunnerSpec = T.type_alias do
|
||||||
|
T.any(
|
||||||
|
T::Hash[Symbol, T.any(String, T::Hash[Symbol, String], Integer, T::Boolean)], # Linux
|
||||||
|
T::Hash[Symbol, T.any(String, T::Boolean)], # macOS
|
||||||
|
)
|
||||||
|
end
|
||||||
|
private_constant :RunnerSpec
|
||||||
|
RunnerHashValue = T.type_alias { T.any(Symbol, RunnerSpec, T.nilable(OS::Mac::Version)) }
|
||||||
|
private_constant :RunnerHashValue
|
||||||
|
# rubocop:enable Style/MutableConstant
|
||||||
|
|
||||||
|
sig {
|
||||||
|
params(
|
||||||
|
available_runners: T::Array[T::Hash[Symbol, RunnerHashValue]],
|
||||||
|
testing_formulae: T::Array[TestRunnerFormula],
|
||||||
|
deleted_formulae: T.nilable(T::Array[String]),
|
||||||
|
dependent_matrix: T::Boolean,
|
||||||
|
).void
|
||||||
|
}
|
||||||
|
def initialize(available_runners, testing_formulae, deleted_formulae, dependent_matrix:)
|
||||||
|
@available_runners = T.let(available_runners, T::Array[T::Hash[Symbol, RunnerHashValue]])
|
||||||
|
@testing_formulae = T.let(testing_formulae, T::Array[TestRunnerFormula])
|
||||||
|
@deleted_formulae = T.let(deleted_formulae, T.nilable(T::Array[String]))
|
||||||
|
@dependent_matrix = T.let(dependent_matrix, T::Boolean)
|
||||||
|
# FIXME: Should have type `RunnerSpec`, but Sorbet can't infer that that's correct.
|
||||||
|
@active_runners = T.let([], T::Array[RunnerHashValue])
|
||||||
|
|
||||||
|
generate_runners!
|
||||||
|
|
||||||
|
freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def generate_runners!
|
||||||
|
@available_runners.each do |runner|
|
||||||
|
@active_runners << runner.fetch(:runner_spec) if add_runner?(runner)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(runner: T::Hash[Symbol, RunnerHashValue]).returns([Symbol, Symbol, T.nilable(OS::Mac::Version)]) }
|
||||||
|
def unpack_runner(runner)
|
||||||
|
platform = runner.fetch(:platform)
|
||||||
|
raise "Unexpected platform: #{platform}" if !platform.is_a?(Symbol) || [:macos, :linux].exclude?(platform)
|
||||||
|
|
||||||
|
arch = runner.fetch(:arch)
|
||||||
|
raise "Unexpected arch: #{arch}" if !arch.is_a?(Symbol) || [:arm64, :x86_64].exclude?(arch)
|
||||||
|
|
||||||
|
macos_version = runner.fetch(:macos_version)
|
||||||
|
if !macos_version.nil? && !macos_version.is_a?(OS::Mac::Version)
|
||||||
|
raise "Unexpected macos_version: #{macos_version}"
|
||||||
|
end
|
||||||
|
|
||||||
|
[platform, arch, macos_version]
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(runner: T::Hash[Symbol, RunnerHashValue]).returns(T::Boolean) }
|
||||||
|
def add_runner?(runner)
|
||||||
|
if @dependent_matrix
|
||||||
|
formulae_have_untested_dependents?(runner)
|
||||||
|
else
|
||||||
|
return true if @deleted_formulae.present?
|
||||||
|
|
||||||
|
compatible_formulae = @testing_formulae.dup
|
||||||
|
|
||||||
|
platform, arch, macos_version = unpack_runner(runner)
|
||||||
|
|
||||||
|
compatible_formulae.select! { |formula| formula.send(:"#{platform}_compatible?") }
|
||||||
|
compatible_formulae.select! { |formula| formula.send(:"#{arch}_compatible?") }
|
||||||
|
compatible_formulae.select! { |formula| formula.compatible_with?(macos_version) } if macos_version
|
||||||
|
|
||||||
|
compatible_formulae.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(runner: T::Hash[Symbol, RunnerHashValue]).returns(T::Boolean) }
|
||||||
|
def formulae_have_untested_dependents?(runner)
|
||||||
|
platform, arch, macos_version = unpack_runner(runner)
|
||||||
|
|
||||||
|
@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.send(:"#{platform}_compatible?")
|
||||||
|
next false unless formula.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! { |dependent_f| dependent_f.send(:"#{platform}_compatible?") }
|
||||||
|
compatible_dependents.select! { |dependent_f| dependent_f.send(:"#{arch}_compatible?") }
|
||||||
|
compatible_dependents.select! { |dependent_f| dependent_f.compatible_with?(macos_version) } if macos_version
|
||||||
|
|
||||||
|
(compatible_dependents - @testing_formulae).present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user