brew/Library/Homebrew/tap_auditor.rb

106 lines
3.4 KiB
Ruby
Raw Normal View History

2020-11-18 10:05:23 +01:00
# typed: true
# frozen_string_literal: true
module Homebrew
# Auditor for checking common violations in {Tap}s.
2020-11-18 10:05:23 +01:00
class TapAuditor
attr_reader :name, :path, :formula_names, :formula_aliases, :formula_renames, :cask_tokens,
formula_auditor: create a versioned formula dependent conflict allowlist 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.
2022-08-18 15:27:23 +08:00
:tap_audit_exceptions, :tap_style_exceptions, :tap_pypi_formula_mappings, :problems
2020-11-18 10:05:23 +01:00
sig { params(tap: Tap, strict: T.nilable(T::Boolean)).void }
2020-11-18 10:05:23 +01:00
def initialize(tap, strict:)
Homebrew.with_no_api_env do
@name = tap.name
@path = tap.path
@tap_audit_exceptions = tap.audit_exceptions
@tap_style_exceptions = tap.style_exceptions
@tap_pypi_formula_mappings = tap.pypi_formula_mappings
@problems = []
@cask_tokens = tap.cask_tokens.map do |cask_token|
cask_token.split("/").last
end
@formula_aliases = tap.aliases.map do |formula_alias|
formula_alias.split("/").last
end
@formula_renames = tap.formula_renames
@formula_names = tap.formula_names.map do |formula_name|
formula_name.split("/").last
end
end
2020-11-18 10:05:23 +01:00
end
sig { void }
def audit
audit_json_files
audit_tap_formula_lists
audit_aliases_renames_duplicates
2020-11-18 10:05:23 +01:00
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
2020-11-18 10:05:23 +01:00
sig { void }
def audit_aliases_renames_duplicates
duplicates = formula_aliases & formula_renames.keys
return if duplicates.none?
problem "The following should either be an alias or a rename, not both: #{duplicates.to_sentence}"
end
sig { params(message: String).void }
def problem(message)
2024-03-07 16:20:20 +00:00
@problems << ({ message:, location: nil, corrected: false })
end
2020-11-18 10:05:23 +01:00
private
2020-11-18 10:05:23 +01:00
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
2020-11-18 10:05:23 +01:00
2020-12-21 12:53:12 -05:00
list = list.keys if list.is_a? Hash
invalid_formulae_casks = list.select do |formula_or_cask_name|
formula_auditor: create a versioned formula dependent conflict allowlist 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.
2022-08-18 15:27:23 +08:00
formula_names.exclude?(formula_or_cask_name) &&
formula_aliases.exclude?(formula_or_cask_name) &&
cask_tokens.exclude?(formula_or_cask_name)
2020-11-18 10:05:23 +01:00
end
return if invalid_formulae_casks.empty?
problem <<~EOS
#{list_file}.json references
2020-12-21 12:53:12 -05:00
formulae or casks that are not found in the #{@name} tap.
Invalid formulae or casks: #{invalid_formulae_casks.join(", ")}
EOS
2020-11-18 10:05:23 +01:00
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
2020-11-18 10:05:23 +01:00
end
end
end