From 042d6cc97e886c6f2817500083cda6c856b5350a Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Wed, 5 Feb 2025 23:16:08 -0800 Subject: [PATCH 01/12] Remove JSON v3 cask logic --- Library/Homebrew/cask/cask.rb | 65 +++---------------- Library/Homebrew/dev-cmd/generate-cask-api.rb | 2 - Library/Homebrew/tap.rb | 16 ----- 3 files changed, 8 insertions(+), 75 deletions(-) diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index 4a82c576f9..ee1f6dcae6 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -400,63 +400,15 @@ module Cask } end - def to_internal_api_hash - api_hash = { - "token" => token, - "name" => name, - "desc" => desc, - "homepage" => homepage, - "url" => url, - "version" => version, - "sha256" => sha256, - "artifacts" => artifacts_list(compact: true), - "ruby_source_path" => ruby_source_path, - "ruby_source_sha256" => ruby_source_checksum.fetch(:sha256), - } - - if deprecation_date - api_hash["deprecation_date"] = deprecation_date - api_hash["deprecation_reason"] = deprecation_reason - api_hash["deprecation_replacement"] = deprecation_replacement - end - - if disable_date - api_hash["disable_date"] = disable_date - api_hash["disable_reason"] = disable_reason - api_hash["disable_replacement"] = disable_replacement - end - - if (url_specs_hash = url_specs).present? - api_hash["url_specs"] = url_specs_hash - end - - api_hash["caskfile_only"] = true if caskfile_only? - api_hash["conflicts_with"] = conflicts_with if conflicts_with.present? - api_hash["depends_on"] = depends_on if depends_on.present? - api_hash["container"] = container.pairs if container - api_hash["caveats"] = caveats if caveats.present? - api_hash["auto_updates"] = auto_updates if auto_updates - api_hash["languages"] = languages if languages.present? - - api_hash - end - HASH_KEYS_TO_SKIP = %w[outdated installed versions].freeze private_constant :HASH_KEYS_TO_SKIP - def to_hash_with_variations(hash_method: :to_h) - case hash_method - when :to_h - if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? - return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup) - end - when :to_internal_api_hash - raise ArgumentError, "API Hash must be generated from Ruby source files" if loaded_from_api? - else - raise ArgumentError, "Unknown hash method #{hash_method.inspect}" + def to_hash_with_variations + if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? + return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup) end - hash = public_send(hash_method) + hash = to_h variations = {} if @dsl.on_system_blocks_exist? @@ -471,7 +423,7 @@ module Cask Homebrew::SimulateSystem.with(os:, arch:) do refresh - public_send(hash_method).each do |key, value| + to_h.each do |key, value| next if HASH_KEYS_TO_SKIP.include? key next if value.to_s == hash[key].to_s @@ -485,11 +437,11 @@ module Cask end end - hash["variations"] = variations if hash_method != :to_internal_api_hash || variations.present? + hash["variations"] = variations hash end - def artifacts_list(compact: false, uninstall_only: false) + def artifacts_list(uninstall_only: false) artifacts.filter_map do |artifact| case artifact when Artifact::AbstractFlightBlock @@ -498,8 +450,7 @@ module Cask next if uninstall_only && !uninstall_flight_block # Only indicate whether this block is used as we don't load it from the API - # We can skip this entirely once we move to internal JSON v3. - { artifact.summarize.to_sym => nil } unless compact + { artifact.summarize.to_sym => nil } else zap_artifact = artifact.is_a?(Artifact::Zap) uninstall_artifact = artifact.respond_to?(:uninstall_phase) || artifact.respond_to?(:post_uninstall_phase) diff --git a/Library/Homebrew/dev-cmd/generate-cask-api.rb b/Library/Homebrew/dev-cmd/generate-cask-api.rb index ac35895e3d..6f75828061 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-api.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-api.rb @@ -62,8 +62,6 @@ module Homebrew raise end - homebrew_cask_tap_json = JSON.generate(tap.to_internal_api_hash) - File.write("api/internal/v3/homebrew-cask.json", homebrew_cask_tap_json) unless args.dry_run? canonical_json = JSON.pretty_generate(tap.cask_renames) File.write("_data/cask_canonical.json", "#{canonical_json}\n") unless args.dry_run? end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 52794cb99b..340f9c71a5 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -1491,22 +1491,6 @@ class CoreCaskTap < AbstractCoreTap migrations end end - - sig { returns(T::Hash[String, T.untyped]) } - def to_internal_api_hash - casks_api_hash = cask_tokens.to_h do |token| - cask = Cask::CaskLoader.load(token) - cask_hash = cask.to_hash_with_variations(hash_method: :to_internal_api_hash) - [token, cask_hash] - end - - { - "tap_git_head" => git_head, - "renames" => cask_renames, - "tap_migrations" => tap_migrations, - "casks" => casks_api_hash, - } - end end # Permanent configuration per {Tap} using `git-config(1)`. From 839198d21eb96348f67dbd6d62842cca8458c0cd Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Wed, 5 Feb 2025 23:36:57 -0800 Subject: [PATCH 02/12] Remove JSON v3 formula logic --- .../Homebrew/dev-cmd/generate-formula-api.rb | 2 - Library/Homebrew/formula.rb | 101 +------- Library/Homebrew/tap.rb | 17 -- .../api/internal_tap_json/formula_spec.rb | 170 -------------- .../internal_tap_json/homebrew-core.json | 218 ------------------ .../homebrew-core/Formula/f/fennel.rb | 25 -- .../homebrew-core/Formula/i/inko.rb | 45 ---- .../homebrew-core/Formula/p/ponyc.rb | 60 ----- .../homebrew-core/formula_renames.json | 7 - .../homebrew-core/tap_migrations.json | 7 - 10 files changed, 11 insertions(+), 641 deletions(-) delete mode 100644 Library/Homebrew/test/api/internal_tap_json/formula_spec.rb delete mode 100644 Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json delete mode 100644 Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb delete mode 100644 Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb delete mode 100644 Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb delete mode 100644 Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json delete mode 100644 Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json diff --git a/Library/Homebrew/dev-cmd/generate-formula-api.rb b/Library/Homebrew/dev-cmd/generate-formula-api.rb index a3fe69de0d..c87dc2a798 100644 --- a/Library/Homebrew/dev-cmd/generate-formula-api.rb +++ b/Library/Homebrew/dev-cmd/generate-formula-api.rb @@ -60,8 +60,6 @@ module Homebrew raise end - homebrew_core_tap_json = JSON.generate(tap.to_internal_api_hash) - File.write("api/internal/v3/homebrew-core.json", homebrew_core_tap_json) unless args.dry_run? canonical_json = JSON.pretty_generate(tap.formula_renames.merge(tap.alias_table)) File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run? end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 4dbd9124f1..889b6b6f58 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2572,85 +2572,8 @@ class Formula hsh end - def to_internal_api_hash - api_hash = { - "desc" => desc, - "license" => SPDX.license_expression_to_string(license), - "homepage" => homepage, - "urls" => urls_hash.transform_values(&:compact), - "post_install_defined" => post_install_defined?, - "ruby_source_path" => ruby_source_path, - "ruby_source_sha256" => ruby_source_checksum&.hexdigest, - } - - # Exclude default values. - api_hash["revision"] = revision unless revision.zero? - api_hash["version_scheme"] = version_scheme unless version_scheme.zero? - - # Optional values. - api_hash["keg_only_reason"] = keg_only_reason.to_hash if keg_only_reason - api_hash["pour_bottle_only_if"] = self.class.pour_bottle_only_if.to_s if self.class.pour_bottle_only_if - api_hash["link_overwrite"] = self.class.link_overwrite_paths.to_a if self.class.link_overwrite_paths.present? - api_hash["caveats"] = caveats_with_placeholders if caveats - api_hash["service"] = service.to_hash if service? - - if stable - api_hash["version"] = stable&.version&.to_s - api_hash["bottle"] = bottle_hash(compact_for_api: true) if bottle_defined? - end - - if (versioned_formulae_list = versioned_formulae.presence) - # Could we just use `versioned_formulae_names` here instead? - api_hash["versioned_formulae"] = versioned_formulae_list.map(&:name) - end - - if (dependencies = internal_dependencies_hash(:stable).presence) - api_hash["dependencies"] = dependencies - end - - if (head_dependencies = internal_dependencies_hash(:head).presence) - api_hash["head_dependencies"] = head_dependencies - end - - if (requirements_array = serialized_requirements.presence) - api_hash["requirements"] = requirements_array - end - - if conflicts.present? - api_hash["conflicts_with"] = conflicts.map(&:name) - api_hash["conflicts_with_reasons"] = conflicts.map(&:reason) - end - - if deprecation_date - api_hash["deprecation_date"] = deprecation_date - api_hash["deprecation_reason"] = deprecation_reason - api_hash["deprecation_replacement"] = deprecation_replacement - end - - if disable_date - api_hash["disable_date"] = disable_date - api_hash["disable_reason"] = disable_reason - api_hash["disable_replacement"] = disable_replacement - end - - api_hash - end - - def to_hash_with_variations(hash_method: :to_hash) - if loaded_from_api? && hash_method == :to_internal_api_hash - raise ArgumentError, "API Hash must be generated from Ruby source files" - end - - namespace_prefix = case hash_method - when :to_hash - "Variations" - when :to_internal_api_hash - "APIVariations" - else - raise ArgumentError, "Unknown hash method #{hash_method.inspect}" - end - - hash = public_send(hash_method) + def to_hash_with_variations + hash = to_hash # Take from API, merging in local install status. if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? @@ -2669,13 +2592,13 @@ class Formula next unless bottle_tag.valid_combination? Homebrew::SimulateSystem.with(os:, arch:) do - variations_namespace = Formulary.class_s("#{namespace_prefix}#{bottle_tag.to_sym.capitalize}") + variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}") variations_formula_class = Formulary.load_formula(name, path, formula_contents, variations_namespace, flags: self.class.build_flags, ignore_errors: true) variations_formula = variations_formula_class.new(name, path, :stable, alias_path:, force_bottle:) - variations_formula.public_send(hash_method).each do |key, value| + variations_formula.to_hash.each do |key, value| next if value.to_s == hash[key].to_s variations[bottle_tag.to_sym] ||= {} @@ -2685,12 +2608,12 @@ class Formula end end - hash["variations"] = variations if hash_method != :to_internal_api_hash || variations.present? + hash["variations"] = variations hash end # Returns the bottle information for a formula. - def bottle_hash(compact_for_api: false) + def bottle_hash hash = {} stable_spec = stable return hash unless stable_spec @@ -2698,8 +2621,8 @@ class Formula bottle_spec = stable_spec.bottle_specification - hash["rebuild"] = bottle_spec.rebuild if !compact_for_api || !bottle_spec.rebuild.zero? - hash["root_url"] = bottle_spec.root_url unless compact_for_api + hash["rebuild"] = bottle_spec.rebuild + hash["root_url"] = bottle_spec.root_url hash["files"] = {} bottle_spec.collector.each_tag do |tag| @@ -2710,11 +2633,9 @@ class Formula file_hash = {} file_hash["cellar"] = os_cellar - unless compact_for_api - filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild) - path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename) - file_hash["url"] = "#{bottle_spec.root_url}/#{path}" - end + filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild) + path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename) + file_hash["url"] = "#{bottle_spec.root_url}/#{path}" file_hash["sha256"] = checksum hash["files"][tag.to_sym] = file_hash diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 340f9c71a5..40b3ebec8e 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -1400,23 +1400,6 @@ class CoreTap < AbstractCoreTap end end end - - sig { returns(T::Hash[String, T.untyped]) } - def to_internal_api_hash - formulae_api_hash = formula_names.to_h do |name| - formula = Formulary.factory(name) - formula_hash = formula.to_hash_with_variations(hash_method: :to_internal_api_hash) - [name, formula_hash] - end - - { - "tap_git_head" => git_head, - "aliases" => alias_table, - "renames" => formula_renames, - "tap_migrations" => tap_migrations, - "formulae" => formulae_api_hash, - } - end end # A specialized {Tap} class for homebrew-cask. diff --git a/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb b/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb deleted file mode 100644 index 6e79402e99..0000000000 --- a/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb +++ /dev/null @@ -1,170 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "Internal Tap JSON -- Formula", type: :system do - include FileUtils - - let(:internal_tap_json) { File.read(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-core.json").chomp } - let(:tap_git_head) { "9977471165641744a829d3e494fa563407503297" } - - context "when generating JSON", :needs_macos do - before do - cp_r(TEST_FIXTURE_DIR/"internal_tap_json/homebrew-core", HOMEBREW_TAP_DIRECTORY/"homebrew") - - # NOTE: Symlinks can't be copied recursively so we create them manually here. - (HOMEBREW_TAP_DIRECTORY/"homebrew/homebrew-core").tap do |core_tap| - mkdir(core_tap/"Aliases") - ln_s(core_tap/"Formula/f/fennel.rb", core_tap/"Aliases/fennel-lang") - ln_s(core_tap/"Formula/p/ponyc.rb", core_tap/"Aliases/ponyc-lang") - end - end - - it "creates the expected hash" do - api_hash = CoreTap.instance.to_internal_api_hash - api_hash["tap_git_head"] = tap_git_head # tricky to mock - - expect(JSON.pretty_generate(api_hash)).to eq(internal_tap_json) - end - end - - context "when loading JSON" do - before do - ENV["HOMEBREW_INTERNAL_JSON_V3"] = "1" - ENV.delete("HOMEBREW_NO_INSTALL_FROM_API") - - allow(Homebrew::API).to receive(:fetch_json_api_file) - .with("internal/v3/homebrew-core.jws.json") - .and_return([JSON.parse(internal_tap_json, freeze: true), false]) - - # `Tap.tap_migration_oldnames` looks for renames in every - # tap so `CoreCaskTap.tap_migrations` gets called and tries to - # fetch stuff from the API. This just avoids errors. - allow(Homebrew::API).to receive(:fetch_json_api_file) - .with("cask_tap_migrations.jws.json", anything) - .and_return([{}, false]) - - # To allow `formula_names.txt` to be written to the cache. - (HOMEBREW_CACHE/"api").mkdir - end - - it "loads tap aliases" do - expect(CoreTap.instance.alias_table).to eq({ - "fennel-lang" => "fennel", - "ponyc-lang" => "ponyc", - }) - end - - it "loads formula renames" do - expect(CoreTap.instance.formula_renames).to eq({ - "advancemenu" => "advancemame", - "amtk" => "libgedit-amtk", - "annie" => "lux", - "antlr2" => "antlr@2", - "romanesco" => "fennel", - }) - end - - it "loads tap migrations" do - expect(CoreTap.instance.tap_migrations).to eq({ - "adobe-air-sdk" => "homebrew/cask", - "android-ndk" => "homebrew/cask", - "android-platform-tools" => "homebrew/cask", - "android-sdk" => "homebrew/cask", - "app-engine-go-32" => "homebrew/cask/google-cloud-sdk", - }) - end - - it "loads tap git head" do - expect(Homebrew::API::Formula.tap_git_head) - .to eq(tap_git_head) - end - - context "when loading formulae" do - let(:fennel_metadata) do - { - "dependencies" => ["lua"], - "desc" => "Lua Lisp Language", - "full_name" => "fennel", - "homepage" => "https://fennel-lang.org", - "license" => "MIT", - "name" => "fennel", - "ruby_source_path" => "Formula/f/fennel.rb", - "tap" => "homebrew/core", - "tap_git_head" => tap_git_head, - "versions" => { "bottle"=>true, "head"=>nil, "stable"=>"1.4.0" }, - "ruby_source_checksum" => { - "sha256" => "5856e655fd1cea11496d67bc27fb14fee5cfbdea63c697c3773c7f247581197d", - }, - } - end - - let(:ponyc_metadata) do - { - "desc" => "Object-oriented, actor-model, capabilities-secure programming language", - "full_name" => "ponyc", - "homepage" => "https://www.ponylang.io/", - "license" => "BSD-2-Clause", - "name" => "ponyc", - "ruby_source_path" => "Formula/p/ponyc.rb", - "tap" => "homebrew/core", - "tap_git_head" => tap_git_head, - "uses_from_macos" => [{ "llvm"=>[:build, :test] }, "zlib"], - "uses_from_macos_bounds" => [{}, {}], - "versions" => { "bottle"=>true, "head"=>nil, "stable"=>"0.58.1" }, - "ruby_source_checksum" => { - "sha256" => "81d51c25d18710191beb62f9f380bae3d878aad815a65ec1ee2a3b132c1fadb3", - }, - } - end - - let(:inko_metadata) do - { - "desc" => "Safe and concurrent object-oriented programming language", - "full_name" => "inko", - "homepage" => "https://inko-lang.org/", - "license" => "MPL-2.0", - "name" => "inko", - "ruby_source_path" => "Formula/i/inko.rb", - "tap" => "homebrew/core", - "tap_git_head" => tap_git_head, - "dependencies" => ["llvm@15", "zstd"], - "uses_from_macos" => ["libffi", "ruby"], - "uses_from_macos_bounds" => [{ since: :catalina }, { since: :sierra }], - "versions" => { "bottle"=>true, "head"=>"HEAD", "stable"=>"0.14.0" }, - "ruby_source_checksum" => { - "sha256" => "843f6b5652483b971c83876201d68c95d5f32e67e55a75ac7c95d68c4350aa1c", - }, - } - end - - it "loads fennel" do - fennel = Formulary.factory("fennel") - expect(fennel.to_hash).to include(**fennel_metadata) - end - - it "loads fennel from rename" do - fennel = Formulary.factory("romanesco") - expect(fennel.to_hash).to include(**fennel_metadata) - end - - it "loads fennel from alias" do - fennel = Formulary.factory("fennel-lang") - expect(fennel.to_hash).to include(**fennel_metadata) - end - - it "loads ponyc" do - ponyc = Formulary.factory("ponyc") - expect(ponyc.to_hash).to include(**ponyc_metadata) - end - - it "loads ponyc from alias" do - ponyc = Formulary.factory("ponyc-lang") - expect(ponyc.to_hash).to include(**ponyc_metadata) - end - - it "loads ink" do - inko = Formulary.factory("inko") - expect(inko.to_hash).to include(**inko_metadata) - end - end - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json deleted file mode 100644 index b704184417..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "tap_git_head": "9977471165641744a829d3e494fa563407503297", - "aliases": { - "fennel-lang": "fennel", - "ponyc-lang": "ponyc" - }, - "renames": { - "advancemenu": "advancemame", - "amtk": "libgedit-amtk", - "annie": "lux", - "antlr2": "antlr@2", - "romanesco": "fennel" - }, - "tap_migrations": { - "adobe-air-sdk": "homebrew/cask", - "android-ndk": "homebrew/cask", - "android-platform-tools": "homebrew/cask", - "android-sdk": "homebrew/cask", - "app-engine-go-32": "homebrew/cask/google-cloud-sdk" - }, - "formulae": { - "fennel": { - "desc": "Lua Lisp Language", - "license": "MIT", - "homepage": "https://fennel-lang.org", - "urls": { - "stable": { - "url": "https://github.com/bakpakin/Fennel/archive/refs/tags/1.4.0.tar.gz", - "checksum": "161eb7f17f86e95de09070214d042fb25372f71ad266f451431f3109e87965c7" - } - }, - "post_install_defined": false, - "ruby_source_path": "Formula/f/fennel.rb", - "ruby_source_sha256": "5856e655fd1cea11496d67bc27fb14fee5cfbdea63c697c3773c7f247581197d", - "version": "1.4.0", - "bottle": { - "files": { - "all": { - "cellar": ":any_skip_relocation", - "sha256": "f46028597883cbc38864c61bd3fa402da9cb90ce97415d51a7b5279bc17f7bd0" - } - } - }, - "dependencies": { - "lua": null - } - }, - "inko": { - "desc": "Safe and concurrent object-oriented programming language", - "license": "MPL-2.0", - "homepage": "https://inko-lang.org/", - "urls": { - "stable": { - "url": "https://releases.inko-lang.org/0.14.0.tar.gz", - "checksum": "4e2c82911d6026f76c42ccc164dc45b1b5e331db2e9557460d9319d682668e65" - }, - "head": { - "url": "https://github.com/inko-lang/inko.git", - "branch": "main" - } - }, - "post_install_defined": false, - "ruby_source_path": "Formula/i/inko.rb", - "ruby_source_sha256": "843f6b5652483b971c83876201d68c95d5f32e67e55a75ac7c95d68c4350aa1c", - "version": "0.14.0", - "bottle": { - "files": { - "arm64_sonoma": { - "cellar": ":any", - "sha256": "f6ff66fdfb3aac69263c32a8a29d13e9d28a80ae33807f34460e55d8c1b228c6" - }, - "arm64_ventura": { - "cellar": ":any", - "sha256": "be59d916d29d85bb8bc4474eb1c7d42a56236835c3c21b0e36fb9e9df0a25e6e" - }, - "arm64_monterey": { - "cellar": ":any", - "sha256": "9522c1f89b997dedaa3167ce4dbfa4a2d8c660acddecd32a99a515922e851b52" - }, - "sonoma": { - "cellar": ":any", - "sha256": "8e32d823ce9712ae2d5a2b9cbe0c9b727223098b3e66b003da087032be9f6818" - }, - "ventura": { - "cellar": ":any", - "sha256": "178865db1e2b60b4085a2465e8a3879794030a6d22c99b58c95e4bdf5418ef1b" - }, - "monterey": { - "cellar": ":any", - "sha256": "6ef924939c42d7bb2ec4e0d65cf293147a593f829619928d2580b419ec19b26c" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "sha256": "14a02c119990d6a17062290439ac74e6667b41dcb90b18cd90b36d2a09715e10" - } - } - }, - "dependencies": { - "coreutils": { - "tags": [ - "build" - ] - }, - "rust": { - "tags": [ - "build" - ] - }, - "llvm@15": null, - "zstd": null, - "libffi": { - "uses_from_macos": { - "since": "catalina" - } - }, - "ruby": { - "uses_from_macos": { - "since": "sierra" - } - } - }, - "head_dependencies": { - "coreutils": { - "tags": [ - "build" - ] - }, - "rust": { - "tags": [ - "build" - ] - }, - "llvm@15": null, - "zstd": null, - "libffi": { - "uses_from_macos": { - "since": "catalina" - } - }, - "ruby": { - "uses_from_macos": { - "since": "sierra" - } - } - } - }, - "ponyc": { - "desc": "Object-oriented, actor-model, capabilities-secure programming language", - "license": "BSD-2-Clause", - "homepage": "https://www.ponylang.io/", - "urls": { - "stable": { - "url": "https://github.com/ponylang/ponyc.git", - "tag": "0.58.1", - "revision": "fe3895eb4af494bf36d7690641bdfb5755db8350" - } - }, - "post_install_defined": false, - "ruby_source_path": "Formula/p/ponyc.rb", - "ruby_source_sha256": "81d51c25d18710191beb62f9f380bae3d878aad815a65ec1ee2a3b132c1fadb3", - "version": "0.58.1", - "bottle": { - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "sha256": "e3aecfcf02aea56d53d82691e2ad7a780f771023d7070271bfce96b17439a34d" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "sha256": "6ff83717191e16e4f852fb3be8f838afba312cc39e601bb5cebd2a618a328658" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "sha256": "25c91bce200583a96f4cea34f31393c8f10eadcab363cc7d4d864d15f5f97e25" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "sha256": "5f4c550ce33e2970e0ada18a409755fa62936181289a21c15582ff80343866b6" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "sha256": "f26c799f45013685da779bf2008ebe1907f9b3a93d5f260ce271a3f3b628da50" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "sha256": "1cff10d068b36b18b253d235424c4f5aef71ff9ee44f2522c4b041dd4383ec30" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "sha256": "ab49318d75eed3ee932c8e5add22f252ec0c852aad94945022877f926e93899f" - } - } - }, - "dependencies": { - "cmake": { - "tags": [ - "build" - ] - }, - "python@3.12": { - "tags": [ - "build" - ] - }, - "llvm": { - "tags": [ - "build", - "test" - ], - "uses_from_macos": null - }, - "zlib": { - "uses_from_macos": null - } - } - } - } -} diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb deleted file mode 100644 index c98a3f26c2..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/f/fennel.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Fennel < Formula - desc "Lua Lisp Language" - homepage "https://fennel-lang.org" - url "https://github.com/bakpakin/Fennel/archive/refs/tags/1.4.0.tar.gz" - sha256 "161eb7f17f86e95de09070214d042fb25372f71ad266f451431f3109e87965c7" - license "MIT" - - bottle do - sha256 cellar: :any_skip_relocation, all: "f46028597883cbc38864c61bd3fa402da9cb90ce97415d51a7b5279bc17f7bd0" - end - - depends_on "lua" - - def install - system "make" - bin.install "fennel" - - lua = Formula["lua"] - (share/"lua"/lua.version.major_minor).install "fennel.lua" - end - - test do - assert_match "hello, world!", shell_output("#{bin}/fennel -e '(print \"hello, world!\")'") - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb deleted file mode 100644 index 9f56b2d0f4..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/i/inko.rb +++ /dev/null @@ -1,45 +0,0 @@ -class Inko < Formula - desc "Safe and concurrent object-oriented programming language" - homepage "https://inko-lang.org/" - url "https://releases.inko-lang.org/0.14.0.tar.gz" - sha256 "4e2c82911d6026f76c42ccc164dc45b1b5e331db2e9557460d9319d682668e65" - license "MPL-2.0" - head "https://github.com/inko-lang/inko.git", branch: "main" - - bottle do - sha256 cellar: :any, arm64_sonoma: "f6ff66fdfb3aac69263c32a8a29d13e9d28a80ae33807f34460e55d8c1b228c6" - sha256 cellar: :any, arm64_ventura: "be59d916d29d85bb8bc4474eb1c7d42a56236835c3c21b0e36fb9e9df0a25e6e" - sha256 cellar: :any, arm64_monterey: "9522c1f89b997dedaa3167ce4dbfa4a2d8c660acddecd32a99a515922e851b52" - sha256 cellar: :any, sonoma: "8e32d823ce9712ae2d5a2b9cbe0c9b727223098b3e66b003da087032be9f6818" - sha256 cellar: :any, ventura: "178865db1e2b60b4085a2465e8a3879794030a6d22c99b58c95e4bdf5418ef1b" - sha256 cellar: :any, monterey: "6ef924939c42d7bb2ec4e0d65cf293147a593f829619928d2580b419ec19b26c" - sha256 cellar: :any_skip_relocation, x86_64_linux: "14a02c119990d6a17062290439ac74e6667b41dcb90b18cd90b36d2a09715e10" - end - - depends_on "coreutils" => :build - depends_on "rust" => :build - depends_on "llvm@15" - depends_on "zstd" - - uses_from_macos "libffi", since: :catalina - uses_from_macos "ruby", since: :sierra - - def install - ENV.prepend_path "PATH", Formula["coreutils"].opt_libexec/"gnubin" - system "make", "build", "PREFIX=#{prefix}" - system "make", "install", "PREFIX=#{prefix}" - end - - test do - (testpath/"hello.inko").write <<~EOS - import std.stdio.STDOUT - - class async Main { - fn async main { - STDOUT.new.print('Hello, world!') - } - } - EOS - assert_equal "Hello, world!\n", shell_output("#{bin}/inko run hello.inko") - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb deleted file mode 100644 index 63ad8bdfab..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/Formula/p/ponyc.rb +++ /dev/null @@ -1,60 +0,0 @@ -class Ponyc < Formula - desc "Object-oriented, actor-model, capabilities-secure programming language" - homepage "https://www.ponylang.io/" - url "https://github.com/ponylang/ponyc.git", - tag: "0.58.1", - revision: "fe3895eb4af494bf36d7690641bdfb5755db8350" - license "BSD-2-Clause" - - bottle do - sha256 cellar: :any_skip_relocation, arm64_sonoma: "e3aecfcf02aea56d53d82691e2ad7a780f771023d7070271bfce96b17439a34d" - sha256 cellar: :any_skip_relocation, arm64_ventura: "6ff83717191e16e4f852fb3be8f838afba312cc39e601bb5cebd2a618a328658" - sha256 cellar: :any_skip_relocation, arm64_monterey: "25c91bce200583a96f4cea34f31393c8f10eadcab363cc7d4d864d15f5f97e25" - sha256 cellar: :any_skip_relocation, sonoma: "5f4c550ce33e2970e0ada18a409755fa62936181289a21c15582ff80343866b6" - sha256 cellar: :any_skip_relocation, ventura: "f26c799f45013685da779bf2008ebe1907f9b3a93d5f260ce271a3f3b628da50" - sha256 cellar: :any_skip_relocation, monterey: "1cff10d068b36b18b253d235424c4f5aef71ff9ee44f2522c4b041dd4383ec30" - sha256 cellar: :any_skip_relocation, x86_64_linux: "ab49318d75eed3ee932c8e5add22f252ec0c852aad94945022877f926e93899f" - end - - depends_on "cmake" => :build - depends_on "python@3.12" => :build - - uses_from_macos "llvm" => [:build, :test] - uses_from_macos "zlib" - - # We use LLVM to work around an error while building bundled `google-benchmark` with GCC - fails_with :gcc do - cause <<-EOS - .../src/gbenchmark/src/thread_manager.h:50:31: error: expected ')' before '(' token - 50 | GUARDED_BY(GetBenchmarkMutex()) Result results; - | ^ - EOS - end - - def install - inreplace "CMakeLists.txt", "PONY_COMPILER=\"${CMAKE_C_COMPILER}\"", "PONY_COMPILER=\"#{ENV.cc}\"" if OS.linux? - - ENV["CMAKE_FLAGS"] = "-DCMAKE_OSX_SYSROOT=#{MacOS.sdk_path}" if OS.mac? - ENV["MAKEFLAGS"] = "build_flags=-j#{ENV.make_jobs}" - - system "make", "libs" - system "make", "configure" - system "make", "build" - system "make", "install", "DESTDIR=#{prefix}" - end - - test do - # ENV["CC"] returns llvm_clang, which does not work in a test block. - ENV.clang - - system "#{bin}/ponyc", "-rexpr", "#{prefix}/packages/stdlib" - - (testpath/"test/main.pony").write <<~EOS - actor Main - new create(env: Env) => - env.out.print("Hello World!") - EOS - system "#{bin}/ponyc", "test" - assert_equal "Hello World!", shell_output("./test1").strip - end -end diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json deleted file mode 100644 index ff923dc297..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/formula_renames.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "advancemenu": "advancemame", - "amtk": "libgedit-amtk", - "annie": "lux", - "antlr2": "antlr@2", - "romanesco": "fennel" -} diff --git a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json b/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json deleted file mode 100644 index 2bdd3fd5c6..0000000000 --- a/Library/Homebrew/test/support/fixtures/internal_tap_json/homebrew-core/tap_migrations.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "adobe-air-sdk": "homebrew/cask", - "android-ndk": "homebrew/cask", - "android-platform-tools": "homebrew/cask", - "android-sdk": "homebrew/cask", - "app-engine-go-32": "homebrew/cask/google-cloud-sdk" -} From eead014ceb02942c529cf89e2832a11dead420ea Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Thu, 6 Feb 2025 20:11:30 -0800 Subject: [PATCH 03/12] cask/cask: remove unnecessary tests The `compact:` option got removed from `Cask::Cask#artifacts_list` in 042d6cc97e886c6f2817500083cda6c856b5350a. --- Library/Homebrew/test/cask/cask_spec.rb | 33 ------------------------- 1 file changed, 33 deletions(-) diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb index 038316a682..30d2059f96 100644 --- a/Library/Homebrew/test/cask/cask_spec.rb +++ b/Library/Homebrew/test/cask/cask_spec.rb @@ -236,23 +236,6 @@ RSpec.describe Cask::Cask, :cask do expect(cask.artifacts_list).to eq(expected_artifacts) end - it "skips flight blocks when compact is true" do - expected_artifacts = [ - { uninstall: [{ - rmdir: "#{TEST_TMPDIR}/empty_directory_path", - trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"], - }] }, - { pkg: ["ManyArtifacts/ManyArtifacts.pkg"] }, - { app: ["ManyArtifacts/ManyArtifacts.app"] }, - { zap: [{ - rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"], - trash: "~/Library/Logs/ManyArtifacts.log", - }] }, - ] - - expect(cask.artifacts_list(compact: true)).to eq(expected_artifacts) - end - it "returns only uninstall artifacts when uninstall_only is true" do expected_artifacts = [ { uninstall_preflight: nil }, @@ -270,22 +253,6 @@ RSpec.describe Cask::Cask, :cask do expect(cask.artifacts_list(uninstall_only: true)).to eq(expected_artifacts) end - - it "skips flight blocks and returns only uninstall artifacts when compact and uninstall_only are true" do - expected_artifacts = [ - { uninstall: [{ - rmdir: "#{TEST_TMPDIR}/empty_directory_path", - trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"], - }] }, - { app: ["ManyArtifacts/ManyArtifacts.app"] }, - { zap: [{ - rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"], - trash: "~/Library/Logs/ManyArtifacts.log", - }] }, - ] - - expect(cask.artifacts_list(compact: true, uninstall_only: true)).to eq(expected_artifacts) - end end describe "#uninstall_flight_blocks?" do From f916f27d822f305cda06c6fd64d7a1daf08def2f Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Thu, 6 Feb 2025 21:04:08 -0800 Subject: [PATCH 04/12] remove all references to JSON v3 The logic has now been removed in previous commits. This just removes some references to the `HOMEBREW_INTERNAL_JSON_V3` environment variable along with reverting the changes to the `Cachable` class that were originally added in bd72ec812c3ed656dfcf8e24f77df142a1fe9cc1. --- Library/Homebrew/api.rb | 5 -- Library/Homebrew/api/formula.rb | 34 +++----- Library/Homebrew/dev-cmd/tests.rb | 1 - Library/Homebrew/extend/cachable.rb | 10 +-- Library/Homebrew/formulary.rb | 117 ++++++++-------------------- Library/Homebrew/tap.rb | 2 - 6 files changed, 45 insertions(+), 124 deletions(-) diff --git a/Library/Homebrew/api.rb b/Library/Homebrew/api.rb index c88ef32fb5..7dcc3c696d 100644 --- a/Library/Homebrew/api.rb +++ b/Library/Homebrew/api.rb @@ -184,11 +184,6 @@ module Homebrew Tap.fetch(org, repo) end - - sig { returns(T::Boolean) } - def self.internal_json_v3? - ENV["HOMEBREW_INTERNAL_JSON_V3"].present? - end end sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) } diff --git a/Library/Homebrew/api/formula.rb b/Library/Homebrew/api/formula.rb index d905bb4c85..1bcd622da4 100644 --- a/Library/Homebrew/api/formula.rb +++ b/Library/Homebrew/api/formula.rb @@ -11,7 +11,6 @@ module Homebrew extend Cachable DEFAULT_API_FILENAME = "formula.jws.json" - INTERNAL_V3_API_FILENAME = "internal/v3/homebrew-core.jws.json" private_class_method :cache @@ -43,33 +42,24 @@ module Homebrew sig { returns(Pathname) } def self.cached_json_file_path - if Homebrew::API.internal_json_v3? - HOMEBREW_CACHE_API/INTERNAL_V3_API_FILENAME - else - HOMEBREW_CACHE_API/DEFAULT_API_FILENAME - end + HOMEBREW_CACHE_API/DEFAULT_API_FILENAME end sig { returns(T::Boolean) } def self.download_and_cache_data! - if Homebrew::API.internal_json_v3? - json_formulae, updated = Homebrew::API.fetch_json_api_file INTERNAL_V3_API_FILENAME - overwrite_cache! T.cast(json_formulae, T::Hash[String, T.untyped]) - else - json_formulae, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME + json_formulae, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME - cache["aliases"] = {} - cache["renames"] = {} - cache["formulae"] = json_formulae.to_h do |json_formula| - json_formula["aliases"].each do |alias_name| - cache["aliases"][alias_name] = json_formula["name"] - end - (json_formula["oldnames"] || [json_formula["oldname"]].compact).each do |oldname| - cache["renames"][oldname] = json_formula["name"] - end - - [json_formula["name"], json_formula.except("name")] + cache["aliases"] = {} + cache["renames"] = {} + cache["formulae"] = json_formulae.to_h do |json_formula| + json_formula["aliases"].each do |alias_name| + cache["aliases"][alias_name] = json_formula["name"] end + (json_formula["oldnames"] || [json_formula["oldname"]].compact).each do |oldname| + cache["renames"][oldname] = json_formula["name"] + end + + [json_formula["name"], json_formula.except("name")] end updated diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index ac69184300..697a2a0684 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -219,7 +219,6 @@ module Homebrew # TODO: remove this and fix tests when possible. ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1" - ENV.delete("HOMEBREW_INTERNAL_JSON_V3") ENV["USER"] ||= system_command!("id", args: ["-nu"]).stdout.chomp diff --git a/Library/Homebrew/extend/cachable.rb b/Library/Homebrew/extend/cachable.rb index d3d2794bc3..b124c638e6 100644 --- a/Library/Homebrew/extend/cachable.rb +++ b/Library/Homebrew/extend/cachable.rb @@ -7,16 +7,8 @@ module Cachable @cache ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped])) end - # NOTE: We overwrite here instead of using `Hash#clear` to handle frozen hashes. sig { void } def clear_cache - overwrite_cache!({}) - end - - private - - sig { params(hash: T::Hash[T.untyped, T.untyped]).void } - def overwrite_cache!(hash) - @cache = hash + cache.clear end end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 0529671e9a..5dd9e2ec4c 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -213,71 +213,38 @@ module Formulary end end - add_deps = if Homebrew::API.internal_json_v3? - lambda do |deps| - T.bind(self, SoftwareSpec) + add_deps = lambda do |spec| + T.bind(self, SoftwareSpec) - deps&.each do |name, info| - tags = case info&.dig("tags") - in Array => tag_list - tag_list.map(&:to_sym) - in String => tag - tag.to_sym - else - nil - end + dep_json = json_formula.fetch("#{spec}_dependencies", json_formula) - if info&.key?("uses_from_macos") - bounds = info["uses_from_macos"].dup || {} - bounds.deep_transform_keys!(&:to_sym) - bounds.deep_transform_values!(&:to_sym) + dep_json["dependencies"]&.each do |dep| + # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux + next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && + !Homebrew::SimulateSystem.simulating_or_running_on_macos? - if tags - uses_from_macos name => tags, **bounds - else - uses_from_macos name, **bounds - end - elsif tags - depends_on name => tags - else - depends_on name - end - end + depends_on dep end - else - lambda do |spec| - T.bind(self, SoftwareSpec) - dep_json = json_formula.fetch("#{spec}_dependencies", json_formula) - - dep_json["dependencies"]&.each do |dep| + [:build, :test, :recommended, :optional].each do |type| + dep_json["#{type}_dependencies"]&.each do |dep| # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && !Homebrew::SimulateSystem.simulating_or_running_on_macos? - depends_on dep + depends_on dep => type end + end - [:build, :test, :recommended, :optional].each do |type| - dep_json["#{type}_dependencies"]&.each do |dep| - # Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux - next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) && - !Homebrew::SimulateSystem.simulating_or_running_on_macos? + dep_json["uses_from_macos"]&.each_with_index do |dep, index| + bounds = dep_json.fetch("uses_from_macos_bounds", [])[index].dup || {} + bounds.deep_transform_keys!(&:to_sym) + bounds.deep_transform_values!(&:to_sym) - depends_on dep => type - end - end - - dep_json["uses_from_macos"]&.each_with_index do |dep, index| - bounds = dep_json.fetch("uses_from_macos_bounds", [])[index].dup || {} - bounds.deep_transform_keys!(&:to_sym) - bounds.deep_transform_values!(&:to_sym) - - if dep.is_a?(Hash) - uses_from_macos dep.deep_transform_values(&:to_sym).merge(bounds) - else - uses_from_macos dep, bounds - end + if dep.is_a?(Hash) + uses_from_macos dep.deep_transform_values(&:to_sym).merge(bounds) + else + uses_from_macos dep, bounds end end end @@ -299,15 +266,10 @@ module Formulary using: urls_stable["using"]&.to_sym, }.compact url urls_stable["url"], **url_spec - version Homebrew::API.internal_json_v3? ? json_formula["version"] : json_formula["versions"]["stable"] + version json_formula["versions"]["stable"] sha256 urls_stable["checksum"] if urls_stable["checksum"].present? - if Homebrew::API.internal_json_v3? - instance_exec(json_formula["dependencies"], &add_deps) - else - instance_exec(:stable, &add_deps) - end - + instance_exec(:stable, &add_deps) requirements[:stable]&.each do |req| depends_on req end @@ -322,23 +284,14 @@ module Formulary }.compact url urls_head["url"], **url_spec - if Homebrew::API.internal_json_v3? - instance_exec(json_formula["head_dependencies"], &add_deps) - else - instance_exec(:head, &add_deps) - end - + instance_exec(:head, &add_deps) requirements[:head]&.each do |req| depends_on req end end end - bottles_stable = if Homebrew::API.internal_json_v3? - json_formula["bottle"] - else - json_formula["bottle"]["stable"] - end.presence + bottles_stable = json_formula["bottle"]["stable"].presence if bottles_stable bottle do @@ -426,26 +379,20 @@ module Formulary .gsub(HOMEBREW_HOME_PLACEHOLDER, Dir.home) end - @tap_git_head_string = if Homebrew::API.internal_json_v3? - Homebrew::API::Formula.tap_git_head - else - json_formula["tap_git_head"] - end + @tap_git_head_string = json_formula["tap_git_head"] def tap_git_head self.class.instance_variable_get(:@tap_git_head_string) end - unless Homebrew::API.internal_json_v3? - @oldnames_array = json_formula["oldnames"] || [json_formula["oldname"]].compact - def oldnames - self.class.instance_variable_get(:@oldnames_array) - end + @oldnames_array = json_formula["oldnames"] || [json_formula["oldname"]].compact + def oldnames + self.class.instance_variable_get(:@oldnames_array) + end - @aliases_array = json_formula.fetch("aliases", []) - def aliases - self.class.instance_variable_get(:@aliases_array) - end + @aliases_array = json_formula.fetch("aliases", []) + def aliases + self.class.instance_variable_get(:@aliases_array) end @versioned_formulae_array = json_formula.fetch("versioned_formulae", []) diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 40b3ebec8e..a00f95b9e1 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -1307,8 +1307,6 @@ class CoreTap < AbstractCoreTap @tap_migrations ||= if Homebrew::EnvConfig.no_install_from_api? ensure_installed! super - elsif Homebrew::API.internal_json_v3? - Homebrew::API::Formula.tap_migrations else migrations, = Homebrew::API.fetch_json_api_file "formula_tap_migrations.jws.json", stale_seconds: TAP_MIGRATIONS_STALE_SECONDS From 17bfef29f2d6eeb72569c13df9a6801ecf60e9a2 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Fri, 7 Feb 2025 13:23:52 +0000 Subject: [PATCH 05/12] brew.sh: improve HOMEBREW_FORCE_BREW_WRAPPER error message Rather than just explaining what is happening, let's explain a bit more why this is happening and how to fix it. --- Library/Homebrew/brew.sh | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index 8f936dfc55..ee48f55738 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -186,27 +186,36 @@ source "${HOMEBREW_LIBRARY}/Homebrew/utils/helpers.sh" # (i.e. not defined above this line e.g. formulae or --cellar). if [[ -z "${HOMEBREW_NO_FORCE_BREW_WRAPPER:-}" && -n "${HOMEBREW_FORCE_BREW_WRAPPER:-}" ]] then + HOMEBREW_FORCE_BREW_WRAPPER_WITHOUT_BREW="${HOMEBREW_FORCE_BREW_WRAPPER%/brew}" if [[ -z "${HOMEBREW_BREW_WRAPPER:-}" ]] then odie < Date: Tue, 4 Feb 2025 10:30:16 -0500 Subject: [PATCH 06/12] livecheck: Add support for POST requests livecheck currently doesn't support `POST` requests but it wasn't entirely clear how best to handle that. I initially approached it as a `Post` strategy but unfortunately that would have required us to handle response body parsing (e.g., JSON, XML, etc.) in some fashion. We could borrow some of the logic from related strategies but we would still be stuck having to update `Post` whenever we add a strategy for a new format. Instead, this implements `POST` support by borrowing ideas from the `using: :post` and `data` `url` options found in formulae. This uses a `post_form` option to handle form data and `post_json` to handle JSON data, encoding the hash argument for each into the appropriate format. The presence of either option means that curl will use a `POST` request. With this approach, we can make a `POST` request using any strategy that calls `Strategy::page_headers` or `::page_content` (directly or indirectly) and everything else works the same as usual. The only change needed in related strategies was to pass the options through to the `Strategy` methods. For example, if we need to parse a JSON response from a `POST` request, we add a `post_data` or `post_json` hash to the `livecheck` block `url` and use `strategy :json` with a `strategy` block. This leans on existing patterns that we're already familiar with and shouldn't require any notable maintenance burden when adding new strategies, so it seems like a better approach than a `Post` strategy. --- Library/Homebrew/livecheck.rb | 42 ++-- Library/Homebrew/livecheck/livecheck.rb | 8 + Library/Homebrew/livecheck/strategy.rb | 62 +++++- Library/Homebrew/livecheck/strategy.rbi | 9 + Library/Homebrew/livecheck/strategy/crate.rb | 12 +- .../livecheck/strategy/header_match.rb | 10 +- Library/Homebrew/livecheck/strategy/json.rb | 12 +- .../Homebrew/livecheck/strategy/page_match.rb | 12 +- .../Homebrew/livecheck/strategy/sparkle.rb | 17 +- Library/Homebrew/livecheck/strategy/xml.rb | 12 +- Library/Homebrew/livecheck/strategy/yaml.rb | 12 +- .../Homebrew/test/livecheck/strategy_spec.rb | 203 ++++++++++++++++++ Library/Homebrew/test/livecheck_spec.rb | 38 +++- docs/Brew-Livecheck.md | 18 ++ 14 files changed, 418 insertions(+), 49 deletions(-) create mode 100644 Library/Homebrew/livecheck/strategy.rbi diff --git a/Library/Homebrew/livecheck.rb b/Library/Homebrew/livecheck.rb index 0dc87c91b6..ebfcc7ec52 100644 --- a/Library/Homebrew/livecheck.rb +++ b/Library/Homebrew/livecheck.rb @@ -20,6 +20,14 @@ class Livecheck sig { returns(T.nilable(String)) } attr_reader :skip_msg + # A block used by strategies to identify version information. + sig { returns(T.nilable(Proc)) } + attr_reader :strategy_block + + # Options used by `Strategy` methods to modify `curl` behavior. + sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) } + attr_reader :url_options + sig { params(package_or_resource: T.any(Cask::Cask, T.class_of(Formula), Resource)).void } def initialize(package_or_resource) @package_or_resource = package_or_resource @@ -32,6 +40,7 @@ class Livecheck @strategy_block = T.let(nil, T.nilable(Proc)) @throttle = T.let(nil, T.nilable(Integer)) @url = T.let(nil, T.any(NilClass, String, Symbol)) + @url_options = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped])) end # Sets the `@referenced_cask_name` instance variable to the provided `String` @@ -134,9 +143,6 @@ class Livecheck end end - sig { returns(T.nilable(Proc)) } - attr_reader :strategy_block - # Sets the `@throttle` instance variable to the provided `Integer` or returns # the `@throttle` instance variable when no argument is provided. sig { @@ -158,13 +164,22 @@ class Livecheck # `@url` instance variable when no argument is provided. The argument can be # a `String` (a URL) or a supported `Symbol` corresponding to a URL in the # formula/cask/resource (e.g. `:stable`, `:homepage`, `:head`, `:url`). + # Any options provided to the method are passed through to `Strategy` methods + # (`page_headers`, `page_content`). sig { params( # URL to check for version information. - url: T.any(String, Symbol), + url: T.any(String, Symbol), + post_form: T.nilable(T::Hash[T.any(String, Symbol), String]), + post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), ).returns(T.nilable(T.any(String, Symbol))) } - def url(url = T.unsafe(nil)) + def url(url = T.unsafe(nil), post_form: nil, post_json: nil) + raise ArgumentError, "Only use `post_form` or `post_json`, not both" if post_form && post_json + + options = { post_form:, post_json: }.compact + @url_options = options if options.present? + case url when nil @url @@ -183,14 +198,15 @@ class Livecheck sig { returns(T::Hash[String, T.untyped]) } def to_hash { - "cask" => @referenced_cask_name, - "formula" => @referenced_formula_name, - "regex" => @regex, - "skip" => @skip, - "skip_msg" => @skip_msg, - "strategy" => @strategy, - "throttle" => @throttle, - "url" => @url, + "cask" => @referenced_cask_name, + "formula" => @referenced_formula_name, + "regex" => @regex, + "skip" => @skip, + "skip_msg" => @skip_msg, + "strategy" => @strategy, + "throttle" => @throttle, + "url" => @url, + "url_options" => @url_options, } end end diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index 609e830941..4c0832dec0 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -614,6 +614,7 @@ module Homebrew referenced_livecheck = referenced_formula_or_cask&.livecheck livecheck_url = livecheck.url || referenced_livecheck&.url + livecheck_url_options = livecheck.url_options || referenced_livecheck&.url_options livecheck_regex = livecheck.regex || referenced_livecheck&.regex livecheck_strategy = livecheck.strategy || referenced_livecheck&.strategy livecheck_strategy_block = livecheck.strategy_block || referenced_livecheck&.strategy_block @@ -673,6 +674,7 @@ module Homebrew elsif original_url.present? && original_url != "None" puts "URL: #{original_url}" end + puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present? puts "URL (processed): #{url}" if url != original_url if strategies.present? && verbose puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}" @@ -701,6 +703,7 @@ module Homebrew strategy_args = { regex: livecheck_regex, + url_options: livecheck_url_options, homebrew_curl:, } # TODO: Set `cask`/`url` args based on the presence of the keyword arg @@ -807,6 +810,7 @@ module Homebrew version_info[:meta][:url][:strategy] = strategy_data[:url] end version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url] + version_info[:meta][:url][:options] = livecheck_url_options if livecheck_url_options.present? version_info[:meta][:url][:homebrew_curl] = homebrew_curl if homebrew_curl.present? end version_info[:meta][:strategy] = strategy_name if strategy.present? @@ -856,6 +860,7 @@ module Homebrew livecheck = resource.livecheck livecheck_reference = livecheck.formula livecheck_url = livecheck.url + livecheck_url_options = livecheck.url_options livecheck_regex = livecheck.regex livecheck_strategy = livecheck.strategy livecheck_strategy_block = livecheck.strategy_block @@ -893,6 +898,7 @@ module Homebrew elsif original_url.present? && original_url != "None" puts "URL: #{original_url}" end + puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present? puts "URL (processed): #{url}" if url != original_url if strategies.present? && verbose puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}" @@ -923,6 +929,7 @@ module Homebrew strategy_args = { url:, regex: livecheck_regex, + url_options: livecheck_url_options, homebrew_curl: false, }.compact @@ -1012,6 +1019,7 @@ module Homebrew resource_version_info[:meta][:url][:strategy] = strategy_data[:url] end resource_version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data&.dig(:final_url) + resource_version_info[:meta][:url][:options] = livecheck_url_options if livecheck_url_options.present? end resource_version_info[:meta][:strategy] = strategy_name if strategy.present? if strategies.present? diff --git a/Library/Homebrew/livecheck/strategy.rb b/Library/Homebrew/livecheck/strategy.rb index be885df754..950c8578e7 100644 --- a/Library/Homebrew/livecheck/strategy.rb +++ b/Library/Homebrew/livecheck/strategy.rb @@ -166,20 +166,59 @@ module Homebrew end end + # Creates `curl` `--data` or `--json` arguments (for `POST` requests`) + # from related `livecheck` block `url` options. + # + # @param post_form [Hash, nil] data to encode using `URI::encode_www_form` + # @param post_json [Hash, nil] data to encode using `JSON::generate` + # @return [Array] + sig { + params( + post_form: T.nilable(T::Hash[T.any(String, Symbol), String]), + post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), + ).returns(T::Array[String]) + } + def post_args(post_form: nil, post_json: nil) + if post_form.present? + require "uri" + ["--data", URI.encode_www_form(post_form)] + elsif post_json.present? + require "json" + ["--json", JSON.generate(post_json)] + else + [] + end + end + # Collects HTTP response headers, starting with the provided URL. # Redirections will be followed and all the response headers are # collected into an array of hashes. # # @param url [String] the URL to fetch + # @param url_options [Hash] options to modify curl behavior # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Array] - sig { params(url: String, homebrew_curl: T::Boolean).returns(T::Array[T::Hash[String, String]]) } - def self.page_headers(url, homebrew_curl: false) + sig { + params( + url: String, + url_options: T::Hash[Symbol, T.untyped], + homebrew_curl: T::Boolean, + ).returns(T::Array[T::Hash[String, String]]) + } + def self.page_headers(url, url_options: {}, homebrew_curl: false) headers = [] + if url_options[:post_form].present? || url_options[:post_json].present? + curl_post_args = ["--request", "POST", *post_args( + post_form: url_options[:post_form], + post_json: url_options[:post_json], + )] + end + [:default, :browser].each do |user_agent| begin parsed_output = curl_headers( + *curl_post_args, "--max-redirs", MAX_REDIRECTIONS.to_s, url, @@ -205,13 +244,28 @@ module Homebrew # array with the error message instead. # # @param url [String] the URL of the content to check + # @param url_options [Hash] options to modify curl behavior # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Hash] - sig { params(url: String, homebrew_curl: T::Boolean).returns(T::Hash[Symbol, T.untyped]) } - def self.page_content(url, homebrew_curl: false) + sig { + params( + url: String, + url_options: T::Hash[Symbol, T.untyped], + homebrew_curl: T::Boolean, + ).returns(T::Hash[Symbol, T.untyped]) + } + def self.page_content(url, url_options: {}, homebrew_curl: false) + if url_options[:post_form].present? || url_options[:post_json].present? + curl_post_args = ["--request", "POST", *post_args( + post_form: url_options[:post_form], + post_json: url_options[:post_json], + )] + end + stderr = T.let(nil, T.nilable(String)) [:default, :browser].each do |user_agent| stdout, stderr, status = curl_output( + *curl_post_args, *PAGE_CONTENT_CURL_ARGS, url, **DEFAULT_CURL_OPTIONS, use_homebrew_curl: homebrew_curl || !curl_supports_fail_with_body?, diff --git a/Library/Homebrew/livecheck/strategy.rbi b/Library/Homebrew/livecheck/strategy.rbi new file mode 100644 index 0000000000..218530f2c3 --- /dev/null +++ b/Library/Homebrew/livecheck/strategy.rbi @@ -0,0 +1,9 @@ +# typed: strict + +module Homebrew + module Livecheck + module Strategy + include Kernel + end + end +end diff --git a/Library/Homebrew/livecheck/strategy/crate.rb b/Library/Homebrew/livecheck/strategy/crate.rb index 7c6f942353..e37e3da27d 100644 --- a/Library/Homebrew/livecheck/strategy/crate.rb +++ b/Library/Homebrew/livecheck/strategy/crate.rb @@ -81,11 +81,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) match_data = { matches: {}, regex:, url: } match_data[:cached] = true if provided_content.is_a?(String) @@ -97,7 +97,13 @@ module Homebrew content = if provided_content provided_content else - match_data.merge!(Strategy.page_content(match_data[:url], homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + match_data[:url], + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data unless content diff --git a/Library/Homebrew/livecheck/strategy/header_match.rb b/Library/Homebrew/livecheck/strategy/header_match.rb index 460f8025f7..e6f2c3a3aa 100644 --- a/Library/Homebrew/livecheck/strategy/header_match.rb +++ b/Library/Homebrew/livecheck/strategy/header_match.rb @@ -74,14 +74,18 @@ module Homebrew url: String, regex: T.nilable(Regexp), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, homebrew_curl: false, **unused, &block) match_data = { matches: {}, regex:, url: } - headers = Strategy.page_headers(url, homebrew_curl:) + headers = Strategy.page_headers( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ) # Merge the headers from all responses into one hash merged_headers = headers.reduce(&:merge) diff --git a/Library/Homebrew/livecheck/strategy/json.rb b/Library/Homebrew/livecheck/strategy/json.rb index b8f05e29ae..e7ac5e51d4 100644 --- a/Library/Homebrew/livecheck/strategy/json.rb +++ b/Library/Homebrew/livecheck/strategy/json.rb @@ -102,11 +102,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank? match_data = { matches: {}, regex:, url: } @@ -116,7 +116,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/page_match.rb b/Library/Homebrew/livecheck/strategy/page_match.rb index 8f68676b54..7f2daaae1d 100644 --- a/Library/Homebrew/livecheck/strategy/page_match.rb +++ b/Library/Homebrew/livecheck/strategy/page_match.rb @@ -85,11 +85,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) if regex.blank? && block.blank? raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a regex or `strategy` block" end @@ -101,7 +101,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/sparkle.rb b/Library/Homebrew/livecheck/strategy/sparkle.rb index de16eaec18..cf5c56a3c6 100644 --- a/Library/Homebrew/livecheck/strategy/sparkle.rb +++ b/Library/Homebrew/livecheck/strategy/sparkle.rb @@ -217,13 +217,13 @@ module Homebrew # @return [Hash] sig { params( - url: String, - regex: T.nilable(Regexp), - _unused: T.untyped, - block: T.nilable(Proc), + url: String, + regex: T.nilable(Regexp), + unused: T.untyped, + block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, **_unused, &block) + def self.find_versions(url:, regex: nil, **unused, &block) if regex.present? && block.blank? raise ArgumentError, "#{Utils.demodulize(T.must(name))} only supports a regex when using a `strategy` block" @@ -231,7 +231,12 @@ module Homebrew match_data = { matches: {}, regex:, url: } - match_data.merge!(Strategy.page_content(url)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + ), + ) content = match_data.delete(:content) return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/xml.rb b/Library/Homebrew/livecheck/strategy/xml.rb index e8b741e9ff..f0dfffa13e 100644 --- a/Library/Homebrew/livecheck/strategy/xml.rb +++ b/Library/Homebrew/livecheck/strategy/xml.rb @@ -142,11 +142,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank? match_data = { matches: {}, regex:, url: } @@ -156,7 +156,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/livecheck/strategy/yaml.rb b/Library/Homebrew/livecheck/strategy/yaml.rb index 371128a7a9..e3feaf02e5 100644 --- a/Library/Homebrew/livecheck/strategy/yaml.rb +++ b/Library/Homebrew/livecheck/strategy/yaml.rb @@ -102,11 +102,11 @@ module Homebrew regex: T.nilable(Regexp), provided_content: T.nilable(String), homebrew_curl: T::Boolean, - _unused: T.untyped, + unused: T.untyped, block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **unused, &block) raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank? match_data = { matches: {}, regex:, url: } @@ -116,7 +116,13 @@ module Homebrew match_data[:cached] = true provided_content else - match_data.merge!(Strategy.page_content(url, homebrew_curl:)) + match_data.merge!( + Strategy.page_content( + url, + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, + ), + ) match_data[:content] end return match_data if content.blank? diff --git a/Library/Homebrew/test/livecheck/strategy_spec.rb b/Library/Homebrew/test/livecheck/strategy_spec.rb index 7ed67125bd..95f5222fef 100644 --- a/Library/Homebrew/test/livecheck/strategy_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy_spec.rb @@ -5,6 +5,102 @@ require "livecheck/strategy" RSpec.describe Homebrew::Livecheck::Strategy do subject(:strategy) { described_class } + let(:url) { "https://brew.sh/" } + let(:redirection_url) { "https://brew.sh/redirection" } + + let(:post_hash) do + { + "empty" => "", + "boolean" => "true", + "number" => "1", + "string" => "a + b = c", + } + end + let(:post_hash_symbol_keys) do + { + empty: "", + boolean: "true", + number: "1", + string: "a + b = c", + } + end + let(:form_string) { "empty=&boolean=true&number=1&string=a+%2B+b+%3D+c" } + let(:json_string) { '{"empty":"","boolean":"true","number":"1","string":"a + b = c"}' } + + let(:response_hash) do + response_hash = {} + + response_hash[:ok] = { + status_code: "200", + status_text: "OK", + headers: { + "cache-control" => "max-age=604800", + "content-type" => "text/html; charset=UTF-8", + "date" => "Wed, 1 Jan 2020 01:23:45 GMT", + "expires" => "Wed, 31 Jan 2020 01:23:45 GMT", + "last-modified" => "Thu, 1 Jan 2019 01:23:45 GMT", + "content-length" => "123", + }, + } + + response_hash[:redirection] = { + status_code: "301", + status_text: "Moved Permanently", + headers: { + "cache-control" => "max-age=604800", + "content-type" => "text/html; charset=UTF-8", + "date" => "Wed, 1 Jan 2020 01:23:45 GMT", + "expires" => "Wed, 31 Jan 2020 01:23:45 GMT", + "last-modified" => "Thu, 1 Jan 2019 01:23:45 GMT", + "content-length" => "123", + "location" => redirection_url, + }, + } + + response_hash + end + + let(:body) do + <<~HTML + + + + + Thank you! + + +

