mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

We have an audit that checks each formula's dependency tree for multiple versions of the same software. We have an allowlist that allows us to ignore this audit, but this allowlist requires each formula with a conflict in its dependency tree to be listed there. Here, I propose the reverse: if formula `foo` appears in the `versioned_formula_dependent_conflicts_allowlist`, then all its dependents will not fail the versioned dependencies conflict because of a conflict with formula `foo`. I'd like to do this in the case of `python`, where I think the versioned dependencies conflict check hurts us more than helps us. Versioned dependency conflicts are most problematic in the case of libraries with the same install name but incompatible ABIs. This is almost never a problem with Python: almost no formulae link with the Python framework on macOS (in part due to one of our audits that disallows Python framework linkage in Python modules). Moreover, the various Python frameworks that we ship have the version in the install name. The above _might_ be a problem on Linux, since we allow unrestricted linkage with `libpython`. However, we don't even check versioned conflicts on Linux, so we aren't as concerned about this in the first place. This is also a lot more convenient than adding the dependents of some Python formula one by one as they acquire conflicts due to changes in other formulae. I've also amended `tap_auditor` to allow the use of formula aliases in an allowlist, to allow us to add `python` to this allowlist instead of each individual versioned Python formula. See also discussion at Homebrew/homebrew-core#108307.
94 lines
2.9 KiB
Ruby
94 lines
2.9 KiB
Ruby
# typed: true
|
|
# frozen_string_literal: true
|
|
|
|
module Homebrew
|
|
# Auditor for checking common violations in {Tap}s.
|
|
#
|
|
# @api private
|
|
class TapAuditor
|
|
extend T::Sig
|
|
|
|
attr_reader :name, :path, :formula_names, :formula_aliases, :cask_tokens,
|
|
:tap_audit_exceptions, :tap_style_exceptions, :tap_pypi_formula_mappings, :problems
|
|
|
|
sig { params(tap: Tap, strict: T.nilable(T::Boolean)).void }
|
|
def initialize(tap, strict:)
|
|
@name = tap.name
|
|
@path = tap.path
|
|
@cask_tokens = tap.cask_tokens
|
|
@tap_audit_exceptions = tap.audit_exceptions
|
|
@tap_style_exceptions = tap.style_exceptions
|
|
@tap_pypi_formula_mappings = tap.pypi_formula_mappings
|
|
@problems = []
|
|
|
|
@formula_aliases = tap.aliases
|
|
@formula_names = tap.formula_names.map do |formula_name|
|
|
formula_name.split("/").last
|
|
end
|
|
end
|
|
|
|
sig { void }
|
|
def audit
|
|
audit_json_files
|
|
audit_tap_formula_lists
|
|
end
|
|
|
|
sig { void }
|
|
def audit_json_files
|
|
json_patterns = Tap::HOMEBREW_TAP_JSON_FILES.map { |pattern| @path/pattern }
|
|
Pathname.glob(json_patterns).each do |file|
|
|
JSON.parse file.read
|
|
rescue JSON::ParserError
|
|
problem "#{file.to_s.delete_prefix("#{@path}/")} contains invalid JSON"
|
|
end
|
|
end
|
|
|
|
sig { void }
|
|
def audit_tap_formula_lists
|
|
check_formula_list_directory "audit_exceptions", @tap_audit_exceptions
|
|
check_formula_list_directory "style_exceptions", @tap_style_exceptions
|
|
check_formula_list "pypi_formula_mappings", @tap_pypi_formula_mappings
|
|
end
|
|
|
|
sig { params(message: String).void }
|
|
def problem(message)
|
|
@problems << ({ message: message, location: nil })
|
|
end
|
|
|
|
private
|
|
|
|
sig { params(list_file: String, list: T.untyped).void }
|
|
def check_formula_list(list_file, list)
|
|
unless [Hash, Array].include? list.class
|
|
problem <<~EOS
|
|
#{list_file}.json should contain a JSON array
|
|
of formula names or a JSON object mapping formula names to values
|
|
EOS
|
|
return
|
|
end
|
|
|
|
list = list.keys if list.is_a? Hash
|
|
invalid_formulae_casks = list.select do |formula_or_cask_name|
|
|
formula_names.exclude?(formula_or_cask_name) &&
|
|
formula_aliases.exclude?(formula_or_cask_name) &&
|
|
cask_tokens.exclude?("#{@name}/#{formula_or_cask_name}")
|
|
end
|
|
|
|
return if invalid_formulae_casks.empty?
|
|
|
|
problem <<~EOS
|
|
#{list_file}.json references
|
|
formulae or casks that are not found in the #{@name} tap.
|
|
Invalid formulae or casks: #{invalid_formulae_casks.join(", ")}
|
|
EOS
|
|
end
|
|
|
|
sig { params(directory_name: String, lists: Hash).void }
|
|
def check_formula_list_directory(directory_name, lists)
|
|
lists.each do |list_name, list|
|
|
check_formula_list "#{directory_name}/#{list_name}", list
|
|
end
|
|
end
|
|
end
|
|
end
|