diff --git a/Library/Homebrew/bintray.rb b/Library/Homebrew/bintray.rb index 26b9583184591a534dade6cd7ffb2d7e0e08fc57..3e200c5f80c7d444c423726f980da6ee45a718e3 100644 --- a/Library/Homebrew/bintray.rb +++ b/Library/Homebrew/bintray.rb @@ -148,11 +148,7 @@ class Bintray end end - def upload_bottle_json(json_files, publish_package: false, warn_on_error: false) - bottles_hash = json_files.reduce({}) do |hash, json_file| - hash.deep_merge(JSON.parse(IO.read(json_file))) - end - + def upload_bottles(bottles_hash, publish_package: false, warn_on_error: false) formula_packaged = {} bottles_hash.each do |formula_name, bottle_hash| diff --git a/Library/Homebrew/dev-cmd/pr-upload.rb b/Library/Homebrew/dev-cmd/pr-upload.rb index aa83627f63736011c0e89205d8bd68e5f708467a..08e59b9c2e8bc77f8b25859134199f5df0612faf 100644 --- a/Library/Homebrew/dev-cmd/pr-upload.rb +++ b/Library/Homebrew/dev-cmd/pr-upload.rb @@ -11,7 +11,7 @@ module Homebrew usage_banner <<~EOS `pr-upload` [<options>] - Apply the bottle commit and publish bottles to Bintray. + Apply the bottle commit and publish bottles to Bintray or GitHub Releases. EOS switch "--no-publish", description: "Apply the bottle commit and upload the bottles, but don't publish them." @@ -30,30 +30,37 @@ module Homebrew end end - def check_bottled_formulae(json_files) - hashes = json_files.reduce({}) do |hash, json| - hash.deep_merge(JSON.parse(IO.read(json))) - end - - hashes.each do |name, hash| - formula_path = HOMEBREW_REPOSITORY/hash["formula"]["path"] + def check_bottled_formulae(bottles_hash) + bottles_hash.each do |name, bottle_hash| + formula_path = HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"] formula_version = Formulary.factory(formula_path).pkg_version - bottle_version = PkgVersion.parse hash["formula"]["pkg_version"] + bottle_version = PkgVersion.parse bottle_hash["formula"]["pkg_version"] next if formula_version == bottle_version odie "Bottles are for #{name} #{bottle_version} but formula is version #{formula_version}!" end end + def github_releases?(bottles_hash) + @github_releases ||= bottles_hash.values.all? do |bottle_hash| + root_url = bottle_hash["bottle"]["root_url"] + url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX + _, _, _, tag = *url_match + + tag + end + end + def pr_upload args = pr_upload_args.parse - bintray_org = args.bintray_org || "homebrew" - bintray = Bintray.new(org: bintray_org) - json_files = Dir["*.json"] odie "No JSON files found in the current working directory" if json_files.empty? + bottles_hash = json_files.reduce({}) do |hash, json_file| + hash.deep_merge(JSON.parse(IO.read(json_file))) + end + bottle_args = ["bottle", "--merge", "--write"] bottle_args << "--verbose" if args.verbose? bottle_args << "--debug" if args.debug? @@ -62,14 +69,51 @@ module Homebrew bottle_args += json_files if args.dry_run? + service = if github_releases?(bottles_hash) + "GitHub Releases" + else + "Bintray" + end puts "brew #{bottle_args.join " "}" - puts "Upload bottles described by these JSON files to Bintray:\n #{json_files.join("\n ")}" + puts "Upload bottles described by these JSON files to #{service}:\n #{json_files.join("\n ")}" + return + end + + check_bottled_formulae(bottles_hash) + + safe_system HOMEBREW_BREW_FILE, *bottle_args + + if github_releases?(bottles_hash) + # Handle uploading to GitHub Releases. + bottles_hash.each_value do |bottle_hash| + root_url = bottle_hash["bottle"]["root_url"] + url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX + _, user, repo, tag = *url_match + + # Ensure a release is created. + release = begin + GitHub.get_release user, repo, tag + odebug "Existing GitHub release \"#{tag}\" found" + rescue GitHub::HTTPNotFoundError + odebug "Creating new GitHub release \"#{tag}\"" + GitHub.create_or_update_release user, repo, tag + end + + # Upload bottles as release assets. + bottle_hash["bottle"]["tags"].each_value do |tag_hash| + remote_file = tag_hash["filename"] + local_file = tag_hash["local_filename"] + odebug "Uploading #{remote_file}" + GitHub.upload_release_asset user, repo, release["id"], local_file: local_file, remote_file: remote_file + end + end else - check_bottled_formulae(json_files) - safe_system HOMEBREW_BREW_FILE, *bottle_args - bintray.upload_bottle_json(json_files, - publish_package: !args.no_publish?, - warn_on_error: args.warn_on_upload_failure?) + # Handle uploading to Bintray. + bintray_org = args.bintray_org || "homebrew" + bintray = Bintray.new(org: bintray_org) + bintray.upload_bottles(bottles_hash, + publish_package: !args.no_publish?, + warn_on_error: args.warn_on_upload_failure?) end end end diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index 3159ea53883c7e312381f22820af4df8aba4f736..227e581f8ea4957e4f4a378057a1eaba1ab19558 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -112,6 +112,8 @@ HOMEBREW_PULL_API_REGEX = %r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze HOMEBREW_PULL_OR_COMMIT_URL_REGEX = %r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze +HOMEBREW_RELEASES_URL_REGEX = + %r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}.freeze require "PATH" diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index 0d6f66eb51c24cfc0d978f261b2fb6b5fa7c8d4b..f6368a97a298c79f1688460db2efb80308beb402 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -175,7 +175,7 @@ module GitHub end end - def open_api(url, data: nil, request_method: nil, scopes: [].freeze, parse_json: true) + def open_api(url, data: nil, data_binary_path: nil, request_method: nil, scopes: [].freeze, parse_json: true) # This is a no-op if the user is opting out of using the GitHub API. return block_given? ? yield({}) : {} if Homebrew::EnvConfig.no_github_api? @@ -200,6 +200,11 @@ module GitHub end end + if data_binary_path.present? + args += ["--data-binary", "@#{data_binary_path}"] + args += ["--header", "Content-Type: application/gzip"] + end + headers_tmpfile = Tempfile.new("github_api_headers", HOMEBREW_TEMP) begin if data @@ -467,6 +472,33 @@ module GitHub scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) end + def get_release(user, repo, tag) + url = "#{API_URL}/repos/#{user}/#{repo}/releases/tags/#{tag}" + open_api(url, request_method: :GET) + end + + def create_or_update_release(user, repo, tag, id: nil, name: nil, draft: false) + url = "#{API_URL}/repos/#{user}/#{repo}/releases" + method = if id + url += "/#{id}" + :PATCH + else + :POST + end + data = { + tag_name: tag, + name: name || tag, + draft: draft, + } + open_api(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) + end + + def upload_release_asset(user, repo, id, local_file: nil, remote_file: nil) + url = "https://uploads.github.com/repos/#{user}/#{repo}/releases/#{id}/assets" + url += "?name=#{remote_file}" if remote_file + open_api(url, data_binary_path: local_file, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) + end + def get_artifact_url(user, repo, pr, workflow_id: "tests.yml", artifact_name: "bottles") scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES base_url = "#{API_URL}/repos/#{user}/#{repo}" diff --git a/docs/Manpage.md b/docs/Manpage.md index ef0263e06c491978bfc61cff48f0dbd3ff8c11b7..51f632983b893b6e225a8c5402219d8ee4d4cef7 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -1113,7 +1113,7 @@ Requires write access to the repository. ### `pr-upload` [*`options`*] -Apply the bottle commit and publish bottles to Bintray. +Apply the bottle commit and publish bottles to Bintray or GitHub Releases. * `--no-publish`: Apply the bottle commit and upload the bottles, but don't publish them. diff --git a/manpages/brew.1 b/manpages/brew.1 index acdb80a889c1ff61d591d0c37db8b8eeca879f62..d3d48f589409c58ad4d2edf9f62a32c265f05306 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -1547,7 +1547,7 @@ Use the specified \fIURL\fR as the root of the bottle\'s URL instead of Homebrew Use the specified Bintray repository to automatically mirror stable URLs defined in the formulae (default: \fBmirror\fR)\. . .SS "\fBpr\-upload\fR [\fIoptions\fR]" -Apply the bottle commit and publish bottles to Bintray\. +Apply the bottle commit and publish bottles to Bintray or GitHub Releases\. . .TP \fB\-\-no\-publish\fR