Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bump-cask-pr: add arch-specific version support #15725

Merged
merged 2 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions Library/Homebrew/cask/cask.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Cask
class Cask
def appcast; end

def appdir; end

def artifacts; end

def auto_updates; end
Expand All @@ -14,32 +16,32 @@ module Cask

def container; end

def depends_on; end

def desc; end

def depends_on; end
def discontinued?; end

def homepage; end

def language; end

def languages; end

def livecheck; end

def livecheckable?; end

def name; end

def on_system_blocks_exist?; end

def sha256; end

def staged_path; end

def url; end

def version; end

def appdir; end

def discontinued?; end

def livecheck; end

def livecheckable?; end
end
end
211 changes: 127 additions & 84 deletions Library/Homebrew/dev-cmd/bump-cask-pr.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "cask"
require "cask/download"
require "cli/parser"
require "extend/version-parser"
require "utils/tar"

module Homebrew
Expand Down Expand Up @@ -38,6 +39,10 @@ def bump_cask_pr_args
description: "Don't try to fork the repository."
flag "--version=",
description: "Specify the new <version> for the cask."
flag "--version-arm=",
description: "Specify the new cask <version> for the ARM architecture."
flag "--version-intel=",
description: "Specify the new cask <version> for the Intel architecture."
flag "--message=",
description: "Prepend <message> to the default pull request message."
flag "--url=",
Expand All @@ -51,11 +56,14 @@ def bump_cask_pr_args

conflicts "--dry-run", "--write"
conflicts "--no-audit", "--online"
conflicts "--version=", "--version-arm="
conflicts "--version=", "--version-intel="

named_args :cask, number: 1, without_api: true
end
end

sig { void }
def bump_cask_pr
args = bump_cask_pr_args.parse

Expand All @@ -68,19 +76,18 @@ def bump_cask_pr
ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s

# Use the user's browser, too.
ENV["BROWSER"] = Homebrew::EnvConfig.browser
ENV["BROWSER"] = EnvConfig.browser

cask = args.named.to_casks.first

odie "This cask is not in a tap!" if cask.tap.blank?
odie "This cask's tap is not a Git repository!" unless cask.tap.git?

new_version = unless (new_version = args.version).nil?
raise UsageError, "`--version` must not be empty." if new_version.blank?

new_version = :latest if ["latest", ":latest"].include?(new_version)
Cask::DSL::Version.new(new_version)
end
new_version = VersionParser.new(
general: args.version,
intel: args.version_intel,
arm: args.version_arm,
)

new_hash = unless (new_hash = args.sha256).nil?
raise UsageError, "`--sha256` must not be empty." if new_hash.blank?
Expand All @@ -98,85 +105,33 @@ def bump_cask_pr
end
end

if new_version.nil? && new_base_url.nil? && new_hash.nil?
if new_version.blank? && new_base_url.nil? && new_hash.nil?
raise UsageError, "No `--version`, `--url` or `--sha256` argument specified!"
end

old_version = cask.version
old_hash = cask.sha256

check_pull_requests(cask, state: "open", args: args)
# if we haven't already found open requests, try for an exact match across closed requests
check_pull_requests(cask, state: "closed", args: args, version: new_version) if new_version.present?

old_contents = File.read(cask.sourcefile_path)
check_pull_requests(cask, args: args, new_version: new_version)

replacement_pairs = []
replacement_pairs ||= []
branch_name = "bump-#{cask.token}"
commit_message = nil

if new_version
branch_name += "-#{new_version.tr(",:", "-")}"
commit_message_version = if new_version.before_comma == old_version.before_comma
new_version
else
new_version.before_comma
end
commit_message ||= "#{cask.token} #{commit_message_version}"

old_version_regex = old_version.latest? ? ":latest" : "[\"']#{Regexp.escape(old_version.to_s)}[\"']"

