diff --git a/Library/Homebrew/Gemfile b/Library/Homebrew/Gemfile index 86fc80f2dd189..d678540fb7ac9 100644 --- a/Library/Homebrew/Gemfile +++ b/Library/Homebrew/Gemfile @@ -30,9 +30,6 @@ end group :man, optional: true do gem "kramdown", require: false end -group :pr_upload, optional: true do - gem "json_schemer", require: false -end group :prof, optional: true do gem "ruby-prof", require: false gem "stackprof", require: false @@ -70,6 +67,9 @@ end group :audit, :bump_unversioned_casks, :livecheck, optional: true do gem "rexml", require: false end +group :audit, :pr_upload, optional: true do + gem "json_schemer", require: false +end # vendored gems (no group) gem "addressable" diff --git a/Library/Homebrew/data/schemas/pypi_formula_mappings.schema.json b/Library/Homebrew/data/schemas/pypi_formula_mappings.schema.json new file mode 100644 index 0000000000000..dbbaf765e9351 --- /dev/null +++ b/Library/Homebrew/data/schemas/pypi_formula_mappings.schema.json @@ -0,0 +1,55 @@ +{ + "$id": "https://brew.sh/pypi_formula_mappings.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "pypi_formula_mappings", + "type": "object", + "additionalProperties": { + "oneOf": [ + { "$ref": "#/$defs/package_name" }, + { + "type": "object", + "properties": { + "package_name": { + "description": "Normalized PyPI package name with optional extras or empty string to skip main package", + "type": "string", + "oneOf": [ + { "$ref": "#/$defs/package_name" }, + { "maxLength": 0 } + ] + }, + "exclude_packages": { + "description": "PyPI packages to exclude from result", + "type": "array", + "items": { "$ref": "#/$defs/package_name" }, + "minItems": 1 + }, + "extra_packages": { + "description": "PyPI packages to include with result", + "type": "array", + "items": { "$ref": "#/$defs/requirement_specifier" }, + "minItems": 1 + }, + "dependencies": { + "description": "Formulae that need to be installed to resolve PyPI packages", + "type": "array", + "items": { "type":"string" }, + "minItems": 1 + } + }, + "additionalProperties": false + } + ] + }, + "$defs": { + "package_name": { + "description": "Normalized PyPI package name with optional extras", + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*(\\[[a-z0-9][a-z0-9-]*(,[a-z0-9][a-z0-9-]*)*\\])?$" + }, + "requirement_specifier": { + "description": "Normalized PyPI package name with optional extras and optional pinned version", + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*(\\[[a-z0-9][a-z0-9-]*(,[a-z0-9][a-z0-9-]*)*\\])?(==.*)?$" + } + } +} diff --git a/Library/Homebrew/tap_auditor.rb b/Library/Homebrew/tap_auditor.rb index 2e7c3cd08059b..396acd1c74e37 100644 --- a/Library/Homebrew/tap_auditor.rb +++ b/Library/Homebrew/tap_auditor.rb @@ -47,6 +47,15 @@ def audit_json_files rescue JSON::ParserError problem "#{file.to_s.delete_prefix("#{@path}/")} contains invalid JSON" end + + require "json_schemer" + schemer = JSONSchemer.schema(HOMEBREW_DATA_PATH/"schemas/pypi_formula_mappings.schema.json") + return if schemer.valid?(@tap_pypi_formula_mappings) + + problem <<~EOS + pypi_formula_mappings.json schema validation failed with following errors: + #{schemer.validate(@tap_pypi_formula_mappings).map { |error| "* #{error["error"]}" }.join("\n")} + EOS end sig { void } @@ -54,6 +63,13 @@ 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 + + @tap_pypi_formula_mappings.each_value do |formula_mapping| + next unless formula_mapping.is_a?(Hash) + next unless formula_mapping.key?("dependencies") + + check_formula_list "pypi_formula_mappings", formula_mapping["dependencies"] + end end sig { void }