From 036723a668051002dbb2a17a599150eb55d5e9a7 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sun, 3 Dec 2023 21:59:03 -0500 Subject: [PATCH 1/9] Add `deprecate!` and `disable!` methods to the Cask DSL --- Library/Homebrew/cask/cask.rb | 6 ++++ Library/Homebrew/cask/dsl.rb | 41 ++++++++++++++++++++++++--- Library/Homebrew/deprecate_disable.rb | 31 +++++++++++++++----- Library/Homebrew/formula.rb | 4 +-- Library/Homebrew/formula_auditor.rb | 2 +- Library/Homebrew/formulary.rb | 2 +- 6 files changed, 71 insertions(+), 15 deletions(-) diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index b9dfebb6ec165..2ebf119e60a32 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -331,6 +331,12 @@ def to_h "conflicts_with" => conflicts_with, "container" => container&.pairs, "auto_updates" => auto_updates, + "deprecated" => deprecated?, + "deprecation_date" => deprecation_date, + "deprecation_reason" => deprecation_reason, + "disabled" => disabled?, + "disable_date" => disable_date, + "disable_reason" => disable_reason, "tap_git_head" => tap_git_head, "languages" => languages, "ruby_source_path" => ruby_source_path, diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index e8a1bf39b036c..1a08657cef5f5 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -84,6 +84,14 @@ class DSL :url, :version, :appdir, + :deprecate!, + :deprecated?, + :deprecation_date, + :deprecation_reason, + :disable!, + :disabled?, + :disable_date, + :disable_reason, :discontinued?, :livecheck, :livecheckable?, @@ -96,9 +104,9 @@ class DSL extend Predicable include OnSystem::MacOSOnly - attr_reader :cask, :token + attr_reader :cask, :token, :deprecation_date, :deprecation_reason, :disable_date, :disable_reason - attr_predicate :on_system_blocks_exist? + attr_predicate :on_system_blocks_exist?, :disabled?, :livecheckable? def initialize(cask) @cask = cask @@ -316,9 +324,15 @@ def caveats(*strings, &block) end def discontinued? + # odeprecated "`discontinued?`", "`deprecated?` or `disabled?`" @caveats&.discontinued? == true end + # TODO: replace with with attr_predicate once discontinued? is disabled + def deprecated? + @deprecated == true || @caveats&.discontinued? == true + end + # @api public def auto_updates(auto_updates = nil) set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates } @@ -337,8 +351,27 @@ def livecheck(&block) @livecheck.instance_eval(&block) end - def livecheckable? - @livecheckable == true + # @api public + def deprecate!(date:, because:) + @deprecation_date = Date.parse(date) + return if @deprecation_date > Date.today + + @deprecation_reason = because + @deprecated = true + end + + # @api public + def disable!(date:, because:) + @disable_date = Date.parse(date) + + if @disable_date > Date.today + @deprecation_reason = because + @deprecated = true + return + end + + @disable_reason = because + @disabled = true end ORDINARY_ARTIFACT_CLASSES.each do |klass| diff --git a/Library/Homebrew/deprecate_disable.rb b/Library/Homebrew/deprecate_disable.rb index 2b31d467a7627..b64e98e854c4f 100644 --- a/Library/Homebrew/deprecate_disable.rb +++ b/Library/Homebrew/deprecate_disable.rb @@ -7,7 +7,12 @@ module DeprecateDisable module_function - DEPRECATE_DISABLE_REASONS = { + SHARED_DEPRECATE_DISABLE_REASONS = { + repo_archived: "has an archived upstream repository", + repo_removed: "has a removed upstream repository", + }.freeze + + FORMULA_DEPRECATE_DISABLE_REASONS = { does_not_build: "does not build", no_license: "has no license", repo_archived: "has an archived upstream repository", @@ -20,20 +25,32 @@ module DeprecateDisable "a different checksum than the current one. " \ "Upstream's repository might have been compromised. " \ "We can re-package this once upstream has confirmed that they retagged their release", + **SHARED_DEPRECATE_DISABLE_REASONS, }.freeze - def deprecate_disable_info(formula) - if formula.deprecated? + CASK_DEPRECATE_DISABLE_REASONS = { + discontinued: "is discontinued upstream", + unsigned_artifact: "has an unsigned binary which prevents it from running on Apple Silicon devices " \ + "under standard macOS security policy", + **SHARED_DEPRECATE_DISABLE_REASONS, + }.freeze + + def deprecate_disable_info(formula_or_cask) + if formula_or_cask.deprecated? type = :deprecated - reason = formula.deprecation_reason - elsif formula.disabled? + reason = formula_or_cask.deprecation_reason + elsif formula_or_cask.disabled? type = :disabled - reason = formula.disable_reason + reason = formula_or_cask.disable_reason else return end - reason = DEPRECATE_DISABLE_REASONS[reason] if DEPRECATE_DISABLE_REASONS.key? reason + reason = if formula_or_cask.is_a?(Formula) && FORMULA_DEPRECATE_DISABLE_REASONS.key?(reason) + FORMULA_DEPRECATE_DISABLE_REASONS[reason] + elsif formula_or_cask.is_a?(Cask::Cask) && CASK_DEPRECATE_DISABLE_REASONS.key?(reason) + CASK_DEPRECATE_DISABLE_REASONS[reason] + end [type, reason] end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 9eb6a6d5fa8c0..cdae57e8b8503 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -3550,7 +3550,7 @@ def pour_bottle?(only_if: nil, &block) #
deprecate! date: "2020-08-27", because: :unmaintained
#
deprecate! date: "2020-08-27", because: "has been replaced by foo"
# @see https://docs.brew.sh/Deprecating-Disabling-and-Removing-Formulae - # @see DeprecateDisable::DEPRECATE_DISABLE_REASONS + # @see DeprecateDisable::FORMULA_DEPRECATE_DISABLE_REASONS def deprecate!(date:, because:) @deprecation_date = Date.parse(date) return if @deprecation_date > Date.today @@ -3585,7 +3585,7 @@ def deprecated? #
disable! date: "2020-08-27", because: :does_not_build
#
disable! date: "2020-08-27", because: "has been replaced by foo"
# @see https://docs.brew.sh/Deprecating-Disabling-and-Removing-Formulae - # @see DeprecateDisable::DEPRECATE_DISABLE_REASONS + # @see DeprecateDisable::FORMULA_DEPRECATE_DISABLE_REASONS def disable!(date:, because:) @disable_date = Date.parse(date) diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index 8edcbb6799953..da319e10937f6 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -412,7 +412,7 @@ def audit_deps return if formula.disabled? return if formula.deprecated? && - formula.deprecation_reason != DeprecateDisable::DEPRECATE_DISABLE_REASONS[:versioned_formula] + formula.deprecation_reason != DeprecateDisable::FORMULA_DEPRECATE_DISABLE_REASONS[:versioned_formula] problem <<~EOS #{formula.full_name} contains conflicting version recursive dependencies: diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 014006ff21771..5d6e47c5b0934 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -464,7 +464,7 @@ def self.convert_to_string_or_symbol(string) def self.convert_to_deprecate_disable_reason_string_or_symbol(string) require "deprecate_disable" - return string unless DeprecateDisable::DEPRECATE_DISABLE_REASONS.keys.map(&:to_s).include?(string) + return string unless DeprecateDisable::FORMULARY_DEPRECATE_DISABLE_REASONS.keys.map(&:to_s).include?(string) string.to_sym end From 641a80475ea7e7174c55ba1c8457c364a4603290 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 4 Dec 2023 00:30:49 -0500 Subject: [PATCH 2/9] Update cask logic to handle deprecations and disables --- Library/Homebrew/cask/cask_loader.rb | 10 ++++++++ Library/Homebrew/cask/exceptions.rb | 17 ++++++++++++++ Library/Homebrew/cask/info.rb | 2 ++ Library/Homebrew/cask/installer.rb | 12 ++++++++++ Library/Homebrew/cmd/info.rb | 10 ++------ Library/Homebrew/deprecate_disable.rb | 34 ++++++++++++++++++++------- Library/Homebrew/diagnostic.rb | 12 ++++++++++ Library/Homebrew/formula_installer.rb | 21 ++++++----------- Library/Homebrew/formulary.rb | 12 +++------- 9 files changed, 90 insertions(+), 40 deletions(-) diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index 22b7efe25e73d..93332576ba0b0 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -286,6 +286,16 @@ def load(config:) desc json_cask[:desc] homepage json_cask[:homepage] + if (deprecation_date = json_cask[:deprecation_date].presence) + reason = DeprecateDisable.to_reason_string_or_symbol json_cask[:deprecation_reason], type: :cask + deprecate! date: deprecation_date, because: reason + end + + if (disable_date = json_cask[:disable_date].presence) + reason = DeprecateDisable.to_reason_string_or_symbol json_cask[:disable_reason], type: :cask + deprecate! date: disable_date, because: reason + end + auto_updates json_cask[:auto_updates] unless json_cask[:auto_updates].nil? conflicts_with(**json_cask[:conflicts_with]) if json_cask[:conflicts_with].present? diff --git a/Library/Homebrew/cask/exceptions.rb b/Library/Homebrew/cask/exceptions.rb index 05bdde818ac8e..acbc0fa338d54 100644 --- a/Library/Homebrew/cask/exceptions.rb +++ b/Library/Homebrew/cask/exceptions.rb @@ -54,6 +54,23 @@ def to_s end end + # Error when a cask cannot be installed. + # + # @api private + class CaskCannotBeInstalledError < AbstractCaskErrorWithToken + attr_reader :message + + def initialize(token, message) + super(token) + @message = message + end + + sig { returns(String) } + def to_s + "Cask '#{token}' has been #{message}" + end + end + # Error when a cask conflicts with another cask. # # @api private diff --git a/Library/Homebrew/cask/info.rb b/Library/Homebrew/cask/info.rb index 64caa572a732c..e80a88024a5dd 100644 --- a/Library/Homebrew/cask/info.rb +++ b/Library/Homebrew/cask/info.rb @@ -12,6 +12,8 @@ def self.get_info(cask) output = +"#{title_info(cask)}\n" output << "#{Formatter.url(cask.homepage)}\n" if cask.homepage + deprecate_disable = DeprecateDisable.message(cask) + output << "#{deprecate_disable.capitalize}\n" if deprecate_disable output << installation_info(cask) repo = repo_info(cask) output << "#{repo}\n" if repo diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index a9e42cc6b3565..45383d30ec728 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -93,6 +93,7 @@ def install old_config = @cask.config predecessor = @cask if reinstall? && @cask.installed? + check_deprecate_disable check_conflicts print caveats @@ -124,6 +125,17 @@ def install raise end + def check_deprecate_disable + deprecate_disable_type = DeprecateDisable.type(@cask) + return if deprecate_disable_type.blank? + + if deprecate_disable_type == :deprecated + opoo "#{@cask.token} has been #{DeprecateDisable.message(@cask)}" + elsif deprecate_disable_type == :disabled + raise CaskCannotBeInstalledError.new(@cask, DeprecateDisable.message(@cask)) + end + end + def check_conflicts return unless @cask.conflicts_with diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index ca7ac0f28b904..705790a8b3dbd 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -278,14 +278,8 @@ def info_formula(formula, args:) puts formula.desc if formula.desc puts Formatter.url(formula.homepage) if formula.homepage - deprecate_disable_type, deprecate_disable_reason = DeprecateDisable.deprecate_disable_info formula - if deprecate_disable_type.present? - if deprecate_disable_reason.present? - puts "#{deprecate_disable_type.capitalize} because it #{deprecate_disable_reason}!" - else - puts "#{deprecate_disable_type.capitalize}!" - end - end + deprecate_disable_info_string = DeprecateDisable.message(formula) + puts deprecate_disable_info_string.capitalize if deprecate_disable_info_string.present? conflicts = formula.conflicts.map do |conflict| reason = " (because #{conflict.reason})" if conflict.reason diff --git a/Library/Homebrew/deprecate_disable.rb b/Library/Homebrew/deprecate_disable.rb index b64e98e854c4f..8ade837dea58a 100644 --- a/Library/Homebrew/deprecate_disable.rb +++ b/Library/Homebrew/deprecate_disable.rb @@ -35,23 +35,39 @@ module DeprecateDisable **SHARED_DEPRECATE_DISABLE_REASONS, }.freeze - def deprecate_disable_info(formula_or_cask) - if formula_or_cask.deprecated? - type = :deprecated - reason = formula_or_cask.deprecation_reason + def type(formula_or_cask) + return :deprecated if formula_or_cask.deprecated? + return :disabled if formula_or_cask.disabled? + end + + def message(formula_or_cask) + return if type(formula_or_cask).blank? + + reason = if formula_or_cask.deprecated? + formula_or_cask.deprecation_reason elsif formula_or_cask.disabled? - type = :disabled - reason = formula_or_cask.disable_reason - else - return + formula_or_cask.disable_reason end reason = if formula_or_cask.is_a?(Formula) && FORMULA_DEPRECATE_DISABLE_REASONS.key?(reason) FORMULA_DEPRECATE_DISABLE_REASONS[reason] elsif formula_or_cask.is_a?(Cask::Cask) && CASK_DEPRECATE_DISABLE_REASONS.key?(reason) CASK_DEPRECATE_DISABLE_REASONS[reason] + else + reason + end + + return "#{type(formula_or_cask)} because it #{reason}!" if reason.present? + + "#{type(formula_or_cask)}!" + end + + def to_reason_string_or_symbol(string, type:) + if (type == :formula && FORMULA_DEPRECATE_DISABLE_REASONS.key?(string.to_sym)) || + (type == :cask && CASK_DEPRECATE_DISABLE_REASONS.key?(string.to_sym)) + return string.to_sym end - [type, reason] + string end end diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 512f475b77bd7..f0bec7ee802d4 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -642,6 +642,18 @@ def check_deprecated_disabled EOS end + def check_cask_deprecated_disabled + deprecated_or_disabled = Cask::Caskroom.casks.select(&:deprecated?) + deprecated_or_disabled += Cask::Caskroom.casks.select(&:disabled?) + return if deprecated_or_disabled.empty? + + <<~EOS + Some installed casks are deprecated or disabled. + You should find replacements for the following casks: + #{deprecated_or_disabled.sort_by(&:token).uniq * "\n "} + EOS + end + sig { returns(T.nilable(String)) } def check_git_status return unless Utils::Git.available? diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index ce55a97d9d752..9b46b15d02e8f 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -200,21 +200,14 @@ def install_bottle_for?(dep, build) sig { void } def prelude - type, reason = DeprecateDisable.deprecate_disable_info formula - if type.present? - case type - when :deprecated - if reason.present? - opoo "#{formula.full_name} has been deprecated because it #{reason}!" - else - opoo "#{formula.full_name} has been deprecated!" - end - when :disabled - if reason.present? - raise CannotInstallFormulaError, "#{formula.full_name} has been disabled because it #{reason}!" - end + deprecate_disable_type = DeprecateDisable.type(formula) + if deprecate_disable_type.present? + message = "#{formula.full_name} has been #{DeprecateDisable.message(formula)}" - raise CannotInstallFormulaError, "#{formula.full_name} has been disabled!" + if deprecate_disable_type == :deprecated + opoo message + elsif deprecate_disable_type == :disabled + raise CannotInstallFormulaError, message end end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 5d6e47c5b0934..90857aa3b9a56 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -7,6 +7,7 @@ require "utils/bottles" require "service" require "utils/curl" +require "deprecate_disable" require "active_support/core_ext/hash/deep_transform_values" @@ -301,12 +302,12 @@ def self.load_formula_from_api(name, flags:) end if (deprecation_date = json_formula["deprecation_date"].presence) - reason = Formulary.convert_to_deprecate_disable_reason_string_or_symbol json_formula["deprecation_reason"] + reason = DeprecateDisable.to_reason_string_or_symbol json_formula["deprecation_reason"], type: :formula deprecate! date: deprecation_date, because: reason end if (disable_date = json_formula["disable_date"].presence) - reason = Formulary.convert_to_deprecate_disable_reason_string_or_symbol json_formula["disable_reason"] + reason = DeprecateDisable.to_reason_string_or_symbol json_formula["disable_reason"], type: :formula disable! date: disable_date, because: reason end @@ -462,13 +463,6 @@ def self.convert_to_string_or_symbol(string) string end - def self.convert_to_deprecate_disable_reason_string_or_symbol(string) - require "deprecate_disable" - return string unless DeprecateDisable::FORMULARY_DEPRECATE_DISABLE_REASONS.keys.map(&:to_s).include?(string) - - string.to_sym - end - # A {FormulaLoader} returns instances of formulae. # Subclasses implement loaders for particular sources of formulae. class FormulaLoader From 53c1e6ecdba7d29ea7369741c5dca6029325d450 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 4 Dec 2023 00:38:32 -0500 Subject: [PATCH 3/9] Add `deprecate!` and `disable!` to cask stanza order --- Library/Homebrew/rubocops/cask/ast/stanza.rb | 6 +++--- Library/Homebrew/rubocops/cask/constants/stanza.rb | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/rubocops/cask/ast/stanza.rb b/Library/Homebrew/rubocops/cask/ast/stanza.rb index e9cc111b3a552..d793076060e7a 100644 --- a/Library/Homebrew/rubocops/cask/ast/stanza.rb +++ b/Library/Homebrew/rubocops/cask/ast/stanza.rb @@ -74,9 +74,9 @@ def ==(other) Constants::STANZA_ORDER.each do |stanza_name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{stanza_name}? # def url? - stanza_name == :#{stanza_name} # stanza_name == :url - end # end + def #{stanza_name.to_s.chomp("!")}? # def url? + stanza_name == :#{stanza_name} # stanza_name == :url + end # end RUBY end end diff --git a/Library/Homebrew/rubocops/cask/constants/stanza.rb b/Library/Homebrew/rubocops/cask/constants/stanza.rb index 54fcdb73fc320..2fe782a17dc35 100644 --- a/Library/Homebrew/rubocops/cask/constants/stanza.rb +++ b/Library/Homebrew/rubocops/cask/constants/stanza.rb @@ -19,6 +19,7 @@ module Constants [:language], [:url, :appcast, :name, :desc, :homepage], [:livecheck], + [:deprecate!, :disable!], [ :auto_updates, :conflicts_with, From 7eb3f1a3142a285cc00e4f707de4681b27260a93 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 4 Dec 2023 13:43:33 -0500 Subject: [PATCH 4/9] Replace `discontinued?` with `deprecated?` --- Library/Homebrew/cask/audit.rb | 12 +++++----- Library/Homebrew/cask/dsl/caveats.rb | 1 + Library/Homebrew/livecheck/skip_conditions.rb | 23 +++++++++++++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 025771cd2724b..c0a826a2c87d2 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -289,7 +289,7 @@ def audit_latest_with_auto_updates sig { params(livecheck_result: T.any(NilClass, T::Boolean, Symbol)).void } def audit_hosting_with_livecheck(livecheck_result: audit_livecheck_version) - return if cask.discontinued? + return if cask.deprecated? || cask.disabled? return if cask.version&.latest? return unless cask.url return if block_url_offline? @@ -544,7 +544,7 @@ def audit_livecheck_version ) end - # Respect cask skip conditions (e.g. discontinued, latest, unversioned) + # Respect cask skip conditions (e.g. deprecated, disabled, latest, unversioned) skip_info ||= Homebrew::Livecheck::SkipConditions.skip_information(cask) return :skip if skip_info.present? @@ -682,8 +682,8 @@ def audit_gitlab_prerelease_version sig { void } def audit_github_repository_archived - # Discontinued casks may have an archived repo. - return if cask.discontinued? + # Deprecated/disabled casks may have an archived repo. + return if cask.deprecated? || cask.disabled? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? return if user.nil? @@ -696,8 +696,8 @@ def audit_github_repository_archived sig { void } def audit_gitlab_repository_archived - # Discontinued casks may have an archived repo. - return if cask.discontinued? + # Deprecated/disabled casks may have an archived repo. + return if cask.deprecated? || cask.disabled? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? return if user.nil? diff --git a/Library/Homebrew/cask/dsl/caveats.rb b/Library/Homebrew/cask/dsl/caveats.rb index eaf45d0284fec..1a9c4ad4f0e4b 100644 --- a/Library/Homebrew/cask/dsl/caveats.rb +++ b/Library/Homebrew/cask/dsl/caveats.rb @@ -163,6 +163,7 @@ def eval_caveats(&block) end caveat :discontinued do + # odeprecated "`caveats :discontinued`", "`deprecated!`" @discontinued = true <<~EOS #{@cask} has been officially discontinued upstream. diff --git a/Library/Homebrew/livecheck/skip_conditions.rb b/Library/Homebrew/livecheck/skip_conditions.rb index fe8c52668b21c..d6401fa70473d 100644 --- a/Library/Homebrew/livecheck/skip_conditions.rb +++ b/Library/Homebrew/livecheck/skip_conditions.rb @@ -119,10 +119,24 @@ def formula_versioned(formula, livecheckable, full_name: false, verbose: false) verbose: T::Boolean, ).returns(Hash) } - def cask_discontinued(cask, livecheckable, full_name: false, verbose: false) - return {} if !cask.discontinued? || livecheckable + def cask_deprecated(cask, livecheckable, full_name: false, verbose: false) + return {} if !cask.deprecated? || livecheckable - Livecheck.status_hash(cask, "discontinued", full_name: full_name, verbose: verbose) + Livecheck.status_hash(cask, "deprecated", full_name: full_name, verbose: verbose) + end + + sig { + params( + cask: Cask::Cask, + livecheckable: T::Boolean, + full_name: T::Boolean, + verbose: T::Boolean, + ).returns(Hash) + } + def cask_disabled(cask, livecheckable, full_name: false, verbose: false) + return {} if !cask.disabled? || livecheckable + + Livecheck.status_hash(cask, "disabled", full_name: full_name, verbose: verbose) end sig { @@ -165,7 +179,8 @@ def cask_url_unversioned(cask, livecheckable, full_name: false, verbose: false) # Skip conditions for casks. CASK_CHECKS = [ :package_or_resource_skip, - :cask_discontinued, + :cask_deprecated, + :cask_disabled, :cask_version_latest, :cask_url_unversioned, ].freeze From 40d3ab6a5d22ab5918b25eb165b2c0175239c526 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 4 Dec 2023 13:43:39 -0500 Subject: [PATCH 5/9] Add tests and fix `brew typecheck` --- Library/Homebrew/cask/cask.rbi | 12 ++- .../Homebrew/test/deprecate_disable_spec.rb | 89 +++++++++++++++++++ Library/Homebrew/test/formulary_spec.rb | 11 --- .../test/livecheck/skip_conditions_spec.rb | 86 +++++++++++++++++- .../support/fixtures/cask/everything.json | 6 ++ 5 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 Library/Homebrew/test/deprecate_disable_spec.rb diff --git a/Library/Homebrew/cask/cask.rbi b/Library/Homebrew/cask/cask.rbi index a284d97e04e60..2a7d80d53d30d 100644 --- a/Library/Homebrew/cask/cask.rbi +++ b/Library/Homebrew/cask/cask.rbi @@ -20,7 +20,17 @@ module Cask def desc; end - def discontinued?; end + def deprecated?; end + + def deprecation_date; end + + def deprecation_reason; end + + def disabled?; end + + def disable_date; end + + def disable_reason; end def homepage; end diff --git a/Library/Homebrew/test/deprecate_disable_spec.rb b/Library/Homebrew/test/deprecate_disable_spec.rb new file mode 100644 index 0000000000000..27b62458f94a1 --- /dev/null +++ b/Library/Homebrew/test/deprecate_disable_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "deprecate_disable" + +describe DeprecateDisable do + let(:deprecated_formula) do + instance_double(Formula, deprecated?: true, disabled?: false, deprecation_reason: :does_not_build) + end + let(:disabled_formula) do + instance_double(Formula, deprecated?: false, disabled?: true, disable_reason: "is broken") + end + let(:deprecated_cask) do + instance_double(Cask::Cask, deprecated?: true, disabled?: false, deprecation_reason: :discontinued) + end + let(:disabled_cask) do + instance_double(Cask::Cask, deprecated?: false, disabled?: true, disable_reason: nil) + end + + before do + allow(deprecated_formula).to receive(:is_a?).with(Formula).and_return(true) + allow(deprecated_formula).to receive(:is_a?).with(Cask::Cask).and_return(false) + allow(disabled_formula).to receive(:is_a?).with(Formula).and_return(true) + allow(disabled_formula).to receive(:is_a?).with(Cask::Cask).and_return(false) + allow(deprecated_cask).to receive(:is_a?).with(Formula).and_return(false) + allow(deprecated_cask).to receive(:is_a?).with(Cask::Cask).and_return(true) + allow(disabled_cask).to receive(:is_a?).with(Formula).and_return(false) + allow(disabled_cask).to receive(:is_a?).with(Cask::Cask).and_return(true) + end + + describe "::type" do + it "returns :deprecated if the formula is deprecated" do + expect(described_class.type(deprecated_formula)).to eq :deprecated + end + + it "returns :disabled if the formula is disabled" do + expect(described_class.type(disabled_formula)).to eq :disabled + end + + it "returns :deprecated if the cask is deprecated" do + expect(described_class.type(deprecated_cask)).to eq :deprecated + end + + it "returns :disabled if the cask is disabled" do + expect(described_class.type(disabled_cask)).to eq :disabled + end + end + + describe "::message" do + it "returns a deprecation message with a preset formula reason" do + expect(described_class.message(deprecated_formula)) + .to eq "deprecated because it does not build!" + end + + it "returns a disable message with a custom reason" do + expect(described_class.message(disabled_formula)) + .to eq "disabled because it is broken!" + end + + it "returns a deprecation message with a preset cask reason" do + expect(described_class.message(deprecated_cask)) + .to eq "deprecated because it is discontinued upstream!" + end + + it "returns a deprecation message with no reason" do + expect(described_class.message(disabled_cask)) + .to eq "disabled!" + end + end + + describe "::to_reason_string_or_symbol" do + it "returns the original string if it isn't a formula preset reason" do + expect(described_class.to_reason_string_or_symbol("discontinued", type: :formula)).to eq "discontinued" + end + + it "returns the original string if it isn't a cask preset reason" do + expect(described_class.to_reason_string_or_symbol("does_not_build", type: :cask)).to eq "does_not_build" + end + + it "returns a symbol if the original string is a formula preset reason" do + expect(described_class.to_reason_string_or_symbol("does_not_build", type: :formula)) + .to eq :does_not_build + end + + it "returns a symbol if the original string is a cask preset reason" do + expect(described_class.to_reason_string_or_symbol("discontinued", type: :cask)) + .to eq :discontinued + end + end +end diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index eaebc27379b3c..9cb4b64b80324 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -521,15 +521,4 @@ def formula_json_contents(extra_items = {}) expect(described_class.convert_to_string_or_symbol(":foo")).to eq :foo end end - - describe "::convert_to_deprecate_disable_reason_string_or_symbol" do - it "returns the original string if it isn't a preset reason" do - expect(described_class.convert_to_deprecate_disable_reason_string_or_symbol("foo")).to eq "foo" - end - - it "returns a symbol if the original string is a preset reason" do - expect(described_class.convert_to_deprecate_disable_reason_string_or_symbol("does_not_build")) - .to eq :does_not_build - end - end end diff --git a/Library/Homebrew/test/livecheck/skip_conditions_spec.rb b/Library/Homebrew/test/livecheck/skip_conditions_spec.rb index 8e7bde04e9713..ba6840923b1f4 100644 --- a/Library/Homebrew/test/livecheck/skip_conditions_spec.rb +++ b/Library/Homebrew/test/livecheck/skip_conditions_spec.rb @@ -104,6 +104,28 @@ discontinued end end, + deprecated: Cask::Cask.new("test_deprecated") do + version "0.0.1" + sha256 :no_check + + url "https://brew.sh/test-0.0.1.tgz" + name "Test Deprecate" + desc "Deprecated test cask" + homepage "https://brew.sh" + + deprecate! date: "2020-06-25", because: :discontinued + end, + disabled: Cask::Cask.new("test_disabled") do + version "0.0.1" + sha256 :no_check + + url "https://brew.sh/test-0.0.1.tgz" + name "Test Disable" + desc "Disabled test cask" + homepage "https://brew.sh" + + disable! date: "2020-06-25", because: :discontinued + end, latest: Cask::Cask.new("test_latest") do version :latest sha256 :no_check @@ -225,7 +247,21 @@ cask: { discontinued: { cask: "test_discontinued", - status: "discontinued", + status: "deprecated", + meta: { + livecheckable: false, + }, + }, + deprecated: { + cask: "test_deprecated", + status: "deprecated", + meta: { + livecheckable: false, + }, + }, + disabled: { + cask: "test_disabled", + status: "disabled", meta: { livecheckable: false, }, @@ -330,6 +366,20 @@ end end + context "when a cask without a livecheckable is deprecated" do + it "skips" do + expect(skip_conditions.skip_information(casks[:deprecated])) + .to eq(status_hashes[:cask][:deprecated]) + end + end + + context "when a cask without a livecheckable is disabled" do + it "skips" do + expect(skip_conditions.skip_information(casks[:disabled])) + .to eq(status_hashes[:cask][:disabled]) + end + end + context "when a cask without a livecheckable has `version :latest`" do it "skips" do expect(skip_conditions.skip_information(casks[:latest])) @@ -428,7 +478,21 @@ context "when a cask without a livecheckable is discontinued" do it "errors" do expect { skip_conditions.referenced_skip_information(casks[:discontinued], original_name) } - .to raise_error(RuntimeError, "Referenced cask (test_discontinued) is skipped as discontinued") + .to raise_error(RuntimeError, "Referenced cask (test_discontinued) is skipped as deprecated") + end + end + + context "when a cask without a livecheckable is deprecated" do + it "errors" do + expect { skip_conditions.referenced_skip_information(casks[:deprecated], original_name) } + .to raise_error(RuntimeError, "Referenced cask (test_deprecated) is skipped as deprecated") + end + end + + context "when a cask without a livecheckable is disabled" do + it "errors" do + expect { skip_conditions.referenced_skip_information(casks[:disabled], original_name) } + .to raise_error(RuntimeError, "Referenced cask (test_disabled) is skipped as disabled") end end @@ -537,7 +601,23 @@ context "when the cask is discontinued without a livecheckable" do it "prints skip information" do expect { skip_conditions.print_skip_information(status_hashes[:cask][:discontinued]) } - .to output("test_discontinued: discontinued\n").to_stdout + .to output("test_discontinued: deprecated\n").to_stdout + .and not_to_output.to_stderr + end + end + + context "when the cask is deprecated without a livecheckable" do + it "prints skip information" do + expect { skip_conditions.print_skip_information(status_hashes[:cask][:deprecated]) } + .to output("test_deprecated: deprecated\n").to_stdout + .and not_to_output.to_stderr + end + end + + context "when the cask is disabled without a livecheckable" do + it "prints skip information" do + expect { skip_conditions.print_skip_information(status_hashes[:cask][:disabled]) } + .to output("test_disabled: disabled\n").to_stdout .and not_to_output.to_stderr end end diff --git a/Library/Homebrew/test/support/fixtures/cask/everything.json b/Library/Homebrew/test/support/fixtures/cask/everything.json index b720fd44e756e..f29f88a4fa4dd 100644 --- a/Library/Homebrew/test/support/fixtures/cask/everything.json +++ b/Library/Homebrew/test/support/fixtures/cask/everything.json @@ -90,6 +90,12 @@ "type": "naked" }, "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, "tap_git_head": null, "languages": [ "en", From c5f33aaf99ce3cd4037ccbf38e50b5ca4f40f875 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 4 Dec 2023 14:11:31 -0500 Subject: [PATCH 6/9] Load `nil` reasons from API correctly --- Library/Homebrew/deprecate_disable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/deprecate_disable.rb b/Library/Homebrew/deprecate_disable.rb index 8ade837dea58a..adc3801cfa70e 100644 --- a/Library/Homebrew/deprecate_disable.rb +++ b/Library/Homebrew/deprecate_disable.rb @@ -63,8 +63,8 @@ def message(formula_or_cask) end def to_reason_string_or_symbol(string, type:) - if (type == :formula && FORMULA_DEPRECATE_DISABLE_REASONS.key?(string.to_sym)) || - (type == :cask && CASK_DEPRECATE_DISABLE_REASONS.key?(string.to_sym)) + if (type == :formula && FORMULA_DEPRECATE_DISABLE_REASONS.key?(string&.to_sym)) || + (type == :cask && CASK_DEPRECATE_DISABLE_REASONS.key?(string&.to_sym)) return string.to_sym end From 472dd95b1699a97fd93736c25a25c27377c8a5bf Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sat, 16 Dec 2023 20:08:26 -0500 Subject: [PATCH 7/9] Fix cask loading from the API Co-authored-by: Kevin --- Library/Homebrew/cask/cask_loader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index 93332576ba0b0..53d2183816f22 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -293,7 +293,7 @@ def load(config:) if (disable_date = json_cask[:disable_date].presence) reason = DeprecateDisable.to_reason_string_or_symbol json_cask[:disable_reason], type: :cask - deprecate! date: disable_date, because: reason + disable! date: disable_date, because: reason end auto_updates json_cask[:auto_updates] unless json_cask[:auto_updates].nil? From 6431822e7b652610855381ae24391f7979f8a995 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sat, 16 Dec 2023 20:08:42 -0500 Subject: [PATCH 8/9] Apply feedback --- Library/Homebrew/cask/installer.rb | 7 ++++--- Library/Homebrew/deprecate_disable.rb | 14 +++----------- Library/Homebrew/formula_installer.rb | 5 +++-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index 45383d30ec728..494193a252bc2 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -127,11 +127,12 @@ def install def check_deprecate_disable deprecate_disable_type = DeprecateDisable.type(@cask) - return if deprecate_disable_type.blank? + return if deprecate_disable_type.nil? - if deprecate_disable_type == :deprecated + case deprecate_disable_type + when :deprecated opoo "#{@cask.token} has been #{DeprecateDisable.message(@cask)}" - elsif deprecate_disable_type == :disabled + when :disabled raise CaskCannotBeInstalledError.new(@cask, DeprecateDisable.message(@cask)) end end diff --git a/Library/Homebrew/deprecate_disable.rb b/Library/Homebrew/deprecate_disable.rb index adc3801cfa70e..55d6a24add6a9 100644 --- a/Library/Homebrew/deprecate_disable.rb +++ b/Library/Homebrew/deprecate_disable.rb @@ -7,11 +7,6 @@ module DeprecateDisable module_function - SHARED_DEPRECATE_DISABLE_REASONS = { - repo_archived: "has an archived upstream repository", - repo_removed: "has a removed upstream repository", - }.freeze - FORMULA_DEPRECATE_DISABLE_REASONS = { does_not_build: "does not build", no_license: "has no license", @@ -25,19 +20,16 @@ module DeprecateDisable "a different checksum than the current one. " \ "Upstream's repository might have been compromised. " \ "We can re-package this once upstream has confirmed that they retagged their release", - **SHARED_DEPRECATE_DISABLE_REASONS, }.freeze CASK_DEPRECATE_DISABLE_REASONS = { - discontinued: "is discontinued upstream", - unsigned_artifact: "has an unsigned binary which prevents it from running on Apple Silicon devices " \ - "under standard macOS security policy", - **SHARED_DEPRECATE_DISABLE_REASONS, + discontinued: "is discontinued upstream", }.freeze def type(formula_or_cask) return :deprecated if formula_or_cask.deprecated? - return :disabled if formula_or_cask.disabled? + + :disabled if formula_or_cask.disabled? end def message(formula_or_cask) diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 9b46b15d02e8f..dd8a0e450ec89 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -204,9 +204,10 @@ def prelude if deprecate_disable_type.present? message = "#{formula.full_name} has been #{DeprecateDisable.message(formula)}" - if deprecate_disable_type == :deprecated + case deprecate_disable_type + when :deprecated opoo message - elsif deprecate_disable_type == :disabled + when :disabled raise CannotInstallFormulaError, message end end From c21b8bd68ad1b716ba9df34662a61eb828acbc01 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sat, 16 Dec 2023 23:10:20 -0500 Subject: [PATCH 9/9] Fix deprecation message Co-authored-by: Bo Anderson --- Library/Homebrew/cask/dsl/caveats.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/cask/dsl/caveats.rb b/Library/Homebrew/cask/dsl/caveats.rb index 1a9c4ad4f0e4b..ba0ab886f8b74 100644 --- a/Library/Homebrew/cask/dsl/caveats.rb +++ b/Library/Homebrew/cask/dsl/caveats.rb @@ -163,7 +163,7 @@ def eval_caveats(&block) end caveat :discontinued do - # odeprecated "`caveats :discontinued`", "`deprecated!`" + # odeprecated "`caveats :discontinued`", "`deprecate!`" @discontinued = true <<~EOS #{@cask} has been officially discontinued upstream.