From d53dab62c9de8fe7bc34c69fe8a44fb0b8fd32b6 Mon Sep 17 00:00:00 2001 From: Steve Peters Date: Wed, 22 Dec 2021 11:36:06 -0800 Subject: [PATCH] bump-revision: add --remove-bottle-block option This removes a bottle block for the specified formulae while bumping the revision. --- Library/Homebrew/cli/args.rbi | 3 + Library/Homebrew/dev-cmd/bump-revision.rb | 3 + .../test/utils/ast/formula_ast_spec.rb | 201 ++++++++++++++++++ Library/Homebrew/utils/ast.rb | 33 +++ 4 files changed, 240 insertions(+) diff --git a/Library/Homebrew/cli/args.rbi b/Library/Homebrew/cli/args.rbi index 552720dbaf..2e8fc8695b 100644 --- a/Library/Homebrew/cli/args.rbi +++ b/Library/Homebrew/cli/args.rbi @@ -3,6 +3,9 @@ module Homebrew module CLI class Args < OpenStruct + sig { returns(T::Boolean) } + def remove_bottle_block?; end + sig { returns(T::Boolean) } def strict?; end diff --git a/Library/Homebrew/dev-cmd/bump-revision.rb b/Library/Homebrew/dev-cmd/bump-revision.rb index 01eb0ba9c6..29113aa938 100644 --- a/Library/Homebrew/dev-cmd/bump-revision.rb +++ b/Library/Homebrew/dev-cmd/bump-revision.rb @@ -18,6 +18,8 @@ module Homebrew EOS switch "-n", "--dry-run", description: "Print what would be done rather than doing it." + switch "--remove-bottle-block", + description: "Remove the bottle block in addition to bumping the revision." switch "--write-only", description: "Make the expected file modifications without taking any Git actions." flag "--message=", @@ -60,6 +62,7 @@ module Homebrew else formula_ast.replace_stanza(:revision, new_revision) end + formula_ast.remove_stanza(:bottle) if args.remove_bottle_block? formula.path.atomic_write(formula_ast.process) end diff --git a/Library/Homebrew/test/utils/ast/formula_ast_spec.rb b/Library/Homebrew/test/utils/ast/formula_ast_spec.rb index abff5d07c4..32fb13bcfe 100644 --- a/Library/Homebrew/test/utils/ast/formula_ast_spec.rb +++ b/Library/Homebrew/test/utils/ast/formula_ast_spec.rb @@ -46,6 +46,207 @@ describe Utils::AST::FormulaAST do end end + describe "#remove_stanza" do + context "when stanza to be removed is a single line followed by a blank line" do + subject(:formula_ast) do + described_class.new <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + license :cannot_represent + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + let(:new_contents) do + <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + it "removes the line containing the stanza" do + formula_ast.remove_stanza(:license) + expect(formula_ast.process).to eq(new_contents) + end + end + + context "when stanza to be removed is a multiline block followed by a blank line" do + subject(:formula_ast) do + described_class.new <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + license all_of: [ + :public_domain, + "MIT", + "GPL-3.0-or-later" => { with: "Autoconf-exception-3.0" }, + ] + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + let(:new_contents) do + <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + it "removes the lines containing the stanza" do + formula_ast.remove_stanza(:license) + expect(formula_ast.process).to eq(new_contents) + end + end + + context "when stanza to be removed has a comment on the same line" do + subject(:formula_ast) do + described_class.new <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + license :cannot_represent # comment + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + let(:new_contents) do + <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + # comment + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + it "removes the stanza but keeps the comment and its whitespace" do + formula_ast.remove_stanza(:license) + expect(formula_ast.process).to eq(new_contents) + end + end + + context "when stanza to be removed has a comment on the next line" do + subject(:formula_ast) do + described_class.new <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + license :cannot_represent + # comment + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + let(:new_contents) do + <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + # comment + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + it "removes the stanza but keeps the comment" do + formula_ast.remove_stanza(:license) + expect(formula_ast.process).to eq(new_contents) + end + end + + context "when stanza to be removed has newlines before and after" do + subject(:formula_ast) do + described_class.new <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + + head do + url "https://brew.sh/foo.git" + end + end + RUBY + end + + let(:new_contents) do + <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + + head do + url "https://brew.sh/foo.git" + end + end + RUBY + end + + it "removes the stanza and preceding newline" do + formula_ast.remove_stanza(:bottle) + expect(formula_ast.process).to eq(new_contents) + end + end + + context "when stanza to be removed is at the end of the formula" do + subject(:formula_ast) do + described_class.new <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + license :cannot_represent + + bottle do + sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra + end + end + RUBY + end + + let(:new_contents) do + <<~RUBY.chomp + class Foo < Formula + url "https://brew.sh/foo-1.0.tar.gz" + license :cannot_represent + end + RUBY + end + + it "removes the stanza and preceding newline" do + formula_ast.remove_stanza(:bottle) + expect(formula_ast.process).to eq(new_contents) + end + end + end + describe "#add_bottle_block" do let(:bottle_output) do <<~RUBY.chomp.indent(2) diff --git a/Library/Homebrew/utils/ast.rb b/Library/Homebrew/utils/ast.rb index bc4e389b23..5531fa376e 100644 --- a/Library/Homebrew/utils/ast.rb +++ b/Library/Homebrew/utils/ast.rb @@ -114,6 +114,39 @@ module Utils add_stanza(:bottle, "\n#{bottle_output.chomp}", type: :block_call) end + sig { params(name: Symbol, type: T.nilable(Symbol)).void } + def remove_stanza(name, type: nil) + stanza_node = stanza(name, type: type) + raise "Could not find '#{name}' stanza!" if stanza_node.blank? + + # stanza is probably followed by a newline character + # try to delete it if so + stanza_range = stanza_node.source_range + trailing_range = stanza_range.with(begin_pos: stanza_range.end_pos, + end_pos: stanza_range.end_pos + 1) + if trailing_range.source.chomp.empty? + stanza_range = stanza_range.adjust(end_pos: 1) + + # stanza_node is probably indented + # since a trailing newline has been removed, + # try to delete leading whitespace on line + leading_range = stanza_range.with(begin_pos: stanza_range.begin_pos - stanza_range.column, + end_pos: stanza_range.begin_pos) + if leading_range.source.strip.empty? + stanza_range = stanza_range.adjust(begin_pos: -stanza_range.column) + + # if the stanza was preceded by a blank line, it should be removed + # that is, if the two previous characters are newlines, + # then delete one of them + leading_range = stanza_range.with(begin_pos: stanza_range.begin_pos - 2, + end_pos: stanza_range.begin_pos) + stanza_range = stanza_range.adjust(begin_pos: -1) if leading_range.source.chomp.chomp.empty? + end + end + + tree_rewriter.remove(stanza_range) + end + sig { params(name: Symbol, replacement: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void } def replace_stanza(name, replacement, type: nil) stanza_node = stanza(name, type: type)