Merge branch 'mohammad' of https://github.com/mohammadzainabbas/brew into mohammad
This commit is contained in:
commit
37eef10b92
@ -67,7 +67,7 @@ GEM
|
|||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
parallel (1.22.1)
|
parallel (1.22.1)
|
||||||
parallel_tests (3.12.0)
|
parallel_tests (3.12.1)
|
||||||
parallel
|
parallel
|
||||||
parlour (8.0.0)
|
parlour (8.0.0)
|
||||||
commander (~> 4.5)
|
commander (~> 4.5)
|
||||||
@ -185,7 +185,7 @@ GEM
|
|||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8.2)
|
unf_ext (0.0.8.2)
|
||||||
unicode-display_width (2.2.0)
|
unicode-display_width (2.3.0)
|
||||||
unparser (0.6.4)
|
unparser (0.6.4)
|
||||||
diff-lcs (~> 1.3)
|
diff-lcs (~> 1.3)
|
||||||
parser (>= 3.1.0)
|
parser (>= 3.1.0)
|
||||||
|
|||||||
@ -26,7 +26,7 @@ module Homebrew
|
|||||||
Homebrew::API.fetch "#{formula_api_path}/#{name}.json"
|
Homebrew::API.fetch "#{formula_api_path}/#{name}.json"
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(Array) }
|
sig { returns(Hash) }
|
||||||
def all_formulae
|
def all_formulae
|
||||||
@all_formulae ||= begin
|
@all_formulae ||= begin
|
||||||
curl_args = %w[--compressed --silent https://formulae.brew.sh/api/formula.json]
|
curl_args = %w[--compressed --silent https://formulae.brew.sh/api/formula.json]
|
||||||
@ -37,11 +37,23 @@ module Homebrew
|
|||||||
|
|
||||||
json_formulae = JSON.parse(cached_formula_json_file.read)
|
json_formulae = JSON.parse(cached_formula_json_file.read)
|
||||||
|
|
||||||
|
@all_aliases = {}
|
||||||
json_formulae.to_h do |json_formula|
|
json_formulae.to_h do |json_formula|
|
||||||
|
json_formula["aliases"].each do |alias_name|
|
||||||
|
@all_aliases[alias_name] = json_formula["name"]
|
||||||
|
end
|
||||||
|
|
||||||
[json_formula["name"], json_formula.except("name")]
|
[json_formula["name"], json_formula.except("name")]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { returns(Hash) }
|
||||||
|
def all_aliases
|
||||||
|
all_formulae if @all_aliases.blank?
|
||||||
|
|
||||||
|
@all_aliases
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -24,7 +24,7 @@ module Cask
|
|||||||
|
|
||||||
def initialize(cask, appcast: nil, download: nil, quarantine: nil,
|
def initialize(cask, appcast: nil, download: nil, quarantine: nil,
|
||||||
token_conflicts: nil, online: nil, strict: nil, signing: nil,
|
token_conflicts: nil, online: nil, strict: nil, signing: nil,
|
||||||
new_cask: nil)
|
new_cask: nil, only: [], except: [])
|
||||||
|
|
||||||
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
|
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
|
||||||
online = new_cask if online.nil?
|
online = new_cask if online.nil?
|
||||||
@ -47,44 +47,22 @@ module Cask
|
|||||||
@signing = signing
|
@signing = signing
|
||||||
@new_cask = new_cask
|
@new_cask = new_cask
|
||||||
@token_conflicts = token_conflicts
|
@token_conflicts = token_conflicts
|
||||||
|
@only = only
|
||||||
|
@except = except
|
||||||
end
|
end
|
||||||
|
|
||||||
def run!
|
def run!
|
||||||
check_denylist
|
only_audits = @only
|
||||||
check_reverse_migration
|
except_audits = @except
|
||||||
check_required_stanzas
|
|
||||||
check_version
|
private_methods.map(&:to_s).grep(/^check_/).each do |audit_method_name|
|
||||||
check_sha256
|
name = audit_method_name.delete_prefix("check_")
|
||||||
check_desc
|
next if !only_audits.empty? && only_audits&.exclude?(name)
|
||||||
check_url
|
next if except_audits&.include?(name)
|
||||||
check_unnecessary_verified
|
|
||||||
check_missing_verified
|
send(audit_method_name)
|
||||||
check_no_match
|
end
|
||||||
check_generic_artifacts
|
|
||||||
check_token_valid
|
|
||||||
check_token_bad_words
|
|
||||||
check_token_conflicts
|
|
||||||
check_languages
|
|
||||||
check_download
|
|
||||||
check_https_availability
|
|
||||||
check_single_pre_postflight
|
|
||||||
check_single_uninstall_zap
|
|
||||||
check_untrusted_pkg
|
|
||||||
livecheck_result = check_livecheck_version
|
|
||||||
check_hosting_with_livecheck(livecheck_result: livecheck_result)
|
|
||||||
check_appcast_and_livecheck
|
|
||||||
check_latest_with_appcast_or_livecheck
|
|
||||||
check_latest_with_auto_updates
|
|
||||||
check_stanza_requires_uninstall
|
|
||||||
check_appcast_contains_version
|
|
||||||
check_gitlab_repository
|
|
||||||
check_gitlab_repository_archived
|
|
||||||
check_gitlab_prerelease_version
|
|
||||||
check_github_repository
|
|
||||||
check_github_repository_archived
|
|
||||||
check_github_prerelease_version
|
|
||||||
check_bitbucket_repository
|
|
||||||
check_signing
|
|
||||||
self
|
self
|
||||||
rescue => e
|
rescue => e
|
||||||
odebug e, e.backtrace
|
odebug e, e.backtrace
|
||||||
@ -100,10 +78,27 @@ module Cask
|
|||||||
@warnings ||= []
|
@warnings ||= []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def errors?
|
||||||
|
errors.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def warnings?
|
||||||
|
warnings.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def success?
|
||||||
|
!(errors? || warnings?)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(message: T.nilable(String), location: T.nilable(String)).void }
|
||||||
def add_error(message, location: nil)
|
def add_error(message, location: nil)
|
||||||
errors << ({ message: message, location: location })
|
errors << ({ message: message, location: location })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(message: T.nilable(String), location: T.nilable(String)).void }
|
||||||
def add_warning(message, location: nil)
|
def add_warning(message, location: nil)
|
||||||
if strict?
|
if strict?
|
||||||
add_error message, location: location
|
add_error message, location: location
|
||||||
@ -112,14 +107,6 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors?
|
|
||||||
errors.any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def warnings?
|
|
||||||
warnings.any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def result
|
def result
|
||||||
if errors?
|
if errors?
|
||||||
Formatter.error("failed")
|
Formatter.error("failed")
|
||||||
@ -150,12 +137,9 @@ module Cask
|
|||||||
summary.join("\n")
|
summary.join("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
def success?
|
|
||||||
!(errors? || warnings?)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_untrusted_pkg
|
def check_untrusted_pkg
|
||||||
odebug "Auditing pkg stanza: allow_untrusted"
|
odebug "Auditing pkg stanza: allow_untrusted"
|
||||||
|
|
||||||
@ -170,6 +154,7 @@ module Cask
|
|||||||
add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
|
add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_stanza_requires_uninstall
|
def check_stanza_requires_uninstall
|
||||||
odebug "Auditing stanzas which require an uninstall"
|
odebug "Auditing stanzas which require an uninstall"
|
||||||
|
|
||||||
@ -179,6 +164,7 @@ module Cask
|
|||||||
add_error "installer and pkg stanzas require an uninstall stanza"
|
add_error "installer and pkg stanzas require an uninstall stanza"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_single_pre_postflight
|
def check_single_pre_postflight
|
||||||
odebug "Auditing preflight and postflight stanzas"
|
odebug "Auditing preflight and postflight stanzas"
|
||||||
|
|
||||||
@ -195,6 +181,7 @@ module Cask
|
|||||||
add_error "only a single postflight stanza is allowed"
|
add_error "only a single postflight stanza is allowed"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_single_uninstall_zap
|
def check_single_uninstall_zap
|
||||||
odebug "Auditing single uninstall_* and zap stanzas"
|
odebug "Auditing single uninstall_* and zap stanzas"
|
||||||
|
|
||||||
@ -221,6 +208,7 @@ module Cask
|
|||||||
add_error "only a single zap stanza is allowed"
|
add_error "only a single zap stanza is allowed"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_required_stanzas
|
def check_required_stanzas
|
||||||
odebug "Auditing required stanzas"
|
odebug "Auditing required stanzas"
|
||||||
[:version, :sha256, :url, :homepage].each do |sym|
|
[:version, :sha256, :url, :homepage].each do |sym|
|
||||||
@ -233,58 +221,66 @@ module Cask
|
|||||||
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
|
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_version
|
sig { void }
|
||||||
return unless cask.version
|
def check_description_present
|
||||||
|
# Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
|
||||||
|
return if cask.tap == "homebrew/cask-fonts"
|
||||||
|
|
||||||
check_no_string_version_latest
|
add_warning "Cask should have a description. Please add a `desc` stanza." if cask.desc.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_no_string_version_latest
|
def check_no_string_version_latest
|
||||||
odebug "Verifying version :latest does not appear as a string ('latest')"
|
return unless cask.version
|
||||||
|
|
||||||
|
odebug "Auditing version :latest does not appear as a string ('latest')"
|
||||||
return unless cask.version.raw_version == "latest"
|
return unless cask.version.raw_version == "latest"
|
||||||
|
|
||||||
add_error "you should use version :latest instead of version 'latest'"
|
add_error "you should use version :latest instead of version 'latest'"
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_sha256
|
sig { void }
|
||||||
|
def check_sha256_no_check_if_latest
|
||||||
return unless cask.sha256
|
return unless cask.sha256
|
||||||
|
|
||||||
check_sha256_no_check_if_latest
|
odebug "Auditing sha256 :no_check with version :latest"
|
||||||
check_sha256_no_check_if_unversioned
|
|
||||||
check_sha256_actually_256
|
|
||||||
check_sha256_invalid
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_sha256_no_check_if_latest
|
|
||||||
odebug "Verifying sha256 :no_check with version :latest"
|
|
||||||
return unless cask.version.latest?
|
return unless cask.version.latest?
|
||||||
return if cask.sha256 == :no_check
|
return if cask.sha256 == :no_check
|
||||||
|
|
||||||
add_error "you should use sha256 :no_check when version is :latest"
|
add_error "you should use sha256 :no_check when version is :latest"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_sha256_no_check_if_unversioned
|
def check_sha256_no_check_if_unversioned
|
||||||
|
return unless cask.sha256
|
||||||
return if cask.sha256 == :no_check
|
return if cask.sha256 == :no_check
|
||||||
|
|
||||||
add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
|
add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_sha256_actually_256
|
def check_sha256_actually_256
|
||||||
odebug "Verifying sha256 string is a legal SHA-256 digest"
|
return unless cask.sha256
|
||||||
|
|
||||||
|
odebug "Auditing sha256 string is a legal SHA-256 digest"
|
||||||
return unless cask.sha256.is_a?(Checksum)
|
return unless cask.sha256.is_a?(Checksum)
|
||||||
return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
|
return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
|
||||||
|
|
||||||
add_error "sha256 string must be of 64 hexadecimal characters"
|
add_error "sha256 string must be of 64 hexadecimal characters"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_sha256_invalid
|
def check_sha256_invalid
|
||||||
odebug "Verifying sha256 is not a known invalid value"
|
return unless cask.sha256
|
||||||
|
|
||||||
|
odebug "Auditing sha256 is not a known invalid value"
|
||||||
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
return unless cask.sha256 == empty_sha256
|
return unless cask.sha256 == empty_sha256
|
||||||
|
|
||||||
add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
|
add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_appcast_and_livecheck
|
def check_appcast_and_livecheck
|
||||||
return unless cask.appcast
|
return unless cask.appcast
|
||||||
|
|
||||||
@ -295,6 +291,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_latest_with_appcast_or_livecheck
|
def check_latest_with_appcast_or_livecheck
|
||||||
return unless cask.version.latest?
|
return unless cask.version.latest?
|
||||||
|
|
||||||
@ -302,6 +299,7 @@ module Cask
|
|||||||
add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable?
|
add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_latest_with_auto_updates
|
def check_latest_with_auto_updates
|
||||||
return unless cask.version.latest?
|
return unless cask.version.latest?
|
||||||
return unless cask.auto_updates
|
return unless cask.auto_updates
|
||||||
@ -311,7 +309,8 @@ module Cask
|
|||||||
|
|
||||||
LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
|
LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
|
||||||
|
|
||||||
def check_hosting_with_livecheck(livecheck_result:)
|
sig { params(livecheck_result: T::Boolean).void }
|
||||||
|
def check_hosting_with_livecheck(livecheck_result: check_livecheck_version)
|
||||||
return if cask.discontinued? || cask.version.latest?
|
return if cask.discontinued? || cask.version.latest?
|
||||||
return if block_url_offline? || cask.appcast || cask.livecheckable?
|
return if block_url_offline? || cask.appcast || cask.livecheckable?
|
||||||
return if livecheck_result == :auto_detected
|
return if livecheck_result == :auto_detected
|
||||||
@ -330,24 +329,12 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_desc
|
|
||||||
return if cask.desc.present?
|
|
||||||
|
|
||||||
# Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
|
|
||||||
return if cask.tap == "homebrew/cask-fonts"
|
|
||||||
|
|
||||||
add_warning "Cask should have a description. Please add a `desc` stanza."
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_url
|
|
||||||
return unless cask.url
|
|
||||||
|
|
||||||
check_download_url_format
|
|
||||||
end
|
|
||||||
|
|
||||||
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
|
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_download_url_format
|
def check_download_url_format
|
||||||
|
return unless cask.url
|
||||||
|
|
||||||
odebug "Auditing URL format"
|
odebug "Auditing URL format"
|
||||||
if bad_sourceforge_url?
|
if bad_sourceforge_url?
|
||||||
add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
|
add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
|
||||||
@ -356,82 +343,9 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def bad_url_format?(regex, valid_formats_array)
|
|
||||||
return false unless cask.url.to_s.match?(regex)
|
|
||||||
|
|
||||||
valid_formats_array.none? { |format| cask.url.to_s =~ format }
|
|
||||||
end
|
|
||||||
|
|
||||||
def bad_sourceforge_url?
|
|
||||||
bad_url_format?(/sourceforge/,
|
|
||||||
[
|
|
||||||
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
|
|
||||||
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
|
|
||||||
])
|
|
||||||
end
|
|
||||||
|
|
||||||
def bad_osdn_url?
|
|
||||||
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
|
|
||||||
end
|
|
||||||
|
|
||||||
def homepage
|
|
||||||
URI(cask.homepage.to_s).host
|
|
||||||
end
|
|
||||||
|
|
||||||
def domain
|
|
||||||
URI(cask.url.to_s).host
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_match_homepage?
|
|
||||||
host = cask.url.to_s
|
|
||||||
host_uri = URI(host)
|
|
||||||
host = if host.match?(/:\d/) && host_uri.port != 80
|
|
||||||
"#{host_uri.host}:#{host_uri.port}"
|
|
||||||
else
|
|
||||||
host_uri.host
|
|
||||||
end
|
|
||||||
home = homepage.downcase
|
|
||||||
if (split_host = host.split(".")).length >= 3
|
|
||||||
host = split_host[-2..].join(".")
|
|
||||||
end
|
|
||||||
if (split_home = homepage.split(".")).length >= 3
|
|
||||||
home = split_home[-2..].join(".")
|
|
||||||
end
|
|
||||||
host == home
|
|
||||||
end
|
|
||||||
|
|
||||||
def strip_url_scheme(url)
|
|
||||||
url.sub(%r{^[^:/]+://(www\.)?}, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_from_verified
|
|
||||||
strip_url_scheme(cask.url.verified)
|
|
||||||
end
|
|
||||||
|
|
||||||
def verified_matches_url?
|
|
||||||
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
|
|
||||||
verified_domain, verified_path = url_from_verified.split("/", 2)
|
|
||||||
|
|
||||||
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
|
|
||||||
(!verified_path || url_path&.start_with?(verified_path))
|
|
||||||
end
|
|
||||||
|
|
||||||
def verified_present?
|
|
||||||
cask.url.verified.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def file_url?
|
|
||||||
URI(cask.url.to_s).scheme == "file"
|
|
||||||
end
|
|
||||||
|
|
||||||
def block_url_offline?
|
|
||||||
return false if online?
|
|
||||||
|
|
||||||
cask.url.from_block?
|
|
||||||
end
|
|
||||||
|
|
||||||
VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified"
|
VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified"
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_unnecessary_verified
|
def check_unnecessary_verified
|
||||||
return if block_url_offline?
|
return if block_url_offline?
|
||||||
return unless verified_present?
|
return unless verified_present?
|
||||||
@ -443,6 +357,7 @@ module Cask
|
|||||||
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_missing_verified
|
def check_missing_verified
|
||||||
return if block_url_offline?
|
return if block_url_offline?
|
||||||
return if file_url?
|
return if file_url?
|
||||||
@ -454,6 +369,7 @@ module Cask
|
|||||||
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_no_match
|
def check_no_match
|
||||||
return if block_url_offline?
|
return if block_url_offline?
|
||||||
return unless verified_present?
|
return unless verified_present?
|
||||||
@ -464,6 +380,7 @@ module Cask
|
|||||||
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_generic_artifacts
|
def check_generic_artifacts
|
||||||
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
|
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
|
||||||
unless artifact.target.absolute?
|
unless artifact.target.absolute?
|
||||||
@ -472,6 +389,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_languages
|
def check_languages
|
||||||
@cask.languages.each do |language|
|
@cask.languages.each do |language|
|
||||||
Locale.parse(language)
|
Locale.parse(language)
|
||||||
@ -480,6 +398,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_token_conflicts
|
def check_token_conflicts
|
||||||
return unless token_conflicts?
|
return unless token_conflicts?
|
||||||
return unless core_formula_names.include?(cask.token)
|
return unless core_formula_names.include?(cask.token)
|
||||||
@ -488,6 +407,7 @@ module Cask
|
|||||||
"#{Formatter.url(core_formula_url)}"
|
"#{Formatter.url(core_formula_url)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_token_valid
|
def check_token_valid
|
||||||
add_error "cask token contains non-ascii characters" unless cask.token.ascii_only?
|
add_error "cask token contains non-ascii characters" unless cask.token.ascii_only?
|
||||||
add_error "cask token + should be replaced by -plus-" if cask.token.include? "+"
|
add_error "cask token + should be replaced by -plus-" if cask.token.include? "+"
|
||||||
@ -505,6 +425,7 @@ module Cask
|
|||||||
add_error "cask token should not have leading or trailing hyphens"
|
add_error "cask token should not have leading or trailing hyphens"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_token_bad_words
|
def check_token_bad_words
|
||||||
return unless new_cask?
|
return unless new_cask?
|
||||||
|
|
||||||
@ -532,19 +453,7 @@ module Cask
|
|||||||
add_warning "cask token mentions framework"
|
add_warning "cask token mentions framework"
|
||||||
end
|
end
|
||||||
|
|
||||||
def core_tap
|
sig { void }
|
||||||
@core_tap ||= CoreTap.instance
|
|
||||||
end
|
|
||||||
|
|
||||||
def core_formula_names
|
|
||||||
core_tap.formula_names
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def core_formula_url
|
|
||||||
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_download
|
def check_download
|
||||||
return if download.blank? || cask.url.blank?
|
return if download.blank? || cask.url.blank?
|
||||||
|
|
||||||
@ -554,11 +463,11 @@ module Cask
|
|||||||
add_error "download not possible: #{e}"
|
add_error "download not possible: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_signing
|
def check_signing
|
||||||
return if !signing? || download.blank? || cask.url.blank?
|
return if !signing? || download.blank? || cask.url.blank?
|
||||||
|
|
||||||
odebug "Auditing signing"
|
odebug "Auditing signing"
|
||||||
odebug cask.artifacts
|
|
||||||
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
|
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
|
||||||
|
|
||||||
return if artifacts.empty?
|
return if artifacts.empty?
|
||||||
@ -598,6 +507,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
|
||||||
def check_livecheck_version
|
def check_livecheck_version
|
||||||
return unless appcast?
|
return unless appcast?
|
||||||
|
|
||||||
@ -635,6 +545,7 @@ module Cask
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_appcast_contains_version
|
def check_appcast_contains_version
|
||||||
return unless appcast?
|
return unless appcast?
|
||||||
return if cask.appcast.to_s.empty?
|
return if cask.appcast.to_s.empty?
|
||||||
@ -661,6 +572,7 @@ module Cask
|
|||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_github_prerelease_version
|
def check_github_prerelease_version
|
||||||
return if cask.tap == "homebrew/cask-versions"
|
return if cask.tap == "homebrew/cask-versions"
|
||||||
|
|
||||||
@ -674,6 +586,7 @@ module Cask
|
|||||||
add_error error if error
|
add_error error if error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_gitlab_prerelease_version
|
def check_gitlab_prerelease_version
|
||||||
return if cask.tap == "homebrew/cask-versions"
|
return if cask.tap == "homebrew/cask-versions"
|
||||||
|
|
||||||
@ -688,6 +601,7 @@ module Cask
|
|||||||
add_error error if error
|
add_error error if error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_github_repository_archived
|
def check_github_repository_archived
|
||||||
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
|
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
|
||||||
return if user.nil?
|
return if user.nil?
|
||||||
@ -708,6 +622,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_gitlab_repository_archived
|
def check_gitlab_repository_archived
|
||||||
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
|
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
|
||||||
return if user.nil?
|
return if user.nil?
|
||||||
@ -728,6 +643,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_github_repository
|
def check_github_repository
|
||||||
return unless new_cask?
|
return unless new_cask?
|
||||||
|
|
||||||
@ -740,6 +656,7 @@ module Cask
|
|||||||
add_error error if error
|
add_error error if error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_gitlab_repository
|
def check_gitlab_repository
|
||||||
return unless new_cask?
|
return unless new_cask?
|
||||||
|
|
||||||
@ -752,6 +669,7 @@ module Cask
|
|||||||
add_error error if error
|
add_error error if error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def check_bitbucket_repository
|
def check_bitbucket_repository
|
||||||
return unless new_cask?
|
return unless new_cask?
|
||||||
|
|
||||||
@ -764,6 +682,62 @@ module Cask
|
|||||||
add_error error if error
|
add_error error if error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def check_denylist
|
||||||
|
return unless cask.tap
|
||||||
|
return unless cask.tap.official?
|
||||||
|
return unless (reason = Denylist.reason(cask.token))
|
||||||
|
|
||||||
|
add_error "#{cask.token} is not allowed: #{reason}"
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def check_reverse_migration
|
||||||
|
return unless new_cask?
|
||||||
|
return unless cask.tap
|
||||||
|
return unless cask.tap.official?
|
||||||
|
return unless cask.tap.tap_migrations.key?(cask.token)
|
||||||
|
|
||||||
|
add_error "#{cask.token} is listed in tap_migrations.json"
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def check_https_availability
|
||||||
|
return unless download
|
||||||
|
|
||||||
|
if cask.url && !cask.url.using
|
||||||
|
validate_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
|
||||||
|
user_agents: [cask.url.user_agent])
|
||||||
|
end
|
||||||
|
|
||||||
|
if cask.appcast && appcast?
|
||||||
|
validate_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless cask.homepage
|
||||||
|
|
||||||
|
validate_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
|
||||||
|
user_agents: [:browser, :default],
|
||||||
|
check_content: true,
|
||||||
|
strict: strict?)
|
||||||
|
end
|
||||||
|
|
||||||
|
# sig {
|
||||||
|
# params(url_to_check: T.any(String, URL), url_type: String, cask_token: String, tap: Tap,
|
||||||
|
# options: T.untyped).void
|
||||||
|
# }
|
||||||
|
def validate_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
|
||||||
|
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
|
||||||
|
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
|
||||||
|
|
||||||
|
if problem
|
||||||
|
add_error problem unless exception
|
||||||
|
elsif exception
|
||||||
|
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(regex: T.any(String, Regexp)).returns(T.nilable(T::Array[String])) }
|
||||||
def get_repo_data(regex)
|
def get_repo_data(regex)
|
||||||
return unless online?
|
return unless online?
|
||||||
|
|
||||||
@ -777,52 +751,107 @@ module Cask
|
|||||||
[user, repo]
|
[user, repo]
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_denylist
|
sig {
|
||||||
return unless cask.tap
|
params(regex: T.any(String, Regexp), valid_formats_array: T::Array[T.any(String, Regexp)]).returns(T::Boolean)
|
||||||
return unless cask.tap.official?
|
}
|
||||||
return unless (reason = Denylist.reason(cask.token))
|
def bad_url_format?(regex, valid_formats_array)
|
||||||
|
return false unless cask.url.to_s.match?(regex)
|
||||||
|
|
||||||
add_error "#{cask.token} is not allowed: #{reason}"
|
valid_formats_array.none? { |format| cask.url.to_s =~ format }
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_reverse_migration
|
sig { returns(T::Boolean) }
|
||||||
return unless new_cask?
|
def bad_sourceforge_url?
|
||||||
return unless cask.tap
|
bad_url_format?(/sourceforge/,
|
||||||
return unless cask.tap.official?
|
[
|
||||||
return unless cask.tap.tap_migrations.key?(cask.token)
|
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
|
||||||
|
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
|
||||||
add_error "#{cask.token} is listed in tap_migrations.json"
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_https_availability
|
sig { returns(T::Boolean) }
|
||||||
return unless download
|
def bad_osdn_url?
|
||||||
|
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
|
||||||
if cask.url && !cask.url.using
|
|
||||||
check_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
|
|
||||||
user_agents: [cask.url.user_agent])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if cask.appcast && appcast?
|
# sig { returns(String) }
|
||||||
check_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
|
def homepage
|
||||||
|
URI(cask.homepage.to_s).host
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless cask.homepage
|
# sig { returns(String) }
|
||||||
|
def domain
|
||||||
check_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
|
URI(cask.url.to_s).host
|
||||||
user_agents: [:browser, :default],
|
|
||||||
check_content: true,
|
|
||||||
strict: strict?)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
|
sig { returns(T::Boolean) }
|
||||||
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
|
def url_match_homepage?
|
||||||
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
|
host = cask.url.to_s
|
||||||
|
host_uri = URI(host)
|
||||||
|
host = if host.match?(/:\d/) && host_uri.port != 80
|
||||||
|
"#{host_uri.host}:#{host_uri.port}"
|
||||||
|
else
|
||||||
|
host_uri.host
|
||||||
|
end
|
||||||
|
home = homepage.downcase
|
||||||
|
if (split_host = host.split(".")).length >= 3
|
||||||
|
host = split_host[-2..].join(".")
|
||||||
|
end
|
||||||
|
if (split_home = homepage.split(".")).length >= 3
|
||||||
|
home = split_home[-2..].join(".")
|
||||||
|
end
|
||||||
|
host == home
|
||||||
|
end
|
||||||
|
|
||||||
if problem
|
# sig { params(url: String).returns(String) }
|
||||||
add_error problem unless exception
|
def strip_url_scheme(url)
|
||||||
elsif exception
|
url.sub(%r{^[^:/]+://(www\.)?}, "")
|
||||||
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
|
end
|
||||||
end
|
|
||||||
|
# sig { returns(String) }
|
||||||
|
def url_from_verified
|
||||||
|
strip_url_scheme(cask.url.verified)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def verified_matches_url?
|
||||||
|
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
|
||||||
|
verified_domain, verified_path = url_from_verified.split("/", 2)
|
||||||
|
|
||||||
|
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
|
||||||
|
(!verified_path || url_path&.start_with?(verified_path))
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def verified_present?
|
||||||
|
cask.url.verified.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def file_url?
|
||||||
|
URI(cask.url.to_s).scheme == "file"
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def block_url_offline?
|
||||||
|
return false if online?
|
||||||
|
|
||||||
|
cask.url.from_block?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(Tap) }
|
||||||
|
def core_tap
|
||||||
|
@core_tap ||= CoreTap.instance
|
||||||
|
end
|
||||||
|
|
||||||
|
# sig { returns(T::Array[String]) }
|
||||||
|
def core_formula_names
|
||||||
|
core_tap.formula_names
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def core_formula_url
|
||||||
|
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -144,7 +144,6 @@ module Cask
|
|||||||
quarantine: @quarantine,
|
quarantine: @quarantine,
|
||||||
)
|
)
|
||||||
audit.run!
|
audit.run!
|
||||||
audit
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def language_blocks
|
def language_blocks
|
||||||
|
|||||||
@ -579,6 +579,7 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.autoremove(dry_run: false)
|
def self.autoremove(dry_run: false)
|
||||||
|
require "utils/autoremove"
|
||||||
require "cask/caskroom"
|
require "cask/caskroom"
|
||||||
|
|
||||||
# If this runs after install, uninstall, reinstall or upgrade,
|
# If this runs after install, uninstall, reinstall or upgrade,
|
||||||
@ -593,7 +594,7 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
casks = Cask::Caskroom.casks
|
casks = Cask::Caskroom.casks
|
||||||
|
|
||||||
removable_formulae = Formula.unused_formulae_with_no_dependents(formulae, casks)
|
removable_formulae = Utils::Autoremove.removable_formulae(formulae, casks)
|
||||||
|
|
||||||
return if removable_formulae.blank?
|
return if removable_formulae.blank?
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ module Homebrew
|
|||||||
def leaves
|
def leaves
|
||||||
args = leaves_args.parse
|
args = leaves_args.parse
|
||||||
|
|
||||||
leaves_list = Formula.formulae_with_no_formula_dependents(Formula.installed)
|
leaves_list = Formula.installed - Formula.installed.flat_map(&:runtime_formula_dependencies)
|
||||||
|
|
||||||
leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request?
|
leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request?
|
||||||
leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency?
|
leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency?
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
# HOMEBREW_CELLAR and HOMEBREW_PREFIX are set by extend/ENV/super.rb
|
# HOMEBREW_CELLAR and HOMEBREW_PREFIX are set by extend/ENV/super.rb
|
||||||
# HOMEBREW_REPOSITORY is set by bin/brew
|
# HOMEBREW_REPOSITORY is set by bin/brew
|
||||||
|
# Trailing colon in MANPATH adds default man dirs to search path in Linux, does no harm in macOS.
|
||||||
|
# Please do not submit PRs to remove it!
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
homebrew-shellenv() {
|
homebrew-shellenv() {
|
||||||
if [[ "${HOMEBREW_PATH%%:"${HOMEBREW_PREFIX}"/sbin*}" == "${HOMEBREW_PREFIX}/bin" ]]
|
if [[ "${HOMEBREW_PATH%%:"${HOMEBREW_PREFIX}"/sbin*}" == "${HOMEBREW_PREFIX}/bin" ]]
|
||||||
|
|||||||
@ -1049,11 +1049,11 @@ module Homebrew
|
|||||||
when :quarantine_available
|
when :quarantine_available
|
||||||
nil
|
nil
|
||||||
when :xattr_broken
|
when :xattr_broken
|
||||||
"There's no working version of `xattr` on this system."
|
"No Cask quarantine support available: there's no working version of `xattr` on this system."
|
||||||
when :no_swift
|
when :no_swift
|
||||||
"Swift is not available on this system."
|
"No Cask quarantine support available: there's no available version of `swift` on this system."
|
||||||
else
|
else
|
||||||
"Unknown support status"
|
"No Cask quarantine support available: unknown reason."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1844,46 +1844,6 @@ class Formula
|
|||||||
end.uniq(&:name)
|
end.uniq(&:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# An array of all installed {Formula} with {Cask} dependents.
|
|
||||||
# @private
|
|
||||||
def self.formulae_with_cask_dependents(casks)
|
|
||||||
casks.flat_map { |cask| cask.depends_on[:formula] }
|
|
||||||
.compact
|
|
||||||
.map { |f| Formula[f] }
|
|
||||||
.flat_map { |f| [f, *f.runtime_formula_dependencies].compact }
|
|
||||||
end
|
|
||||||
|
|
||||||
# An array of all installed {Formula} without {Formula} dependents
|
|
||||||
# @private
|
|
||||||
def self.formulae_with_no_formula_dependents(formulae)
|
|
||||||
return [] if formulae.blank?
|
|
||||||
|
|
||||||
formulae - formulae.flat_map(&:runtime_formula_dependencies)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Recursive function that returns an array of {Formula} without
|
|
||||||
# {Formula} dependents that weren't installed on request.
|
|
||||||
# @private
|
|
||||||
def self.unused_formulae_with_no_formula_dependents(formulae)
|
|
||||||
unused_formulae = formulae_with_no_formula_dependents(formulae).reject do |f|
|
|
||||||
Tab.for_keg(f.any_installed_keg).installed_on_request
|
|
||||||
end
|
|
||||||
|
|
||||||
if unused_formulae.present?
|
|
||||||
unused_formulae += unused_formulae_with_no_formula_dependents(formulae - unused_formulae)
|
|
||||||
end
|
|
||||||
|
|
||||||
unused_formulae
|
|
||||||
end
|
|
||||||
|
|
||||||
# An array of {Formula} without {Formula} or {Cask}
|
|
||||||
# dependents that weren't installed on request.
|
|
||||||
# @private
|
|
||||||
def self.unused_formulae_with_no_dependents(formulae, casks)
|
|
||||||
unused_formulae = unused_formulae_with_no_formula_dependents(formulae)
|
|
||||||
unused_formulae - formulae_with_cask_dependents(casks)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.installed_with_alias_path(alias_path)
|
def self.installed_with_alias_path(alias_path)
|
||||||
return [] if alias_path.nil?
|
return [] if alias_path.nil?
|
||||||
|
|
||||||
@ -2744,6 +2704,10 @@ class Formula
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Whether this formula was loaded using the formulae.brew.sh API
|
||||||
|
# @private
|
||||||
|
attr_accessor :loaded_from_api
|
||||||
|
|
||||||
# Whether this formula contains OS/arch-specific blocks
|
# Whether this formula contains OS/arch-specific blocks
|
||||||
# (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
|
# (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
|
||||||
# @private
|
# @private
|
||||||
|
|||||||
@ -1165,7 +1165,7 @@ class FormulaInstaller
|
|||||||
|
|
||||||
if pour_bottle?(output_warning: true)
|
if pour_bottle?(output_warning: true)
|
||||||
formula.fetch_bottle_tab
|
formula.fetch_bottle_tab
|
||||||
elsif Homebrew::EnvConfig.install_from_api?
|
elsif formula.core_formula? && Homebrew::EnvConfig.install_from_api?
|
||||||
odie "Unable to build #{formula.name} from source with HOMEBREW_INSTALL_FROM_API."
|
odie "Unable to build #{formula.name} from source with HOMEBREW_INSTALL_FROM_API."
|
||||||
else
|
else
|
||||||
formula.fetch_patches
|
formula.fetch_patches
|
||||||
@ -1202,6 +1202,7 @@ class FormulaInstaller
|
|||||||
tab.unused_options = []
|
tab.unused_options = []
|
||||||
tab.built_as_bottle = true
|
tab.built_as_bottle = true
|
||||||
tab.poured_from_bottle = true
|
tab.poured_from_bottle = true
|
||||||
|
tab.loaded_from_api = formula.class.loaded_from_api
|
||||||
tab.installed_as_dependency = installed_as_dependency?
|
tab.installed_as_dependency = installed_as_dependency?
|
||||||
tab.installed_on_request = installed_on_request?
|
tab.installed_on_request = installed_on_request?
|
||||||
tab.time = Time.now.to_i
|
tab.time = Time.now.to_i
|
||||||
|
|||||||
@ -217,6 +217,7 @@ module Formulary
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
klass.loaded_from_api = true
|
||||||
mod.const_set(class_s, klass)
|
mod.const_set(class_s, klass)
|
||||||
|
|
||||||
cache[:api] ||= {}
|
cache[:api] ||= {}
|
||||||
@ -529,6 +530,14 @@ module Formulary
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Load aliases from the API.
|
||||||
|
class AliasAPILoader < FormulaAPILoader
|
||||||
|
def initialize(alias_name)
|
||||||
|
super Homebrew::API::Formula.all_aliases[alias_name]
|
||||||
|
@alias_path = Formulary.core_alias_path(alias_name).to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Return a {Formula} instance for the given reference.
|
# Return a {Formula} instance for the given reference.
|
||||||
# `ref` is a string containing:
|
# `ref` is a string containing:
|
||||||
#
|
#
|
||||||
@ -655,6 +664,7 @@ module Formulary
|
|||||||
if ref.start_with?("homebrew/core/") && Homebrew::EnvConfig.install_from_api?
|
if ref.start_with?("homebrew/core/") && Homebrew::EnvConfig.install_from_api?
|
||||||
name = ref.split("/", 3).last
|
name = ref.split("/", 3).last
|
||||||
return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
|
return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
|
||||||
|
return AliasAPILoader.new(name) if Homebrew::API::Formula.all_aliases.key?(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
return TapLoader.new(ref, from: from)
|
return TapLoader.new(ref, from: from)
|
||||||
@ -662,14 +672,15 @@ module Formulary
|
|||||||
|
|
||||||
return FromPathLoader.new(ref) if File.extname(ref) == ".rb" && Pathname.new(ref).expand_path.exist?
|
return FromPathLoader.new(ref) if File.extname(ref) == ".rb" && Pathname.new(ref).expand_path.exist?
|
||||||
|
|
||||||
if Homebrew::EnvConfig.install_from_api? && Homebrew::API::Formula.all_formulae.key?(ref)
|
if Homebrew::EnvConfig.install_from_api?
|
||||||
return FormulaAPILoader.new(ref)
|
return FormulaAPILoader.new(ref) if Homebrew::API::Formula.all_formulae.key?(ref)
|
||||||
|
return AliasAPILoader.new(ref) if Homebrew::API::Formula.all_aliases.key?(ref)
|
||||||
end
|
end
|
||||||
|
|
||||||
formula_with_that_name = core_path(ref)
|
formula_with_that_name = core_path(ref)
|
||||||
return FormulaLoader.new(ref, formula_with_that_name) if formula_with_that_name.file?
|
return FormulaLoader.new(ref, formula_with_that_name) if formula_with_that_name.file?
|
||||||
|
|
||||||
possible_alias = CoreTap.instance.alias_dir/ref
|
possible_alias = core_alias_path(ref)
|
||||||
return AliasLoader.new(possible_alias) if possible_alias.file?
|
return AliasLoader.new(possible_alias) if possible_alias.file?
|
||||||
|
|
||||||
possible_tap_formulae = tap_paths(ref)
|
possible_tap_formulae = tap_paths(ref)
|
||||||
@ -705,6 +716,10 @@ module Formulary
|
|||||||
CoreTap.instance.formula_dir/"#{name.to_s.downcase}.rb"
|
CoreTap.instance.formula_dir/"#{name.to_s.downcase}.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.core_alias_path(name)
|
||||||
|
CoreTap.instance.alias_dir/name.to_s.downcase
|
||||||
|
end
|
||||||
|
|
||||||
def self.tap_paths(name, taps = Dir[HOMEBREW_LIBRARY/"Taps/*/*/"])
|
def self.tap_paths(name, taps = Dir[HOMEBREW_LIBRARY/"Taps/*/*/"])
|
||||||
name = name.to_s.downcase
|
name = name.to_s.downcase
|
||||||
taps.map do |tap|
|
taps.map do |tap|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ class Tab < OpenStruct
|
|||||||
"installed_as_dependency" => false,
|
"installed_as_dependency" => false,
|
||||||
"installed_on_request" => false,
|
"installed_on_request" => false,
|
||||||
"poured_from_bottle" => false,
|
"poured_from_bottle" => false,
|
||||||
|
"loaded_from_api" => false,
|
||||||
"time" => Time.now.to_i,
|
"time" => Time.now.to_i,
|
||||||
"source_modified_time" => formula.source_modified_time.to_i,
|
"source_modified_time" => formula.source_modified_time.to_i,
|
||||||
"compiler" => compiler,
|
"compiler" => compiler,
|
||||||
@ -189,6 +190,7 @@ class Tab < OpenStruct
|
|||||||
"installed_as_dependency" => false,
|
"installed_as_dependency" => false,
|
||||||
"installed_on_request" => false,
|
"installed_on_request" => false,
|
||||||
"poured_from_bottle" => false,
|
"poured_from_bottle" => false,
|
||||||
|
"loaded_from_api" => false,
|
||||||
"time" => nil,
|
"time" => nil,
|
||||||
"source_modified_time" => 0,
|
"source_modified_time" => 0,
|
||||||
"stdlib" => nil,
|
"stdlib" => nil,
|
||||||
@ -332,6 +334,7 @@ class Tab < OpenStruct
|
|||||||
"unused_options" => unused_options.as_flags,
|
"unused_options" => unused_options.as_flags,
|
||||||
"built_as_bottle" => built_as_bottle,
|
"built_as_bottle" => built_as_bottle,
|
||||||
"poured_from_bottle" => poured_from_bottle,
|
"poured_from_bottle" => poured_from_bottle,
|
||||||
|
"loaded_from_api" => loaded_from_api,
|
||||||
"installed_as_dependency" => installed_as_dependency,
|
"installed_as_dependency" => installed_as_dependency,
|
||||||
"installed_on_request" => installed_on_request,
|
"installed_on_request" => installed_on_request,
|
||||||
"changed_files" => changed_files&.map(&:to_s),
|
"changed_files" => changed_files&.map(&:to_s),
|
||||||
@ -384,6 +387,7 @@ class Tab < OpenStruct
|
|||||||
"Built from source"
|
"Built from source"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
s << "using the formulae.brew.sh API" if loaded_from_api
|
||||||
s << Time.at(time).strftime("on %Y-%m-%d at %H:%M:%S") if time
|
s << Time.at(time).strftime("on %Y-%m-%d at %H:%M:%S") if time
|
||||||
|
|
||||||
unless used_options.empty?
|
unless used_options.empty?
|
||||||
|
|||||||
@ -33,13 +33,15 @@ describe Cask::Audit, :cask do
|
|||||||
let(:cask) { instance_double(Cask::Cask) }
|
let(:cask) { instance_double(Cask::Cask) }
|
||||||
let(:new_cask) { nil }
|
let(:new_cask) { nil }
|
||||||
let(:online) { nil }
|
let(:online) { nil }
|
||||||
|
let(:only) { [] }
|
||||||
let(:strict) { nil }
|
let(:strict) { nil }
|
||||||
let(:token_conflicts) { nil }
|
let(:token_conflicts) { nil }
|
||||||
let(:audit) {
|
let(:audit) {
|
||||||
described_class.new(cask, online: online,
|
described_class.new(cask, online: online,
|
||||||
strict: strict,
|
strict: strict,
|
||||||
new_cask: new_cask,
|
new_cask: new_cask,
|
||||||
token_conflicts: token_conflicts)
|
token_conflicts: token_conflicts,
|
||||||
|
only: only)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe "#new" do
|
describe "#new" do
|
||||||
@ -121,6 +123,8 @@ describe Cask::Audit, :cask do
|
|||||||
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
||||||
|
|
||||||
describe "required stanzas" do
|
describe "required stanzas" do
|
||||||
|
let(:only) { ["required_stanzas"] }
|
||||||
|
|
||||||
%w[version sha256 url name homepage].each do |stanza|
|
%w[version sha256 url name homepage].each do |stanza|
|
||||||
context "when missing #{stanza}" do
|
context "when missing #{stanza}" do
|
||||||
let(:cask_token) { "missing-#{stanza}" }
|
let(:cask_token) { "missing-#{stanza}" }
|
||||||
@ -132,6 +136,7 @@ describe Cask::Audit, :cask do
|
|||||||
|
|
||||||
describe "token validation" do
|
describe "token validation" do
|
||||||
let(:strict) { true }
|
let(:strict) { true }
|
||||||
|
let(:only) { ["token_valid"] }
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
cask '#{cask_token}' do
|
cask '#{cask_token}' do
|
||||||
@ -228,6 +233,7 @@ describe Cask::Audit, :cask do
|
|||||||
|
|
||||||
describe "token bad words" do
|
describe "token bad words" do
|
||||||
let(:new_cask) { true }
|
let(:new_cask) { true }
|
||||||
|
let(:only) { ["token_bad_words", "reverse_migration"] }
|
||||||
let(:online) { false }
|
let(:online) { false }
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
@ -343,6 +349,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "locale validation" do
|
describe "locale validation" do
|
||||||
|
let(:only) { ["languages"] }
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask "locale-cask-test", <<~RUBY
|
tmp_cask "locale-cask-test", <<~RUBY
|
||||||
cask 'locale-cask-test' do
|
cask 'locale-cask-test' do
|
||||||
@ -390,6 +397,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "pkg allow_untrusted checks" do
|
describe "pkg allow_untrusted checks" do
|
||||||
|
let(:only) { ["untrusted_pkg"] }
|
||||||
let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" }
|
let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" }
|
||||||
|
|
||||||
context "when the Cask has no pkg stanza" do
|
context "when the Cask has no pkg stanza" do
|
||||||
@ -412,6 +420,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "signing checks" do
|
describe "signing checks" do
|
||||||
|
let(:only) { ["signing"] }
|
||||||
let(:download_double) { instance_double(Cask::Download) }
|
let(:download_double) { instance_double(Cask::Download) }
|
||||||
let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
|
let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
|
||||||
|
|
||||||
@ -459,6 +468,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "livecheck should be skipped" do
|
describe "livecheck should be skipped" do
|
||||||
|
let(:only) { ["livecheck_version"] }
|
||||||
let(:online) { true }
|
let(:online) { true }
|
||||||
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
|
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
|
||||||
|
|
||||||
@ -512,6 +522,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "when the Cask stanza requires uninstall" do
|
describe "when the Cask stanza requires uninstall" do
|
||||||
|
let(:only) { ["stanza_requires_uninstall"] }
|
||||||
let(:message) { "installer and pkg stanzas require an uninstall stanza" }
|
let(:message) { "installer and pkg stanzas require an uninstall stanza" }
|
||||||
|
|
||||||
context "when the Cask does not require an uninstall" do
|
context "when the Cask does not require an uninstall" do
|
||||||
@ -681,12 +692,14 @@ describe Cask::Audit, :cask do
|
|||||||
let(:message) { "you should use version :latest instead of version 'latest'" }
|
let(:message) { "you should use version :latest instead of version 'latest'" }
|
||||||
|
|
||||||
context "when version is 'latest'" do
|
context "when version is 'latest'" do
|
||||||
|
let(:only) { ["no_string_version_latest"] }
|
||||||
let(:cask_token) { "version-latest-string" }
|
let(:cask_token) { "version-latest-string" }
|
||||||
|
|
||||||
it { is_expected.to fail_with(message) }
|
it { is_expected.to fail_with(message) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when version is :latest" do
|
context "when version is :latest" do
|
||||||
|
let(:only) { ["sha256_no_check_if_latest"] }
|
||||||
let(:cask_token) { "version-latest-with-checksum" }
|
let(:cask_token) { "version-latest-with-checksum" }
|
||||||
|
|
||||||
it { is_expected.not_to fail_with(message) }
|
it { is_expected.not_to fail_with(message) }
|
||||||
@ -695,18 +708,21 @@ describe Cask::Audit, :cask do
|
|||||||
|
|
||||||
describe "sha256 checks" do
|
describe "sha256 checks" do
|
||||||
context "when version is :latest and sha256 is not :no_check" do
|
context "when version is :latest and sha256 is not :no_check" do
|
||||||
|
let(:only) { ["sha256_no_check_if_latest"] }
|
||||||
let(:cask_token) { "version-latest-with-checksum" }
|
let(:cask_token) { "version-latest-with-checksum" }
|
||||||
|
|
||||||
it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
|
it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when sha256 is not a legal SHA-256 digest" do
|
context "when sha256 is not a legal SHA-256 digest" do
|
||||||
|
let(:only) { ["sha256_actually_256"] }
|
||||||
let(:cask_token) { "invalid-sha256" }
|
let(:cask_token) { "invalid-sha256" }
|
||||||
|
|
||||||
it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
|
it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when sha256 is sha256 for empty string" do
|
context "when sha256 is sha256 for empty string" do
|
||||||
|
let(:only) { ["sha256_invalid"] }
|
||||||
let(:cask_token) { "sha256-for-empty-string" }
|
let(:cask_token) { "sha256-for-empty-string" }
|
||||||
|
|
||||||
it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
|
it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
|
||||||
@ -714,6 +730,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "hosting with livecheck checks" do
|
describe "hosting with livecheck checks" do
|
||||||
|
let(:only) { ["hosting_with_livecheck"] }
|
||||||
let(:message) { /please add a livecheck/ }
|
let(:message) { /please add a livecheck/ }
|
||||||
|
|
||||||
context "when the download does not use hosting with a livecheck" do
|
context "when the download does not use hosting with a livecheck" do
|
||||||
@ -761,6 +778,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "latest with appcast checks" do
|
describe "latest with appcast checks" do
|
||||||
|
let(:only) { ["latest_with_appcast_or_livecheck"] }
|
||||||
let(:message) { "Casks with an `appcast` should not use `version :latest`." }
|
let(:message) { "Casks with an `appcast` should not use `version :latest`." }
|
||||||
|
|
||||||
context "when the Cask is :latest and does not have an appcast" do
|
context "when the Cask is :latest and does not have an appcast" do
|
||||||
@ -783,6 +801,8 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "denylist checks" do
|
describe "denylist checks" do
|
||||||
|
let(:only) { ["denylist"] }
|
||||||
|
|
||||||
context "when the Cask is not on the denylist" do
|
context "when the Cask is not on the denylist" do
|
||||||
let(:cask_token) { "adobe-air" }
|
let(:cask_token) { "adobe-air" }
|
||||||
|
|
||||||
@ -805,6 +825,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "latest with auto_updates checks" do
|
describe "latest with auto_updates checks" do
|
||||||
|
let(:only) { ["latest_with_auto_updates"] }
|
||||||
let(:message) { "Casks with `version :latest` should not use `auto_updates`." }
|
let(:message) { "Casks with `version :latest` should not use `auto_updates`." }
|
||||||
|
|
||||||
context "when the Cask is :latest and does not have auto_updates" do
|
context "when the Cask is :latest and does not have auto_updates" do
|
||||||
@ -833,6 +854,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "preferred download URL formats" do
|
describe "preferred download URL formats" do
|
||||||
|
let(:only) { ["download_url_format"] }
|
||||||
let(:message) { /URL format incorrect/ }
|
let(:message) { /URL format incorrect/ }
|
||||||
|
|
||||||
context "with incorrect SourceForge URL format" do
|
context "with incorrect SourceForge URL format" do
|
||||||
@ -867,6 +889,8 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "generic artifact checks" do
|
describe "generic artifact checks" do
|
||||||
|
let(:only) { ["generic_artifacts"] }
|
||||||
|
|
||||||
context "with relative target" do
|
context "with relative target" do
|
||||||
let(:cask_token) { "generic-artifact-relative-target" }
|
let(:cask_token) { "generic-artifact-relative-target" }
|
||||||
|
|
||||||
@ -887,6 +911,8 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "url checks" do
|
describe "url checks" do
|
||||||
|
let(:only) { %w[unnecessary_verified missing_verified no_match] }
|
||||||
|
|
||||||
context "with a block" do
|
context "with a block" do
|
||||||
let(:cask_token) { "booby-trap" }
|
let(:cask_token) { "booby-trap" }
|
||||||
|
|
||||||
@ -900,7 +926,7 @@ describe Cask::Audit, :cask do
|
|||||||
let(:online) { false }
|
let(:online) { false }
|
||||||
|
|
||||||
it "does not evaluate the block" do
|
it "does not evaluate the block" do
|
||||||
expect(run).not_to pass
|
expect(run).not_to fail_with(/Boom/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -915,6 +941,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "token conflicts" do
|
describe "token conflicts" do
|
||||||
|
let(:only) { ["token_conflicts"] }
|
||||||
let(:cask_token) { "with-binary" }
|
let(:cask_token) { "with-binary" }
|
||||||
let(:token_conflicts) { true }
|
let(:token_conflicts) { true }
|
||||||
|
|
||||||
@ -935,6 +962,7 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "audit of downloads" do
|
describe "audit of downloads" do
|
||||||
|
let(:only) { ["download"] }
|
||||||
let(:cask_token) { "basic-cask" }
|
let(:cask_token) { "basic-cask" }
|
||||||
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
||||||
let(:download_double) { instance_double(Cask::Download) }
|
let(:download_double) { instance_double(Cask::Download) }
|
||||||
@ -959,6 +987,7 @@ describe Cask::Audit, :cask do
|
|||||||
|
|
||||||
context "when an exception is raised" do
|
context "when an exception is raised" do
|
||||||
let(:cask) { instance_double(Cask::Cask) }
|
let(:cask) { instance_double(Cask::Cask) }
|
||||||
|
let(:only) { ["description_present"] }
|
||||||
|
|
||||||
it "fails the audit" do
|
it "fails the audit" do
|
||||||
expect(cask).to receive(:tap).and_raise(StandardError.new)
|
expect(cask).to receive(:tap).and_raise(StandardError.new)
|
||||||
@ -966,7 +995,8 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "without description" do
|
describe "checking description" do
|
||||||
|
let(:only) { ["description_present"] }
|
||||||
let(:cask_token) { "without-description" }
|
let(:cask_token) { "without-description" }
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
@ -996,7 +1026,6 @@ describe Cask::Audit, :cask do
|
|||||||
expect(run).to warn_with(/should have a description/)
|
expect(run).to warn_with(/should have a description/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context "with description" do
|
context "with description" do
|
||||||
let(:cask_token) { "with-description" }
|
let(:cask_token) { "with-description" }
|
||||||
@ -1018,9 +1047,13 @@ describe Cask::Audit, :cask do
|
|||||||
expect(run).to pass
|
expect(run).to pass
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "checking verified" do
|
||||||
|
let(:only) { %w[unnecessary_verified missing_verified no_match required_stanzas] }
|
||||||
|
let(:cask_token) { "foo" }
|
||||||
|
|
||||||
context "when the url matches the homepage" do
|
context "when the url matches the homepage" do
|
||||||
let(:cask_token) { "foo" }
|
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
cask '#{cask_token}' do
|
cask '#{cask_token}' do
|
||||||
@ -1039,7 +1072,6 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when the url does not match the homepage" do
|
context "when the url does not match the homepage" do
|
||||||
let(:cask_token) { "foo" }
|
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
cask '#{cask_token}' do
|
cask '#{cask_token}' do
|
||||||
@ -1058,7 +1090,6 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when the url does not match the homepage with verified" do
|
context "when the url does not match the homepage with verified" do
|
||||||
let(:cask_token) { "foo" }
|
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
cask "#{cask_token}" do
|
cask "#{cask_token}" do
|
||||||
@ -1077,7 +1108,6 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when there is no homepage" do
|
context "when there is no homepage" do
|
||||||
let(:cask_token) { "foo" }
|
|
||||||
let(:cask) do
|
let(:cask) do
|
||||||
tmp_cask cask_token.to_s, <<~RUBY
|
tmp_cask cask_token.to_s, <<~RUBY
|
||||||
cask '#{cask_token}' do
|
cask '#{cask_token}' do
|
||||||
@ -1125,3 +1155,4 @@ describe Cask::Audit, :cask do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@ -450,136 +450,6 @@ describe Formula do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_context "with formulae for dependency testing" do
|
|
||||||
let(:formula_with_deps) do
|
|
||||||
formula "zero" do
|
|
||||||
url "zero-1.0"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:formula_is_dep1) do
|
|
||||||
formula "one" do
|
|
||||||
url "one-1.1"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:formula_is_dep2) do
|
|
||||||
formula "two" do
|
|
||||||
url "two-1.1"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:formulae) do
|
|
||||||
[
|
|
||||||
formula_with_deps,
|
|
||||||
formula_is_dep1,
|
|
||||||
formula_is_dep2,
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep1,
|
|
||||||
formula_is_dep2])
|
|
||||||
allow(formula_is_dep1).to receive(:runtime_formula_dependencies).and_return([formula_is_dep2])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "::formulae_with_no_formula_dependents" do
|
|
||||||
include_context "with formulae for dependency testing"
|
|
||||||
|
|
||||||
it "filters out dependencies" do
|
|
||||||
expect(described_class.formulae_with_no_formula_dependents(formulae))
|
|
||||||
.to eq([formula_with_deps])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "::unused_formulae_with_no_formula_dependents" do
|
|
||||||
include_context "with formulae for dependency testing"
|
|
||||||
|
|
||||||
let(:tab_from_keg) { double }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(Tab).to receive(:for_keg).and_return(tab_from_keg)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify "installed on request" do
|
|
||||||
allow(tab_from_keg).to receive(:installed_on_request).and_return(true)
|
|
||||||
expect(described_class.unused_formulae_with_no_formula_dependents(formulae))
|
|
||||||
.to eq([])
|
|
||||||
end
|
|
||||||
|
|
||||||
specify "not installed on request" do
|
|
||||||
allow(tab_from_keg).to receive(:installed_on_request).and_return(false)
|
|
||||||
expect(described_class.unused_formulae_with_no_formula_dependents(formulae))
|
|
||||||
.to eq(formulae)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_context "with formulae and casks for dependency testing" do
|
|
||||||
include_context "with formulae for dependency testing"
|
|
||||||
|
|
||||||
require "cask/cask_loader"
|
|
||||||
|
|
||||||
let(:cask_one_dep) do
|
|
||||||
Cask::CaskLoader.load(+<<-RUBY)
|
|
||||||
cask "red" do
|
|
||||||
depends_on formula: "two"
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:cask_multiple_deps) do
|
|
||||||
Cask::CaskLoader.load(+<<-RUBY)
|
|
||||||
cask "blue" do
|
|
||||||
depends_on formula: "zero"
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:cask_no_deps1) do
|
|
||||||
Cask::CaskLoader.load(+<<-RUBY)
|
|
||||||
cask "green" do
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:cask_no_deps2) do
|
|
||||||
Cask::CaskLoader.load(+<<-RUBY)
|
|
||||||
cask "purple" do
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:casks_no_deps) { [cask_no_deps1, cask_no_deps2] }
|
|
||||||
let(:casks_one_dep) { [cask_no_deps1, cask_no_deps2, cask_one_dep] }
|
|
||||||
let(:casks_multiple_deps) { [cask_no_deps1, cask_no_deps2, cask_multiple_deps] }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(described_class).to receive("[]").with("zero").and_return(formula_with_deps)
|
|
||||||
allow(described_class).to receive("[]").with("one").and_return(formula_is_dep1)
|
|
||||||
allow(described_class).to receive("[]").with("two").and_return(formula_is_dep2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "::formulae_with_cask_dependents" do
|
|
||||||
include_context "with formulae and casks for dependency testing"
|
|
||||||
|
|
||||||
specify "no dependents" do
|
|
||||||
expect(described_class.formulae_with_cask_dependents(casks_no_deps))
|
|
||||||
.to eq([])
|
|
||||||
end
|
|
||||||
|
|
||||||
specify "one dependent" do
|
|
||||||
expect(described_class.formulae_with_cask_dependents(casks_one_dep))
|
|
||||||
.to eq([formula_is_dep2])
|
|
||||||
end
|
|
||||||
|
|
||||||
specify "multiple dependents" do
|
|
||||||
expect(described_class.formulae_with_cask_dependents(casks_multiple_deps))
|
|
||||||
.to eq(formulae)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "::inreplace" do
|
describe "::inreplace" do
|
||||||
specify "raises build error on failure" do
|
specify "raises build error on failure" do
|
||||||
f = formula do
|
f = formula do
|
||||||
|
|||||||
162
Library/Homebrew/test/utils/autoremove_spec.rb
Normal file
162
Library/Homebrew/test/utils/autoremove_spec.rb
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "utils/autoremove"
|
||||||
|
|
||||||
|
describe Utils::Autoremove do
|
||||||
|
shared_context "with formulae for dependency testing" do
|
||||||
|
let(:formula_with_deps) do
|
||||||
|
formula "zero" do
|
||||||
|
url "zero-1.0"
|
||||||
|
|
||||||
|
depends_on "three" => :build
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:formula_is_dep1) do
|
||||||
|
formula "one" do
|
||||||
|
url "one-1.1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:formula_is_dep2) do
|
||||||
|
formula "two" do
|
||||||
|
url "two-1.1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:formula_is_build_dep) do
|
||||||
|
formula "three" do
|
||||||
|
url "three-1.1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:formulae) do
|
||||||
|
[
|
||||||
|
formula_with_deps,
|
||||||
|
formula_is_dep1,
|
||||||
|
formula_is_dep2,
|
||||||
|
formula_is_build_dep,
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:tab_from_keg) { double }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep1,
|
||||||
|
formula_is_dep2])
|
||||||
|
allow(formula_is_dep1).to receive(:runtime_formula_dependencies).and_return([formula_is_dep2])
|
||||||
|
|
||||||
|
allow(Tab).to receive(:for_keg).and_return(tab_from_keg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "::formulae_with_no_formula_dependents" do
|
||||||
|
include_context "with formulae for dependency testing"
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Formulary).to receive(:factory).with("three").and_return(formula_is_build_dep)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formulae are bottles" do
|
||||||
|
it "filters out runtime dependencies" do
|
||||||
|
allow(tab_from_keg).to receive(:poured_from_bottle).and_return(true)
|
||||||
|
expect(described_class.send(:formulae_with_no_formula_dependents, formulae))
|
||||||
|
.to eq([formula_with_deps, formula_is_build_dep])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formulae are built from source" do
|
||||||
|
it "filters out runtime and build dependencies" do
|
||||||
|
allow(tab_from_keg).to receive(:poured_from_bottle).and_return(false)
|
||||||
|
expect(described_class.send(:formulae_with_no_formula_dependents, formulae))
|
||||||
|
.to eq([formula_with_deps])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "::unused_formulae_with_no_formula_dependents" do
|
||||||
|
include_context "with formulae for dependency testing"
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(tab_from_keg).to receive(:poured_from_bottle).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
specify "installed on request" do
|
||||||
|
allow(tab_from_keg).to receive(:installed_on_request).and_return(true)
|
||||||
|
expect(described_class.send(:unused_formulae_with_no_formula_dependents, formulae))
|
||||||
|
.to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
specify "not installed on request" do
|
||||||
|
allow(tab_from_keg).to receive(:installed_on_request).and_return(false)
|
||||||
|
expect(described_class.send(:unused_formulae_with_no_formula_dependents, formulae))
|
||||||
|
.to match_array(formulae)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_context "with formulae and casks for dependency testing" do
|
||||||
|
include_context "with formulae for dependency testing"
|
||||||
|
|
||||||
|
require "cask/cask_loader"
|
||||||
|
|
||||||
|
let(:cask_one_dep) do
|
||||||
|
Cask::CaskLoader.load(+<<-RUBY)
|
||||||
|
cask "red" do
|
||||||
|
depends_on formula: "two"
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:cask_multiple_deps) do
|
||||||
|
Cask::CaskLoader.load(+<<-RUBY)
|
||||||
|
cask "blue" do
|
||||||
|
depends_on formula: "zero"
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:cask_no_deps1) do
|
||||||
|
Cask::CaskLoader.load(+<<-RUBY)
|
||||||
|
cask "green" do
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:cask_no_deps2) do
|
||||||
|
Cask::CaskLoader.load(+<<-RUBY)
|
||||||
|
cask "purple" do
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:casks_no_deps) { [cask_no_deps1, cask_no_deps2] }
|
||||||
|
let(:casks_one_dep) { [cask_no_deps1, cask_no_deps2, cask_one_dep] }
|
||||||
|
let(:casks_multiple_deps) { [cask_no_deps1, cask_no_deps2, cask_multiple_deps] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Formula).to receive("[]").with("zero").and_return(formula_with_deps)
|
||||||
|
allow(Formula).to receive("[]").with("one").and_return(formula_is_dep1)
|
||||||
|
allow(Formula).to receive("[]").with("two").and_return(formula_is_dep2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "::formulae_with_cask_dependents" do
|
||||||
|
include_context "with formulae and casks for dependency testing"
|
||||||
|
|
||||||
|
specify "no dependents" do
|
||||||
|
expect(described_class.send(:formulae_with_cask_dependents, casks_no_deps))
|
||||||
|
.to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
specify "one dependent" do
|
||||||
|
expect(described_class.send(:formulae_with_cask_dependents, casks_one_dep))
|
||||||
|
.to eq([formula_is_dep2])
|
||||||
|
end
|
||||||
|
|
||||||
|
specify "multiple dependents" do
|
||||||
|
expect(described_class.send(:formulae_with_cask_dependents, casks_multiple_deps))
|
||||||
|
.to match_array([formula_with_deps, formula_is_dep1, formula_is_dep2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
68
Library/Homebrew/utils/autoremove.rb
Normal file
68
Library/Homebrew/utils/autoremove.rb
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Utils
|
||||||
|
# Helper function for finding autoremovable formulae.
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
module Autoremove
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# An array of all installed {Formula} with {Cask} dependents.
|
||||||
|
# @private
|
||||||
|
def formulae_with_cask_dependents(casks)
|
||||||
|
casks.flat_map { |cask| cask.depends_on[:formula] }
|
||||||
|
.compact
|
||||||
|
.map { |f| Formula[f] }
|
||||||
|
.flat_map { |f| [f, *f.runtime_formula_dependencies].compact }
|
||||||
|
end
|
||||||
|
private_class_method :formulae_with_cask_dependents
|
||||||
|
|
||||||
|
# An array of all installed {Formula} without runtime {Formula}
|
||||||
|
# dependents for bottles and without build {Formula} dependents
|
||||||
|
# for those built from source.
|
||||||
|
# @private
|
||||||
|
def formulae_with_no_formula_dependents(formulae)
|
||||||
|
return [] if formulae.blank?
|
||||||
|
|
||||||
|
dependents = []
|
||||||
|
formulae.each do |formula|
|
||||||
|
dependents += formula.runtime_formula_dependencies
|
||||||
|
|
||||||
|
# Ignore build dependencies when the formula is a bottle
|
||||||
|
next if Tab.for_keg(formula.any_installed_keg).poured_from_bottle
|
||||||
|
|
||||||
|
formula.deps.select(&:build?).each do |dep|
|
||||||
|
suppress(FormulaUnavailableError) { dependents << dep.to_formula }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
formulae - dependents
|
||||||
|
end
|
||||||
|
private_class_method :formulae_with_no_formula_dependents
|
||||||
|
|
||||||
|
# Recursive function that returns an array of {Formula} without
|
||||||
|
# {Formula} dependents that weren't installed on request.
|
||||||
|
# @private
|
||||||
|
def unused_formulae_with_no_formula_dependents(formulae)
|
||||||
|
unused_formulae = formulae_with_no_formula_dependents(formulae).reject do |f|
|
||||||
|
Tab.for_keg(f.any_installed_keg).installed_on_request
|
||||||
|
end
|
||||||
|
|
||||||
|
if unused_formulae.present?
|
||||||
|
unused_formulae += unused_formulae_with_no_formula_dependents(formulae - unused_formulae)
|
||||||
|
end
|
||||||
|
|
||||||
|
unused_formulae
|
||||||
|
end
|
||||||
|
private_class_method :unused_formulae_with_no_formula_dependents
|
||||||
|
|
||||||
|
# An array of {Formula} without {Formula} or {Cask}
|
||||||
|
# dependents that weren't installed on request and without
|
||||||
|
# build dependencies for {Formula} installed from source.
|
||||||
|
# @private
|
||||||
|
def removable_formulae(formulae, casks)
|
||||||
|
unused_formulae = unused_formulae_with_no_formula_dependents(formulae)
|
||||||
|
unused_formulae - formulae_with_cask_dependents(casks)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -57,7 +57,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mechanize-2.8.5/lib"
|
|||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/method_source-1.0.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/method_source-1.0.0/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mustache-1.1.1/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mustache-1.1.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel-1.22.1/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel-1.22.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel_tests-3.12.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel_tests-3.12.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parser-3.1.2.1/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parser-3.1.2.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rainbow-3.1.1/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rainbow-3.1.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.10175/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.10175/lib"
|
||||||
@ -85,7 +85,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-wait-0.0.9/lib"
|
|||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec_junit_formatter-0.5.1/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec_junit_formatter-0.5.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-ast-1.21.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-ast-1.21.0/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.11.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.11.0/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-2.2.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-2.3.0/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.35.1/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.35.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.15.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.15.0/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.16.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.16.0/lib"
|
||||||
|
|||||||
@ -1866,6 +1866,8 @@ Only supports GitHub Actions as a CI provider. This is because Homebrew uses Git
|
|||||||
Don't pass `--online` to `brew audit` and skip `brew livecheck`.
|
Don't pass `--online` to `brew audit` and skip `brew livecheck`.
|
||||||
* `--skip-dependents`:
|
* `--skip-dependents`:
|
||||||
Don't test any dependents.
|
Don't test any dependents.
|
||||||
|
* `--skip-livecheck`:
|
||||||
|
Don't test livecheck.
|
||||||
* `--skip-recursive-dependents`:
|
* `--skip-recursive-dependents`:
|
||||||
Only test the direct dependents.
|
Only test the direct dependents.
|
||||||
* `--only-cleanup-before`:
|
* `--only-cleanup-before`:
|
||||||
|
|||||||
@ -2672,6 +2672,10 @@ Don\'t pass \fB\-\-online\fR to \fBbrew audit\fR and skip \fBbrew livecheck\fR\.
|
|||||||
Don\'t test any dependents\.
|
Don\'t test any dependents\.
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-\-skip\-livecheck\fR
|
||||||
|
Don\'t test livecheck\.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
\fB\-\-skip\-recursive\-dependents\fR
|
\fB\-\-skip\-recursive\-dependents\fR
|
||||||
Only test the direct dependents\.
|
Only test the direct dependents\.
|
||||||
.
|
.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user