Class: Homebrew::DevCmd::GenerateCaskCiMatrix Private
- Inherits:
-
AbstractCommand
- Object
- AbstractCommand
- Homebrew::DevCmd::GenerateCaskCiMatrix
- Defined in:
- dev-cmd/generate-cask-ci-matrix.rb,
sorbet/rbi/dsl/homebrew/dev_cmd/generate_cask_ci_matrix.rbi
This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.
Defined Under Namespace
Classes: Args
Constant Summary collapse
- MAX_JOBS =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
256
- INTEL_RUNNERS =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
Weight for each arch must add up to 1.0.
T.let({ { symbol: :ventura, name: "macos-13", arch: :intel } => 1.0, }.freeze, T::Hash[T::Hash[Symbol, T.any(Symbol, String)], Float])
- ARM_RUNNERS =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let({ { symbol: :sonoma, name: "macos-14", arch: :arm } => 0.0, { symbol: :sequoia, name: "macos-15", arch: :arm } => 1.0, }.freeze, T::Hash[T::Hash[Symbol, T.any(Symbol, String)], Float])
- RUNNERS =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let(INTEL_RUNNERS.merge(ARM_RUNNERS).freeze, T::Hash[T::Hash[Symbol, T.any(Symbol, String)], Float])
Instance Method Summary collapse
- #architectures(cask_content:) ⇒ Array<Symbol> private
- #args ⇒ Homebrew::DevCmd::GenerateCaskCiMatrix::Args private
- #filter_runners(cask_content) ⇒ Hash{Hash{Symbol => Symbol, String} => Float} private
- #find_changed_files(tap) ⇒ Hash{Symbol => Array<String>} private
- #generate_matrix(tap, labels: [], cask_names: [], skip_install: false, new_cask: false) ⇒ Array<Hash{Symbol => String, Boolean, Array<String>}> private
- #random_runner(available_runners = ARM_RUNNERS) ⇒ Hash{Symbol => Symbol, String} private
- #run ⇒ void private
- #runners(cask_content:) ⇒ Array<(Array<Hash{Symbol => Symbol, String}>, Boolean)> private
Methods inherited from AbstractCommand
command, command_name, dev_cmd?, #initialize, parser, ruby_cmd?
Constructor Details
This class inherits a constructor from Homebrew::AbstractCommand
Instance Method Details
#architectures(cask_content:) ⇒ Array<Symbol>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 170 def architectures(cask_content:) case cask_content when /depends_on\s+arch:\s+:arm64/ [:arm] when /depends_on\s+arch:\s+:x86_64/ [:intel] when /\barch\b/, /\bon_(arm|intel)\b/ [:arm, :intel] else RUNNERS.keys.map { |r| r.fetch(:arch) }.uniq.sort end end |
#args ⇒ Homebrew::DevCmd::GenerateCaskCiMatrix::Args
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
10 |
# File 'sorbet/rbi/dsl/homebrew/dev_cmd/generate_cask_ci_matrix.rbi', line 10 def args; end |
#filter_runners(cask_content) ⇒ Hash{Hash{Symbol => Symbol, String} => Float}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
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 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 118 def filter_runners(cask_content) # Retrieve arguments from `depends_on macos:` required_macos = case cask_content when /depends_on\s+macos:\s+\[([^\]]+)\]/ T.must(Regexp.last_match(1)).scan(/\s*(?:"([=<>]=)\s+)?:([^\s",]+)"?,?\s*/).map do |match| { version: T.must(match[1]).to_sym, comparator: match[0] || "==", } end when /depends_on\s+macos:\s+"?:([^\s"]+)"?/ # e.g. `depends_on macos: :big_sur` [ { version: T.must(Regexp.last_match(1)).to_sym, comparator: "==", }, ] when /depends_on\s+macos:\s+"([=<>]=)\s+:([^\s"]+)"/ # e.g. `depends_on macos: ">= :monterey"` [ { version: T.must(Regexp.last_match(2)).to_sym, comparator: Regexp.last_match(1), }, ] when /depends_on\s+macos:/ # In this case, `depends_on macos:` is present but wasn't matched by the # previous regexes. We want this to visibly fail so we can address the # shortcoming instead of quietly defaulting to `RUNNERS`. odie "Unhandled `depends_on macos` argument" else [] end filtered_runners = RUNNERS.select do |runner, _| required_macos.any? do |r| MacOSVersion.from_symbol(runner.fetch(:symbol).to_sym).compare( r.fetch(:comparator), MacOSVersion.from_symbol(r.fetch(:version).to_sym), ) end end filtered_runners = RUNNERS.dup if filtered_runners.empty? archs = architectures(cask_content:) filtered_runners.select! do |runner, _| archs.include?(runner.fetch(:arch)) end RUNNERS end |
#find_changed_files(tap) ⇒ Hash{Symbol => Array<String>}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 311 def find_changed_files(tap) commit_range_start = Utils.safe_popen_read("git", "rev-parse", "origin").chomp commit_range_end = Utils.safe_popen_read("git", "rev-parse", "HEAD").chomp commit_range = "#{commit_range_start}...#{commit_range_end}" modified_files = Utils.safe_popen_read("git", "diff", "--name-only", "--diff-filter=AMR", commit_range) .split("\n") .map do |path| Pathname(path) end added_files = Utils.safe_popen_read("git", "diff", "--name-only", "--diff-filter=A", commit_range) .split("\n") .map do |path| Pathname(path) end modified_ruby_files = modified_files.select { |path| path.extname == ".rb" } modified_command_files = modified_files.select { |path| path.ascend.to_a.last.to_s == "cmd" } modified_github_actions_files = modified_files.select do |path| path.to_s.start_with?(".github/actions/") end modified_cask_files = modified_files.select { |path| tap.cask_file?(path.to_s) } { modified_files:, added_files:, modified_ruby_files:, modified_command_files:, modified_github_actions_files:, modified_cask_files:, } end |
#generate_matrix(tap, labels: [], cask_names: [], skip_install: false, new_cask: false) ⇒ Array<Hash{Symbol => String, Boolean, Array<String>}>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
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 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 222 def generate_matrix(tap, labels: [], cask_names: [], skip_install: false, new_cask: false) odie "This command must be run from inside a tap directory." unless tap changed_files = find_changed_files(tap) ruby_files_in_wrong_directory = T.must(changed_files[:modified_ruby_files]) - ( T.must(changed_files[:modified_cask_files]) + T.must(changed_files[:modified_command_files]) + T.must(changed_files[:modified_github_actions_files]) ) if ruby_files_in_wrong_directory.any? ruby_files_in_wrong_directory.each do |path| puts "::error file=#{path}::File is in wrong directory." end odie "Found Ruby files in wrong directory:\n#{ruby_files_in_wrong_directory.join("\n")}" end cask_files_to_check = if cask_names.any? cask_names.map do |cask_name| Cask::CaskLoader.find_cask_in_tap(cask_name, tap).relative_path_from(tap.path) end else T.must(changed_files[:modified_cask_files]) end jobs = cask_files_to_check.count odie "Maximum job matrix size exceeded: #{jobs}/#{MAX_JOBS}" if jobs > MAX_JOBS cask_files_to_check.flat_map do |path| cask_token = path.basename(".rb") audit_args = ["--online"] audit_args << "--new" if T.must(changed_files[:added_files]).include?(path) || new_cask audit_args << "--signing" audit_exceptions = [] audit_exceptions << %w[homepage_https_availability] if labels.include?("ci-skip-homepage") if labels.include?("ci-skip-livecheck") audit_exceptions << %w[hosting_with_livecheck livecheck_https_availability livecheck_min_os livecheck_version] end audit_exceptions << "livecheck_min_os" if labels.include?("ci-skip-livecheck-min-os") if labels.include?("ci-skip-repository") audit_exceptions << %w[github_repository github_prerelease_version gitlab_repository gitlab_prerelease_version bitbucket_repository] end if labels.include?("ci-skip-token") audit_exceptions << %w[token_conflicts token_valid token_bad_words] end audit_args << "--except" << audit_exceptions.join(",") if audit_exceptions.any? cask_content = path.read runners, multi_os = runners(cask_content:) runners.product(architectures(cask_content:)).filter_map do |runner, arch| native_runner_arch = arch == runner.fetch(:arch) # If it's just a single OS test then we can just use the two real arch runners. next if !native_runner_arch && !multi_os arch_args = native_runner_arch ? [] : ["--arch=#{arch}"] { name: "test #{cask_token} (#{runner.fetch(:name)}, #{arch})", tap: tap.name, cask: { token: cask_token, path: "./#{path}", }, audit_args: audit_args + arch_args, fetch_args: arch_args, skip_install: labels.include?("ci-skip-install") || !native_runner_arch || skip_install, runner: runner.fetch(:name), } end end end |
#random_runner(available_runners = ARM_RUNNERS) ⇒ Hash{Symbol => Symbol, String}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
187 188 189 190 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 187 def random_runner(available_runners = ARM_RUNNERS) T.must(available_runners.max_by { |(_, weight)| rand ** (1.0 / weight) }) .first end |
#run ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
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 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 52 def run skip_install = args.skip_install? new_cask = args.new? casks = args.named if args.casks? pr_url = args.named if args.url? syntax_only = args.syntax_only? repository = ENV.fetch("GITHUB_REPOSITORY", nil) raise UsageError, "The GITHUB_REPOSITORY environment variable must be set." if repository.blank? tap = T.let(Tap.fetch(repository), Tap) unless syntax_only raise UsageError, "Either `--cask` or `--url` must be specified." if !args.casks? && !args.url? raise UsageError, "Please provide a cask or url argument" if casks.blank? && pr_url.blank? end raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1 labels = if pr_url pr = GitHub::API.open_rest(pr_url.first) pr.fetch("labels").map { |l| l.fetch("name") } else [] end runner = random_runner[:name] syntax_job = { name: "syntax", tap: tap.name, runner:, } matrix = [syntax_job] if !syntax_only && !labels&.include?("ci-syntax-only") cask_jobs = if casks&.any? generate_matrix(tap, labels:, cask_names: casks, skip_install:, new_cask:) else generate_matrix(tap, labels:, skip_install:, new_cask:) end if cask_jobs.any? # If casks were changed, skip `audit` for whole tap. syntax_job[:skip_audit] = true # The syntax job only runs `style` at this point, which should work on Linux. # Running on macOS is currently faster though, since `homebrew/cask` and # `homebrew/core` are already tapped on macOS CI machines. # syntax_job[:runner] = "ubuntu-latest" end matrix += cask_jobs end syntax_job[:name] += " (#{syntax_job[:runner]})" puts JSON.pretty_generate(matrix) github_output = ENV.fetch("GITHUB_OUTPUT", nil) return unless github_output File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |f| f.puts "matrix=#{JSON.generate(matrix)}" end end |
#runners(cask_content:) ⇒ Array<(Array<Hash{Symbol => Symbol, String}>, Boolean)>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'dev-cmd/generate-cask-ci-matrix.rb', line 193 def runners(cask_content:) filtered_runners = filter_runners(cask_content) macos_version_found = cask_content.match?(/\bMacOS\s*\.version\b/m) filtered_macos_found = filtered_runners.keys.any? do |runner| ( macos_version_found && cask_content.include?(runner[:symbol].inspect) ) || cask_content.include?("on_#{runner[:symbol]}") end if filtered_macos_found # If the cask varies on a MacOS version, test it on every possible macOS version. [filtered_runners.keys, true] else # Otherwise, select a runner from each architecture based on weighted random sample. grouped_runners = filtered_runners.group_by { |runner, _| runner.fetch(:arch) } selected_runners = grouped_runners.map do |_, runners| random_runner(runners.to_h) end [selected_runners, false] end end |