replacement_pairs << [
/version\s+#{old_version_regex}/m,
"version #{new_version.latest? ? ":latest" : "\"#{new_version}\""}",
]
if new_version.latest? || new_hash == :no_check
opoo "Ignoring specified `--sha256=` argument." if new_hash.is_a?(String)
replacement_pairs << [/"#{old_hash}"/, ":no_check"] if old_hash != :no_check
elsif old_hash == :no_check && new_hash != :no_check
replacement_pairs << [":no_check", "\"#{new_hash}\""] if new_hash.is_a?(String)
elsif old_hash != :no_check
if new_hash.nil? || cask.languages.present?
if new_hash && cask.languages.present?
opoo "Multiple checksum replacements required; ignoring specified `--sha256` argument."
end
tmp_contents = Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
replacement_pairs.uniq.compact,
read_only_run: true,
silent: true)

tmp_cask = Cask::CaskLoader.load(tmp_contents)
tmp_config = tmp_cask.config

OnSystem::ARCH_OPTIONS.each do |arch|
SimulateSystem.with arch: arch do
languages = cask.languages
languages = [nil] if languages.empty?
languages.each do |language|
new_hash_config = if language.blank?
tmp_config
else
tmp_config.merge(Cask::Config.new(explicit: { languages: [language] }))
end

new_hash_cask = Cask::CaskLoader.load(tmp_contents)
new_hash_cask.config = new_hash_config
old_hash = new_hash_cask.sha256.to_s

cask_download = Cask::Download.new(new_hash_cask, quarantine: true)
download = cask_download.fetch(verify_download_integrity: false)
Utils::Tar.validate_file(download)

replacement_pairs << [new_hash_cask.sha256.to_s, download.sha256]
end
end
end
elsif new_hash
opoo "Cask contains multiple hashes; only updating hash for current arch." if cask.on_system_blocks_exist?
replacement_pairs << [old_hash.to_s, new_hash]
if new_version.present?
# For simplicity, our naming defers to the arm version if we multiple architectures are specified
branch_version = new_version.arm || new_version.general
if branch_version.is_a?(Cask::DSL::Version)
commit_version = if branch_version.before_comma == cask.version.before_comma
branch_version
else
branch_version.before_comma
end
branch_name = "bump-#{cask.token}-#{branch_version.tr(",:", "-")}"
commit_message ||= "#{cask.token} #{commit_version}"
end
replacement_pairs = replace_version_and_checksum(cask, new_hash, new_version, replacement_pairs)
end
# Now that we have all replacement pairs, we will replace them further down

old_contents = File.read(cask.sourcefile_path)

if new_base_url
commit_message ||= "#{cask.token}: update URL"
Expand All @@ -194,34 +149,121 @@ def bump_cask_pr

commit_message ||= "#{cask.token}: update checksum" if new_hash

# Remove nested arrays where elements are identical
replacement_pairs = replacement_pairs.reject { |pair| pair[0] == pair[1] }.uniq.compact
Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
replacement_pairs.uniq.compact,
replacement_pairs,
read_only_run: args.dry_run?,
silent: args.quiet?)

run_cask_audit(cask, old_contents, args: args)
run_cask_style(cask, old_contents, args: args)

pr_info = {
sourcefile_path: cask.sourcefile_path,
old_contents: old_contents,
branch_name: branch_name,
commit_message: commit_message,
tap: cask.tap,
old_contents: old_contents,
pr_message: "Created with `brew bump-cask-pr`.",
sourcefile_path: cask.sourcefile_path,
tap: cask.tap,
}
GitHub.create_bump_pr(pr_info, args: args)
end

