diff --git a/Library/Homebrew/dev-cmd/pr-pull.rb b/Library/Homebrew/dev-cmd/pr-pull.rb index da8444543d..c08badc32f 100644 --- a/Library/Homebrew/dev-cmd/pr-pull.rb +++ b/Library/Homebrew/dev-cmd/pr-pull.rb @@ -55,8 +55,8 @@ module Homebrew flag "--message=", depends_on: "--autosquash", description: "Message to include when autosquashing revision bumps, deletions and rebuilds." - flag "--artifact=", - description: "Download artifacts with the specified name (default: `bottles`)." + flag "--artifact-pattern=", "--artifact=", + description: "Download artifacts with the specified pattern (default: `bottles{,_*}`)." flag "--tap=", description: "Target tap repository (default: `homebrew/core`)." flag "--root-url=", @@ -81,7 +81,7 @@ module Homebrew ensure_executable!("unzip", reason: "extracting CI artifacts") workflows = args.workflows.presence || ["tests.yml"] - artifact = args.artifact || "bottles" + artifact_pattern = args.artifact_pattern || "bottles{,_*}" tap = Tap.fetch(args.tap || CoreTap.instance.name) raise TapUnavailableError, tap.name unless tap.installed? @@ -143,7 +143,7 @@ module Homebrew workflows.each do |workflow| workflow_run = GitHub.get_workflow_run( - user, repo, pr, workflow_id: workflow, artifact_name: artifact + user, repo, pr, workflow_id: workflow, artifact_pattern: ) if args.ignore_missing_artifacts.present? && T.must(args.ignore_missing_artifacts).include?(workflow) && @@ -155,8 +155,9 @@ module Homebrew end ohai "Downloading bottles for workflow: #{workflow}" - url = GitHub.get_artifact_url(workflow_run) - GitHub.download_artifact(url, pr) + + urls = GitHub.get_artifact_urls(workflow_run) + urls.each { |url| GitHub.download_artifact(url, pr) } end next if args.no_upload? diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/pr_pull.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/pr_pull.rbi index c1ccfee16c..fc8f719367 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/pr_pull.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/pr_pull.rbi @@ -8,6 +8,9 @@ class Homebrew::CLI::Args sig { returns(T.nilable(String)) } def artifact; end + sig { returns(T.nilable(String)) } + def artifact_pattern; end + sig { returns(T::Boolean) } def autosquash?; end diff --git a/Library/Homebrew/test/utils/github_spec.rb b/Library/Homebrew/test/utils/github_spec.rb index 1c01709361..56a047c5af 100644 --- a/Library/Homebrew/test/utils/github_spec.rb +++ b/Library/Homebrew/test/utils/github_spec.rb @@ -46,10 +46,10 @@ RSpec.describe GitHub do end end - describe "::get_artifact_url", :needs_network do + describe "::get_artifact_urls", :needs_network do it "fails to find a nonexistent workflow" do expect do - described_class.get_artifact_url( + described_class.get_artifact_urls( described_class.get_workflow_run("Homebrew", "homebrew-core", "1"), ) end.to raise_error(/No matching check suite found/) @@ -57,19 +57,27 @@ RSpec.describe GitHub do it "fails to find artifacts that don't exist" do expect do - described_class.get_artifact_url( + described_class.get_artifact_urls( described_class.get_workflow_run("Homebrew", "homebrew-core", "135608", - workflow_id: "triage.yml", artifact_name: "false_artifact"), + workflow_id: "triage.yml", artifact_pattern: "false_artifact"), ) - end.to raise_error(/No artifact .+ was found/) + end.to raise_error(/No artifacts with the pattern .+ were found/) end - it "gets an artifact link" do - url = described_class.get_artifact_url( + it "gets artifact URLs" do + urls = described_class.get_artifact_urls( described_class.get_workflow_run("Homebrew", "homebrew-core", "135608", - workflow_id: "triage.yml", artifact_name: "event_payload"), + workflow_id: "triage.yml", artifact_pattern: "event_payload"), ) - expect(url).to eq("https://api.github.com/repos/Homebrew/homebrew-core/actions/artifacts/781984175/zip") + expect(urls).to eq(["https://api.github.com/repos/Homebrew/homebrew-core/actions/artifacts/781984175/zip"]) + end + + it "supports pattern matching" do + urls = described_class.get_artifact_urls( + described_class.get_workflow_run("Homebrew", "brew", "17068", + workflow_id: "pkg-installer.yml", artifact_pattern: "Homebrew-*.pkg"), + ) + expect(urls).to eq(["https://api.github.com/repos/Homebrew/brew/actions/artifacts/1405050842/zip"]) end end diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index 17f6ec40b0..d2cde79847 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -283,7 +283,7 @@ module GitHub API.open_rest(url, data_binary_path: local_file, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) end - def self.get_workflow_run(user, repo, pull_request, workflow_id: "tests.yml", artifact_name: "bottles") + def self.get_workflow_run(user, repo, pull_request, workflow_id: "tests.yml", artifact_pattern: "bottles{,_*}") scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES # GraphQL unfortunately has no way to get the workflow yml name, so we need an extra REST call. @@ -333,11 +333,11 @@ module GitHub [] end - [check_suite, user, repo, pull_request, workflow_id, scopes, artifact_name] + [check_suite, user, repo, pull_request, workflow_id, scopes, artifact_pattern] end - def self.get_artifact_url(workflow_array) - check_suite, user, repo, pr, workflow_id, scopes, artifact_name = *workflow_array + def self.get_artifact_urls(workflow_array) + check_suite, user, repo, pr, workflow_id, scopes, artifact_pattern = *workflow_array if check_suite.empty? raise API::Error, <<~EOS No matching check suite found for these criteria! @@ -357,18 +357,20 @@ module GitHub run_id = check_suite.last["workflowRun"]["databaseId"] artifacts = API.open_rest("#{API_URL}/repos/#{user}/#{repo}/actions/runs/#{run_id}/artifacts", scopes:) - artifact = artifacts["artifacts"].select do |art| - art["name"] == artifact_name - end + matching_artifacts = + artifacts["artifacts"] + .group_by { |art| art["name"] } + .select { |name| File.fnmatch?(artifact_pattern, name, File::FNM_EXTGLOB) } + .map { |_, arts| arts.last } - if artifact.empty? + if matching_artifacts.empty? raise API::Error, <<~EOS - No artifact with the name `#{artifact_name}` was found! + No artifacts with the pattern `#{artifact_pattern}` were found! #{Formatter.url check_suite.last["workflowRun"]["url"]} EOS end - artifact.last["archive_download_url"] + matching_artifacts.map { |art| art["archive_download_url"] } end def self.public_member_usernames(org, per_page: 100) diff --git a/completions/bash/brew b/completions/bash/brew index b539b283fa..318ae832a4 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -1760,7 +1760,7 @@ _brew_pr_pull() { case "${cur}" in -*) __brewcomp " - --artifact + --artifact-pattern --autosquash --branch-okay --clean diff --git a/completions/fish/brew.fish b/completions/fish/brew.fish index 52c3fc97e2..9167474328 100644 --- a/completions/fish/brew.fish +++ b/completions/fish/brew.fish @@ -1191,7 +1191,7 @@ __fish_brew_complete_arg 'pr-publish' -l workflow -d 'Target workflow filename ( __fish_brew_complete_cmd 'pr-pull' 'Download and publish bottles, and apply the bottle commit from a pull request with artifacts generated by GitHub Actions' -__fish_brew_complete_arg 'pr-pull' -l artifact -d 'Download artifacts with the specified name (default: `bottles`)' +__fish_brew_complete_arg 'pr-pull' -l artifact-pattern -d 'Download artifacts with the specified pattern (default: `bottles{,_*}`)' __fish_brew_complete_arg 'pr-pull' -l autosquash -d 'Automatically reformat and reword commits in the pull request to our preferred format' __fish_brew_complete_arg 'pr-pull' -l branch-okay -d 'Do not warn if pulling to a branch besides the repository default (useful for testing)' __fish_brew_complete_arg 'pr-pull' -l clean -d 'Do not amend the commits from pull requests' diff --git a/completions/zsh/_brew b/completions/zsh/_brew index f1d3e95448..65e86cf5b5 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -1482,7 +1482,7 @@ _brew_pr_publish() { # brew pr-pull _brew_pr_pull() { _arguments \ - '--artifact[Download artifacts with the specified name (default: `bottles`)]' \ + '--artifact-pattern[Download artifacts with the specified pattern (default: `bottles{,_*}`)]' \ '(--clean)--autosquash[Automatically reformat and reword commits in the pull request to our preferred format]' \ '--branch-okay[Do not warn if pulling to a branch besides the repository default (useful for testing)]' \ '(--autosquash)--clean[Do not amend the commits from pull requests]' \ diff --git a/docs/Manpage.md b/docs/Manpage.md index 427057872a..529a11a10e 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -2423,9 +2423,9 @@ repository. : Message to include when autosquashing revision bumps, deletions and rebuilds. -`--artifact` +`--artifact-pattern` -: Download artifacts with the specified name (default: `bottles`). +: Download artifacts with the specified pattern (default: `bottles{,_*}`). `--tap` diff --git a/manpages/brew.1 b/manpages/brew.1 index b12240eae6..16e11d1742 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -1543,8 +1543,8 @@ Specify a committer name and email in \fBgit\fP\[u2019]s standard author format\ \fB\-\-message\fP Message to include when autosquashing revision bumps, deletions and rebuilds\. .TP -\fB\-\-artifact\fP -Download artifacts with the specified name (default: \fBbottles\fP)\. +\fB\-\-artifact\-pattern\fP +Download artifacts with the specified pattern (default: \fBbottles{,_*}\fP)\. .TP \fB\-\-tap\fP Target tap repository (default: \fBhomebrew/core\fP)\.