Merge pull request #20134 from Homebrew/homebrew_core_cask_name_audit

audit: ensure that official formula and cask names don't conflict.
This commit is contained in:
Mike McQuaid 2025-06-23 15:33:00 +00:00 committed by GitHub
commit 4759ca9430
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 48 additions and 74 deletions

View File

@ -166,7 +166,7 @@ jobs:
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@main uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: true
cask: true cask: true
test-bot: false test-bot: false

View File

@ -27,7 +27,7 @@ module Cask
sig { sig {
params( params(
cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean, token_conflicts: T.nilable(T::Boolean), cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean,
online: T.nilable(T::Boolean), strict: T.nilable(T::Boolean), signing: T.nilable(T::Boolean), online: T.nilable(T::Boolean), strict: T.nilable(T::Boolean), signing: T.nilable(T::Boolean),
new_cask: T.nilable(T::Boolean), only: T::Array[String], except: T::Array[String] new_cask: T.nilable(T::Boolean), only: T::Array[String], except: T::Array[String]
).void ).void
@ -35,14 +35,13 @@ module Cask
def initialize( def initialize(
cask, cask,
download: false, quarantine: false, download: false, quarantine: false,
token_conflicts: nil, online: nil, strict: nil, signing: nil, online: nil, strict: nil, signing: nil,
new_cask: nil, only: [], except: [] new_cask: nil, only: [], except: []
) )
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing` # `new_cask` implies `online`, `strict` and `signing`
online = new_cask if online.nil? online = new_cask if online.nil?
strict = new_cask if strict.nil? strict = new_cask if strict.nil?
signing = new_cask if signing.nil? signing = new_cask if signing.nil?
token_conflicts = new_cask if token_conflicts.nil?
# `online` and `signing` imply `download` # `online` and `signing` imply `download`
download ||= online || signing download ||= online || signing
@ -53,7 +52,6 @@ module Cask
@strict = strict @strict = strict
@signing = signing @signing = signing
@new_cask = new_cask @new_cask = new_cask
@token_conflicts = token_conflicts
@only = only @only = only
@except = except @except = except
end end
@ -70,9 +68,6 @@ module Cask
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def strict? = !!@strict def strict? = !!@strict
sig { returns(T::Boolean) }
def token_conflicts? = !!@token_conflicts
sig { returns(::Cask::Audit) } sig { returns(::Cask::Audit) }
def run! def run!
only_audits = @only only_audits = @only
@ -430,15 +425,10 @@ module Cask
sig { void } sig { void }
def audit_token_conflicts def audit_token_conflicts
return unless token_conflicts?
Homebrew.with_no_api_env do Homebrew.with_no_api_env do
return unless core_formula_names.include?(cask.token) return unless core_formula_names.include?(cask.token)
add_error( add_error("cask token conflicts with an existing homebrew/core formula: #{Formatter.url(core_formula_url)}")
"possible duplicate, cask token conflicts with Homebrew core formula: #{Formatter.url(core_formula_url)}",
strict_only: true,
)
end end
end end

View File

@ -11,17 +11,17 @@ module Cask
params( params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean), cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean),
audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean), audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean),
audit_token_conflicts: T.nilable(T::Boolean), audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean, audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String] any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).returns(T::Set[String]) ).returns(T::Set[String])
} }
def self.audit( def self.audit(
cask, audit_download: false, audit_online: nil, audit_strict: nil, audit_signing: nil, cask, audit_download: false, audit_online: nil, audit_strict: nil, audit_signing: nil,
audit_token_conflicts: nil, audit_new_cask: nil, quarantine: false, any_named_args: false, language: nil, audit_new_cask: nil, quarantine: false, any_named_args: false, language: nil,
only: [], except: [] only: [], except: []
) )
new( new(
cask, audit_download:, audit_online:, audit_strict:, audit_signing:, audit_token_conflicts:, cask, audit_download:, audit_online:, audit_strict:, audit_signing:,
audit_new_cask:, quarantine:, any_named_args:, language:, only:, except: audit_new_cask:, quarantine:, any_named_args:, language:, only:, except:
).audit ).audit
end end
@ -36,7 +36,7 @@ module Cask
params( params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean), cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean),
audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean), audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean),
audit_token_conflicts: T.nilable(T::Boolean), audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean, audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String] any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).void ).void
} }
@ -46,7 +46,6 @@ module Cask
audit_online: nil, audit_online: nil,
audit_strict: nil, audit_strict: nil,
audit_signing: nil, audit_signing: nil,
audit_token_conflicts: nil,
audit_new_cask: nil, audit_new_cask: nil,
quarantine: false, quarantine: false,
any_named_args: false, any_named_args: false,
@ -61,7 +60,6 @@ module Cask
@audit_strict = audit_strict @audit_strict = audit_strict
@audit_signing = audit_signing @audit_signing = audit_signing
@quarantine = quarantine @quarantine = quarantine
@audit_token_conflicts = audit_token_conflicts
@any_named_args = any_named_args @any_named_args = any_named_args
@language = language @language = language
@only = only @only = only
@ -127,15 +125,14 @@ module Cask
def audit_cask_instance(cask) def audit_cask_instance(cask)
audit = Audit.new( audit = Audit.new(
cask, cask,
online: @audit_online, online: @audit_online,
strict: @audit_strict, strict: @audit_strict,
signing: @audit_signing, signing: @audit_signing,
new_cask: @audit_new_cask, new_cask: @audit_new_cask,
token_conflicts: @audit_token_conflicts, download: @audit_download,
download: @audit_download, quarantine: @quarantine,
quarantine: @quarantine, only: @only,
only: @only, except: @except,
except: @except,
) )
audit.run! audit.run!
end end

View File

@ -58,8 +58,10 @@ module Homebrew
hidden: true hidden: true
switch "--[no-]signing", switch "--[no-]signing",
description: "Audit for app signatures, which are required by macOS on ARM." description: "Audit for app signatures, which are required by macOS on ARM."
# should be odeprecated in future
switch "--token-conflicts", switch "--token-conflicts",
description: "Audit for token conflicts." description: "Audit for token conflicts.",
hidden: true
flag "--tap=", flag "--tap=",
description: "Check formulae and casks within the given tap, specified as <user>`/`<repo>." description: "Check formulae and casks within the given tap, specified as <user>`/`<repo>."
switch "--fix", switch "--fix",
@ -251,18 +253,17 @@ module Homebrew
# For switches, we add `|| nil` so that `nil` will be passed # For switches, we add `|| nil` so that `nil` will be passed
# instead of `false` if they aren't set. # instead of `false` if they aren't set.
# This way, we can distinguish between "not set" and "set to false". # This way, we can distinguish between "not set" and "set to false".
audit_online: args.online? || nil, audit_online: args.online? || nil,
audit_strict: args.strict? || nil, audit_strict: args.strict? || nil,
# No need for `|| nil` for `--[no-]signing` # No need for `|| nil` for `--[no-]signing`
# because boolean switches are already `nil` if not passed # because boolean switches are already `nil` if not passed
audit_signing: args.signing?, audit_signing: args.signing?,
audit_new_cask: args.new? || nil, audit_new_cask: args.new? || nil,
audit_token_conflicts: args.token_conflicts? || nil, quarantine: true,
quarantine: true, any_named_args: !no_named_args,
any_named_args: !no_named_args, only: args.only || [],
only: args.only || [], except: args.except || [],
except: args.except || [],
).to_a ).to_a
end end
end.uniq end.uniq

View File

@ -274,10 +274,7 @@ module Homebrew
bitbucket_repository] bitbucket_repository]
end end
if labels.include?("ci-skip-token") audit_exceptions << %w[token_valid token_bad_words] if labels.include?("ci-skip-token")
audit_exceptions << %w[token_conflicts token_valid
token_bad_words]
end
audit_args << "--except" << audit_exceptions.join(",") if audit_exceptions.any? audit_args << "--except" << audit_exceptions.join(",") if audit_exceptions.any?

View File

@ -170,9 +170,15 @@ module Homebrew
last_word_connector: " or ")}." last_word_connector: " or ")}."
end end
return unless @strict
return unless @core_tap return unless @core_tap
if CoreCaskTap.instance.cask_tokens.include?(name)
problem "Formula name conflicts with an existing Homebrew/cask cask's token."
return
end
return unless @strict
problem "'#{name}' is not allowed in homebrew/core." if MissingFormula.disallowed_reason(name) problem "'#{name}' is not allowed in homebrew/core." if MissingFormula.disallowed_reason(name)
if Formula.aliases.include? name if Formula.aliases.include? name
@ -185,6 +191,11 @@ module Homebrew
return return
end end
if CoreCaskTap.instance.cask_tokens.include?(name)
problem "Formula name conflicts with an existing Homebrew/cask cask's token."
return
end
return if formula.core_formula? return if formula.core_formula?
return unless Formula.core_names.include?(name) return unless Formula.core_names.include?(name)

View File

@ -49,13 +49,11 @@ RSpec.describe Cask::Audit, :cask do
let(:only) { [] } let(:only) { [] }
let(:except) { [] } let(:except) { [] }
let(:strict) { nil } let(:strict) { nil }
let(:token_conflicts) { nil }
let(:signing) { nil } let(:signing) { nil }
let(:audit) do let(:audit) do
described_class.new(cask, online:, described_class.new(cask, online:,
strict:, strict:,
new_cask:, new_cask:,
token_conflicts:,
signing:, signing:,
only:, only:,
except:) except:)
@ -72,10 +70,6 @@ RSpec.describe Cask::Audit, :cask do
it "implies `strict`" do it "implies `strict`" do
expect(audit).to be_strict expect(audit).to be_strict
end end
it "implies `token_conflicts`" do
expect(audit.token_conflicts?).to be true
end
end end
context "when `online` is specified" do context "when `online` is specified" do
@ -949,34 +943,15 @@ RSpec.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 }
context "when cask token conflicts with a core formula" do context "when cask token conflicts with a core formula" do
let(:formula_names) { %w[with-binary other-formula] } let(:formula_names) { %w[with-binary other-formula] }
context "when `--strict` is passed" do it "warns about conflicts" do
let(:strict) { true } expect(audit).to receive(:core_formula_names).and_return(formula_names)
expect(run).to error_with(/cask token conflicts/)
it "warns about duplicates" do
expect(audit).to receive(:core_formula_names).and_return(formula_names)
expect(run).to error_with(/possible duplicate/)
end
end end
context "when `--strict` is not passed" do
it "does not warn about duplicates" do
expect(audit).to receive(:core_formula_names).and_return(formula_names)
expect(run).not_to error_with(/possible duplicate/)
end
end
end
context "when cask token does not conflict with a core formula" do
let(:formula_names) { %w[other-formula] }
it { is_expected.to pass }
end end
end end

View File

@ -1321,6 +1321,7 @@ Software vendors are often inconsistent with their naming. By enforcing strict n
* Prevent duplicate submissions * Prevent duplicate submissions
* Minimize renaming events * Minimize renaming events
* Unambiguously boil down the name of the software into a unique identifier * Unambiguously boil down the name of the software into a unique identifier
* Avoid conflicts with Homebrew/homebrew-core formulae
Details of software names and brands will inevitably be lost in the conversion to a minimal token. To capture the vendors full name for a distribution, use the [`name`](#stanza-name) within a cask, which accepts an unrestricted UTF-8 string. Details of software names and brands will inevitably be lost in the conversion to a minimal token. To capture the vendors full name for a distribution, use the [`name`](#stanza-name) within a cask, which accepts an unrestricted UTF-8 string.
@ -1362,7 +1363,9 @@ Details of software names and brands will inevitably be lost in the conversion t
* If the result of this process is a generic term, such as “Macintosh Installer”, try prepending the name of the vendor or developer, followed by a hyphen. If that doesnt work, then just create the best name you can, based on the vendors web page. * If the result of this process is a generic term, such as “Macintosh Installer”, try prepending the name of the vendor or developer, followed by a hyphen. If that doesnt work, then just create the best name you can, based on the vendors web page.
* If the result conflicts with the name of an existing cask, make yours unique by prepending the name of the vendor or developer, followed by a hyphen. Example: [unison.rb](https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/u/unison.rb) and [panic-unison.rb](https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/p/panic-unison.rb). * If the result conflicts with the name of an existing cask or Homebrew/homebrew-core formula, make yours unique by prepending the name of the vendor or developer, followed by a hyphen. Example: [unison.rb](https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/u/unison.rb) and [panic-unison.rb](https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/p/panic-unison.rb).
* If the result still conflicts with the name of an existing Homebrew/homebrew-core formula, adjust the name to better describe the difference by e.g. appending `-app`. Example: `appium` formula and `appium-desktop` cask, `angband` formula and `angband-app` cask.
* Inevitably, there are a small number of exceptions not covered by the rules. Dont hesitate to [use the forum](https://github.com/orgs/Homebrew/discussions) if you have a problem. * Inevitably, there are a small number of exceptions not covered by the rules. Dont hesitate to [use the forum](https://github.com/orgs/Homebrew/discussions) if you have a problem.