def check_pull_requests(cask, state:, args:, version: nil)
sig {
params(
cask: Cask::Cask,
new_hash: T.nilable(String),
new_version: VersionParser,
replacement_pairs: T::Array[[T.any(Regexp, String), T.any(Regexp, String)]],
).returns(T::Array[[T.any(Regexp, String), T.any(Regexp, String)]])
}
def replace_version_and_checksum(cask, new_hash, new_version, replacement_pairs)
# When blocks are absent, arch is not relevant. For consistency, we simulate the arm architecture.
arch_options = cask.on_system_blocks_exist? ? OnSystem::ARCH_OPTIONS : [:arm]
arch_options.each do |arch|
SimulateSystem.with arch: arch do
old_cask = Cask::CaskLoader.load(cask.sourcefile_path)
old_version = old_cask.version
bump_version = new_version.send(arch) || new_version.general

old_version_regex = old_version.latest? ? ":latest" : %Q(["']#{Regexp.escape(old_version.to_s)}["'])
replacement_pairs << [/version\s+#{old_version_regex}/m,
"version #{bump_version.latest? ? ":latest" : %Q("#{bump_version}")}"]

# We are replacing our version here so we can get the new hash
tmp_contents = Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
replacement_pairs.uniq.compact,
read_only_run: true,
silent: true)

tmp_cask = Cask::CaskLoader.load(tmp_contents)
old_hash = tmp_cask.sha256
if tmp_cask.version.latest? || new_hash == :no_check
opoo "Ignoring specified `--sha256=` argument." if new_hash.is_a?(String)
replacement_pairs << [/"#{old_hash}"/, ":no_check"] if old_hash != :no_check
elsif old_hash == :no_check && new_hash != :no_check
replacement_pairs << [":no_check", "\"#{new_hash}\""] if new_hash.is_a?(String)
elsif old_hash != :no_check
if new_hash && cask.languages.present?
opoo "Multiple checksum replacements required; ignoring specified `--sha256` argument."
end
languages = if cask.languages.empty?
[nil]
else
cask.languages
end
languages.each do |language|
new_cask = Cask::CaskLoader.load(tmp_contents)
new_cask.config = if language.blank?
tmp_cask.config
else
tmp_cask.config.merge(Cask::Config.new(explicit: { languages: [language] }))
end
download = Cask::Download.new(new_cask, quarantine: true).fetch(verify_download_integrity: false)
Utils::Tar.validate_file(download)

if new_cask.sha256.to_s != download.sha256
replacement_pairs << [new_cask.sha256.to_s,
download.sha256]
end
end
elsif new_hash
opoo "Cask contains multiple hashes; only updating hash for current arch." if cask.on_system_blocks_exist?
replacement_pairs << [old_hash.to_s, new_hash]
end
end
end
replacement_pairs
end

sig { params(cask: Cask::Cask, args: Homebrew::CLI::Args, new_version: VersionParser).void }
def check_pull_requests(cask, args:, new_version:)
tap_remote_repo = cask.tap.full_name || cask.tap.remote_repo

GitHub.check_for_duplicate_pull_requests(cask.token, tap_remote_repo,
state: state,
version: version,
state: "open",
version: nil,
file: cask.sourcefile_path.relative_path_from(cask.tap.path).to_s,
args: args)

# if we haven't already found open requests, try for an exact match across closed requests
new_version.instance_variables.each do |version_type|
version = new_version.instance_variable_get(version_type)
next if version.blank?

GitHub.check_for_duplicate_pull_requests(
cask.token,
tap_remote_repo,
state: "closed",
version: version,
file: cask.sourcefile_path.relative_path_from(cask.tap.path).to_s,
args: args,
)
end
end

sig { params(cask: Cask::Cask, old_contents: String, args: T.untyped).void }
def run_cask_audit(cask, old_contents, args:)
if args.dry_run?
if args.no_audit?
Expand Down Expand Up @@ -249,6 +291,7 @@ def run_cask_audit(cask, old_contents, args:)
odie "`brew audit` failed!"
end

sig { params(cask: Cask::Cask, old_contents: String, args: T.untyped).void }
def run_cask_style(cask, old_contents, args:)
if args.dry_run?
if args.no_style?
Expand Down
Loading