diff --git a/Library/Homebrew/dev-cmd/bump-compatibility-version.rb b/Library/Homebrew/dev-cmd/bump-compatibility-version.rb new file mode 100644 index 0000000000000..9786514df95b4 --- /dev/null +++ b/Library/Homebrew/dev-cmd/bump-compatibility-version.rb @@ -0,0 +1,74 @@ +# typed: strict +# frozen_string_literal: true + +require "abstract_command" +require "formula" + +module Homebrew + module DevCmd + class BumpCompatibilityVersion < AbstractCommand + cmd_args do + description <<~EOS + Create a commit to increment the compatibility_version of . If no + compatibility_version is present, "compatibility_version 1" will be added. + EOS + switch "-n", "--dry-run", + description: "Print what would be done rather than doing it." + switch "--write-only", + description: "Make the expected file modifications without taking any Git actions." + flag "--message=", + description: "Append to the default commit message." + + conflicts "--dry-run", "--write-only" + + named_args :formula, min: 1, without_api: true + end + + sig { override.void } + def run + # As this command is simplifying user-run commands then let's just use a + # user path, too. + ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s + + Homebrew.install_bundler_gems!(groups: ["ast"]) unless args.dry_run? + + args.named.to_formulae.each do |formula| + current_compatibility_version = formula.compatibility_version || 0 + new_compatibility_version = current_compatibility_version + 1 + + if args.dry_run? + unless args.quiet? + old_text = "compatibility_version #{current_compatibility_version}" + new_text = "compatibility_version #{new_compatibility_version}" + if formula.compatibility_version.nil? + ohai "add #{new_text.inspect}" + else + ohai "replace #{old_text.inspect} with #{new_text.inspect}" + end + end + else + require "utils/ast" + + formula_ast = Utils::AST::FormulaAST.new(formula.path.read) + if formula.compatibility_version.nil? + formula_ast.add_stanza(:compatibility_version, new_compatibility_version) + else + formula_ast.replace_stanza(:compatibility_version, new_compatibility_version) + end + formula.path.atomic_write(formula_ast.process) + end + + message = "#{formula.name}: compatibility_version bump #{args.message}" + if args.dry_run? + ohai "git commit --no-edit --verbose --message=#{message} -- #{formula.path}" + elsif !args.write_only? + formula.path.parent.cd do + safe_system "git", "commit", "--no-edit", "--verbose", + "--message=#{message}", "--", formula.path + end + end + end + end + end + end +end diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_compatibility_version.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_compatibility_version.rbi new file mode 100644 index 0000000000000..c5315cf6736c0 --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_compatibility_version.rbi @@ -0,0 +1,25 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Homebrew::DevCmd::BumpCompatibilityVersion`. +# Please instead update this file by running `bin/tapioca dsl Homebrew::DevCmd::BumpCompatibilityVersion`. + + +class Homebrew::DevCmd::BumpCompatibilityVersion + sig { returns(Homebrew::DevCmd::BumpCompatibilityVersion::Args) } + def args; end +end + +class Homebrew::DevCmd::BumpCompatibilityVersion::Args < Homebrew::CLI::Args + sig { returns(T::Boolean) } + def dry_run?; end + + sig { returns(T.nilable(String)) } + def message; end + + sig { returns(T::Boolean) } + def n?; end + + sig { returns(T::Boolean) } + def write_only?; end +end diff --git a/Library/Homebrew/test/dev-cmd/bump-compatibility-version_spec.rb b/Library/Homebrew/test/dev-cmd/bump-compatibility-version_spec.rb new file mode 100644 index 0000000000000..3c8a328985a2c --- /dev/null +++ b/Library/Homebrew/test/dev-cmd/bump-compatibility-version_spec.rb @@ -0,0 +1,55 @@ +# typed: true +# frozen_string_literal: true + +require "cmd/shared_examples/args_parse" +require "dev-cmd/bump-compatibility-version" + +RSpec.describe Homebrew::DevCmd::BumpCompatibilityVersion do + it_behaves_like "parseable arguments" + + describe "#run" do + before do + allow(Homebrew).to receive(:install_bundler_gems!) + end + + it "adds compatibility_version 1 with --write-only" do + formula_path = mktmpdir/"foo.rb" + formula_path.write <<~RUBY + class Foo < Formula + url "https://brew.sh/foo-1.0" + end + RUBY + formula = formula("foo", path: formula_path) do + T.bind(self, T.class_of(Formula)) + url "https://brew.sh/foo-1.0" + end + command = described_class.new(["--write-only", "foo"]) + allow(command.args.named).to receive(:to_formulae).and_return([formula]) + + command.run + + expect(formula_path.read).to include " compatibility_version 1\n" + end + + it "increments compatibility_version with --write-only" do + formula_path = mktmpdir/"foo.rb" + formula_path.write <<~RUBY + class Foo < Formula + url "https://brew.sh/foo-1.0" + compatibility_version 2 + end + RUBY + formula = formula("foo", path: formula_path) do + T.bind(self, T.class_of(Formula)) + url "https://brew.sh/foo-1.0" + compatibility_version 2 + end + command = described_class.new(["--write-only", "foo"]) + allow(command.args.named).to receive(:to_formulae).and_return([formula]) + + command.run + + expect(formula_path.read).to include " compatibility_version 3\n" + end + end +end diff --git a/completions/bash/brew b/completions/bash/brew index 318d8f44a7a64..ea89a62db32c3 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -588,6 +588,26 @@ _brew_bump_cask_pr() { __brew_complete_casks } +_brew_bump_compatibility_version() { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${cur}" in + -*) + __brewcomp " + --debug + --dry-run + --help + --message + --quiet + --verbose + --write-only + " + return + ;; + *) ;; + esac + __brew_complete_formulae +} + _brew_bump_formula_pr() { local cur="${COMP_WORDS[COMP_CWORD]}" case "${cur}" in @@ -3678,6 +3698,7 @@ _brew() { bottle) _brew_bottle ;; bump) _brew_bump ;; bump-cask-pr) _brew_bump_cask_pr ;; + bump-compatibility-version) _brew_bump_compatibility_version ;; bump-formula-pr) _brew_bump_formula_pr ;; bump-revision) _brew_bump_revision ;; bump-unversioned-casks) _brew_bump_unversioned_casks ;; diff --git a/completions/fish/brew.fish b/completions/fish/brew.fish index af88ebd5cc0bd..7d77f33f854a5 100644 --- a/completions/fish/brew.fish +++ b/completions/fish/brew.fish @@ -474,6 +474,17 @@ __fish_brew_complete_arg 'bump-cask-pr' -l write-only -d 'Make the expected file __fish_brew_complete_arg 'bump-cask-pr' -a '(__fish_brew_suggest_casks_all)' +__fish_brew_complete_cmd 'bump-compatibility-version' 'Create a commit to increment the compatibility_version of formula' +__fish_brew_complete_arg 'bump-compatibility-version' -l debug -d 'Display any debugging information' +__fish_brew_complete_arg 'bump-compatibility-version' -l dry-run -d 'Print what would be done rather than doing it' +__fish_brew_complete_arg 'bump-compatibility-version' -l help -d 'Show this message' +__fish_brew_complete_arg 'bump-compatibility-version' -l message -d 'Append message to the default commit message' +__fish_brew_complete_arg 'bump-compatibility-version' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'bump-compatibility-version' -l verbose -d 'Make some output more verbose' +__fish_brew_complete_arg 'bump-compatibility-version' -l write-only -d 'Make the expected file modifications without taking any Git actions' +__fish_brew_complete_arg 'bump-compatibility-version' -a '(__fish_brew_suggest_formulae_all)' + + __fish_brew_complete_cmd 'bump-formula-pr' 'Create a pull request to update formula with a new URL or a new tag' __fish_brew_complete_arg 'bump-formula-pr' -l commit -d 'When passed with `--write-only`, generate a new commit after writing changes to the formula file' __fish_brew_complete_arg 'bump-formula-pr' -l debug -d 'Display any debugging information' diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt index ae91a03242f92..f53b78093ddf3 100644 --- a/completions/internal_commands_list.txt +++ b/completions/internal_commands_list.txt @@ -13,6 +13,7 @@ autoremove bottle bump bump-cask-pr +bump-compatibility-version bump-formula-pr bump-revision bump-unversioned-casks diff --git a/completions/zsh/_brew b/completions/zsh/_brew index be0747040cd96..40247bfada08b 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -171,6 +171,7 @@ __brew_internal_commands() { 'bottle:Generate a bottle (binary package) from a formula that was installed with `--build-bottle`' 'bump:Displays out-of-date packages and the latest version available' 'bump-cask-pr:Create a pull request to update cask with a new version' + 'bump-compatibility-version:Create a commit to increment the compatibility_version of formula' 'bump-formula-pr:Create a pull request to update formula with a new URL or a new tag' 'bump-revision:Create a commit to increment the revision of formula' 'bump-unversioned-casks:Check all casks with unversioned URLs in a given tap for updates' @@ -624,6 +625,20 @@ _brew_bump_cask_pr() { '*:cask:__brew_casks' } +# brew bump-compatibility-version +_brew_bump_compatibility_version() { + _arguments \ + '--debug[Display any debugging information]' \ + '(--write-only)--dry-run[Print what would be done rather than doing it]' \ + '--help[Show this message]' \ + '--message[Append message to the default commit message]' \ + '--quiet[Make some output more quiet]' \ + '--verbose[Make some output more verbose]' \ + '(--dry-run)--write-only[Make the expected file modifications without taking any Git actions]' \ + - formula \ + '*:formula:__brew_formulae' +} + # brew bump-formula-pr _brew_bump_formula_pr() { _arguments \ diff --git a/docs/Formula-Cookbook.md b/docs/Formula-Cookbook.md index a692a38abe551..49fb77762315c 100644 --- a/docs/Formula-Cookbook.md +++ b/docs/Formula-Cookbook.md @@ -202,6 +202,8 @@ Bump `compatibility_version` by `1` when a formula update requires any recursive Do not change `compatibility_version` for updates that do not require dependent `revision` bumps. It should never decrease and should only increment by `1` at a time. +Use [`brew bump-compatibility-version`](Manpage.md#bump-compatibility-version-options-formula-) to increment `compatibility_version` mechanically. + [`brew audit`](Manpage.md) checks this relationship in both directions. If a formula's `compatibility_version` increases, at least one recursive dependent in the same pull request must also increase `revision` by `1`. If a formula's `revision` increases because a changed recursive dependency also changed versions, that dependency must increase `compatibility_version` by `1`. These checks are based on dependent `revision` bumps in the pull request, not on general ABI analysis. Homebrew cannot automatically detect every compatibility break that is not covered by linkage or formula tests, so maintainers may still need to bump `compatibility_version` and dependent `revision`s manually when they know a rebuild is required. diff --git a/docs/Homebrew-homebrew-core-Maintainer-Guide.md b/docs/Homebrew-homebrew-core-Maintainer-Guide.md index 6b03aa480547c..1176641f6b12d 100644 --- a/docs/Homebrew-homebrew-core-Maintainer-Guide.md +++ b/docs/Homebrew-homebrew-core-Maintainer-Guide.md @@ -169,6 +169,7 @@ The following checklist is intended to help maintainers decide on whether to mer - due to other formulae needing revision bumps - suggest using the following command: # in this example: PR is for `libuv` formula and `urbit` needs revision bump + brew bump-compatibility-version --write-only libuv brew bump-revision --message 'for libuv' urbit - make sure it has one commit per revision bump diff --git a/docs/Manpage.md b/docs/Manpage.md index ed0a7a917df8e..d20fc9ec99354 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -2922,6 +2922,23 @@ supplied by the user. : Use the specified GitHub organization for forking. +### `bump-compatibility-version` \[*`options`*\] *`formula`* \[...\] + +Create a commit to increment the compatibility\_version of *`formula`*. If no +compatibility\_version is present, "compatibility\_version 1" will be added. + +`-n`, `--dry-run` + +: Print what would be done rather than doing it. + +`--write-only` + +: Make the expected file modifications without taking any Git actions. + +`--message` + +: Append *`message`* to the default commit message. + ### `bump-formula-pr` \[*`options`*\] \[*`formula`*\] Create a pull request to update *`formula`* with a new URL or a new tag. diff --git a/manpages/brew.1 b/manpages/brew.1 index 8908ca31bc241..68895e940700d 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -1850,6 +1850,17 @@ Specify the \fISHA\-256\fP checksum of the new download\. .TP \fB\-\-fork\-org\fP Use the specified GitHub organization for forking\. +.SS "\fBbump\-compatibility\-version\fP \fR[\fIoptions\fP] \fIformula\fP \fR[\.\.\.]" +Create a commit to increment the compatibility_version of \fIformula\fP\&\. If no compatibility_version is present, \[u201c]compatibility_version 1\[u201d] will be added\. +.TP +\fB\-n\fP, \fB\-\-dry\-run\fP +Print what would be done rather than doing it\. +.TP +\fB\-\-write\-only\fP +Make the expected file modifications without taking any Git actions\. +.TP +\fB\-\-message\fP +Append \fImessage\fP to the default commit message\. .SS "\fBbump\-formula\-pr\fP \fR[\fIoptions\fP] \fR[\fIformula\fP]" Create a pull request to update \fIformula\fP with a new URL or a new tag\. .P