diff --git a/Library/Homebrew/cask.rb b/Library/Homebrew/cask.rb index f4b32a21fd4a4fd4ed11b3e3440e3cb3e2bd6676..dacb4c9da3f46c2ff21ffe2461476404a55f24f4 100644 --- a/Library/Homebrew/cask.rb +++ b/Library/Homebrew/cask.rb @@ -23,4 +23,3 @@ require "cask/staged" require "cask/topological_hash" require "cask/url" require "cask/utils" -require "cask/verify" diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index a9603a50e8e17fee1dd6917723975e914db29b81..1cc322e50730454122767afa8f93740e48bb3477 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -255,20 +255,20 @@ module Cask add_error "you should use sha256 :no_check when version is :latest" end - def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256") - odebug "Verifying #{stanza} string is a legal SHA-256 digest" - return unless sha256.is_a?(String) - return if sha256.length == 64 && sha256[/^[0-9a-f]+$/i] + def check_sha256_actually_256 + odebug "Verifying sha256 string is a legal SHA-256 digest" + return unless cask.sha256.is_a?(Checksum) + return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i] - add_error "#{stanza} string must be of 64 hexadecimal characters" + add_error "sha256 string must be of 64 hexadecimal characters" end - def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256") - odebug "Verifying #{stanza} is not a known invalid value" + def check_sha256_invalid + odebug "Verifying sha256 is not a known invalid value" empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - return unless sha256 == empty_sha256 + return unless cask.sha256 == empty_sha256 - add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}" + add_error "cannot use the sha256 for an empty string: #{empty_sha256}" end def check_latest_with_appcast @@ -428,8 +428,7 @@ module Cask return unless download && cask.url odebug "Auditing download" - downloaded_path = download.perform - Verify.all(cask, downloaded_path) + download.fetch rescue => e add_error "download not possible: #{e}" end diff --git a/Library/Homebrew/cask/cmd/fetch.rb b/Library/Homebrew/cask/cmd/fetch.rb index 7add6ae1dc8a0f9949c859b3697484aa131e3f53..855efa7f0fbd6f67330af5256d5c3899161224b7 100644 --- a/Library/Homebrew/cask/cmd/fetch.rb +++ b/Library/Homebrew/cask/cmd/fetch.rb @@ -32,7 +32,6 @@ module Cask require "cask/installer" options = { - force: args.force?, quarantine: args.quarantine?, }.compact @@ -41,8 +40,9 @@ module Cask casks.each do |cask| puts Installer.caveats(cask) ohai "Downloading external files for Cask #{cask}" - downloaded_path = Download.new(cask, **options).perform - Verify.all(cask, downloaded_path) + download = Download.new(cask, **options) + download.clear_cache if args.force? + downloaded_path = download.fetch ohai "Success! Downloaded to -> #{downloaded_path}" end end diff --git a/Library/Homebrew/cask/cmd/zap.rb b/Library/Homebrew/cask/cmd/zap.rb index d5b5dc3c80a2c5b4bf52dbd168f406649f937912..028c103afda4ddb32a201233807506e963736b38 100644 --- a/Library/Homebrew/cask/cmd/zap.rb +++ b/Library/Homebrew/cask/cmd/zap.rb @@ -32,6 +32,15 @@ module Cask sig { void } def run + self.class.zap_casks(*casks, verbose: args.verbose?, force: args.force?) + end + + sig { params(casks: Cask, force: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean)).void } + def self.zap_casks( + *casks, + force: nil, + verbose: nil + ) require "cask/installer" casks.each do |cask| @@ -43,10 +52,10 @@ module Cask cask = CaskLoader.load(installed_caskfile) end else - raise CaskNotInstalledError, cask unless args.force? + raise CaskNotInstalledError, cask unless force end - Installer.new(cask, verbose: args.verbose?, force: args.force?).zap + Installer.new(cask, verbose: verbose, force: force).zap end end end diff --git a/Library/Homebrew/cask/download.rb b/Library/Homebrew/cask/download.rb index 12e8a575ff3253890611781e22e521f160985eb9..5d577eeb2eaa3e33e0fc334cfa578b5f5097d7a9 100644 --- a/Library/Homebrew/cask/download.rb +++ b/Library/Homebrew/cask/download.rb @@ -4,25 +4,32 @@ require "fileutils" require "cask/cache" require "cask/quarantine" -require "cask/verify" module Cask # A download corresponding to a {Cask}. # # @api private class Download + include Context + attr_reader :cask - def initialize(cask, force: false, quarantine: nil) + def initialize(cask, quarantine: nil) @cask = cask - @force = force @quarantine = quarantine end - def perform - clear_cache - fetch - quarantine + def fetch(verify_download_integrity: true) + downloaded_path = begin + downloader.fetch + downloader.cached_location + rescue => e + error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}") + error.set_backtrace e.backtrace + raise error + end + quarantine(downloaded_path) + self.verify_download_integrity(downloaded_path) if verify_download_integrity downloaded_path end @@ -33,32 +40,43 @@ module Cask end end - private - - attr_reader :force - attr_accessor :downloaded_path - def clear_cache - downloader.clear_cache if force + downloader.clear_cache + end + + def cached_download + downloader.cached_location end - def fetch - downloader.fetch - @downloaded_path = downloader.cached_location - rescue => e - error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}") - error.set_backtrace e.backtrace - raise error + def verify_download_integrity(fn) + if @cask.sha256 == :no_check + opoo "No checksum defined for cask '#{@cask}', skipping verification." + return + end + + begin + ohai "Verifying checksum for cask '#{@cask}'." if verbose? + fn.verify_checksum(@cask.sha256) + rescue ChecksumMissingError + opoo <<~EOS + Cannot verify integrity of '#{fn.basename}'. + No checksum was provided for this cask. + For your reference, the checksum is: + sha256 "#{fn.sha256}" + EOS + end end - def quarantine + private + + def quarantine(path) return if @quarantine.nil? return unless Quarantine.available? if @quarantine - Quarantine.cask!(cask: @cask, download_path: @downloaded_path) + Quarantine.cask!(cask: @cask, download_path: path) else - Quarantine.release!(download_path: @downloaded_path) + Quarantine.release!(download_path: path) end end end diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 149fedb7dec0fc17f9313f69cd0e8b3e8ad06b61..b9f0ca2064d19512e8b3ba9f35476a368845b0ea 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -205,11 +205,14 @@ module Cask def sha256(arg = nil) set_unique_stanza(:sha256, arg.nil?) do - if !arg.is_a?(String) && arg != :no_check + case arg + when :no_check + arg + when String + Checksum.new(:sha256, arg) + else raise CaskInvalidError.new(cask, "invalid 'sha256' value: '#{arg.inspect}'") end - - arg end end diff --git a/Library/Homebrew/cask/exceptions.rb b/Library/Homebrew/cask/exceptions.rb index b5576e9dee0fc08390cbd3089ebe7321c7dbd29f..9778c297255fe0231d64e8be8d78f28bd8519f56 100644 --- a/Library/Homebrew/cask/exceptions.rb +++ b/Library/Homebrew/cask/exceptions.rb @@ -207,76 +207,6 @@ module Cask end end - # Error with a cask's checksum. - # - # @api private - class CaskSha256Error < AbstractCaskErrorWithToken - attr_reader :expected, :actual - - def initialize(token, expected = nil, actual = nil) - super(token) - @expected = expected - @actual = actual - end - end - - # Error when a cask's checksum is missing. - # - # @api private - class CaskSha256MissingError < CaskSha256Error - extend T::Sig - - sig { returns(String) } - def to_s - <<~EOS - Cask '#{token}' requires a checksum: - #{Formatter.identifier("sha256 \"#{actual}\"")} - EOS - end - end - - # Error when a cask's checksum does not match. - # - # @api private - class CaskSha256MismatchError < CaskSha256Error - extend T::Sig - - attr_reader :path - - def initialize(token, expected, actual, path) - super(token, expected, actual) - @path = path - end - - sig { returns(String) } - def to_s - <<~EOS - Checksum for Cask '#{token}' does not match. - Expected: #{Formatter.success(expected.to_s)} - Actual: #{Formatter.error(actual.to_s)} - File: #{path} - To retry an incomplete download, remove the file above. - If the issue persists, visit: - #{Formatter.url("https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/reporting_bugs/checksum_does_not_match_error.md")} - EOS - end - end - - # Error when a cask has no checksum and the `--require-sha` flag is passed. - # - # @api private - class CaskNoShasumError < CaskSha256Error - extend T::Sig - - sig { returns(String) } - def to_s - <<~EOS - Cask '#{token}' does not have a sha256 checksum defined and was not installed. - This means you have the #{Formatter.identifier("--require-sha")} option set, perhaps in your HOMEBREW_CASK_OPTS. - EOS - end - end - # Error during quarantining of a file. # # @api private diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index 5c918660863401ae4332b7849ece222bb472e091..23eda92fe7a53c356c0fa9c9b7cf7ce535583e91 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -8,7 +8,6 @@ require "cask/topological_hash" require "cask/config" require "cask/download" require "cask/staged" -require "cask/verify" require "cask/quarantine" require "cgi" @@ -68,7 +67,6 @@ module Cask satisfy_dependencies download - verify end def stage @@ -156,7 +154,7 @@ module Cask return @downloaded_path if @downloaded_path odebug "Downloading" - @downloaded_path = Download.new(@cask, force: false, quarantine: quarantine?).perform + @downloaded_path = Download.new(@cask, quarantine: quarantine?).fetch odebug "Downloaded to -> #{@downloaded_path}" @downloaded_path end @@ -165,11 +163,10 @@ module Cask odebug "Checking cask has checksum" return unless @cask.sha256 == :no_check - raise CaskNoShasumError, @cask.token - end - - def verify - Verify.all(@cask, @downloaded_path) + raise CaskError, <<~EOS + Cask '#{@cask}' does not have a sha256 checksum defined and was not installed. + This means you have the #{Formatter.identifier("--require-sha")} option set, perhaps in your HOMEBREW_CASK_OPTS. + EOS end def primary_container diff --git a/Library/Homebrew/cask/verify.rb b/Library/Homebrew/cask/verify.rb deleted file mode 100644 index 65f9998dee45ec49365c2602677b76da67e0341a..0000000000000000000000000000000000000000 --- a/Library/Homebrew/cask/verify.rb +++ /dev/null @@ -1,30 +0,0 @@ -# typed: false -# frozen_string_literal: true - -module Cask - # Helper module for verifying a cask's checksum. - # - # @api private - module Verify - module_function - - def all(cask, downloaded_path) - if cask.sha256 == :no_check - ohai "No SHA-256 checksum defined for Cask '#{cask}', skipping verification." - return - end - - ohai "Verifying SHA-256 checksum for Cask '#{cask}'." - - expected = cask.sha256 - computed = downloaded_path.sha256 - - raise CaskSha256MissingError.new(cask.token, expected, computed) if expected.nil? || expected.empty? - - return if expected == computed - - ohai "Note: Running `brew update` may fix SHA-256 checksum errors." - raise CaskSha256MismatchError.new(cask.token, expected, computed, downloaded_path) - end - end -end diff --git a/Library/Homebrew/checksum.rb b/Library/Homebrew/checksum.rb index 7a3219513d75f585afa811e66c649e2522fb3f69..a0489972b902353b9ed085f1f5d5ac304c8ed045 100644 --- a/Library/Homebrew/checksum.rb +++ b/Library/Homebrew/checksum.rb @@ -13,12 +13,19 @@ class Checksum def initialize(hash_type, hexdigest) @hash_type = hash_type - @hexdigest = hexdigest + @hexdigest = hexdigest.downcase end - delegate [:empty?, :to_s] => :@hexdigest + delegate [:empty?, :to_s, :length, :[]] => :@hexdigest def ==(other) - hash_type == other&.hash_type && hexdigest == other.hexdigest + case other + when String + to_s == other.downcase + when Checksum + hash_type == other.hash_type && hexdigest == other.hexdigest + else + false + end end end diff --git a/Library/Homebrew/cli/args.rb b/Library/Homebrew/cli/args.rb index b12f32712b9f2eb07f7e97beb42a28fcd0d72834..c9bdd72ccf740e38a326d32781bd4c6953ebaa2f 100644 --- a/Library/Homebrew/cli/args.rb +++ b/Library/Homebrew/cli/args.rb @@ -113,7 +113,7 @@ module Homebrew def build_from_source_formulae if build_from_source? || build_bottle? - named.to_formulae.map(&:full_name) + named.to_formulae_and_casks.select { |f| f.is_a?(Formula) }.map(&:full_name) else [] end diff --git a/Library/Homebrew/cli/named_args.rb b/Library/Homebrew/cli/named_args.rb index f827bc070f452ac39e76fed51bc08dbff4957294..87663712c940d728c22adb2ae418f7772ed7baef 100644 --- a/Library/Homebrew/cli/named_args.rb +++ b/Library/Homebrew/cli/named_args.rb @@ -35,10 +35,10 @@ module Homebrew @to_formulae ||= to_formulae_and_casks(only: :formula).freeze end - def to_formulae_and_casks(only: nil, method: nil) + def to_formulae_and_casks(only: nil, ignore_unavailable: nil, method: nil) @to_formulae_and_casks ||= {} @to_formulae_and_casks[only] ||= begin - to_objects(only: only, method: method).reject { |o| o.is_a?(Tap) }.freeze + to_objects(only: only, ignore_unavailable: ignore_unavailable, method: method).freeze end end @@ -49,10 +49,10 @@ module Homebrew .map(&:freeze).freeze end - def to_formulae_and_casks_and_unavailable(method: nil) + def to_formulae_and_casks_and_unavailable(only: nil, method: nil) @to_formulae_casks_unknowns ||= {} @to_formulae_casks_unknowns[method] = downcased_unique_named.map do |name| - load_formula_or_cask(name, method: method) + load_formula_or_cask(name, only: only, method: method) rescue FormulaOrCaskUnavailableError => e e end.uniq.freeze @@ -68,6 +68,9 @@ module Homebrew resolve_formula(name) when :keg resolve_keg(name) + when :kegs + rack = Formulary.to_rack(name) + rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : [] else raise end @@ -108,10 +111,12 @@ module Homebrew # Convert named arguments to {Formula} or {Cask} objects. # If both a formula and cask exist with the same name, returns the # formula and prints a warning unless `only` is specified. - def to_objects(only: nil, method: nil) + def to_objects(only: nil, ignore_unavailable: nil, method: nil) @to_objects ||= {} - @to_objects[only] ||= downcased_unique_named.map do |name| + @to_objects[only] ||= downcased_unique_named.flat_map do |name| load_formula_or_cask(name, only: only, method: method) + rescue NoSuchKegError, FormulaUnavailableError, Cask::CaskUnavailableError + ignore_unavailable ? [] : raise end.uniq.freeze end private :to_objects @@ -142,7 +147,7 @@ module Homebrew paths << formula_path if formula_path.exist? paths << cask_path if cask_path.exist? - paths.empty? ? name : paths + paths.empty? ? Pathname(name) : paths end end.uniq.freeze end @@ -159,11 +164,17 @@ module Homebrew end end - sig { params(only: T.nilable(Symbol)).returns([T::Array[Keg], T::Array[Cask::Cask]]) } - def to_kegs_to_casks(only: nil) - @to_kegs_to_casks ||= to_formulae_and_casks(only: only, method: :keg) - .partition { |o| o.is_a?(Keg) } - .map(&:freeze).freeze + sig do + params(only: T.nilable(Symbol), ignore_unavailable: T.nilable(T::Boolean), all_kegs: T.nilable(T::Boolean)) + .returns([T::Array[Keg], T::Array[Cask::Cask]]) + end + def to_kegs_to_casks(only: nil, ignore_unavailable: nil, all_kegs: nil) + method = all_kegs ? :kegs : :keg + @to_kegs_to_casks ||= {} + @to_kegs_to_casks[method] ||= + to_formulae_and_casks(only: only, ignore_unavailable: ignore_unavailable, method: method) + .partition { |o| o.is_a?(Keg) } + .map(&:freeze).freeze end sig { returns(T::Array[String]) } diff --git a/Library/Homebrew/cmd/fetch.rb b/Library/Homebrew/cmd/fetch.rb index ac03b5df072419e24cc432c6994a5b2d77a996c2..cb39cc36807baee0d3f8fde35a6b5fa992a83169 100644 --- a/Library/Homebrew/cmd/fetch.rb +++ b/Library/Homebrew/cmd/fetch.rb @@ -4,6 +4,7 @@ require "formula" require "fetch" require "cli/parser" +require "cask/download" module Homebrew extend T::Sig @@ -18,8 +19,8 @@ module Homebrew usage_banner <<~EOS `fetch` [<options>] <formula> - Download a bottle (if available) or source packages for <formula>. - For tarballs, also print SHA-256 checksums. + Download a bottle (if available) or source packages for <formula>e + and binaries for <cask>s. For files, also print SHA-256 checksums. EOS switch "--HEAD", description: "Fetch HEAD version instead of stable version." @@ -42,58 +43,98 @@ module Homebrew switch "--force-bottle", description: "Download a bottle if it exists for the current or newest version of macOS, "\ "even if it would not be used during installation." + switch "--[no-]quarantine", + description: "Disable/enable quarantining of downloads (default: enabled).", + env: :cask_opts_quarantine + + switch "--formula", "--formulae", + description: "Treat all named arguments as formulae." + switch "--cask", "--casks", + description: "Treat all named arguments as casks." + conflicts "--formula", "--cask" conflicts "--devel", "--HEAD" conflicts "--build-from-source", "--build-bottle", "--force-bottle" - min_named :formula + conflicts "--cask", "--HEAD" + conflicts "--cask", "--devel" + conflicts "--cask", "--deps" + conflicts "--cask", "-s" + conflicts "--cask", "--build-bottle" + conflicts "--cask", "--force-bottle" + + min_named :formula_or_cask end end def fetch args = fetch_args.parse - if args.deps? - bucket = [] - args.named.to_formulae.each do |f| - bucket << f - bucket.concat f.recursive_dependencies.map(&:to_formula) + only = :formula if args.formula? && !args.cask? + only = :cask if args.cask? && !args.formula? + + bucket = if args.deps? + args.named.to_formulae_and_casks.flat_map do |formula_or_cask| + case formula_or_cask + when Formula + f = formula_or_cask + + [f, *f.recursive_dependencies.map(&:to_formula)] + else + formula_or_cask + end end - bucket.uniq! else - bucket = args.named.to_formulae - end + args.named.to_formulae_and_casks(only: only) + end.uniq puts "Fetching: #{bucket * ", "}" if bucket.size > 1 - bucket.each do |f| - f.print_tap_action verb: "Fetching" - - fetched_bottle = false - if fetch_bottle?(f, args: args) - begin - fetch_formula(f.bottle, args: args) - rescue Interrupt - raise - rescue => e - raise if Homebrew::EnvConfig.developer? - - fetched_bottle = false - onoe e.message - opoo "Bottle fetch failed: fetching the source." - else - fetched_bottle = true + bucket.each do |formula_or_cask| + case formula_or_cask + when Formula + f = formula_or_cask + + f.print_tap_action verb: "Fetching" + + fetched_bottle = false + if fetch_bottle?(f, args: args) + begin + fetch_formula(f.bottle, args: args) + rescue Interrupt + raise + rescue => e + raise if Homebrew::EnvConfig.developer? + + fetched_bottle = false + onoe e.message + opoo "Bottle fetch failed: fetching the source." + else + fetched_bottle = true + end end - end - next if fetched_bottle + next if fetched_bottle - fetch_formula(f, args: args) + fetch_formula(f, args: args) - f.resources.each do |r| - fetch_resource(r, args: args) - r.patches.each { |p| fetch_patch(p, args: args) if p.external? } - end + f.resources.each do |r| + fetch_resource(r, args: args) + r.patches.each { |p| fetch_patch(p, args: args) if p.external? } + end + + f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? } + else + cask = formula_or_cask + + options = { + force: args.force?, + quarantine: args.quarantine?, + }.compact + + options[:quarantine] = true if options[:quarantine].nil? - f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? } + download = Cask::Download.new(cask, **options) + fetch_cask(download, args: args) + end end end @@ -112,6 +153,13 @@ module Homebrew opoo "Formula reports different #{e.hash_type}: #{e.expected}" end + def fetch_cask(cask_download, args:) + fetch_fetchable cask_download, args: args + rescue ChecksumMismatchError => e + retry if retry_fetch?(cask_download, args: args) + opoo "Cask reports different #{e.hash_type}: #{e.expected}" + end + def fetch_patch(p, args:) fetch_fetchable p, args: args rescue ChecksumMismatchError => e diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 908642fcb8834656a669b1b5cdd4385924d515fc..80c8723438707a5c9d6aa8c2d54eedbd889450e5 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -25,11 +25,11 @@ module Homebrew def info_args Homebrew::CLI::Parser.new do usage_banner <<~EOS - `info` [<options>] [<formula>] + `info` [<options>] [<formula>|<cask>] Display brief statistics for your Homebrew installation. - If <formula> is provided, show summary of information about <formula>. + If a <formula> or <cask> is provided, show summary of information about it. EOS switch "--analytics", description: "List global Homebrew analytics data or, if specified, installation and "\ @@ -61,13 +61,23 @@ module Homebrew switch "-v", "--verbose", description: "Show more verbose analytics data for <formula>." + switch "--formula", "--formulae", + description: "Treat all named arguments as formulae." + switch "--cask", "--casks", + description: "Treat all named arguments as casks." + conflicts "--formula", "--cask" + conflicts "--installed", "--all" end end + sig { void } def info args = info_args.parse + only = :formula if args.formula? && !args.cask? + only = :cask if args.cask? && !args.formula? + if args.analytics? if args.days.present? && !VALID_DAYS.include?(args.days) raise UsageError, "--days must be one of #{VALID_DAYS.join(", ")}" @@ -83,20 +93,21 @@ module Homebrew end end - print_analytics(args: args) + print_analytics(args: args, only: only) elsif args.json - print_json(args: args) + print_json(args: args, only: only) elsif args.github? raise FormulaOrCaskUnspecifiedError if args.no_named? - exec_browser(*args.named.to_formulae_and_casks.map { |f| github_info(f) }) + exec_browser(*args.named.to_formulae_and_casks(only: only).map { |f| github_info(f) }) elsif args.no_named? print_statistics else - print_info(args: args) + print_info(args: args, only: only) end end + sig { void } def print_statistics return unless HOMEBREW_CELLAR.exist? @@ -104,13 +115,14 @@ module Homebrew puts "#{count} #{"keg".pluralize(count)}, #{HOMEBREW_CELLAR.dup.abv}" end - def print_analytics(args:) + sig { params(args: CLI::Args, only: T.nilable(Symbol)).void } + def print_analytics(args:, only: nil) if args.no_named? Utils::Analytics.output(args: args) return end - args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i| + args.named.to_formulae_and_casks_and_unavailable(only: only).each_with_index do |obj, i| puts unless i.zero? case obj @@ -126,8 +138,9 @@ module Homebrew end end - def print_info(args:) - args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i| + sig { params(args: CLI::Args, only: T.nilable(Symbol)).void } + def print_info(args:, only: nil) + args.named.to_formulae_and_casks_and_unavailable(only: only).each_with_index do |obj, i| puts unless i.zero? case obj @@ -159,7 +172,8 @@ module Homebrew version_hash[version] end - def print_json(args:) + sig { params(args: CLI::Args, only: T.nilable(Symbol)).void } + def print_json(args:, only: nil) raise FormulaOrCaskUnspecifiedError if !(args.all? || args.installed?) && args.no_named? json = case json_version(args.json) @@ -179,7 +193,7 @@ module Homebrew elsif args.installed? [Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)] else - args.named.to_formulae_to_casks + args.named.to_formulae_to_casks(only: only) end { diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 7b787d923efb733b90b2e40d8c8096a3ce8a2f3b..bb7968a9bf168a8a777eb9b945d8c8477951efbc 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "keg" @@ -19,12 +19,17 @@ module Homebrew def uninstall_args Homebrew::CLI::Parser.new do usage_banner <<~EOS - `uninstall`, `rm`, `remove` [<options>] <formula> + `uninstall`, `rm`, `remove` [<options>] <formula>|<cask> - Uninstall <formula>. + Uninstall a <formula> or <cask>. EOS switch "-f", "--force", - description: "Delete all installed versions of <formula>." + description: "Delete all installed versions of <formula>. Uninstall even if <cask> is not " \ + "installed, overwrite existing files and ignore errors when removing files." + switch "--zap", + description: "Remove all files associated with a <cask>. " \ + "*May remove files which are shared between applications.*" + conflicts "--formula", "--zap" switch "--ignore-dependencies", description: "Don't fail uninstall, even if <formula> is a dependency of any installed "\ "formulae." @@ -35,7 +40,7 @@ module Homebrew description: "Treat all named arguments as casks." conflicts "--formula", "--cask" - min_named :formula + min_named :formula_or_cask end end @@ -45,51 +50,29 @@ module Homebrew only = :formula if args.formula? && !args.cask? only = :cask if args.cask? && !args.formula? - if args.force? - casks = [] - kegs_by_rack = {} + all_kegs, casks = args.named.to_kegs_to_casks(only: only, ignore_unavailable: args.force?, all_kegs: args.force?) + kegs_by_rack = all_kegs.group_by(&:rack) - args.named.each do |name| - if only != :cask - rack = Formulary.to_rack(name) - kegs_by_rack[rack] = rack.subdirs.map { |d| Keg.new(d) } if rack.directory? - end - - next if only == :formula + Uninstall.uninstall_kegs( + kegs_by_rack, + force: args.force?, + ignore_dependencies: args.ignore_dependencies?, + named_args: args.named, + ) - begin - casks << Cask::CaskLoader.load(name) - rescue Cask::CaskUnavailableError - # Since the uninstall was forced, ignore any unavailable casks. - end - end + if args.zap? + Cask::Cmd::Zap.zap_casks( + *casks, + verbose: args.verbose?, + force: args.force?, + ) else - all_kegs, casks = args.named.to_kegs_to_casks(only: only) - kegs_by_rack = all_kegs.group_by(&:rack) - end - - Uninstall.uninstall_kegs(kegs_by_rack, - force: args.force?, - ignore_dependencies: args.ignore_dependencies?, - named_args: args.named) - - return if casks.blank? - - Cask::Cmd::Uninstall.uninstall_casks( - *casks, - binaries: EnvConfig.cask_opts_binaries?, - verbose: args.verbose?, - force: args.force?, - ) - rescue MultipleVersionsInstalledError => e - ofail e - ensure - # If we delete Cellar/newname, then Cellar/oldname symlink - # can become broken and we have to remove it. - if HOMEBREW_CELLAR.directory? - HOMEBREW_CELLAR.children.each do |rack| - rack.unlink if rack.symlink? && !rack.resolved_path_exists? - end + Cask::Cmd::Uninstall.uninstall_casks( + *casks, + binaries: EnvConfig.cask_opts_binaries?, + verbose: args.verbose?, + force: args.force?, + ) end end end diff --git a/Library/Homebrew/dev-cmd/edit.rb b/Library/Homebrew/dev-cmd/edit.rb index 5526e9809e917cfcaf1664fc58a78b6de04c7ed1..dc4a814e5ac58b5464e38f1b24397c32c8bc4eda 100644 --- a/Library/Homebrew/dev-cmd/edit.rb +++ b/Library/Homebrew/dev-cmd/edit.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "formula" @@ -13,17 +13,27 @@ module Homebrew def edit_args Homebrew::CLI::Parser.new do usage_banner <<~EOS - `edit` [<formula>] + `edit` [<formula>|<cask>] - Open <formula> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`, or open the - Homebrew repository for editing if no formula is provided. + Open a <formula> or <cask> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`, + or open the Homebrew repository for editing if no formula is provided. EOS + + switch "--formula", "--formulae", + description: "Treat all named arguments as formulae." + switch "--cask", "--casks", + description: "Treat all named arguments as casks." + conflicts "--formula", "--cask" end end + sig { void } def edit args = edit_args.parse + only = :formula if args.formula? && !args.cask? + only = :cask if args.cask? && !args.formula? + unless (HOMEBREW_REPOSITORY/".git").directory? raise <<~EOS Changes will be lost! @@ -32,7 +42,7 @@ module Homebrew EOS end - paths = args.named.to_formulae_paths.select do |path| + paths = args.named.to_paths(only: only).select do |path| next path if path.exist? raise UsageError, "#{path} doesn't exist on disk. " \ diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index fadca48894bd1f2f2a335086637e96f7893874f9..428140ac4d22ac0345ac9575b6b06f9200de1a28 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -617,15 +617,15 @@ class ChecksumMissingError < ArgumentError; end class ChecksumMismatchError < RuntimeError attr_reader :expected, :hash_type - def initialize(fn, expected, actual) + def initialize(path, expected, actual) @expected = expected @hash_type = expected.hash_type.to_s.upcase super <<~EOS #{@hash_type} mismatch - Expected: #{expected} - Actual: #{actual} - Archive: #{fn} + Expected: #{Formatter.success(expected.to_s)} + Actual: #{Formatter.error(actual.to_s)} + File: #{path} To retry an incomplete download, remove the file above. EOS end diff --git a/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb b/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb index ebb4590511413055d701e8417cc3fb84e7d21396..5619850d0665c93fef2f3a89789c2c08786e8142 100644 --- a/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb +++ b/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb @@ -27,11 +27,13 @@ class JavaRequirement < Requirement def java_home_cmd # TODO: enable for all macOS versions and Linux on next minor release - # but --version is broken on Big Sur today. - if @version && MacOS.version >= :big_sur - odisabled "depends_on :java", - '"depends_on "openjdk@11", "depends_on "openjdk@8" or "depends_on "openjdk"' + # but --version with ranges is broken on Big Sur today. + if MacOS.version >= :big_sur && @version&.end_with?("+") + odisabled %Q(depends_on java: "#{@version}"), + 'depends_on "openjdk@11", depends_on "openjdk@8" or depends_on "openjdk"' end + # odeprecated "depends_on :java", + # 'depends_on "openjdk@11", depends_on "openjdk@8" or depends_on "openjdk"' return unless File.executable?("/usr/libexec/java_home") diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index ee2d2b02189fc8c8da8f9f3edaf3e35f0430e820..be714073e8516f0ce3a8b86dc1008d7193e796d2 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -654,7 +654,7 @@ class FormulaInstaller @show_header = true unless deps.empty? end - sig { params(dep: Formula).void } + sig { params(dep: Dependency).void } def fetch_dependency(dep) df = dep.to_formula fi = FormulaInstaller.new( @@ -675,7 +675,7 @@ class FormulaInstaller fi.fetch end - sig { params(dep: Formula, inherited_options: Options).void } + sig { params(dep: Dependency, inherited_options: Options).void } def install_dependency(dep, inherited_options) df = dep.to_formula tab = Tab.for_formula(df) diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index 68c237471a756f259e7c8382568be30aadff7715..c2a2b749a851690f5e9e0ec0f5ac77886af13b6f 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -146,13 +146,16 @@ class Resource def verify_download_integrity(fn) if fn.file? - ohai "Verifying #{fn.basename} checksum" if verbose? + ohai "Verifying checksum for '#{fn.basename}'." if verbose? fn.verify_checksum(checksum) end rescue ChecksumMissingError - opoo "Cannot verify integrity of #{fn.basename}" - puts "A checksum was not provided for this resource." - puts "For your reference the SHA-256 is: #{fn.sha256}" + opoo <<~EOS + Cannot verify integrity of '#{fn.basename}'. + No checksum was provided for this resource. + For your reference, the checksum is: + sha256 "#{fn.sha256}" + EOS end Checksum::TYPES.each do |type| diff --git a/Library/Homebrew/tap_auditor.rb b/Library/Homebrew/tap_auditor.rb index c38e9abaf6436c1d7cc1e5b627aa89188d730282..d8294815f27459a7354932a275c1ca88ce876cb3 100644 --- a/Library/Homebrew/tap_auditor.rb +++ b/Library/Homebrew/tap_auditor.rb @@ -10,7 +10,7 @@ module Homebrew attr_reader :name, :path, :tap_audit_exceptions, :problems - sig { params(tap: Tap, strict: T::Boolean).void } + sig { params(tap: Tap, strict: T.nilable(T::Boolean)).void } def initialize(tap, strict:) @name = tap.name @path = tap.path diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index 497780f47059f39895dc1786c4d4cf034205687a..285c6ec3cc662fb554be1785b589f02484a9df85 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -808,7 +808,6 @@ describe Cask::Audit, :cask do let(:cask_token) { "with-binary" } let(:cask) { Cask::CaskLoader.load(cask_token) } let(:download_double) { instance_double(Cask::Download) } - let(:verify) { class_double(Cask::Verify).as_stubbed_const } let(:message) { "Download Failed" } before do @@ -817,19 +816,12 @@ describe Cask::Audit, :cask do end it "when download and verification succeed it does not fail" do - expect(download_double).to receive(:perform) - expect(verify).to receive(:all) + expect(download_double).to receive(:fetch) expect(subject).to pass end it "when download fails it fails" do - expect(download_double).to receive(:perform).and_raise(StandardError.new(message)) - expect(subject).to fail_with(/#{message}/) - end - - it "when verification fails it fails" do - expect(download_double).to receive(:perform) - expect(verify).to receive(:all).and_raise(StandardError.new(message)) + expect(download_double).to receive(:fetch).and_raise(StandardError.new(message)) expect(subject).to fail_with(/#{message}/) end end diff --git a/Library/Homebrew/test/cask/cmd/fetch_spec.rb b/Library/Homebrew/test/cask/cmd/fetch_spec.rb index bc68dc9ec03bfbd56e9ca06dd9ace7340c7925ad..e3d12e9b8320b6ee2508cea5efcfa87aac50cd45 100644 --- a/Library/Homebrew/test/cask/cmd/fetch_spec.rb +++ b/Library/Homebrew/test/cask/cmd/fetch_spec.rb @@ -36,7 +36,7 @@ describe Cask::Cmd::Fetch, :cask do end it "prevents double fetch (without nuking existing installation)" do - cached_location = Cask::Download.new(local_transmission).perform + cached_location = Cask::Download.new(local_transmission).fetch old_ctime = File.stat(cached_location).ctime @@ -47,7 +47,7 @@ describe Cask::Cmd::Fetch, :cask do end it "allows double fetch with --force" do - cached_location = Cask::Download.new(local_transmission).perform + cached_location = Cask::Download.new(local_transmission).fetch old_ctime = File.stat(cached_location).ctime sleep(1) diff --git a/Library/Homebrew/test/cask/cmd/install_spec.rb b/Library/Homebrew/test/cask/cmd/install_spec.rb index 7c051d9d1fd989f130a565208f8437ccee080d11..adb7b6da2be5d1490475b11752b9a48771f79380 100644 --- a/Library/Homebrew/test/cask/cmd/install_spec.rb +++ b/Library/Homebrew/test/cask/cmd/install_spec.rb @@ -11,7 +11,6 @@ describe Cask::Cmd::Install, :cask do it "displays the installation progress" do output = Regexp.new <<~EOS ==> Downloading file:.*caffeine.zip - ==> Verifying SHA-256 checksum for Cask 'local-caffeine'. ==> Installing Cask local-caffeine ==> Moving App 'Caffeine.app' to '.*Caffeine.app'. .*local-caffeine was successfully installed! diff --git a/Library/Homebrew/test/cask/cmd/reinstall_spec.rb b/Library/Homebrew/test/cask/cmd/reinstall_spec.rb index 8b93c92fc3918260ff9f366b991cdeb0e52aa352..4cf73637a1c5ff0ba3649d150fb23606ae5736b5 100644 --- a/Library/Homebrew/test/cask/cmd/reinstall_spec.rb +++ b/Library/Homebrew/test/cask/cmd/reinstall_spec.rb @@ -14,7 +14,6 @@ describe Cask::Cmd::Reinstall, :cask do output = Regexp.new <<~EOS ==> Downloading file:.*caffeine.zip Already downloaded: .*--caffeine.zip - ==> Verifying SHA-256 checksum for Cask 'local-caffeine'. ==> Uninstalling Cask local-caffeine ==> Backing App 'Caffeine.app' up to '.*Caffeine.app'. ==> Removing App '.*Caffeine.app'. diff --git a/Library/Homebrew/test/cask/cmd/style_spec.rb b/Library/Homebrew/test/cask/cmd/style_spec.rb index 7862cfd51934ac9b0cd4f0761fbaf7b7127b9e85..c494be65d9b4e95b8c1a072bbdb7231a6a5b4c5e 100644 --- a/Library/Homebrew/test/cask/cmd/style_spec.rb +++ b/Library/Homebrew/test/cask/cmd/style_spec.rb @@ -75,7 +75,7 @@ describe Cask::Cmd::Style, :cask do end it "tries to find paths for all tokens" do - expect(Cask::CaskLoader).to receive(:load).twice.and_return(double("cask", sourcefile_path: nil)) + expect(Cask::CaskLoader).to receive(:load).twice.and_return(instance_double(Cask::Cask, sourcefile_path: nil)) subject end end diff --git a/Library/Homebrew/test/cask/cmd/upgrade_spec.rb b/Library/Homebrew/test/cask/cmd/upgrade_spec.rb index e930e00b7ac9163b64ba05d00f053494620e5987..4c9f1a319720f8d8f73d600fcb2b4da9792e1365 100644 --- a/Library/Homebrew/test/cask/cmd/upgrade_spec.rb +++ b/Library/Homebrew/test/cask/cmd/upgrade_spec.rb @@ -344,7 +344,7 @@ describe Cask::Cmd::Upgrade, :cask do expect { described_class.run("bad-checksum") - }.to raise_error(Cask::CaskSha256MismatchError).and(not_to_output(output_reverted).to_stderr) + }.to raise_error(ChecksumMismatchError).and(not_to_output(output_reverted).to_stderr) expect(bad_checksum).to be_installed expect(bad_checksum_path).to be_a_directory diff --git a/Library/Homebrew/test/cask/verify_spec.rb b/Library/Homebrew/test/cask/download_spec.rb similarity index 56% rename from Library/Homebrew/test/cask/verify_spec.rb rename to Library/Homebrew/test/cask/download_spec.rb index 8acc50baefb50eb7553d8ef403e405ccb5814d10..c439c2200a018b4b456ed2ca3715b105ab76e170 100644 --- a/Library/Homebrew/test/cask/verify_spec.rb +++ b/Library/Homebrew/test/cask/download_spec.rb @@ -2,26 +2,30 @@ # frozen_string_literal: true module Cask - describe Verify, :cask do - describe "::all" do - subject(:verification) { described_class.all(cask, downloaded_path) } + describe Download, :cask do + describe "#verify_download_integrity" do + subject(:verification) { described_class.new(cask).verify_download_integrity(downloaded_path) } let(:cask) { instance_double(Cask, token: "cask", sha256: expected_sha256) } let(:cafebabe) { "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" } let(:deadbeef) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" } let(:computed_sha256) { cafebabe } - let(:downloaded_path) { instance_double(Pathname, sha256: computed_sha256) } + let(:downloaded_path) { Pathname.new("cask.zip") } + + before do + allow(downloaded_path).to receive(:sha256).and_return(computed_sha256) + end context "when the expected checksum is :no_check" do let(:expected_sha256) { :no_check } it "skips the check" do - expect { verification }.to output(/skipping verification/).to_stdout + expect { verification }.to output(/skipping verification/).to_stderr end end context "when expected and computed checksums match" do - let(:expected_sha256) { cafebabe } + let(:expected_sha256) { Checksum.new(:sha256, cafebabe) } it "does not raise an error" do expect { verification }.not_to raise_error @@ -31,24 +35,24 @@ module Cask context "when the expected checksum is nil" do let(:expected_sha256) { nil } - it "raises an error" do - expect { verification }.to raise_error(CaskSha256MissingError, /sha256 "#{computed_sha256}"/) + it "outputs an error" do + expect { verification }.to output(/sha256 "#{computed_sha256}"/).to_stderr end end context "when the expected checksum is empty" do - let(:expected_sha256) { "" } + let(:expected_sha256) { Checksum.new(:sha256, "") } - it "raises an error" do - expect { verification }.to raise_error(CaskSha256MissingError, /sha256 "#{computed_sha256}"/) + it "outputs an error" do + expect { verification }.to output(/sha256 "#{computed_sha256}"/).to_stderr end end context "when expected and computed checksums do not match" do - let(:expected_sha256) { deadbeef } + let(:expected_sha256) { Checksum.new(:sha256, deadbeef) } it "raises an error" do - expect { verification }.to raise_error CaskSha256MismatchError + expect { verification }.to raise_error ChecksumMismatchError end end end diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb index b15bf205cdcd0c484db7c358aa4f18ff01823223..6a9a1bc67a478bae99416290e30cf534842436ae 100644 --- a/Library/Homebrew/test/cask/installer_spec.rb +++ b/Library/Homebrew/test/cask/installer_spec.rb @@ -65,14 +65,14 @@ describe Cask::Installer, :cask do bad_checksum = Cask::CaskLoader.load(cask_path("bad-checksum")) expect { described_class.new(bad_checksum).install - }.to raise_error(Cask::CaskSha256MismatchError) + }.to raise_error(ChecksumMismatchError) end it "blows up on a missing checksum" do missing_checksum = Cask::CaskLoader.load(cask_path("missing-checksum")) expect { described_class.new(missing_checksum).install - }.to raise_error(Cask::CaskSha256MissingError) + }.to output(/Cannot verify integrity/).to_stderr end it "installs fine if sha256 :no_check is used" do @@ -87,7 +87,7 @@ describe Cask::Installer, :cask do no_checksum = Cask::CaskLoader.load(cask_path("no-checksum")) expect { described_class.new(no_checksum, require_sha: true).install - }.to raise_error(Cask::CaskNoShasumError) + }.to raise_error(/--require-sha/) end it "installs fine if sha256 :no_check is used with --require-sha and --force" do @@ -116,7 +116,6 @@ describe Cask::Installer, :cask do }.to output( <<~EOS, ==> Downloading file://#{HOMEBREW_LIBRARY_PATH}/test/support/fixtures/cask/caffeine.zip - ==> Verifying SHA-256 checksum for Cask 'with-installer-manual'. ==> Installing Cask with-installer-manual To complete the installation of Cask with-installer-manual, you must also run the installer at: diff --git a/Library/Homebrew/test/cask/quarantine_spec.rb b/Library/Homebrew/test/cask/quarantine_spec.rb index 779c995eb3a59935d892f0718da016c8c3458dc2..426d7321cfac7fa54ab7f8d788c963f9a6cc54d2 100644 --- a/Library/Homebrew/test/cask/quarantine_spec.rb +++ b/Library/Homebrew/test/cask/quarantine_spec.rb @@ -31,7 +31,7 @@ describe Cask::Quarantine, :cask do it "quarantines Cask fetches" do Cask::Cmd::Fetch.run("local-transmission") local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) - cached_location = Cask::Download.new(local_transmission).perform + cached_location = Cask::Download.new(local_transmission).fetch expect(cached_location).to be_quarantined end @@ -40,7 +40,7 @@ describe Cask::Quarantine, :cask do Cask::Cmd::Audit.run("local-transmission", "--download") local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) - cached_location = Cask::Download.new(local_transmission).perform + cached_location = Cask::Download.new(local_transmission).fetch expect(cached_location).to be_quarantined end @@ -142,7 +142,7 @@ describe Cask::Quarantine, :cask do it "does not quarantine Cask fetches" do Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine") local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) - cached_location = Cask::Download.new(local_transmission).perform + cached_location = Cask::Download.new(local_transmission).fetch expect(cached_location).not_to be_quarantined end @@ -151,7 +151,7 @@ describe Cask::Quarantine, :cask do Cask::Cmd::Audit.run("local-transmission", "--download", "--no-quarantine") local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) - cached_location = Cask::Download.new(local_transmission).perform + cached_location = Cask::Download.new(local_transmission).fetch expect(cached_location).not_to be_quarantined end diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb index 32d5ee74d0b985f5dbfbe1c81f54bef8e87c16de..ee9743e61d95d96c5b756f004107713b8a795fb9 100644 --- a/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb @@ -1,6 +1,6 @@ cask "invalid-manpage-no-section" do version "1.2.3" - sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94" + sha256 "68b7e71a2ca7585b004f52652749589941e3029ff0884e8aa3b099594e0282c0" url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip" homepage "https://brew.sh/with-generic-artifact" diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb index b330074da7367d0d496ad44e031f7883edcab037..b05174b3808774f5550c421c69365bbfdc46d35a 100644 --- a/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb @@ -1,6 +1,6 @@ cask "with-autodetected-manpage-section" do version "1.2.3" - sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94" + sha256 "68b7e71a2ca7585b004f52652749589941e3029ff0884e8aa3b099594e0282c0" url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip" homepage "https://brew.sh/with-autodetected-manpage-section" diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb index 0ed70fff2786f99378835585d4c349a9c83a9b52..2e4886462aad03e7675efd732146011e5d0ecbc4 100644 --- a/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb @@ -1,6 +1,6 @@ cask "with-non-executable-binary" do version "1.2.3" - sha256 "d5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2" + sha256 "306c6ca7407560340797866e077e053627ad409277d1b9da58106fce4cf717cb" url "file://#{TEST_FIXTURE_DIR}/cask/naked_non_executable" homepage "https://brew.sh/with-binary" diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb index 214f33a662975af8891df18f00d79147bc04efe0..cd3140e2bbdcd7b21b685c21ed434b6817982ebc 100644 --- a/Library/Homebrew/uninstall.rb +++ b/Library/Homebrew/uninstall.rb @@ -82,6 +82,16 @@ module Homebrew end end end + rescue MultipleVersionsInstalledError => e + ofail e + ensure + # If we delete Cellar/newname, then Cellar/oldname symlink + # can become broken and we have to remove it. + if HOMEBREW_CELLAR.directory? + HOMEBREW_CELLAR.children.each do |rack| + rack.unlink if rack.symlink? && !rack.resolved_path_exists? + end + end end def handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: false, named_args: []) diff --git a/docs/Manpage.md b/docs/Manpage.md index c78ef52bd4020c7c57474f67b61f3adb159523b1..af61cac3c39722cf3bc72727818d32acb3b3ba70 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -199,8 +199,8 @@ an issue; just ignore this. ### `fetch` [*`options`*] *`formula`* -Download a bottle (if available) or source packages for *`formula`*. -For tarballs, also print SHA-256 checksums. +Download a bottle (if available) or source packages for *`formula`*e +and binaries for *`cask`*s. For files, also print SHA-256 checksums. * `--HEAD`: Fetch HEAD version instead of stable version. @@ -220,6 +220,12 @@ For tarballs, also print SHA-256 checksums. Download source packages (for eventual bottling) rather than a bottle. * `--force-bottle`: Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation. +* `--[no-]quarantine`: + Disable/enable quarantining of downloads (default: enabled). +* `--formula`: + Treat all named arguments as formulae. +* `--cask`: + Treat all named arguments as casks. ### `formulae` @@ -242,11 +248,11 @@ error message if no logs are found. Open *`formula`*'s homepage in a browser, or open Homebrew's own homepage if no formula is provided. -### `info` [*`options`*] [*`formula`*] +### `info` [*`options`*] [*`formula`*|*`cask`*] Display brief statistics for your Homebrew installation. -If *`formula`* is provided, show summary of information about *`formula`*. +If a *`formula`* or *`cask`* is provided, show summary of information about it. * `--analytics`: List global Homebrew analytics data or, if specified, installation and build error data for *`formula`* (provided neither `HOMEBREW_NO_ANALYTICS` nor `HOMEBREW_NO_GITHUB_API` are set). @@ -264,6 +270,10 @@ If *`formula`* is provided, show summary of information about *`formula`*. Print JSON of all available formulae. * `-v`, `--verbose`: Show more verbose analytics data for *`formula`*. +* `--formula`: + Treat all named arguments as formulae. +* `--cask`: + Treat all named arguments as casks. ### `install` [*`options`*] *`formula`*|*`cask`* @@ -567,12 +577,14 @@ If no *`tap`* names are provided, display brief statistics for all installed tap * `--json`: Print a JSON representation of *`tap`*. Currently the default and only accepted value for *`version`* is `v1`. See the docs for examples of using the JSON output: <https://docs.brew.sh/Querying-Brew> -### `uninstall`, `rm`, `remove` [*`options`*] *`formula`* +### `uninstall`, `rm`, `remove` [*`options`*] *`formula`*|*`cask`* -Uninstall *`formula`*. +Uninstall a *`formula`* or *`cask`*. * `-f`, `--force`: - Delete all installed versions of *`formula`*. + Delete all installed versions of *`formula`*. Uninstall even if *`cask`* is not installed, overwrite existing files and ignore errors when removing files. +* `--zap`: + Remove all files associated with a *`cask`*. *May remove files which are shared between applications.* * `--ignore-dependencies`: Don't fail uninstall, even if *`formula`* is a dependency of any installed formulae. * `--formula`: @@ -1001,10 +1013,15 @@ the Cellar and then link it into Homebrew's prefix with `brew link`. * `--version`: Explicitly set the *`version`* of the package being installed. -### `edit` [*`formula`*] +### `edit` [*`formula`*|*`cask`*] + +Open a *`formula`* or *`cask`* in the editor set by `EDITOR` or `HOMEBREW_EDITOR`, +or open the Homebrew repository for editing if no formula is provided. -Open *`formula`* in the editor set by `EDITOR` or `HOMEBREW_EDITOR`, or open the -Homebrew repository for editing if no formula is provided. +* `--formula`: + Treat all named arguments as formulae. +* `--cask`: + Treat all named arguments as casks. ### `extract` [*`options`*] *`formula`* *`tap`* diff --git a/manpages/brew.1 b/manpages/brew.1 index fd918d6f37c94361c6e0b752f490be3c5ffcd194..777ab1cd2f9a97c82496af36bcd86f1e498690ae 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -254,7 +254,7 @@ List all audit methods, which can be run individually if provided as arguments\. Enable debugging and profiling of audit methods\. . .SS "\fBfetch\fR [\fIoptions\fR] \fIformula\fR" -Download a bottle (if available) or source packages for \fIformula\fR\. For tarballs, also print SHA\-256 checksums\. +Download a bottle (if available) or source packages for \fIformula\fRe and binaries for \fIcask\fRs\. For files, also print SHA\-256 checksums\. . .TP \fB\-\-HEAD\fR @@ -292,6 +292,18 @@ Download source packages (for eventual bottling) rather than a bottle\. \fB\-\-force\-bottle\fR Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation\. . +.TP +\fB\-\-[no\-]quarantine\fR +Disable/enable quarantining of downloads (default: enabled)\. +. +.TP +\fB\-\-formula\fR +Treat all named arguments as formulae\. +. +.TP +\fB\-\-cask\fR +Treat all named arguments as casks\. +. .SS "\fBformulae\fR" List all locally installable formulae including short names\. . @@ -313,11 +325,11 @@ The Gist will be marked private and will not appear in listings but will be acce .SS "\fBhome\fR [\fIformula\fR]" Open \fIformula\fR\'s homepage in a browser, or open Homebrew\'s own homepage if no formula is provided\. . -.SS "\fBinfo\fR [\fIoptions\fR] [\fIformula\fR]" +.SS "\fBinfo\fR [\fIoptions\fR] [\fIformula\fR|\fIcask\fR]" Display brief statistics for your Homebrew installation\. . .P -If \fIformula\fR is provided, show summary of information about \fIformula\fR\. +If a \fIformula\fR or \fIcask\fR is provided, show summary of information about it\. . .TP \fB\-\-analytics\fR @@ -351,6 +363,14 @@ Print JSON of all available formulae\. \fB\-v\fR, \fB\-\-verbose\fR Show more verbose analytics data for \fIformula\fR\. . +.TP +\fB\-\-formula\fR +Treat all named arguments as formulae\. +. +.TP +\fB\-\-cask\fR +Treat all named arguments as casks\. +. .SS "\fBinstall\fR [\fIoptions\fR] \fIformula\fR|\fIcask\fR" Install a \fIformula\fR or \fIcask\fR\. Additional options specific to a \fIformula\fR may be appended to the command\. . @@ -785,12 +805,16 @@ Show information on each installed tap\. \fB\-\-json\fR Print a JSON representation of \fItap\fR\. Currently the default and only accepted value for \fIversion\fR is \fBv1\fR\. See the docs for examples of using the JSON output: \fIhttps://docs\.brew\.sh/Querying\-Brew\fR . -.SS "\fBuninstall\fR, \fBrm\fR, \fBremove\fR [\fIoptions\fR] \fIformula\fR" -Uninstall \fIformula\fR\. +.SS "\fBuninstall\fR, \fBrm\fR, \fBremove\fR [\fIoptions\fR] \fIformula\fR|\fIcask\fR" +Uninstall a \fIformula\fR or \fIcask\fR\. . .TP \fB\-f\fR, \fB\-\-force\fR -Delete all installed versions of \fIformula\fR\. +Delete all installed versions of \fIformula\fR\. Uninstall even if \fIcask\fR is not installed, overwrite existing files and ignore errors when removing files\. +. +.TP +\fB\-\-zap\fR +Remove all files associated with a \fIcask\fR\. \fIMay remove files which are shared between applications\.\fR . .TP \fB\-\-ignore\-dependencies\fR @@ -1396,8 +1420,16 @@ Explicitly set the \fIname\fR of the package being installed\. \fB\-\-version\fR Explicitly set the \fIversion\fR of the package being installed\. . -.SS "\fBedit\fR [\fIformula\fR]" -Open \fIformula\fR in the editor set by \fBEDITOR\fR or \fBHOMEBREW_EDITOR\fR, or open the Homebrew repository for editing if no formula is provided\. +.SS "\fBedit\fR [\fIformula\fR|\fIcask\fR]" +Open a \fIformula\fR or \fIcask\fR in the editor set by \fBEDITOR\fR or \fBHOMEBREW_EDITOR\fR, or open the Homebrew repository for editing if no formula is provided\. +. +.TP +\fB\-\-formula\fR +Treat all named arguments as formulae\. +. +.TP +\fB\-\-cask\fR +Treat all named arguments as casks\. . .SS "\fBextract\fR [\fIoptions\fR] \fIformula\fR \fItap\fR" Look through repository history to find the most recent version of \fIformula\fR and create a copy in \fItap\fR\fB/Formula/\fR\fIformula\fR\fB@\fR\fIversion\fR\fB\.rb\fR\. If the tap is not installed yet, attempt to install/clone the tap before continuing\. To extract a formula from a tap that is not \fBhomebrew/core\fR use its fully\-qualified form of \fIuser\fR\fB/\fR\fIrepo\fR\fB/\fR\fIformula\fR\.