Download

+

This download link could have been made publicly available in a reasonable fashion but we appreciate that you jumped through the hoops that we carefully set up!: Example v1.2.3

+

The current legacy version is: Example v0.1.2

+ + + HTML + end + + let(:response_text) do + response_text = {} + + response_text[:ok] = <<~EOS + HTTP/1.1 #{response_hash[:ok][:status_code]} #{response_hash[:ok][:status_text]}\r + Cache-Control: #{response_hash[:ok][:headers]["cache-control"]}\r + Content-Type: #{response_hash[:ok][:headers]["content-type"]}\r + Date: #{response_hash[:ok][:headers]["date"]}\r + Expires: #{response_hash[:ok][:headers]["expires"]}\r + Last-Modified: #{response_hash[:ok][:headers]["last-modified"]}\r + Content-Length: #{response_hash[:ok][:headers]["content-length"]}\r + \r + #{body.rstrip} + EOS + + response_text[:redirection_to_ok] = response_text[:ok].sub( + "HTTP/1.1 #{response_hash[:ok][:status_code]} #{response_hash[:ok][:status_text]}\r", + "HTTP/1.1 #{response_hash[:redirection][:status_code]} #{response_hash[:redirection][:status_text]}\r\n" \ + "Location: #{response_hash[:redirection][:headers]["location"]}\r", + ) + + response_text + end + describe "::from_symbol" do it "returns the Strategy module represented by the Symbol argument" do expect(strategy.from_symbol(:page_match)).to eq(Homebrew::Livecheck::Strategy::PageMatch) @@ -30,6 +126,113 @@ RSpec.describe Homebrew::Livecheck::Strategy do end end + describe "::post_args" do + it "returns an array including `--data` and an encoded form data string" do + expect(strategy.post_args(post_form: post_hash)).to eq(["--data", form_string]) + expect(strategy.post_args(post_form: post_hash_symbol_keys)).to eq(["--data", form_string]) + + # If both `post_form` and `post_json` are present, only `post_form` will + # be used. + expect(strategy.post_args(post_form: post_hash, post_json: post_hash)).to eq(["--data", form_string]) + end + + it "returns an array including `--json` and a JSON string" do + expect(strategy.post_args(post_json: post_hash)).to eq(["--json", json_string]) + expect(strategy.post_args(post_json: post_hash_symbol_keys)).to eq(["--json", json_string]) + end + + it "returns an empty array if `post_form` value is blank" do + expect(strategy.post_args(post_form: {})).to eq([]) + end + + it "returns an empty array if `post_json` value is blank" do + expect(strategy.post_args(post_json: {})).to eq([]) + end + + it "returns an empty array if hash argument doesn't have a `post_form` or `post_json` value" do + expect(strategy.post_args).to eq([]) + end + end + + describe "::page_headers" do + let(:responses) { [response_hash[:ok]] } + + it "returns headers from fetched content" do + allow(strategy).to receive(:curl_headers).and_return({ responses:, body: }) + + expect(strategy.page_headers(url)).to eq([responses.first[:headers]]) + end + + it "handles `post_form` `url` options" do + allow(strategy).to receive(:curl_headers).and_return({ responses:, body: }) + + expect(strategy.page_headers(url, url_options: { post_form: post_hash })) + .to eq([responses.first[:headers]]) + end + + it "returns an empty array if `curl_headers` only raises an `ErrorDuringExecution` error" do + allow(strategy).to receive(:curl_headers).and_raise(ErrorDuringExecution.new([], status: 1)) + + expect(strategy.page_headers(url)).to eq([]) + end + end + + describe "::page_content" do + let(:curl_version) { Version.new("8.7.1") } + let(:success_status) { instance_double(Process::Status, success?: true, exitstatus: 0) } + + it "returns hash including fetched content" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) + + expect(strategy.page_content(url)).to eq({ content: body }) + end + + it "handles `post_form` `url` option" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) + + expect(strategy.page_content(url, url_options: { post_form: post_hash })).to eq({ content: body }) + end + + it "handles `post_json` `url` option" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) + + expect(strategy.page_content(url, url_options: { post_json: post_hash })).to eq({ content: body }) + end + + it "returns error `messages` from `stderr` in the return hash on failure when `stderr` is not `nil`" do + error_message = "curl: (6) Could not resolve host: brew.sh" + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([ + nil, + error_message, + instance_double(Process::Status, success?: false, exitstatus: 6), + ]) + + expect(strategy.page_content(url)).to eq({ messages: [error_message] }) + end + + it "returns default error `messages` in the return hash on failure when `stderr` is `nil`" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([ + nil, + nil, + instance_double(Process::Status, success?: false, exitstatus: 1), + ]) + + expect(strategy.page_content(url)).to eq({ messages: ["cURL failed without a detectable error"] }) + end + + it "returns hash including `final_url` if it differs from initial `url`" do + allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) + allow(strategy).to receive(:curl_output).and_return([response_text[:redirection_to_ok], nil, success_status]) + + expect(strategy.page_content(url)).to eq({ content: body, final_url: redirection_url }) + end + end + describe "::handle_block_return" do it "returns an array of version strings when given a valid value" do expect(strategy.handle_block_return("1.2.3")).to eq(["1.2.3"]) diff --git a/Library/Homebrew/test/livecheck_spec.rb b/Library/Homebrew/test/livecheck_spec.rb index 51f0e1c929..5b75c24ca6 100644 --- a/Library/Homebrew/test/livecheck_spec.rb +++ b/Library/Homebrew/test/livecheck_spec.rb @@ -27,6 +27,15 @@ RSpec.describe Livecheck do end let(:livecheck_c) { described_class.new(c) } + let(:post_hash) do + { + "empty" => "", + "boolean" => "true", + "number" => "1", + "string" => "a + b = c", + } + end + describe "#formula" do it "returns nil if not set" do expect(livecheck_f.formula).to be_nil @@ -137,25 +146,38 @@ RSpec.describe Livecheck do expect(livecheck_c.url).to eq(:url) end + it "sets `url_options` when provided" do + post_args = { post_form: post_hash } + livecheck_f.url(url_string, **post_args) + expect(livecheck_f.url_options).to eq(post_args) + end + it "raises an ArgumentError if the argument isn't a valid Symbol" do expect do livecheck_f.url(:not_a_valid_symbol) end.to raise_error ArgumentError end + + it "raises an ArgumentError if both `post_form` and `post_json` arguments are provided" do + expect do + livecheck_f.url(:stable, post_form: post_hash, post_json: post_hash) + end.to raise_error ArgumentError + end end describe "#to_hash" do it "returns a Hash of all instance variables" do expect(livecheck_f.to_hash).to eq( { - "cask" => nil, - "formula" => nil, - "regex" => nil, - "skip" => false, - "skip_msg" => nil, - "strategy" => nil, - "throttle" => nil, - "url" => nil, + "cask" => nil, + "formula" => nil, + "regex" => nil, + "skip" => false, + "skip_msg" => nil, + "strategy" => nil, + "throttle" => nil, + "url" => nil, + "url_options" => nil, }, ) end diff --git a/docs/Brew-Livecheck.md b/docs/Brew-Livecheck.md index a328437a1c..65cd546248 100644 --- a/docs/Brew-Livecheck.md +++ b/docs/Brew-Livecheck.md @@ -112,6 +112,24 @@ end The referenced formula/cask should be in the same tap, as a reference to a formula/cask from another tap will generate an error if the user doesn't already have it tapped. +### `POST` requests + +Some checks require making a `POST` request and that can be accomplished by adding a `post_form` or `post_json` option to a `livecheck` block `url`. + +```ruby +livecheck do + url "https://example.com/download.php", post_form: { + "Name" => "", + "E-mail" => "", + } + regex(/href=.*?example[._-]v?(\d+(?:\.\d+)+)\.t/i) +end +``` + +`post_form` is used for form data and `post_json` is used for JSON data. livecheck will encode the provided hash value to the appropriate format before making the request. + +`POST` support only applies to strategies that use `Strategy::page_headers` or `::page_content` (directly or indirectly), so it does not apply to `ExtractPlist`, `Git`, `GithubLatest`, `GithubReleases`, etc. + ### `strategy` blocks If the upstream version format needs to be manipulated to match the formula/cask format, a `strategy` block can be used instead of a `regex`. From 2187316262a14e003c981974b7795b0cc0352c56 Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:48:59 -0500 Subject: [PATCH 07/12] Strategy: Expand test coverage Between this commit and the previous one, this brings test coverage for `Livecheck::Strategy` up to 98.18% line coverage and 97.22% branch coverage. The only uncovered areas are some Sorbet `params` calls (which I'm not sure how to cover) and a conditional `break` in `page_headers` that will be refactored away in the future. The increased coverage is primarily in areas that weren't covered before because they call methods that make network requests. I worked around this with stubs and doubles, so we can test this code to some degree. I plan to expand this approach to other areas in livecheck that aren't covered for the same reason and that should significantly increase test coverage (along with some other test improvements that I have lined up). --- .../Homebrew/test/livecheck/strategy_spec.rb | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Library/Homebrew/test/livecheck/strategy_spec.rb b/Library/Homebrew/test/livecheck/strategy_spec.rb index 95f5222fef..ddb3bde2f5 100644 --- a/Library/Homebrew/test/livecheck/strategy_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy_spec.rb @@ -105,25 +105,50 @@ RSpec.describe Homebrew::Livecheck::Strategy do it "returns the Strategy module represented by the Symbol argument" do expect(strategy.from_symbol(:page_match)).to eq(Homebrew::Livecheck::Strategy::PageMatch) end + + it "returns `nil` if the argument is `nil`" do + expect(strategy.from_symbol(nil)).to be_nil + end end describe "::from_url" do - let(:url) { "https://sourceforge.net/projects/test" } + let(:sourceforge_url) { "https://sourceforge.net/projects/test" } - context "when no regex is provided" do + context "when a regex or `strategy` block is not provided" do it "returns an array of usable strategies which doesn't include PageMatch" do - expect(strategy.from_url(url)).to eq([Homebrew::Livecheck::Strategy::Sourceforge]) + expect(strategy.from_url(sourceforge_url)).to eq([Homebrew::Livecheck::Strategy::Sourceforge]) end end - context "when a regex is provided" do + context "when a regex or `strategy` block is provided" do it "returns an array of usable strategies including PageMatch, sorted in descending order by priority" do - expect(strategy.from_url(url, regex_provided: true)) + expect(strategy.from_url(sourceforge_url, regex_provided: true)) .to eq( [Homebrew::Livecheck::Strategy::Sourceforge, Homebrew::Livecheck::Strategy::PageMatch], ) end end + + context "when a `strategy` block is required and one is provided" do + it "returns an array of usable strategies including the specified strategy" do + # The strategies array is naturally in alphabetic order when all + # applicable strategies have the same priority + expect(strategy.from_url(url, livecheck_strategy: :json, block_provided: true)) + .to eq([Homebrew::Livecheck::Strategy::Json, Homebrew::Livecheck::Strategy::PageMatch]) + expect(strategy.from_url(url, livecheck_strategy: :xml, block_provided: true)) + .to eq([Homebrew::Livecheck::Strategy::PageMatch, Homebrew::Livecheck::Strategy::Xml]) + expect(strategy.from_url(url, livecheck_strategy: :yaml, block_provided: true)) + .to eq([Homebrew::Livecheck::Strategy::PageMatch, Homebrew::Livecheck::Strategy::Yaml]) + end + end + + context "when a `strategy` block is required and one is not provided" do + it "returns an array of usable strategies not including the specified strategy" do + expect(strategy.from_url(url, livecheck_strategy: :json, block_provided: false)).to eq([]) + expect(strategy.from_url(url, livecheck_strategy: :xml, block_provided: false)).to eq([]) + expect(strategy.from_url(url, livecheck_strategy: :yaml, block_provided: false)).to eq([]) + end + end end describe "::post_args" do From a16f5666a8fc486c1f991e4142c5e6c9b464da49 Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:12:30 -0500 Subject: [PATCH 08/12] Sparkle: Add homebrew_curl support `Sparkle` is the only strategy with a `find_versions` method that calls `Strategy::page_content` (or `::page_headers`) and doesn't have a `homebrew_curl` parameter. This adds the missing parameter and passes the value to `page_content`, which brings it in line with the other strategies. --- Library/Homebrew/livecheck/strategy/sparkle.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Library/Homebrew/livecheck/strategy/sparkle.rb b/Library/Homebrew/livecheck/strategy/sparkle.rb index cf5c56a3c6..6a298a14ae 100644 --- a/Library/Homebrew/livecheck/strategy/sparkle.rb +++ b/Library/Homebrew/livecheck/strategy/sparkle.rb @@ -214,16 +214,18 @@ module Homebrew # # @param url [String] the URL of the content to check # @param regex [Regexp, nil] a regex for use in a strategy block + # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Hash] sig { params( - url: String, - regex: T.nilable(Regexp), - unused: T.untyped, - block: T.nilable(Proc), + url: String, + regex: T.nilable(Regexp), + homebrew_curl: T::Boolean, + unused: T.untyped, + block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, **unused, &block) + def self.find_versions(url:, regex: nil, homebrew_curl: false, **unused, &block) if regex.present? && block.blank? raise ArgumentError, "#{Utils.demodulize(T.must(name))} only supports a regex when using a `strategy` block" @@ -234,7 +236,8 @@ module Homebrew match_data.merge!( Strategy.page_content( url, - url_options: unused.fetch(:url_options, {}), + url_options: unused.fetch(:url_options, {}), + homebrew_curl:, ), ) content = match_data.delete(:content) From 8997cccddf46401557a5a87b9d047985385c31ea Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Tue, 4 Feb 2025 18:48:00 -0500 Subject: [PATCH 09/12] Livecheck: Expand test coverage This brings line coverage for the `Livecheck` DSL back to 100%, as we had evidently overlooked part of the `#strategy` method. --- Library/Homebrew/test/livecheck_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Library/Homebrew/test/livecheck_spec.rb b/Library/Homebrew/test/livecheck_spec.rb index 5b75c24ca6..d213d227d6 100644 --- a/Library/Homebrew/test/livecheck_spec.rb +++ b/Library/Homebrew/test/livecheck_spec.rb @@ -99,13 +99,23 @@ RSpec.describe Livecheck do end describe "#strategy" do + block = proc { |page, regex| page.scan(regex).map { |match| match[0].tr("_", ".") } } + it "returns nil if not set" do expect(livecheck_f.strategy).to be_nil + expect(livecheck_f.strategy_block).to be_nil end it "returns the Symbol if set" do livecheck_f.strategy(:page_match) expect(livecheck_f.strategy).to eq(:page_match) + expect(livecheck_f.strategy_block).to be_nil + end + + it "sets `strategy_block` when provided" do + livecheck_f.strategy(:page_match, &block) + expect(livecheck_f.strategy).to eq(:page_match) + expect(livecheck_f.strategy_block).to eq(block) end end From 8adc188992bd39dd80e2fb6644480f497b79575b Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Fri, 7 Feb 2025 14:31:50 +0000 Subject: [PATCH 10/12] Import `brew alias` and `brew unalias` commands Import these from the homebrew/aliases tap and deprecate that tap. This required a little messing around with class/module/constant names to get `brew tests` and `brew typecheck` to play nicely. I added also added Sorbet type signatures and integration tests. --- .github/workflows/tests.yml | 4 +- Library/Homebrew/aliases/alias.rb | 113 ++++++++++++++++++ Library/Homebrew/aliases/aliases.rb | 77 ++++++++++++ Library/Homebrew/cmd/alias.rb | 47 ++++++++ Library/Homebrew/cmd/unalias.rb | 24 ++++ Library/Homebrew/official_taps.rb | 2 +- .../sorbet/rbi/dsl/homebrew/cmd/alias.rbi | 16 +++ .../sorbet/rbi/dsl/homebrew/cmd/unalias.rbi | 13 ++ Library/Homebrew/startup/config.rb | 12 ++ Library/Homebrew/test/.brew-aliases/foo | 6 + Library/Homebrew/test/cmd/alias_spec.rb | 19 +++ Library/Homebrew/test/cmd/unalias_spec.rb | 27 +++++ Library/Homebrew/test/spec_helper.rb | 1 + .../test/support/lib/startup/config.rb | 1 + docs/How-to-Create-and-Maintain-a-Tap.md | 2 +- 15 files changed, 359 insertions(+), 5 deletions(-) create mode 100644 Library/Homebrew/aliases/alias.rb create mode 100644 Library/Homebrew/aliases/aliases.rb create mode 100755 Library/Homebrew/cmd/alias.rb create mode 100755 Library/Homebrew/cmd/unalias.rb create mode 100644 Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi create mode 100644 Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi create mode 100755 Library/Homebrew/test/.brew-aliases/foo create mode 100644 Library/Homebrew/test/cmd/alias_spec.rb create mode 100644 Library/Homebrew/test/cmd/unalias_spec.rb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f5050f417..bf9a7e49b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,7 +113,6 @@ jobs: - name: Set up all Homebrew taps run: | - brew tap homebrew/aliases brew tap homebrew/bundle brew tap homebrew/command-not-found brew tap homebrew/formula-analytics @@ -129,8 +128,7 @@ jobs: homebrew/services \ homebrew/test-bot - brew style homebrew/aliases \ - homebrew/command-not-found \ + brew style homebrew/command-not-found \ homebrew/formula-analytics \ homebrew/portable-ruby diff --git a/Library/Homebrew/aliases/alias.rb b/Library/Homebrew/aliases/alias.rb new file mode 100644 index 0000000000..4c93f88b7d --- /dev/null +++ b/Library/Homebrew/aliases/alias.rb @@ -0,0 +1,113 @@ +# typed: strict +# frozen_string_literal: true + +module Homebrew + module Aliases + class Alias + sig { returns(String) } + attr_accessor :name + + sig { returns(T.nilable(String)) } + attr_accessor :command + + sig { params(name: String, command: T.nilable(String)).void } + def initialize(name, command = nil) + @name = T.let(name.strip, String) + @command = T.let(nil, T.nilable(String)) + @script = T.let(nil, T.nilable(Pathname)) + @symlink = T.let(nil, T.nilable(Pathname)) + + @command = if command&.start_with?("!", "%") + command[1..] + elsif command + "brew #{command}" + end + end + + sig { returns(T::Boolean) } + def reserved? + RESERVED.include? name + end + + sig { returns(T::Boolean) } + def cmd_exists? + path = which("brew-#{name}.rb") || which("brew-#{name}") + !path.nil? && path.realpath.parent != HOMEBREW_ALIASES + end + + sig { returns(Pathname) } + def script + @script ||= Pathname.new("#{HOMEBREW_ALIASES}/#{name.gsub(/\W/, "_")}") + end + + sig { returns(Pathname) } + def symlink + @symlink ||= Pathname.new("#{HOMEBREW_PREFIX}/bin/brew-#{name}") + end + + sig { returns(T::Boolean) } + def valid_symlink? + symlink.realpath.parent == HOMEBREW_ALIASES.realpath + rescue NameError + false + end + + sig { void } + def link + FileUtils.rm symlink if File.symlink? symlink + FileUtils.ln_s script, symlink + end + + sig { params(opts: T::Hash[Symbol, T::Boolean]).void } + def write(opts = {}) + odie "'#{name}' is a reserved command. Sorry." if reserved? + odie "'brew #{name}' already exists. Sorry." if cmd_exists? + + return if !opts[:override] && script.exist? + + content = if command + <<~EOS + #: * `#{name}` [args...] + #: `brew #{name}` is an alias for `#{command}` + #{command} $* + EOS + else + <<~EOS + # + # This is a Homebrew alias script. It'll be called when the user + # types `brew #{name}`. Any remaining arguments are passed to + # this script. You can retrieve those with $*, or only the first + # one with $1. Please keep your script on one line. + + # TODO Replace the line below with your script + echo "Hello I'm brew alias "#{name}" and my args are:" $1 + EOS + end + + script.open("w") do |f| + f.write <<~EOS + #! #{`which bash`.chomp} + # alias: brew #{name} + #{content} + EOS + end + script.chmod 0744 + link + end + + sig { void } + def remove + odie "'brew #{name}' is not aliased to anything." if !symlink.exist? || !valid_symlink? + + script.unlink + symlink.unlink + end + + sig { void } + def edit + write(override: false) + exec_editor script.to_s + end + end + end +end diff --git a/Library/Homebrew/aliases/aliases.rb b/Library/Homebrew/aliases/aliases.rb new file mode 100644 index 0000000000..48887b4c4c --- /dev/null +++ b/Library/Homebrew/aliases/aliases.rb @@ -0,0 +1,77 @@ +# typed: strict +# frozen_string_literal: true + +require "aliases/alias" + +module Homebrew + module Aliases + RESERVED = T.let(( + Commands.internal_commands + + Commands.internal_developer_commands + + Commands.internal_commands_aliases + + %w[alias unalias] + ).freeze, T::Array[String]) + + sig { void } + def self.init + FileUtils.mkdir_p HOMEBREW_ALIASES + end + + sig { params(name: String, command: String).void } + def self.add(name, command) + new_alias = Alias.new(name, command) + odie "alias 'brew #{name}' already exists!" if new_alias.script.exist? + new_alias.write + end + + sig { params(name: String).void } + def self.remove(name) + Alias.new(name).remove + end + + sig { params(only: T::Array[String], block: T.proc.params(target: String, cmd: String).void).void } + def self.each(only, &block) + Dir["#{HOMEBREW_ALIASES}/*"].each do |path| + next if path.end_with? "~" # skip Emacs-like backup files + next if File.directory?(path) + + _shebang, _meta, *lines = File.readlines(path) + target = File.basename(path) + next if !only.empty? && only.exclude?(target) + + lines.reject! { |line| line.start_with?("#") || line =~ /^\s*$/ } + first_line = T.must(lines.first) + cmd = first_line.chomp + cmd.sub!(/ \$\*$/, "") + + if cmd.start_with? "brew " + cmd.sub!(/^brew /, "") + else + cmd = "!#{cmd}" + end + + yield target, cmd if block.present? + end + end + + sig { params(aliases: String).void } + def self.show(*aliases) + each([*aliases]) do |target, cmd| + puts "brew alias #{target}='#{cmd}'" + existing_alias = Alias.new(target, cmd) + existing_alias.link unless existing_alias.symlink.exist? + end + end + + sig { params(name: String, command: T.nilable(String)).void } + def self.edit(name, command = nil) + Alias.new(name, command).write unless command.nil? + Alias.new(name, command).edit + end + + sig { void } + def self.edit_all + exec_editor(*Dir[HOMEBREW_ALIASES]) + end + end +end diff --git a/Library/Homebrew/cmd/alias.rb b/Library/Homebrew/cmd/alias.rb new file mode 100755 index 0000000000..85272c67de --- /dev/null +++ b/Library/Homebrew/cmd/alias.rb @@ -0,0 +1,47 @@ +# typed: strict +# frozen_string_literal: true + +require "abstract_command" +require "aliases/aliases" + +module Homebrew + module Cmd + class Alias < AbstractCommand + cmd_args do + usage_banner "`alias` [ ... | =]" + description <<~EOS + Show existing aliases. If no aliases are given, print the whole list. + EOS + switch "--edit", + description: "Edit aliases in a text editor. Either one or all aliases may be opened at once. " \ + "If the given alias doesn't exist it'll be pre-populated with a template." + named_args max: 1 + end + + sig { override.void } + def run + name = args.named.first + name, command = name.split("=", 2) if name.present? + + Aliases.init + + if name.nil? + if args.edit? + Aliases.edit_all + else + Aliases.show + end + elsif command.nil? + if args.edit? + Aliases.edit name + else + Aliases.show name + end + else + Aliases.add name, command + Aliases.edit name if args.edit? + end + end + end + end +end diff --git a/Library/Homebrew/cmd/unalias.rb b/Library/Homebrew/cmd/unalias.rb new file mode 100755 index 0000000000..a7799c6237 --- /dev/null +++ b/Library/Homebrew/cmd/unalias.rb @@ -0,0 +1,24 @@ +# typed: strict +# frozen_string_literal: true + +require "abstract_command" +require "aliases/aliases" + +module Homebrew + module Cmd + class Unalias < AbstractCommand + cmd_args do + description <<~EOS + Remove aliases. + EOS + named_args :alias, min: 1 + end + + sig { override.void } + def run + Aliases.init + args.named.each { |a| Aliases.remove a } + end + end + end +end diff --git a/Library/Homebrew/official_taps.rb b/Library/Homebrew/official_taps.rb index 65c0473342..db5e78c32f 100644 --- a/Library/Homebrew/official_taps.rb +++ b/Library/Homebrew/official_taps.rb @@ -6,7 +6,6 @@ OFFICIAL_CASK_TAPS = %w[ ].freeze OFFICIAL_CMD_TAPS = T.let({ - "homebrew/aliases" => ["alias", "unalias"], "homebrew/bundle" => ["bundle"], "homebrew/command-not-found" => ["command-not-found-init", "which-formula", "which-update"], "homebrew/test-bot" => ["test-bot"], @@ -14,6 +13,7 @@ OFFICIAL_CMD_TAPS = T.let({ }.freeze, T::Hash[String, T::Array[String]]) DEPRECATED_OFFICIAL_TAPS = %w[ + aliases apache binary cask-drivers diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi new file mode 100644 index 0000000000..f3d1819f35 --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/alias.rbi @@ -0,0 +1,16 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Homebrew::Cmd::Alias`. +# Please instead update this file by running `bin/tapioca dsl Homebrew::Cmd::Alias`. + + +class Homebrew::Cmd::Alias + sig { returns(Homebrew::Cmd::Alias::Args) } + def args; end +end + +class Homebrew::Cmd::Alias::Args < Homebrew::CLI::Args + sig { returns(T::Boolean) } + def edit?; end +end diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi new file mode 100644 index 0000000000..0027911431 --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/unalias.rbi @@ -0,0 +1,13 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Homebrew::Cmd::Unalias`. +# Please instead update this file by running `bin/tapioca dsl Homebrew::Cmd::Unalias`. + + +class Homebrew::Cmd::Unalias + sig { returns(Homebrew::Cmd::Unalias::Args) } + def args; end +end + +class Homebrew::Cmd::Unalias::Args < Homebrew::CLI::Args; end diff --git a/Library/Homebrew/startup/config.rb b/Library/Homebrew/startup/config.rb index 225a3a04a7..9a14eb095c 100644 --- a/Library/Homebrew/startup/config.rb +++ b/Library/Homebrew/startup/config.rb @@ -65,3 +65,15 @@ HOMEBREW_RUBY_EXEC_ARGS = [ ENV.fetch("HOMEBREW_RUBY_WARNINGS"), ENV.fetch("HOMEBREW_RUBY_DISABLE_OPTIONS"), ].freeze + +# Location for `brew alias` and `brew unalias` commands. +# +# Unix-Like systems store config in $HOME/.config whose location can be +# overridden by the XDG_CONFIG_HOME environment variable. Unfortunately +# Homebrew strictly filters environment variables in BuildEnvironment. +HOMEBREW_ALIASES = if (path = Pathname.new("~/.config/brew-aliases").expand_path).exist? || + (path = Pathname.new("~/.brew-aliases").expand_path).exist? + path.realpath +else + path +end.freeze diff --git a/Library/Homebrew/test/.brew-aliases/foo b/Library/Homebrew/test/.brew-aliases/foo new file mode 100755 index 0000000000..2e3286c7a0 --- /dev/null +++ b/Library/Homebrew/test/.brew-aliases/foo @@ -0,0 +1,6 @@ +#! /bin/bash +# alias: brew foo +#: * `foo` [args...] +#: `brew foo` is an alias for `brew bar` +brew bar $* + diff --git a/Library/Homebrew/test/cmd/alias_spec.rb b/Library/Homebrew/test/cmd/alias_spec.rb new file mode 100644 index 0000000000..35442f0ad8 --- /dev/null +++ b/Library/Homebrew/test/cmd/alias_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "cmd/alias" +require "cmd/shared_examples/args_parse" + +RSpec.describe Homebrew::Cmd::Alias do + it_behaves_like "parseable arguments" + + it "sets an alias", :integration_test do + expect { brew "alias", "foo=bar" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "alias" } + .to output(/brew alias foo='bar'/).to_stdout + .and not_to_output.to_stderr + .and be_a_success + end +end diff --git a/Library/Homebrew/test/cmd/unalias_spec.rb b/Library/Homebrew/test/cmd/unalias_spec.rb new file mode 100644 index 0000000000..4a9d799404 --- /dev/null +++ b/Library/Homebrew/test/cmd/unalias_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "cmd/unalias" +require "cmd/shared_examples/args_parse" + +RSpec.describe Homebrew::Cmd::Unalias do + it_behaves_like "parseable arguments" + + it "unsets an alias", :integration_test do + expect { brew "alias", "foo=bar" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "alias" } + .to output(/brew alias foo='bar'/).to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "unalias", "foo" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + expect { brew "alias" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + end +end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 5d521a4f44..92c2135b5a 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -63,6 +63,7 @@ TEST_DIRECTORIES = [ HOMEBREW_LOCKS, HOMEBREW_LOGS, HOMEBREW_TEMP, + HOMEBREW_ALIASES, ].freeze # Make `instance_double` and `class_double` diff --git a/Library/Homebrew/test/support/lib/startup/config.rb b/Library/Homebrew/test/support/lib/startup/config.rb index 796ca1ea62..e2cf07ff7e 100644 --- a/Library/Homebrew/test/support/lib/startup/config.rb +++ b/Library/Homebrew/test/support/lib/startup/config.rb @@ -24,6 +24,7 @@ HOMEBREW_DATA_PATH = (HOMEBREW_LIBRARY_PATH/"data").freeze # Paths redirected to a temporary directory and wiped at the end of the test run HOMEBREW_PREFIX = (Pathname(TEST_TMPDIR)/"prefix").freeze +HOMEBREW_ALIASES = (Pathname(TEST_TMPDIR)/"aliases").freeze HOMEBREW_REPOSITORY = HOMEBREW_PREFIX.dup.freeze HOMEBREW_LIBRARY = (HOMEBREW_REPOSITORY/"Library").freeze HOMEBREW_CACHE = (HOMEBREW_PREFIX.parent/"cache").freeze diff --git a/docs/How-to-Create-and-Maintain-a-Tap.md b/docs/How-to-Create-and-Maintain-a-Tap.md index 80ee676bf2..2f3342a4d8 100644 --- a/docs/How-to-Create-and-Maintain-a-Tap.md +++ b/docs/How-to-Create-and-Maintain-a-Tap.md @@ -50,7 +50,7 @@ Unlike formulae, casks must have globally unique names to avoid clashes. This ca You can provide your tap users with custom `brew` commands by adding them in a `cmd` subdirectory. [Read more on external commands](External-Commands.md). -See [homebrew/aliases](https://github.com/Homebrew/homebrew-aliases) for an example of a tap with external commands. +See [Homebrew/test-bot](https://github.com/Homebrew/homebrew-test-bot) for an example of a tap with external commands. ## Upstream taps From ec49a3e9f6e1ba978f9941e219926f099365fa9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 18:31:02 +0000 Subject: [PATCH 11/12] build(deps-dev): bump ruby-lsp in /Library/Homebrew Bumps [ruby-lsp](https://github.com/Shopify/ruby-lsp) from 0.23.8 to 0.23.9. - [Release notes](https://github.com/Shopify/ruby-lsp/releases) - [Commits](https://github.com/Shopify/ruby-lsp/compare/v0.23.8...v0.23.9) --- updated-dependencies: - dependency-name: ruby-lsp dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Library/Homebrew/Gemfile.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Library/Homebrew/Gemfile.lock b/Library/Homebrew/Gemfile.lock index 615e85e794..a2ba8beff5 100644 --- a/Library/Homebrew/Gemfile.lock +++ b/Library/Homebrew/Gemfile.lock @@ -95,7 +95,7 @@ GEM rubocop (~> 1.61) rubocop-sorbet (0.8.7) rubocop (>= 1) - ruby-lsp (0.23.8) + ruby-lsp (0.23.9) language_server-protocol (~> 3.17.0) prism (>= 1.2, < 2.0) rbs (>= 3, < 4) @@ -152,7 +152,6 @@ GEM PLATFORMS aarch64-linux - arm-linux arm64-darwin x86_64-darwin x86_64-linux From 769fb8739fa6af421b01f442ea9101b6a431e051 Mon Sep 17 00:00:00 2001 From: BrewTestBot <1589480+BrewTestBot@users.noreply.github.com> Date: Fri, 7 Feb 2025 19:42:46 +0000 Subject: [PATCH 12/12] Update manpage and completions. Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow. --- completions/bash/brew | 35 ++++++++++++++++++++++++++ completions/fish/brew.fish | 17 ++++++++++++- completions/internal_commands_list.txt | 2 ++ completions/zsh/_brew | 23 ++++++++++++++++- docs/Manpage.md | 34 ++++++++++++------------- manpages/brew.1 | 16 ++++++------ 6 files changed, 100 insertions(+), 27 deletions(-) diff --git a/completions/bash/brew b/completions/bash/brew index 278a38f692..173f29f49c 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -381,6 +381,23 @@ _brew_abv() { __brew_complete_casks } +_brew_alias() { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${cur}" in + -*) + __brewcomp " + --debug + --edit + --help + --quiet + --verbose + " + return + ;; + *) ;; + esac +} + _brew_analytics() { local cur="${COMP_WORDS[COMP_CWORD]}" case "${cur}" in @@ -2473,6 +2490,22 @@ _brew_typecheck() { __brew_complete_tapped } +_brew_unalias() { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${cur}" in + -*) + __brewcomp " + --debug + --help + --quiet + --verbose + " + return + ;; + *) ;; + esac +} + _brew_unbottled() { local cur="${COMP_WORDS[COMP_CWORD]}" case "${cur}" in @@ -2963,6 +2996,7 @@ _brew() { -S) _brew__s ;; -v) _brew__v ;; abv) _brew_abv ;; + alias) _brew_alias ;; analytics) _brew_analytics ;; audit) _brew_audit ;; autoremove) _brew_autoremove ;; @@ -3053,6 +3087,7 @@ _brew() { test) _brew_test ;; tests) _brew_tests ;; typecheck) _brew_typecheck ;; + unalias) _brew_unalias ;; unbottled) _brew_unbottled ;; uninstal) _brew_uninstal ;; uninstall) _brew_uninstall ;; diff --git a/completions/fish/brew.fish b/completions/fish/brew.fish index 2ad245fcf8..ea6849ed4e 100644 --- a/completions/fish/brew.fish +++ b/completions/fish/brew.fish @@ -319,6 +319,14 @@ __fish_brew_complete_arg 'abv; and not __fish_seen_argument -l cask -l casks' -a __fish_brew_complete_arg 'abv; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_all)' +__fish_brew_complete_cmd 'alias' 'Show existing aliases' +__fish_brew_complete_arg 'alias' -l debug -d 'Display any debugging information' +__fish_brew_complete_arg 'alias' -l edit -d 'Edit aliases in a text editor. Either one or all aliases may be opened at once. If the given alias doesn\'t exist it\'ll be pre-populated with a template' +__fish_brew_complete_arg 'alias' -l help -d 'Show this message' +__fish_brew_complete_arg 'alias' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'alias' -l verbose -d 'Make some output more verbose' + + __fish_brew_complete_cmd 'analytics' 'Control Homebrew\'s anonymous aggregate user behaviour analytics' __fish_brew_complete_sub_cmd 'analytics' 'state' __fish_brew_complete_sub_cmd 'analytics' 'on' @@ -555,7 +563,7 @@ __fish_brew_complete_arg 'contributions' -l debug -d 'Display any debugging info __fish_brew_complete_arg 'contributions' -l from -d 'Date (ISO-8601 format) to start searching contributions. Omitting this flag searches the last year' __fish_brew_complete_arg 'contributions' -l help -d 'Show this message' __fish_brew_complete_arg 'contributions' -l quiet -d 'Make some output more quiet' -__fish_brew_complete_arg 'contributions' -l repositories -d 'Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ' +__fish_brew_complete_arg 'contributions' -l repositories -d 'Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ' __fish_brew_complete_arg 'contributions' -l to -d 'Date (ISO-8601 format) to stop searching contributions' __fish_brew_complete_arg 'contributions' -l user -d 'Specify a comma-separated list of GitHub usernames or email addresses to find contributions from. Omitting this flag searches maintainers' __fish_brew_complete_arg 'contributions' -l verbose -d 'Make some output more verbose' @@ -1603,6 +1611,13 @@ __fish_brew_complete_arg 'typecheck' -l verbose -d 'Make some output more verbos __fish_brew_complete_arg 'typecheck' -a '(__fish_brew_suggest_taps_installed)' +__fish_brew_complete_cmd 'unalias' 'Remove aliases' +__fish_brew_complete_arg 'unalias' -l debug -d 'Display any debugging information' +__fish_brew_complete_arg 'unalias' -l help -d 'Show this message' +__fish_brew_complete_arg 'unalias' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'unalias' -l verbose -d 'Make some output more verbose' + + __fish_brew_complete_cmd 'unbottled' 'Show the unbottled dependents of formulae' __fish_brew_complete_arg 'unbottled' -l debug -d 'Display any debugging information' __fish_brew_complete_arg 'unbottled' -l dependents -d 'Skip getting analytics data and sort by number of dependents instead' diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt index a4836ba0fe..b3162e50ae 100644 --- a/completions/internal_commands_list.txt +++ b/completions/internal_commands_list.txt @@ -10,6 +10,7 @@ -S -v abv +alias analytics audit autoremove @@ -100,6 +101,7 @@ tc test tests typecheck +unalias unbottled uninstall unlink diff --git a/completions/zsh/_brew b/completions/zsh/_brew index 0f3618a79d..03b75e2567 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -139,6 +139,7 @@ __brew_internal_commands() { '--prefix:Display Homebrew'\''s install path' '--repository:Display where Homebrew'\''s Git repository is located' '--version:Print the version numbers of Homebrew, Homebrew/homebrew-core and Homebrew/homebrew-cask (if tapped) to standard output' + 'alias:Show existing aliases' 'analytics:Control Homebrew'\''s anonymous aggregate user behaviour analytics' 'audit:Check formula or cask for Homebrew coding style violations' 'autoremove:Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed' @@ -219,6 +220,7 @@ __brew_internal_commands() { 'test:Run the test method provided by an installed formula' 'tests:Run Homebrew'\''s unit and integration tests' 'typecheck:Check for typechecking errors using Sorbet' + 'unalias:Remove aliases' 'unbottled:Show the unbottled dependents of formulae' 'uninstall:Uninstall a formula or cask' 'unlink:Remove symlinks for formula from Homebrew'\''s prefix' @@ -432,6 +434,16 @@ _brew_abv() { '*::cask:__brew_casks' } +# brew alias +_brew_alias() { + _arguments \ + '--debug[Display any debugging information]' \ + '--edit[Edit aliases in a text editor. Either one or all aliases may be opened at once. If the given alias doesn'\''t exist it'\''ll be pre-populated with a template]' \ + '--help[Show this message]' \ + '--quiet[Make some output more quiet]' \ + '--verbose[Make some output more verbose]' +} + # brew analytics _brew_analytics() { _arguments \ @@ -716,7 +728,7 @@ _brew_contributions() { '--from[Date (ISO-8601 format) to start searching contributions. Omitting this flag searches the last year]' \ '--help[Show this message]' \ '--quiet[Make some output more quiet]' \ - '--repositories[Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ]' \ + '--repositories[Specify a comma-separated list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `bundle`, `command-not-found`, `test-bot` and `services`. Omitting this flag, or specifying `--repositories=primary`, searches only the main repositories: brew,core,cask. Specifying `--repositories=all`, searches all repositories. ]' \ '--to[Date (ISO-8601 format) to stop searching contributions]' \ '--user[Specify a comma-separated list of GitHub usernames or email addresses to find contributions from. Omitting this flag searches maintainers]' \ '--verbose[Make some output more verbose]' @@ -1987,6 +1999,15 @@ _brew_typecheck() { '*::tap:__brew_any_tap' } +# brew unalias +_brew_unalias() { + _arguments \ + '--debug[Display any debugging information]' \ + '--help[Show this message]' \ + '--quiet[Make some output more quiet]' \ + '--verbose[Make some output more verbose]' +} + # brew unbottled _brew_unbottled() { _arguments \ diff --git a/docs/Manpage.md b/docs/Manpage.md index cba9428744..e712c08f71 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -102,6 +102,15 @@ If no search term is provided, all locally available formulae are listed. ## COMMANDS +### `alias` \[*`alias`* ... \| *`alias`*=*`command`*\] + +Show existing aliases. If no aliases are given, print the whole list. + +`--edit` + +: Edit aliases in a text editor. Either one or all aliases may be opened at + once. If the given alias doesn't exist it'll be pre-populated with a template. + ### `analytics` \[*`subcommand`*\] Control Homebrew's anonymous aggregate user behaviour analytics. Read more at @@ -1259,6 +1268,10 @@ provided, display brief statistics for all installed taps. accepted value for *`version`* is `v1`. See the docs for examples of using the JSON output: +### `unalias` *`alias`* \[...\] + +Remove aliases. + ### `uninstall`, `remove`, `rm` \[*`options`*\] *`installed_formula`*\|*`installed_cask`* \[...\] Uninstall a *`formula`* or *`cask`*. @@ -2066,10 +2079,10 @@ Summarise contributions to Homebrew repositories. `--repositories` : Specify a comma-separated list of repositories to search. Supported - repositories: `brew`, `core`, `cask`, `aliases`, `bundle`, - `command-not-found`, `test-bot` and `services`. Omitting this flag, or - specifying `--repositories=primary`, searches only the main repositories: - brew,core,cask. Specifying `--repositories=all`, searches all repositories. + repositories: `brew`, `core`, `cask`, `bundle`, `command-not-found`, + `test-bot` and `services`. Omitting this flag, or specifying + `--repositories=primary`, searches only the main repositories: brew,core,cask. + Specifying `--repositories=all`, searches all repositories. `--from` @@ -3102,15 +3115,6 @@ These options are applicable across multiple subcommands. ## OFFICIAL EXTERNAL COMMANDS -### `alias` \[*`alias`* ... \| *`alias`*=*`command`*\] - -Show existing aliases. If no aliases are given, print the whole list. - -`--edit` - -: Edit aliases in a text editor. Either one or all aliases may be opened at - once. If the given alias doesn't exist it'll be pre-populated with a template. - ### `bundle` \[*`subcommand`*\] Bundler for non-Ruby dependencies from Homebrew, Homebrew Cask, Mac App Store, @@ -3536,10 +3540,6 @@ and Linux workers. : Use these tested formulae from formulae steps for a formulae dependents step. -### `unalias` *`alias`* \[...\] - -Remove aliases. - ### `which-formula` \[`--explain`\] *`command`* \[...\] Show which formula(e) provides the given command. diff --git a/manpages/brew.1 b/manpages/brew.1 index 3dfd164deb..7a7755e8ce 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -62,6 +62,11 @@ List all installed formulae\. .SS "\fBsearch\fP \fR[\fItext\fP|\fB/\fP\fItext\fP\fB/\fP]" Perform a substring search of cask tokens and formula names for \fItext\fP\&\. If \fItext\fP is flanked by slashes, it is interpreted as a regular expression\. The search for \fItext\fP is extended online to \fBhomebrew/core\fP and \fBhomebrew/cask\fP\&\. If no search term is provided, all locally available formulae are listed\. .SH "COMMANDS" +.SS "\fBalias\fP \fR[\fIalias\fP \.\.\. | \fIalias\fP=\fIcommand\fP]" +Show existing aliases\. If no aliases are given, print the whole list\. +.TP +\fB\-\-edit\fP +Edit aliases in a text editor\. Either one or all aliases may be opened at once\. If the given alias doesn\[u2019]t exist it\[u2019]ll be pre\-populated with a template\. .SS "\fBanalytics\fP \fR[\fIsubcommand\fP]" Control Homebrew\[u2019]s anonymous aggregate user behaviour analytics\. Read more at .UR https://docs\.brew\.sh/Analytics @@ -788,6 +793,8 @@ Show information on each installed tap\. Print a JSON representation of \fItap\fP\&\. Currently the default and only accepted value for \fIversion\fP is \fBv1\fP\&\. See the docs for examples of using the JSON output: .UR https://docs\.brew\.sh/Querying\-Brew .UE +.SS "\fBunalias\fP \fIalias\fP \fR[\.\.\.]" +Remove aliases\. .SS "\fBuninstall\fP, \fBremove\fP, \fBrm\fP \fR[\fIoptions\fP] \fIinstalled_formula\fP|\fIinstalled_cask\fP \fR[\.\.\.]" Uninstall a \fIformula\fP or \fIcask\fP\&\. .TP @@ -1310,7 +1317,7 @@ Treat all named arguments as casks\. Summarise contributions to Homebrew repositories\. .TP \fB\-\-repositories\fP -Specify a comma\-separated list of repositories to search\. Supported repositories: \fBbrew\fP, \fBcore\fP, \fBcask\fP, \fBaliases\fP, \fBbundle\fP, \fBcommand\-not\-found\fP, \fBtest\-bot\fP and \fBservices\fP\&\. Omitting this flag, or specifying \fB\-\-repositories=primary\fP, searches only the main repositories: brew,core,cask\. Specifying \fB\-\-repositories=all\fP, searches all repositories\. +Specify a comma\-separated list of repositories to search\. Supported repositories: \fBbrew\fP, \fBcore\fP, \fBcask\fP, \fBbundle\fP, \fBcommand\-not\-found\fP, \fBtest\-bot\fP and \fBservices\fP\&\. Omitting this flag, or specifying \fB\-\-repositories=primary\fP, searches only the main repositories: brew,core,cask\. Specifying \fB\-\-repositories=all\fP, searches all repositories\. .TP \fB\-\-from\fP Date (ISO\-8601 format) to start searching contributions\. Omitting this flag searches the last year\. @@ -1989,11 +1996,6 @@ Make some output more verbose\. \fB\-h\fP, \fB\-\-help\fP Show this message\. .SH "OFFICIAL EXTERNAL COMMANDS" -.SS "\fBalias\fP \fR[\fIalias\fP \.\.\. | \fIalias\fP=\fIcommand\fP]" -Show existing aliases\. If no aliases are given, print the whole list\. -.TP -\fB\-\-edit\fP -Edit aliases in a text editor\. Either one or all aliases may be opened at once\. If the given alias doesn\[u2019]t exist it\[u2019]ll be pre\-populated with a template\. .SS "\fBbundle\fP \fR[\fIsubcommand\fP]" Bundler for non\-Ruby dependencies from Homebrew, Homebrew Cask, Mac App Store, Whalebrew and Visual Studio Code\. .TP @@ -2269,8 +2271,6 @@ Use these skipped or failed formulae from formulae steps for a formulae dependen .TP \fB\-\-tested\-formulae\fP Use these tested formulae from formulae steps for a formulae dependents step\. -.SS "\fBunalias\fP \fIalias\fP \fR[\.\.\.]" -Remove aliases\. .SS "\fBwhich\-formula\fP \fR[\fB\-\-explain\fP] \fIcommand\fP \fR[\.\.\.]" Show which formula(e) provides the given command\. .TP