diff --git a/Library/Homebrew/Gemfile.lock b/Library/Homebrew/Gemfile.lock index c3630a38b0..0e0057f50d 100644 --- a/Library/Homebrew/Gemfile.lock +++ b/Library/Homebrew/Gemfile.lock @@ -91,7 +91,7 @@ GEM rubocop-ast (>= 0.0.3, < 1.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.0.3) + rubocop-ast (0.1.0) parser (>= 2.7.0.1) rubocop-performance (1.6.1) rubocop (>= 0.71.0) diff --git a/Library/Homebrew/cmd/--cache.rb b/Library/Homebrew/cmd/--cache.rb index de6c1ac0a0..1dd0bb2c5e 100644 --- a/Library/Homebrew/cmd/--cache.rb +++ b/Library/Homebrew/cmd/--cache.rb @@ -22,9 +22,9 @@ module Homebrew switch "--force-bottle", description: "Show the cache file used when pouring a bottle." switch "--formula", - description: "Show cache files for only formulae" + description: "Only show cache files for formulae." switch "--cask", - description: "Show cache files for only casks" + description: "Only show cache files for casks." conflicts "--build-from-source", "--force-bottle" conflicts "--formula", "--cask" end diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index ef95b00c76..d6ad01feef 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -267,6 +267,7 @@ module Homebrew # Need to rescue before `FormulaUnavailableError` (superclass of this) # is handled, as searching for a formula doesn't make sense here (the # formula was found, but there's a problem with its implementation). + $stderr.puts e.backtrace if Homebrew::EnvConfig.developer? ofail e.message rescue FormulaUnavailableError => e if e.name == "updog" diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index 24f1116cde..095d0bcef0 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -172,14 +172,6 @@ module Homebrew @text.split("\n__END__").first end - def data? - /^[^#]*\bDATA\b/ =~ @text - end - - def end? - /^__END__$/ =~ @text - end - def trailing_newline? /\Z\n/ =~ @text end @@ -239,12 +231,6 @@ module Homebrew end def audit_file - # TODO: check could be in RuboCop - problem "'DATA' was found, but no '__END__'" if text.data? && !text.end? - - # TODO: check could be in RuboCop - problem "'__END__' was found, but 'DATA' is not used" if text.end? && !text.data? - # TODO: check could be in RuboCop if text.to_s.match?(/inreplace [^\n]* do [^\n]*\n[^\n]*\.gsub![^\n]*\n\ *end/m) problem "'inreplace ... do' was used for a single substitution (use the non-block form instead)." diff --git a/Library/Homebrew/dev-cmd/create.rb b/Library/Homebrew/dev-cmd/create.rb index 40f9d28da2..72076c8705 100644 --- a/Library/Homebrew/dev-cmd/create.rb +++ b/Library/Homebrew/dev-cmd/create.rb @@ -23,6 +23,8 @@ module Homebrew description: "Create a basic template for an Autotools-style build." switch "--cmake", description: "Create a basic template for a CMake-style build." + switch "--crystal", + description: "Create a basic template for a Crystal build." switch "--go", description: "Create a basic template for a Go build." switch "--meson", @@ -52,7 +54,7 @@ module Homebrew switch :force switch :verbose switch :debug - conflicts "--autotools", "--cmake", "--go", "--meson", "--perl", "--python", "--rust" + conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--perl", "--python", "--rust" named 1 end end @@ -86,6 +88,8 @@ module Homebrew :autotools elsif args.meson? :meson + elsif args.crystal? + :crystal elsif args.go? :go elsif args.perl? diff --git a/Library/Homebrew/dev-cmd/mirror.rb b/Library/Homebrew/dev-cmd/mirror.rb index e124175205..25c6317363 100644 --- a/Library/Homebrew/dev-cmd/mirror.rb +++ b/Library/Homebrew/dev-cmd/mirror.rb @@ -13,10 +13,10 @@ module Homebrew Reupload the stable URL of a formula to Bintray for use as a mirror. EOS - flag "--bintray-org=", - description: "Upload to the specified Bintray organisation (default: homebrew)." - flag "--bintray-repo=", - description: "Upload to the specified Bintray repository (default: mirror)." + flag "--bintray-org=", + description: "Upload to the specified Bintray organisation (default: `homebrew`)." + flag "--bintray-repo=", + description: "Upload to the specified Bintray repository (default: `mirror`)." switch "--no-publish", description: "Upload to Bintray, but don't publish." switch :verbose diff --git a/Library/Homebrew/dev-cmd/pr-pull.rb b/Library/Homebrew/dev-cmd/pr-pull.rb index bbd1b097e4..c5f82ec116 100644 --- a/Library/Homebrew/dev-cmd/pr-pull.rb +++ b/Library/Homebrew/dev-cmd/pr-pull.rb @@ -34,18 +34,18 @@ module Homebrew description: "When a patch fails to apply, leave in progress and allow user to resolve, "\ "instead of aborting." flag "--workflow=", - description: "Retrieve artifacts from the specified workflow (default: tests.yml)." + description: "Retrieve artifacts from the specified workflow (default: `tests.yml`)." flag "--artifact=", - description: "Download artifacts with the specified name (default: bottles)." + description: "Download artifacts with the specified name (default: `bottles`)." flag "--bintray-org=", - description: "Upload to the specified Bintray organisation (default: homebrew)." + description: "Upload to the specified Bintray organisation (default: `homebrew`)." flag "--tap=", - description: "Target tap repository (default: homebrew/core)." + description: "Target tap repository (default: `homebrew/core`)." flag "--root-url=", description: "Use the specified as the root of the bottle's URL instead of Homebrew's default." flag "--bintray-mirror=", description: "Use the specified Bintray repository to automatically mirror stable URLs "\ - "defined in the formulae (default: mirror)" + "defined in the formulae (default: `mirror`)." switch :verbose switch :debug min_named 1 diff --git a/Library/Homebrew/dev-cmd/pr-upload.rb b/Library/Homebrew/dev-cmd/pr-upload.rb index 86b1d83717..f591bf77dd 100644 --- a/Library/Homebrew/dev-cmd/pr-upload.rb +++ b/Library/Homebrew/dev-cmd/pr-upload.rb @@ -15,12 +15,12 @@ module Homebrew EOS switch "--no-publish", description: "Apply the bottle commit and upload the bottles, but don't publish them." - switch "--dry-run", "-n", + switch "-n", "--dry-run", description: "Print what would be done rather than doing it." - flag "--bintray-org=", - description: "Upload to the specified Bintray organisation (default: homebrew)." - flag "--root-url=", - description: "Use the specified as the root of the bottle's URL instead of Homebrew's default." + flag "--bintray-org=", + description: "Upload to the specified Bintray organisation (default: `homebrew`)." + flag "--root-url=", + description: "Use the specified as the root of the bottle's URL instead of Homebrew's default." switch :verbose switch :debug end diff --git a/Library/Homebrew/extend/os/linux/keg_relocate.rb b/Library/Homebrew/extend/os/linux/keg_relocate.rb index 5371e2ab1e..a707898c03 100644 --- a/Library/Homebrew/extend/os/linux/keg_relocate.rb +++ b/Library/Homebrew/extend/os/linux/keg_relocate.rb @@ -27,6 +27,7 @@ class Keg # patchelf requires that the ELF file have a .dynstr section. # Skip ELF files that do not have a .dynstr section. return if ["cannot find section .dynstr", "strange: no string table"].include?(old_rpath) + unless $CHILD_STATUS.success? raise ErrorDuringExecution.new(cmd_rpath, status: $CHILD_STATUS, output: [[:stderr, old_rpath]]) end @@ -41,15 +42,15 @@ class Keg new_rpath = rpath.join(":") cmd = [patchelf, "--force-rpath", "--set-rpath", new_rpath] - if file.with_interpreter? - old_interpreter = Utils.safe_popen_read(patchelf, "--print-interpreter", file).strip - new_interpreter = if File.readable? "#{new_prefix}/lib/ld.so" - "#{new_prefix}/lib/ld.so" - else - old_interpreter.sub old_prefix, new_prefix - end - cmd << "--set-interpreter" << new_interpreter if old_interpreter != new_interpreter + old_interpreter = file.interpreter + new_interpreter = if old_interpreter.nil? + nil + elsif File.readable? "#{new_prefix}/lib/ld.so" + "#{new_prefix}/lib/ld.so" + else + old_interpreter.sub old_prefix, new_prefix end + cmd << "--set-interpreter" << new_interpreter if old_interpreter != new_interpreter return if old_rpath == new_rpath && old_interpreter == new_interpreter diff --git a/Library/Homebrew/formula_creator.rb b/Library/Homebrew/formula_creator.rb index d68677302b..c59334f878 100644 --- a/Library/Homebrew/formula_creator.rb +++ b/Library/Homebrew/formula_creator.rb @@ -104,6 +104,8 @@ module Homebrew <% if mode == :cmake %> depends_on "cmake" => :build + <% elsif mode == :crystal %> + depends_on "crystal" => :build <% elsif mode == :go %> depends_on "go" => :build <% elsif mode == :meson %> @@ -139,6 +141,9 @@ module Homebrew "--disable-dependency-tracking", "--disable-silent-rules", "--prefix=\#{prefix}" + <% elsif mode == :crystal %> + system "shards", "build", "--release" + bin.install "bin/#{name}" <% elsif mode == :go %> system "go", "build", *std_go_args <% elsif mode == :meson %> diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 086d8ff61f..336f34f757 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -35,6 +35,7 @@ module Formulary begin mod.module_eval(contents, path) rescue NameError, ArgumentError, ScriptError => e + $stderr.puts e.backtrace if Homebrew::EnvConfig.developer? raise FormulaUnreadableError.new(name, e) end class_name = class_s(name) diff --git a/Library/Homebrew/help.rb b/Library/Homebrew/help.rb index 0e41977c72..57196edf7c 100644 --- a/Library/Homebrew/help.rb +++ b/Library/Homebrew/help.rb @@ -103,7 +103,7 @@ module Homebrew help_lines = command_help_lines(path) return if help_lines.blank? - Formatter.wrap(help_lines.join.delete_prefix(" "), COMMAND_DESC_WIDTH) + Formatter.wrap(help_lines.join, COMMAND_DESC_WIDTH) .sub("@hide_from_man_page ", "") .sub(/^\* /, "#{Tty.bold}Usage: brew#{Tty.reset} ") .gsub(/`(.*?)`/m, "#{Tty.bold}\\1#{Tty.reset}") diff --git a/Library/Homebrew/os/linux/elf.rb b/Library/Homebrew/os/linux/elf.rb index 83cd6e8982..51a1240207 100644 --- a/Library/Homebrew/os/linux/elf.rb +++ b/Library/Homebrew/os/linux/elf.rb @@ -68,28 +68,24 @@ module ELFShim elf_type == :executable end - def with_interpreter? - return @with_interpreter if defined? @with_interpreter + def interpreter + return @interpreter if defined? @interpreter - @with_interpreter = if binary_executable? - true - elsif dylib? - if HOMEBREW_PATCHELF_RB - begin - patchelf_patcher.interpreter.present? - rescue PatchELF::PatchError => e - opoo e unless e.to_s.start_with? "No interpreter found" - false - end - elsif which "readelf" - Utils.popen_read("readelf", "-l", to_path).include?(" INTERP ") - elsif which "file" - Utils.popen_read("file", "-L", "-b", to_path).include?(" interpreter ") - else - raise "Please install either readelf (from binutils) or file." + @interpreter = if HOMEBREW_PATCHELF_RB + begin + patchelf_patcher.interpreter + rescue PatchELF::PatchError => e + opoo e unless e.to_s.start_with? "No interpreter found" + nil end + elsif (patchelf = DevelopmentTools.locate "patchelf") + interp = Utils.popen_read(patchelf, "--print-interpreter", to_s, err: :out).strip + $CHILD_STATUS.success? ? interp : nil + elsif (file = DevelopmentTools.locate("file")) + output = Utils.popen_read(file, "-L", "-b", to_s, err: :out).strip + output[/^ELF.*, interpreter (.+?), /, 1] else - false + raise "Please install either patchelf or file." end end diff --git a/Library/Homebrew/rubocops.rb b/Library/Homebrew/rubocops.rb index 7a0f2c70e8..50f01a9cf8 100644 --- a/Library/Homebrew/rubocops.rb +++ b/Library/Homebrew/rubocops.rb @@ -22,5 +22,6 @@ require "rubocops/uses_from_macos" require "rubocops/files" require "rubocops/keg_only" require "rubocops/version" +require "rubocops/deprecate" require "rubocops/rubocop-cask" diff --git a/Library/Homebrew/rubocops/deprecate.rb b/Library/Homebrew/rubocops/deprecate.rb new file mode 100644 index 0000000000..15e936bebc --- /dev/null +++ b/Library/Homebrew/rubocops/deprecate.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "rubocops/extend/formula" + +module RuboCop + module Cop + module FormulaAudit + # This cop audits deprecate! + class Deprecate < FormulaCop + def audit_formula(_node, _class_node, _parent_class_node, body_node) + deprecate_node = find_node_method_by_name(body_node, :deprecate!) + + return if deprecate_node.nil? || deprecate_node.children.length < 3 + + date_node = find_strings(deprecate_node).first + + begin + Date.iso8601(string_content(date_node)) + rescue ArgumentError + fixed_date_string = Date.parse(string_content(date_node)).iso8601 + offending_node(date_node) + problem "Use `#{fixed_date_string}` to comply with ISO 8601" + end + end + + def autocorrect(node) + lambda do |corrector| + fixed_fixed_date_string = Date.parse(string_content(node)).iso8601 + corrector.replace(node.source_range, "\"#{fixed_fixed_date_string}\"") + end + end + end + end + end +end diff --git a/Library/Homebrew/rubocops/extend/formula.rb b/Library/Homebrew/rubocops/extend/formula.rb index 81281cd63a..34b8a4a7f1 100644 --- a/Library/Homebrew/rubocops/extend/formula.rb +++ b/Library/Homebrew/rubocops/extend/formula.rb @@ -495,7 +495,15 @@ module RuboCop when :str node.str_content when :dstr - node.each_child_node(:str).map(&:str_content).join + content = "" + node.each_child_node(:str, :begin) do |child| + content += if child.begin_type? + child.source + else + child.str_content + end + end + content when :const node.const_name when :sym diff --git a/Library/Homebrew/rubocops/lines.rb b/Library/Homebrew/rubocops/lines.rb index 38e26af8b5..faa5f6ff80 100644 --- a/Library/Homebrew/rubocops/lines.rb +++ b/Library/Homebrew/rubocops/lines.rb @@ -195,7 +195,7 @@ module RuboCop end end - class ShellCmd < FormulaCop + class SafePopenCommands < FormulaCop def audit_formula(_node, _class_node, _parent_class_node, body_node) test = find_block(body_node, :test) @@ -223,6 +223,35 @@ module RuboCop end end + class ShellVariables < FormulaCop + def audit_formula(_node, _class_node, _parent_class_node, body_node) + popen_commands = [ + :popen, + :popen_read, + :safe_popen_read, + :popen_write, + :safe_popen_write, + ] + + popen_commands.each do |command| + find_instance_method_call(body_node, "Utils", command) do |method| + next unless match = regex_match_group(parameters(method).first, /^([^"' ]+)=([^"' ]+)(?: (.*))?$/) + + good_args = "Utils.#{command}({ \"#{match[1]}\" => \"#{match[2]}\" }, \"#{match[3]}\")" + + problem "Use `#{good_args}` instead of `#{method.source}`" + end + end + end + + def autocorrect(node) + lambda do |corrector| + match = regex_match_group(node, /^([^"' ]+)=([^"' ]+)(?: (.*))?$/) + corrector.replace(node.source_range, "{ \"#{match[1]}\" => \"#{match[2]}\" }, \"#{match[3]}\"") + end + end + end + class Miscellaneous < FormulaCop def audit_formula(_node, _class_node, _parent_class_node, body_node) # FileUtils is included in Formula diff --git a/Library/Homebrew/rubocops/patches.rb b/Library/Homebrew/rubocops/patches.rb index eeae550780..13cffc6dca 100644 --- a/Library/Homebrew/rubocops/patches.rb +++ b/Library/Homebrew/rubocops/patches.rb @@ -8,7 +8,9 @@ module RuboCop module FormulaAudit # This cop audits patches in Formulae. class Patches < FormulaCop - def audit_formula(_node, _class_node, _parent_class_node, body) + def audit_formula(node, _class_node, _parent_class_node, body) + @full_source_content = source_buffer(node).source + external_patches = find_all_blocks(body, :patch) external_patches.each do |patch_block| url_node = find_every_method_call_by_name(patch_block, :url).first @@ -16,6 +18,14 @@ module RuboCop patch_problems(url_string) end + inline_patches = find_every_method_call_by_name(body, :patch) + inline_patches.each { |patch| inline_patch_problems(patch) } + + if inline_patches.empty? && patch_end? + offending_patch_end_node(node) + problem "patch is missing 'DATA'" + end + patches_node = find_method_def(body, :patches) return if patches_node.nil? @@ -84,6 +94,30 @@ module RuboCop #{patch_url} EOS end + + def inline_patch_problems(patch) + return unless patch_data?(patch) && !patch_end? + + offending_node(patch) + problem "patch is missing '__END__'" + end + + def_node_search :patch_data?, <<~AST + (send nil? :patch (:sym :DATA)) + AST + + def patch_end? + /^__END__$/.match?(@full_source_content) + end + + def offending_patch_end_node(node) + @offensive_node = node + @source_buf = source_buffer(node) + @line_no = node.loc.last_line + 1 + @column = 0 + @length = 7 # "__END__".size + @offense_source_range = source_range(@source_buf, @line_no, @column, @length) + end end end end diff --git a/Library/Homebrew/test/dev-cmd/audit_spec.rb b/Library/Homebrew/test/dev-cmd/audit_spec.rb index 12605668a3..54ead437d0 100644 --- a/Library/Homebrew/test/dev-cmd/audit_spec.rb +++ b/Library/Homebrew/test/dev-cmd/audit_spec.rb @@ -41,8 +41,6 @@ module Homebrew url "https://www.brew.sh/valid-1.0.tar.gz" RUBY - expect(ft).not_to have_data - expect(ft).not_to have_end expect(ft).to have_trailing_newline expect(ft =~ /\burl\b/).to be_truthy @@ -55,20 +53,6 @@ module Homebrew ft = formula_text "newline" expect(ft).to have_trailing_newline end - - specify "#data?" do - ft = formula_text "data", <<~RUBY - patch :DATA - RUBY - - expect(ft).to have_data - end - - specify "#end?" do - ft = formula_text "end", "", patch: "__END__\na patch here" - expect(ft).to have_end - expect(ft.without_patch).to eq("class End < Formula\n \nend") - end end describe FormulaAuditor do @@ -186,31 +170,6 @@ module Homebrew end describe "#audit_file" do - specify "DATA but no __END__" do - fa = formula_auditor "foo", <<~RUBY - class Foo < Formula - url "https://brew.sh/foo-1.0.tgz" - patch :DATA - end - RUBY - - fa.audit_file - expect(fa.problems).to eq(["'DATA' was found, but no '__END__'"]) - end - - specify "__END__ but no DATA" do - fa = formula_auditor "foo", <<~RUBY - class Foo < Formula - url "https://brew.sh/foo-1.0.tgz" - end - __END__ - a patch goes here - RUBY - - fa.audit_file - expect(fa.problems).to eq(["'__END__' was found, but 'DATA' is not used"]) - end - specify "no issue" do fa = formula_auditor "foo", <<~RUBY class Foo < Formula diff --git a/Library/Homebrew/test/rubocops/deprecate_spec.rb b/Library/Homebrew/test/rubocops/deprecate_spec.rb new file mode 100644 index 0000000000..f73ab0be03 --- /dev/null +++ b/Library/Homebrew/test/rubocops/deprecate_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "rubocops/deprecate" + +describe RuboCop::Cop::FormulaAudit::Deprecate do + subject(:cop) { described_class.new } + + context "When auditing formula for deprecate!" do + it "deprecation date is not ISO 8601 compliant" do + expect_offense(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + deprecate! :date => "June 25, 2020" + ^^^^^^^^^^^^^^^ Use `2020-06-25` to comply with ISO 8601 + end + RUBY + end + + it "deprecation date is ISO 8601 compliant" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + deprecate! :date => "2020-06-25" + end + RUBY + end + + it "no deprecation date" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + deprecate! + end + RUBY + end + + it "auto corrects to ISO 8601 format" do + source = <<~RUBY + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + deprecate! :date => "June 25, 2020" + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + deprecate! :date => "2020-06-25" + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + end +end diff --git a/Library/Homebrew/test/rubocops/lines_spec.rb b/Library/Homebrew/test/rubocops/lines_spec.rb index bb31d2e935..9c92e47850 100644 --- a/Library/Homebrew/test/rubocops/lines_spec.rb +++ b/Library/Homebrew/test/rubocops/lines_spec.rb @@ -345,10 +345,10 @@ describe RuboCop::Cop::FormulaAudit::MpiCheck do end end -describe RuboCop::Cop::FormulaAudit::ShellCmd do +describe RuboCop::Cop::FormulaAudit::SafePopenCommands do subject(:cop) { described_class.new } - context "When auditing shell commands" do + context "When auditing popen commands" do it "Utils.popen_read should become Utils.safe_popen_read" do expect_offense(<<~RUBY) class Foo < Formula @@ -440,6 +440,140 @@ describe RuboCop::Cop::FormulaAudit::ShellCmd do end end +describe RuboCop::Cop::FormulaAudit::ShellVariables do + subject(:cop) { described_class.new } + + context "When auditing shell variables" do + it "Shell variables should be expanded in Utils.popen" do + expect_offense(<<~RUBY) + class Foo < Formula + def install + Utils.popen "SHELL=bash foo" + ^^^^^^^^^^^^^^ Use `Utils.popen({ "SHELL" => "bash" }, "foo")` instead of `Utils.popen "SHELL=bash foo"` + end + end + RUBY + end + + it "Shell variables should be expanded in Utils.safe_popen_read" do + expect_offense(<<~RUBY) + class Foo < Formula + def install + Utils.safe_popen_read "SHELL=bash foo" + ^^^^^^^^^^^^^^ Use `Utils.safe_popen_read({ "SHELL" => "bash" }, "foo")` instead of `Utils.safe_popen_read "SHELL=bash foo"` + end + end + RUBY + end + + it "Shell variables should be expanded in Utils.safe_popen_write" do + expect_offense(<<~RUBY) + class Foo < Formula + def install + Utils.safe_popen_write "SHELL=bash foo" + ^^^^^^^^^^^^^^ Use `Utils.safe_popen_write({ "SHELL" => "bash" }, "foo")` instead of `Utils.safe_popen_write "SHELL=bash foo"` + end + end + RUBY + end + + it "Shell variables should be expanded and keep inline string variables in the arguments" do + expect_offense(<<~RUBY) + class Foo < Formula + def install + Utils.popen "SHELL=bash \#{bin}/foo" + ^^^^^^^^^^^^^^^^^^^^^ Use `Utils.popen({ "SHELL" => "bash" }, "\#{bin}/foo")` instead of `Utils.popen "SHELL=bash \#{bin}/foo"` + end + end + RUBY + end + + it "corrects shell variables in Utils.popen" do + source = <<~RUBY + class Foo < Formula + def install + Utils.popen("SHELL=bash foo") + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + def install + Utils.popen({ "SHELL" => "bash" }, "foo") + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + + it "corrects shell variables in Utils.safe_popen_read" do + source = <<~RUBY + class Foo < Formula + def install + Utils.safe_popen_read("SHELL=bash foo") + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + def install + Utils.safe_popen_read({ "SHELL" => "bash" }, "foo") + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + + it "corrects shell variables in Utils.safe_popen_write" do + source = <<~RUBY + class Foo < Formula + def install + Utils.safe_popen_write("SHELL=bash foo") + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + def install + Utils.safe_popen_write({ "SHELL" => "bash" }, "foo") + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + + it "corrects shell variables with inline string variable in arguments" do + source = <<~RUBY + class Foo < Formula + def install + Utils.popen("SHELL=bash \#{bin}/foo") + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + def install + Utils.popen({ "SHELL" => "bash" }, "\#{bin}/foo") + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + end +end + describe RuboCop::Cop::FormulaAudit::Miscellaneous do subject(:cop) { described_class.new } diff --git a/Library/Homebrew/test/rubocops/patches_spec.rb b/Library/Homebrew/test/rubocops/patches_spec.rb index f2a0fc25a7..42883c8458 100644 --- a/Library/Homebrew/test/rubocops/patches_spec.rb +++ b/Library/Homebrew/test/rubocops/patches_spec.rb @@ -163,6 +163,53 @@ describe RuboCop::Cop::FormulaAudit::Patches do end end + context "When auditing inline patches" do + it "reports no offenses for valid inline patches" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + patch :DATA + end + __END__ + patch content here + RUBY + end + + it "reports no offenses for valid nested inline patches" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + stable do + patch :DATA + end + end + __END__ + patch content here + RUBY + end + + it "reports an offense when DATA is found with no __END__" do + expect_offense(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + patch :DATA + ^^^^^^^^^^^ patch is missing '__END__' + end + RUBY + end + + it "reports an offense when __END__ is found with no DATA" do + expect_offense(<<~RUBY) + class Foo < Formula + url 'https://brew.sh/foo-1.0.tgz' + end + __END__ + ^^^^^^^ patch is missing 'DATA' + patch content here + RUBY + end + end + context "When auditing external patches" do it "Patch URLs" do patch_urls = [ diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 3ddf50c79c..254ac11ea1 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -497,7 +497,7 @@ module Kernel path.read .lines .grep(/^#:/) - .map { |line| line.slice(2..-1) } + .map { |line| line.slice(2..-1).delete_prefix(" ") } end def redact_secrets(input, secrets) diff --git a/completions/bash/brew b/completions/bash/brew index b92b594acd..1a57828607 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -162,6 +162,7 @@ _brew_create() { --HEAD --autotools --cmake + --crystal --debug --force --go diff --git a/completions/zsh/_brew b/completions/zsh/_brew index e8402fb475..ff75d1ab09 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -45,20 +45,19 @@ __brew_completion_caching_policy() { tmp=( $1(mw-2N) ) (( $#tmp )) || return 0 - # otherwise, invalidate if latest tap index file is missing or newer than - # cache file + # otherwise, invalidate if latest tap index file is missing or newer than cache file tmp=( ${HOMEBREW_REPOSITORY:-/usr/local/Homebrew}/Library/Taps/*/*/.git/index(om[1]N) ) [[ -z $tmp || $tmp -nt $1 ]] } __brew_formulae() { - local -a formulae + local -a list local comp_cachename=brew_formulae - if _cache_invalid $comp_cachename || ! _retrieve_cache $comp_cachename; then - formulae=($(brew search)) - _store_cache $comp_cachename formulae + if ! _retrieve_cache $comp_cachename; then + list=( $(brew search) ) + _store_cache $comp_cachename list fi - _describe -t formulae 'all formulae' formulae + _describe -t formulae 'all formulae' list } __brew_installed_formulae() { @@ -145,18 +144,17 @@ __brew_common_commands() { } __brew_all_commands() { - local -a commands + local -a list local comp_cachename=brew_all_commands - if _cache_invalid $comp_cachename || ! _retrieve_cache $comp_cachename; then - HOMEBREW_CACHE=$(brew --cache) - HOMEBREW_REPOSITORY=$(brew --repo) - [[ -f "$HOMEBREW_CACHE/all_commands_list.txt" ]] && - commands=($(cat "$HOMEBREW_CACHE/all_commands_list.txt")) || - commands=($(cat "$HOMEBREW_REPOSITORY/completions/internal_commands_list.txt")) - commands=(${commands:#*instal}) # Exclude instal, uninstal, etc. - _store_cache $comp_cachename commands + if ! _retrieve_cache $comp_cachename; then + local cache_dir=$(brew --cache) + [[ -f $cache_dir/all_commands_list.txt ]] && + list=( $(<$cache_dir/all_commands_list.txt) ) || + list=( $(<$(brew --repo)/completions/internal_commands_list.txt) ) + list=( ${list:#*instal} ) # Exclude instal, uninstal, etc. + _store_cache $comp_cachename list fi - _describe -t all-commands 'all commands' commands + _describe -t all-commands 'all commands' list } __brew_commands() { @@ -857,10 +855,10 @@ _brew() { case "$state" in command) # set default cache policy - zstyle -s ":completion:${curcontext%:*}:*" cache-policy tmp - [[ -n $tmp ]] || - zstyle ":completion:${curcontext%:*}:*" cache-policy \ - __brew_completion_caching_policy + zstyle -s ":completion:${curcontext%:*}:*" cache-policy tmp || + zstyle ":completion:${curcontext%:*}:*" cache-policy __brew_completion_caching_policy + zstyle -s ":completion:${curcontext%:*}:*" use-cache tmp || + zstyle ":completion:${curcontext%:*}:*" use-cache true __brew_commands && return 0 ;; @@ -878,10 +876,10 @@ _brew() { # set default cache policy (we repeat this dance because the context # service differs from above) - zstyle -s ":completion:${curcontext%:*}:*" cache-policy tmp - [[ -n $tmp ]] || - zstyle ":completion:${curcontext%:*}:*" cache-policy \ - __brew_completion_caching_policy + zstyle -s ":completion:${curcontext%:*}:*" cache-policy tmp || + zstyle ":completion:${curcontext%:*}:*" cache-policy __brew_completion_caching_policy + zstyle -s ":completion:${curcontext%:*}:*" use-cache tmp || + zstyle ":completion:${curcontext%:*}:*" use-cache true # call completion for named command e.g. _brew_list local completion_func="_brew_${command//-/_}" diff --git a/completions/zsh/_brew_cask b/completions/zsh/_brew_cask index c841b006c3..1a62163294 100644 --- a/completions/zsh/_brew_cask +++ b/completions/zsh/_brew_cask @@ -21,7 +21,7 @@ __brew_all_casks() { local expl local comp_cachename=brew_casks - if _cache_invalid $comp_cachename || ! _retrieve_cache $comp_cachename; then + if ! _retrieve_cache $comp_cachename; then list=( $(brew search --casks) ) _store_cache $comp_cachename list fi diff --git a/docs/Bottles.md b/docs/Bottles.md index 2aa9cb8342..4b1d4fa332 100644 --- a/docs/Bottles.md +++ b/docs/Bottles.md @@ -8,7 +8,7 @@ If a bottle is available and usable it will be downloaded and poured automatical Bottles will not be used if the user requests it (see above), if the formula requests it (with `pour_bottle?`), if any options are specified during installation (bottles are all compiled with default options), if the bottle is not up to date (e.g. lacking a checksum) or if the bottle's `cellar` is not `:any` nor equal to the current `HOMEBREW_CELLAR`. ## Creation -Bottles are created using the [Brew Test Bot](Brew-Test-Bot.md). This happens mostly when people submit pull requests to Homebrew and the `bottle do` block is updated by maintainers when they `brew pr-publish` or `brew pr-pull` the contents of a pull request. For the Homebrew organisations' taps they are uploaded to and downloaded from [Bintray](https://bintray.com/homebrew). +Bottles are created using the [Brew Test Bot](Brew-Test-Bot.md), usually when people submit pull requests to Homebrew. The `bottle do` block is updated by maintainers when they merge a pull request. For the Homebrew organisations' taps they are uploaded to and downloaded from [Bintray](https://bintray.com/homebrew). By default, bottles will be built for the oldest CPU supported by the OS/architecture you're building for (Core 2 for 64-bit OSs). This ensures that bottles are compatible with all computers you might distribute them to. If you *really* want your bottles to be optimised for something else, you can pass the `--bottle-arch=` option to build for another architecture; for example, `brew install foo --build-bottle --bottle-arch=penryn`. Just remember that if you build for a newer architecture some of your users might get binaries they can't run and that would be sad! diff --git a/docs/Brew-Test-Bot-For-Core-Contributors.md b/docs/Brew-Test-Bot-For-Core-Contributors.md index cdff7164c6..5d4665e8b8 100644 --- a/docs/Brew-Test-Bot-For-Core-Contributors.md +++ b/docs/Brew-Test-Bot-For-Core-Contributors.md @@ -8,6 +8,7 @@ If a pull request is correct and doesn't need any modifications to commit messag 1. Ensure the job has already completed successfully. 2. Run `brew pr-publish 12345` where `12345` is the pull request number (or URL). + - Approving a PR for an existing formula will automatically publish the bottles and close the PR, taking care of this step. 3. Watch the [actions queue](https://github.com/Homebrew/homebrew-core/actions) to ensure your job finishes. BrewTestBot will usually notify you of failures with a ping as well. If a pull request needs changes to the commit messages: diff --git a/docs/Homebrew-homebrew-core-Merge-Checklist.md b/docs/Homebrew-homebrew-core-Merge-Checklist.md index 3f2dc6569e..3ee396815f 100644 --- a/docs/Homebrew-homebrew-core-Merge-Checklist.md +++ b/docs/Homebrew-homebrew-core-Merge-Checklist.md @@ -54,7 +54,7 @@ Check for: - if CI is green and... - formula `bottle :unneeded`, you can merge it through GitHub UI - bottles need to be pulled, and... - - the commits are correct and don't need changes, use: `brew pr-publish $PR_ID` + - the commits are correct and don't need changes, approve the PR to trigger an automatic merge (use `brew pr-publish $PR_ID` to trigger manually in case of a new formula) - the commits need to be amended, use `brew pr-pull $PR_ID`, make changes, and `git push` - don't forget to thank the contributor - celebrate the first-time contributors diff --git a/docs/Manpage.md b/docs/Manpage.md index 4762e5d12c..e75a8540b5 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -588,9 +588,9 @@ If *`formula`* is provided, display the file or directory used to cache *`formul * `--force-bottle`: Show the cache file used when pouring a bottle. * `--formula`: - Show cache files for only formulae + Only show cache files for formulae. * `--cask`: - Show cache files for only casks + Only show cache files for casks. ### `--cellar` [*`formula`*] @@ -769,6 +769,8 @@ a simple example. For the complete API, see: Create a basic template for an Autotools-style build. * `--cmake`: Create a basic template for a CMake-style build. +* `--crystal`: + Create a basic template for a Crystal build. * `--go`: Create a basic template for a Go build. * `--meson`: @@ -900,17 +902,17 @@ repository. * `--resolve`: When a patch fails to apply, leave in progress and allow user to resolve, instead of aborting. * `--workflow`: - Retrieve artifacts from the specified workflow (default: tests.yml). + Retrieve artifacts from the specified workflow (default: `tests.yml`). * `--artifact`: - Download artifacts with the specified name (default: bottles). + Download artifacts with the specified name (default: `bottles`). * `--bintray-org`: - Upload to the specified Bintray organisation (default: homebrew). + Upload to the specified Bintray organisation (default: `homebrew`). * `--tap`: - Target tap repository (default: homebrew/core). + Target tap repository (default: `homebrew/core`). * `--root-url`: Use the specified *`URL`* as the root of the bottle's URL instead of Homebrew's default. * `--bintray-mirror`: - Use the specified Bintray repository to automatically mirror stable URLs defined in the formulae (default: mirror) + Use the specified Bintray repository to automatically mirror stable URLs defined in the formulae (default: `mirror`). ### `pr-upload` [*`options`*] @@ -921,7 +923,7 @@ Apply the bottle commit and publish bottles to Bintray. * `-n`, `--dry-run`: Print what would be done rather than doing it. * `--bintray-org`: - Upload to the specified Bintray organisation (default: homebrew). + Upload to the specified Bintray organisation (default: `homebrew`). * `--root-url`: Use the specified *`URL`* as the root of the bottle's URL instead of Homebrew's default. diff --git a/docs/New-Maintainer-Checklist.md b/docs/New-Maintainer-Checklist.md index 7a27dd3ec8..27ce7a1acd 100644 --- a/docs/New-Maintainer-Checklist.md +++ b/docs/New-Maintainer-Checklist.md @@ -33,9 +33,10 @@ A few requests: - In Homebrew/brew, close pull requests using GitHub's "Merge pull request" button in "Create a merge commit" mode. - In Homebrew/homebrew-core, use `brew pr-publish` to close pull requests - that require new bottles or change multiple formulae. If commits need to - be amended use `brew pr-pull` instead. Let these commands auto-close - issues whenever possible (it may take up to 5 minutes). If in doubt, + that require new bottles or change multiple formulae. Note that an approving + review on a pull request for an existing formula will trigger this automatically. + If commits need to be amended use `brew pr-pull` instead. Let these commands + auto-close issues whenever possible (it may take up to 5 minutes). If in doubt, check with e.g. Fork.app that you've not accidentally added merge commits. If bottles are unnecessary, use GitHub's "Merge pull request" button in "Squash and merge" mode for a single formula change. diff --git a/manpages/brew.1 b/manpages/brew.1 index 116fe245bc..b0c5112e79 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -765,11 +765,11 @@ Show the cache file used when pouring a bottle\. . .TP \fB\-\-formula\fR -Show cache files for only formulae +Only show cache files for formulae\. . .TP \fB\-\-cask\fR -Show cache files for only casks +Only show cache files for casks\. . .SS "\fB\-\-cellar\fR [\fIformula\fR]" Display Homebrew\'s Cellar path\. \fIDefault:\fR \fB$(brew \-\-prefix)/Cellar\fR, or if that directory doesn\'t exist, \fB$(brew \-\-repository)/Cellar\fR\. @@ -999,6 +999,10 @@ Create a basic template for an Autotools\-style build\. Create a basic template for a CMake\-style build\. . .TP +\fB\-\-crystal\fR +Create a basic template for a Crystal build\. +. +.TP \fB\-\-go\fR Create a basic template for a Go build\. . @@ -1169,19 +1173,19 @@ When a patch fails to apply, leave in progress and allow user to resolve, instea . .TP \fB\-\-workflow\fR -Retrieve artifacts from the specified workflow (default: tests\.yml)\. +Retrieve artifacts from the specified workflow (default: \fBtests\.yml\fR)\. . .TP \fB\-\-artifact\fR -Download artifacts with the specified name (default: bottles)\. +Download artifacts with the specified name (default: \fBbottles\fR)\. . .TP \fB\-\-bintray\-org\fR -Upload to the specified Bintray organisation (default: homebrew)\. +Upload to the specified Bintray organisation (default: \fBhomebrew\fR)\. . .TP \fB\-\-tap\fR -Target tap repository (default: homebrew/core)\. +Target tap repository (default: \fBhomebrew/core\fR)\. . .TP \fB\-\-root\-url\fR @@ -1189,7 +1193,7 @@ Use the specified \fIURL\fR as the root of the bottle\'s URL instead of Homebrew . .TP \fB\-\-bintray\-mirror\fR -Use the specified Bintray repository to automatically mirror stable URLs defined in the formulae (default: mirror) +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\. @@ -1204,7 +1208,7 @@ Print what would be done rather than doing it\. . .TP \fB\-\-bintray\-org\fR -Upload to the specified Bintray organisation (default: homebrew)\. +Upload to the specified Bintray organisation (default: \fBhomebrew\fR)\. . .TP \fB\-\-root\-url\fR