167 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # typed: true
 | |
| # frozen_string_literal: true
 | |
| 
 | |
| require "abstract_command"
 | |
| require "formula"
 | |
| require "github_packages"
 | |
| require "github_releases"
 | |
| require "extend/hash/deep_merge"
 | |
| 
 | |
| module Homebrew
 | |
|   module DevCmd
 | |
|     class PrUpload < AbstractCommand
 | |
|       cmd_args do
 | |
|         description <<~EOS
 | |
|           Apply the bottle commit and publish bottles to a host.
 | |
|         EOS
 | |
|         switch "--keep-old",
 | |
|                description: "If the formula specifies a rebuild version, " \
 | |
|                             "attempt to preserve its value in the generated DSL."
 | |
|         switch "-n", "--dry-run",
 | |
|                description: "Print what would be done rather than doing it."
 | |
|         switch "--no-commit",
 | |
|                description: "Do not generate a new commit before uploading."
 | |
|         switch "--warn-on-upload-failure",
 | |
|                description: "Warn instead of raising an error if the bottle upload fails. " \
 | |
|                             "Useful for repairing bottle uploads that previously failed."
 | |
|         switch "--upload-only",
 | |
|                description: "Skip running `brew bottle` before uploading."
 | |
|         flag   "--committer=",
 | |
|                description: "Specify a committer name and email in `git`'s standard author format."
 | |
|         flag   "--root-url=",
 | |
|                description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
 | |
|         flag   "--root-url-using=",
 | |
|                description: "Use the specified download strategy class for downloading the bottle's URL instead of " \
 | |
|                             "Homebrew's default."
 | |
| 
 | |
|         conflicts "--upload-only", "--keep-old"
 | |
|         conflicts "--upload-only", "--no-commit"
 | |
| 
 | |
|         named_args :none
 | |
|       end
 | |
| 
 | |
|       sig { override.void }
 | |
|       def run
 | |
|         json_files = Dir["*.bottle.json"]
 | |
|         odie "No bottle JSON files found in the current working directory" if json_files.blank?
 | |
|         bottles_hash = bottles_hash_from_json_files(json_files, args)
 | |
| 
 | |
|         Homebrew.install_bundler_gems!(groups: ["pr_upload"])
 | |
| 
 | |
|         unless args.upload_only?
 | |
|           bottle_args = ["bottle", "--merge", "--write"]
 | |
|           bottle_args << "--verbose" if args.verbose?
 | |
|           bottle_args << "--debug" if args.debug?
 | |
|           bottle_args << "--keep-old" if args.keep_old?
 | |
|           bottle_args << "--root-url=#{args.root_url}" if args.root_url
 | |
|           bottle_args << "--committer=#{args.committer}" if args.committer
 | |
|           bottle_args << "--no-commit" if args.no_commit?
 | |
|           bottle_args << "--root-url-using=#{args.root_url_using}" if args.root_url_using
 | |
|           bottle_args += json_files
 | |
| 
 | |
|           if args.dry_run?
 | |
|             dry_run_service = if github_packages?(bottles_hash)
 | |
|               # GitHub Packages has its own --dry-run handling.
 | |
|               nil
 | |
|             elsif github_releases?(bottles_hash)
 | |
|               "GitHub Releases"
 | |
|             else
 | |
|               odie "Service specified by root_url is not recognized"
 | |
|             end
 | |
| 
 | |
|             if dry_run_service
 | |
|               puts <<~EOS
 | |
|                 brew #{bottle_args.join " "}
 | |
|                 Upload bottles described by these JSON files to #{dry_run_service}:
 | |
|                   #{json_files.join("\n  ")}
 | |
|               EOS
 | |
|               return
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           check_bottled_formulae!(bottles_hash)
 | |
| 
 | |
|           safe_system HOMEBREW_BREW_FILE, *bottle_args
 | |
| 
 | |
|           json_files = Dir["*.bottle.json"]
 | |
|           if json_files.blank?
 | |
|             puts "No bottle JSON files after merge, no upload needed!"
 | |
|             return
 | |
|           end
 | |
| 
 | |
|           # Reload the JSON files (in case `brew bottle --merge` generated
 | |
|           # `all: $SHA256` bottles)
 | |
|           bottles_hash = bottles_hash_from_json_files(json_files, args)
 | |
| 
 | |
|           # Check the bottle commits did not break `brew audit`
 | |
|           unless args.no_commit?
 | |
|             audit_args = ["audit", "--skip-style"]
 | |
|             audit_args << "--verbose" if args.verbose?
 | |
|             audit_args << "--debug" if args.debug?
 | |
|             audit_args += bottles_hash.keys
 | |
|             safe_system HOMEBREW_BREW_FILE, *audit_args
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         if github_releases?(bottles_hash)
 | |
|           github_releases = GitHubReleases.new
 | |
|           github_releases.upload_bottles(bottles_hash)
 | |
|         elsif github_packages?(bottles_hash)
 | |
|           github_packages = GitHubPackages.new
 | |
|           github_packages.upload_bottles(bottles_hash,
 | |
|                                          keep_old:      args.keep_old?,
 | |
|                                          dry_run:       args.dry_run?,
 | |
|                                          warn_on_error: args.warn_on_upload_failure?)
 | |
|         else
 | |
|           odie "Service specified by root_url is not recognized"
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       private
 | |
| 
 | |
|       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 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 GitHubReleases::URL_REGEX
 | |
|           _, _, _, tag = *url_match
 | |
| 
 | |
|           tag
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def github_packages?(bottles_hash)
 | |
|         @github_packages ||= bottles_hash.values.all? do |bottle_hash|
 | |
|           bottle_hash["bottle"]["root_url"].match? GitHubPackages::URL_REGEX
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def bottles_hash_from_json_files(json_files, args)
 | |
|         puts "Reading JSON files: #{json_files.join(", ")}" if args.verbose?
 | |
| 
 | |
|         bottles_hash = json_files.reduce({}) do |hash, json_file|
 | |
|           hash.deep_merge(JSON.parse(File.read(json_file)))
 | |
|         end
 | |
| 
 | |
|         if args.root_url
 | |
|           bottles_hash.each_value do |bottle_hash|
 | |
|             bottle_hash["bottle"]["root_url"] = args.root_url
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         bottles_hash
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | 
