From 1474806527a23e53f650fca371c4027219558b9e Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Mon, 8 Apr 2024 16:38:32 +0100 Subject: [PATCH] Add more `HOMEBREW_FORBIDDEN_*` configuration We already had `HOMEBREW_FORBIDDEN_LICENSES` but this commit adds `HOMEBREW_FORBIDDEN_CASKS`, `HOMEBREW_FORBIDDEN_FORMULAE` and `HOMEBREW_FORBIDDEN_TAPS` for also forbidding those. Relatedly, add `HOMEBREW_FORBIDDEN_OWNER` and `HOMEBREW_FORBIDDEN_OWNER_CONTACT` to allow customising these messages. There were no existing tests for `HOMEBREW_FORBIDDEN_LICENSES` so have added more tests for all of these checks. Co-authored-by: Bo Anderson --- Library/Homebrew/cask/installer.rb | 104 ++++++++++ Library/Homebrew/env_config.rb | 19 ++ Library/Homebrew/formula_installer.rb | 166 +++++++++++---- .../sorbet/rbi/dsl/homebrew/env_config.rbi | 15 ++ Library/Homebrew/tap.rb | 4 +- Library/Homebrew/test/cask/installer_spec.rb | 73 +++++++ Library/Homebrew/test/cli/named_args_spec.rb | 4 +- .../Homebrew/test/formula_installer_spec.rb | 191 ++++++++++++++++++ Library/Homebrew/test/tap_spec.rb | 6 +- docs/Manpage.md | 25 +++ manpages/brew.1 | 19 ++ 11 files changed, 585 insertions(+), 41 deletions(-) diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index dc6b454d27..fcfcb50d4b 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -71,6 +71,9 @@ module Cask download(quiet:, timeout:) satisfy_cask_and_formula_dependencies + + forbidden_tap_check + forbidden_cask_and_formula_check end def stage @@ -571,6 +574,107 @@ on_request: true) gain_permissions_remove(@cask.caskroom_path) end + sig { void } + def forbidden_tap_check + forbidden_taps = Homebrew::EnvConfig.forbidden_taps + return if forbidden_taps.blank? + + forbidden_taps_set = Set.new(forbidden_taps.split.filter_map do |tap| + Tap.fetch(tap) + rescue Tap::InvalidNameError + opoo "Invalid tap name in `HOMEBREW_FORBIDDEN_TAPS`: #{tap}" + nil + end) + + owner = Homebrew::EnvConfig.forbidden_owner + owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) + "\n#{contact}" + end + + unless skip_cask_deps? + cask_and_formula_dependencies.each do |cask_or_formula| + dep_tap = cask_or_formula.tap + next if dep_tap.blank? + next unless forbidden_taps_set.include?(dep_tap) + + dep_full_name = cask_or_formula.full_name + raise CaskCannotBeInstalledError.new(@cask, <<~EOS + The installation of #{@cask} has a dependency #{dep_full_name} + but the #{dep_tap} tap was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} + EOS + ) + end + end + + cask_tap = @cask.tap + return if cask_tap.blank? + return unless forbidden_taps_set.include?(cask_tap) + + raise CaskCannotBeInstalledError.new(@cask, <<~EOS + The installation of #{@cask.full_name} has the tap #{cask_tap} + which was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} + EOS + ) + end + + sig { void } + def forbidden_cask_and_formula_check + forbidden_formulae = Set.new(Homebrew::EnvConfig.forbidden_formulae.to_s.split) + forbidden_casks = Set.new(Homebrew::EnvConfig.forbidden_casks.to_s.split) + return if forbidden_formulae.blank? && forbidden_casks.blank? + + owner = Homebrew::EnvConfig.forbidden_owner + owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) + "\n#{contact}" + end + + unless skip_cask_deps? + cask_and_formula_dependencies.each do |dep_cask_or_formula| + dep_name, dep_type, variable = if dep_cask_or_formula.is_a?(Cask) && forbidden_casks.present? + dep_cask = dep_cask_or_formula + dep_cask_name = if forbidden_casks.include?(dep_cask.token) + dep_cask.token + elsif dep_cask.tap.present? && + forbidden_casks.include?(dep_cask.full_name) + dep_cask.full_name + end + [dep_cask_name, "cask", "HOMEBREW_FORBIDDEN_CASKS"] + elsif dep_cask_or_formula.is_a?(Formula) && forbidden_formulae.present? + dep_formula = dep_cask_or_formula + formula_name = if forbidden_formulae.include?(dep_formula.name) + dep_formula.name + elsif dep_formula.tap.present? && + forbidden_formulae.include?(dep_formula.full_name) + dep_formula.full_name + end + [formula_name, "formula", "HOMEBREW_FORBIDDEN_FORMULAE"] + end + next if dep_name.blank? + + raise CaskCannotBeInstalledError.new(@cask, <<~EOS + The installation of #{@cask} has a dependency #{dep_name} + but the #{dep_name} #{dep_type} was forbidden by #{owner} in `#{variable}`.#{owner_contact} + EOS + ) + end + end + return if forbidden_casks.blank? + + cask_name = if forbidden_casks.include?(@cask.token) + @cask.token + elsif forbidden_casks.include?(@cask.full_name) + @cask.full_name + else + return + end + + raise CaskCannotBeInstalledError.new(@cask, <<~EOS + The installation of #{cask_name} was forbidden by #{owner} + in `HOMEBREW_FORBIDDEN_CASKS`.#{owner_contact} + EOS + ) + end + private # load the same cask file that was used for installation, if possible diff --git a/Library/Homebrew/env_config.rb b/Library/Homebrew/env_config.rb index 5711af7be7..16d2db6222 100644 --- a/Library/Homebrew/env_config.rb +++ b/Library/Homebrew/env_config.rb @@ -185,10 +185,29 @@ module Homebrew description: "Output this many lines of output on formula `system` failures.", default: 15, }, + HOMEBREW_FORBIDDEN_CASKS: { + description: "A space-separated list of casks. Homebrew will refuse to install a " \ + "cask if it or any of its dependencies is on this list.", + }, + HOMEBREW_FORBIDDEN_FORMULAE: { + description: "A space-separated list of formulae. Homebrew will refuse to install a " \ + "formula or cask if it or any of its dependencies is on this list.", + }, HOMEBREW_FORBIDDEN_LICENSES: { description: "A space-separated list of licenses. Homebrew will refuse to install a " \ "formula if it or any of its dependencies has a license on this list.", }, + HOMEBREW_FORBIDDEN_OWNER: { + description: "The person who has set any `HOMEBREW_FORBIDDEN_*` variables.", + default: "you", + }, + HOMEBREW_FORBIDDEN_OWNER_CONTACT: { + description: "How to contact the `HOMEBREW_FORBIDDEN_OWNER`, if set and necessary.", + }, + HOMEBREW_FORBIDDEN_TAPS: { + description: "A space-separated list of taps. Homebrew will refuse to install a " \ + "formula if it or any of its dependencies is in a tap on this list.", + }, HOMEBREW_FORCE_BREWED_CA_CERTIFICATES: { description: "If set, always use a Homebrew-installed `ca-certificates` rather than the system version. " \ "Automatically set if the system version is too old.", diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index d5b49c9e76..a0effec872 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -217,7 +217,10 @@ class FormulaInstaller Tab.clear_cache verify_deps_exist unless ignore_deps? + forbidden_license_check + forbidden_tap_check + forbidden_formula_check check_install_sanity install_fetch_deps unless ignore_deps? @@ -1315,6 +1318,134 @@ on_request: installed_on_request?, options:) @locked ||= [] end + sig { void } + def forbidden_license_check + forbidden_licenses = Homebrew::EnvConfig.forbidden_licenses.to_s.dup + SPDX::ALLOWED_LICENSE_SYMBOLS.each do |s| + pattern = /#{s.to_s.tr("_", " ")}/i + forbidden_licenses.sub!(pattern, s.to_s) + end + forbidden_licenses = forbidden_licenses.split.to_h do |license| + [license, SPDX.license_version_info(license)] + end + + return if forbidden_licenses.blank? + + owner = Homebrew::EnvConfig.forbidden_owner + owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) + "\n#{contact}" + end + + unless ignore_deps? + compute_dependencies.each do |(dep, _options)| + dep_f = dep.to_formula + next unless SPDX.licenses_forbid_installation? dep_f.license, forbidden_licenses + + raise CannotInstallFormulaError, <<~EOS + The installation of #{formula.name} has a dependency on #{dep.name} where all + its licenses were forbidden by #{owner} in `HOMEBREW_FORBIDDEN_LICENSES`: + #{SPDX.license_expression_to_string dep_f.license}.#{owner_contact} + EOS + end + end + + return if only_deps? + + return unless SPDX.licenses_forbid_installation? formula.license, forbidden_licenses + + raise CannotInstallFormulaError, <<~EOS + #{formula.name}'s licenses are all forbidden by #{owner} in `HOMEBREW_FORBIDDEN_LICENSES`: + #{SPDX.license_expression_to_string formula.license}.#{owner_contact} + EOS + end + + sig { void } + def forbidden_tap_check + forbidden_taps = Homebrew::EnvConfig.forbidden_taps + return if forbidden_taps.blank? + + forbidden_taps_set = Set.new(forbidden_taps.split.filter_map do |tap| + Tap.fetch(tap) + rescue Tap::InvalidNameError + opoo "Invalid tap name in `HOMEBREW_FORBIDDEN_TAPS`: #{tap}" + nil + end) + + owner = Homebrew::EnvConfig.forbidden_owner + owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) + "\n#{contact}" + end + + unless ignore_deps? + compute_dependencies.each do |(dep, _options)| + dep_tap = dep.tap + next if dep_tap.blank? + next unless forbidden_taps_set.include?(dep_tap) + + raise CannotInstallFormulaError, <<~EOS + The installation of #{formula.name} has a dependency #{dep.name} + but the #{dep_tap} tap was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} + EOS + end + end + + return if only_deps? + + formula_tap = formula.tap + return if formula_tap.blank? + return unless forbidden_taps_set.include?(formula_tap) + + raise CannotInstallFormulaError, <<~EOS + The installation of #{formula.full_name} has the tap #{formula_tap} + which was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} + EOS + end + + sig { void } + def forbidden_formula_check + forbidden_formulae = Set.new(Homebrew::EnvConfig.forbidden_formulae.to_s.split) + return if forbidden_formulae.blank? + + owner = Homebrew::EnvConfig.forbidden_owner + owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) + "\n#{contact}" + end + + unless ignore_deps? + compute_dependencies.each do |(dep, _options)| + dep_name = if forbidden_formulae.include?(dep.name) + dep.name + elsif dep.tap.present? && + (dep_full_name = "#{dep.tap}/#{dep.name}") && + forbidden_formulae.include?(dep_full_name) + dep_full_name + else + next + end + + raise CannotInstallFormulaError, <<~EOS + The installation of #{formula.name} has a dependency #{dep_name} + but the #{dep_name} formula was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_FORMULAE`.#{owner_contact} + EOS + end + end + + return if only_deps? + + formula_name = if forbidden_formulae.include?(formula.name) + formula.name + elsif forbidden_formulae.include?(formula.full_name) + formula.full_name + else + return + end + + raise CannotInstallFormulaError, <<~EOS + The installation of #{formula_name} was forbidden by #{owner} + in `HOMEBREW_FORBIDDEN_FORMULAE`.#{owner_contact} + EOS + end + private attr_predicate :hold_locks? @@ -1349,39 +1480,4 @@ on_request: installed_on_request?, options:) $stderr.puts @requirement_messages end - - sig { void } - def forbidden_license_check - forbidden_licenses = Homebrew::EnvConfig.forbidden_licenses.to_s.dup - SPDX::ALLOWED_LICENSE_SYMBOLS.each do |s| - pattern = /#{s.to_s.tr("_", " ")}/i - forbidden_licenses.sub!(pattern, s.to_s) - end - forbidden_licenses = forbidden_licenses.split.to_h do |license| - [license, SPDX.license_version_info(license)] - end - - return if forbidden_licenses.blank? - return if ignore_deps? - - compute_dependencies.each do |(dep, _options)| - dep_f = dep.to_formula - next unless SPDX.licenses_forbid_installation? dep_f.license, forbidden_licenses - - raise CannotInstallFormulaError, <<~EOS - The installation of #{formula.name} has a dependency on #{dep.name} where all - its licenses are forbidden by HOMEBREW_FORBIDDEN_LICENSES: - #{SPDX.license_expression_to_string dep_f.license}. - EOS - end - - return if only_deps? - - return unless SPDX.licenses_forbid_installation? formula.license, forbidden_licenses - - raise CannotInstallFormulaError, <<~EOS - #{formula.name}'s licenses are all forbidden by HOMEBREW_FORBIDDEN_LICENSES: - #{SPDX.license_expression_to_string formula.license}. - EOS - end end diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/env_config.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/env_config.rbi index f9c7a8d962..a1a75c94cb 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/homebrew/env_config.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/env_config.rbi @@ -105,9 +105,24 @@ module Homebrew::EnvConfig sig { returns(Integer) } def fail_log_lines; end + sig { returns(T.nilable(::String)) } + def forbidden_casks; end + + sig { returns(T.nilable(::String)) } + def forbidden_formulae; end + sig { returns(T.nilable(::String)) } def forbidden_licenses; end + sig { returns(String) } + def forbidden_owner; end + + sig { returns(T.nilable(::String)) } + def forbidden_owner_contact; end + + sig { returns(T.nilable(::String)) } + def forbidden_taps; end + sig { returns(T::Boolean) } def force_brewed_ca_certificates?; end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 8599a20bfc..89d83acd01 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -39,12 +39,14 @@ class Tap #{HOMEBREW_TAP_STYLE_EXCEPTIONS_DIR}/*.json ].freeze + class InvalidNameError < ArgumentError; end + sig { params(user: String, repo: String).returns(Tap) } def self.fetch(user, repo = T.unsafe(nil)) user, repo = user.split("/", 2) if repo.nil? if [user, repo].any? { |part| part.nil? || part.include?("/") } - raise ArgumentError, "Invalid tap name: '#{[*user, *repo].join("/")}'" + raise InvalidNameError, "Invalid tap name: '#{[*user, *repo].join("/")}'" end user = T.must(user) diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb index 1bdfa80548..031b9d4a99 100644 --- a/Library/Homebrew/test/cask/installer_spec.rb +++ b/Library/Homebrew/test/cask/installer_spec.rb @@ -324,4 +324,77 @@ RSpec.describe Cask::Installer, :cask do expect(Cask::CaskLoader.load(cask_path("local-caffeine"))).not_to be_installed end end + + describe "#forbidden_tap_check" do + it "raises on forbidden tap on cask" do + ENV["HOMEBREW_FORBIDDEN_TAPS"] = tap = "homebrew/forbidden" + + cask = Cask::Cask.new("homebrew-forbidden-tap", tap: Tap.fetch(tap)) do + url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz" + end + + expect do + described_class.new(cask).forbidden_tap_check + end.to raise_error(Cask::CaskCannotBeInstalledError, /has the tap #{tap}/) + end + + it "raises on forbidden tap on dependency" do + ENV["HOMEBREW_FORBIDDEN_TAPS"] = dep_tap = "homebrew/forbidden" + dep_name = "homebrew-forbidden-dependency-tap" + dep_path = Tap.fetch(dep_tap).new_formula_path(dep_name) + dep_path.parent.mkpath + dep_path.write <<~RUBY + class #{Formulary.class_s(dep_name)} < Formula + url "foo" + version "0.1" + end + RUBY + Formulary.cache.delete(dep_path) + + cask = Cask::Cask.new("homebrew-forbidden-dependent-tap") do + url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz" + depends_on formula: dep_name + end + + expect do + described_class.new(cask).forbidden_tap_check + end.to raise_error(Cask::CaskCannotBeInstalledError, /but the #{dep_tap} tap was forbidden/) + ensure + dep_path.parent.parent.rmtree + end + end + + describe "#forbidden_cask_and_formula_check" do + it "raises on forbidden cask" do + ENV["HOMEBREW_FORBIDDEN_CASKS"] = cask_name = "homebrew-forbidden-cask" + cask = Cask::Cask.new(cask_name) do + url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz" + end + + expect do + described_class.new(cask).forbidden_cask_and_formula_check + end.to raise_error(Cask::CaskCannotBeInstalledError, /#{cask_name} was forbidden/) + end + + it "raises on forbidden dependency" do + ENV["HOMEBREW_FORBIDDEN_FORMULAE"] = dep_name = "homebrew-forbidden-dependency-formula" + dep_path = CoreTap.instance.new_formula_path(dep_name) + dep_path.write <<~RUBY + class #{Formulary.class_s(dep_name)} < Formula + url "foo" + version "0.1" + end + RUBY + Formulary.cache.delete(dep_path) + + cask = Cask::Cask.new("homebrew-forbidden-dependent-cask") do + url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz" + depends_on formula: dep_name + end + + expect do + described_class.new(cask).forbidden_cask_and_formula_check + end.to raise_error(Cask::CaskCannotBeInstalledError, /#{dep_name} formula was forbidden/) + end + end end diff --git a/Library/Homebrew/test/cli/named_args_spec.rb b/Library/Homebrew/test/cli/named_args_spec.rb index 7022141099..0d70647a3f 100644 --- a/Library/Homebrew/test/cli/named_args_spec.rb +++ b/Library/Homebrew/test/cli/named_args_spec.rb @@ -312,7 +312,7 @@ RSpec.describe Homebrew::CLI::NamedArgs do it "raises an error for invalid tap" do taps = described_class.new("homebrew/foo", "barbaz") - expect { taps.to_taps }.to raise_error(ArgumentError, /Invalid tap name/) + expect { taps.to_taps }.to raise_error(Tap::InvalidNameError, /Invalid tap name/) end end @@ -333,7 +333,7 @@ RSpec.describe Homebrew::CLI::NamedArgs do it "raises an error for invalid tap" do taps = described_class.new("homebrew/foo", "barbaz") - expect { taps.to_installed_taps }.to raise_error(ArgumentError, /Invalid tap name/) + expect { taps.to_installed_taps }.to raise_error(Tap::InvalidNameError, /Invalid tap name/) end end end diff --git a/Library/Homebrew/test/formula_installer_spec.rb b/Library/Homebrew/test/formula_installer_spec.rb index 971463ea6f..67806d34c6 100644 --- a/Library/Homebrew/test/formula_installer_spec.rb +++ b/Library/Homebrew/test/formula_installer_spec.rb @@ -170,6 +170,197 @@ RSpec.describe FormulaInstaller do end end + describe "#forbidden_license_check" do + it "raises on forbidden license on formula" do + ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "AGPL-3.0" + + f_name = "homebrew-forbidden-license" + f_path = CoreTap.instance.new_formula_path(f_name) + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + license "AGPL-3.0" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory(f_name) + fi = described_class.new(f) + + expect do + fi.forbidden_license_check + end.to raise_error(CannotInstallFormulaError, /#{f_name}'s licenses are all forbidden/) + end + + it "raises on forbidden license on formula with contact instructions" do + ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "AGPL-3.0" + ENV["HOMEBREW_FORBIDDEN_OWNER"] = owner = "your dog" + ENV["HOMEBREW_FORBIDDEN_OWNER_CONTACT"] = contact = "Woof loudly to get this unblocked." + + f_name = "homebrew-forbidden-license" + f_path = CoreTap.instance.new_formula_path(f_name) + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + license "AGPL-3.0" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory(f_name) + fi = described_class.new(f) + + expect do + fi.forbidden_license_check + end.to raise_error(CannotInstallFormulaError, /#{owner}.+\n#{contact}/m) + end + + it "raises on forbidden license on dependency" do + ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "GPL-3.0" + + dep_name = "homebrew-forbidden-dependency-license" + dep_path = CoreTap.instance.new_formula_path(dep_name) + dep_path.write <<~RUBY + class #{Formulary.class_s(dep_name)} < Formula + url "foo" + version "0.1" + license "GPL-3.0" + end + RUBY + Formulary.cache.delete(dep_path) + + f_name = "homebrew-forbidden-dependent-license" + f_path = CoreTap.instance.new_formula_path(f_name) + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + depends_on "#{dep_name}" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory(f_name) + fi = described_class.new(f) + + expect do + fi.forbidden_license_check + end.to raise_error(CannotInstallFormulaError, /dependency on #{dep_name} where all/) + end + end + + describe "#forbidden_tap_check" do + it "raises on forbidden tap on formula" do + ENV["HOMEBREW_FORBIDDEN_TAPS"] = f_tap = "homebrew/forbidden" + f_name = "homebrew-forbidden-tap" + f_path = Tap.fetch(f_tap).new_formula_path(f_name) + f_path.parent.mkpath + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory("#{f_tap}/#{f_name}") + fi = described_class.new(f) + + expect do + fi.forbidden_tap_check + end.to raise_error(CannotInstallFormulaError, /has the tap #{f_tap}/) + ensure + f_path.parent.parent.rmtree + end + + it "raises on forbidden tap on dependency" do + ENV["HOMEBREW_FORBIDDEN_TAPS"] = dep_tap = "homebrew/forbidden" + dep_name = "homebrew-forbidden-dependency-tap" + dep_path = Tap.fetch(dep_tap).new_formula_path(dep_name) + dep_path.parent.mkpath + dep_path.write <<~RUBY + class #{Formulary.class_s(dep_name)} < Formula + url "foo" + version "0.1" + end + RUBY + Formulary.cache.delete(dep_path) + + f_name = "homebrew-forbidden-dependent-tap" + f_path = CoreTap.instance.new_formula_path(f_name) + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + depends_on "#{dep_name}" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory(f_name) + fi = described_class.new(f) + + expect do + fi.forbidden_tap_check + end.to raise_error(CannotInstallFormulaError, /but the #{dep_tap} tap was forbidden/) + ensure + dep_path.parent.parent.rmtree + end + end + + describe "#forbidden_formula_check" do + it "raises on forbidden formula" do + ENV["HOMEBREW_FORBIDDEN_FORMULAE"] = f_name = "homebrew-forbidden-formula" + f_path = CoreTap.instance.new_formula_path(f_name) + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory(f_name) + fi = described_class.new(f) + + expect do + fi.forbidden_formula_check + end.to raise_error(CannotInstallFormulaError, /#{f_name} was forbidden/) + end + + it "raises on forbidden dependency" do + ENV["HOMEBREW_FORBIDDEN_FORMULAE"] = dep_name = "homebrew-forbidden-dependency-formula" + dep_path = CoreTap.instance.new_formula_path(dep_name) + dep_path.write <<~RUBY + class #{Formulary.class_s(dep_name)} < Formula + url "foo" + version "0.1" + end + RUBY + Formulary.cache.delete(dep_path) + + f_name = "homebrew-forbidden-dependent-formula" + f_path = CoreTap.instance.new_formula_path(f_name) + f_path.write <<~RUBY + class #{Formulary.class_s(f_name)} < Formula + url "foo" + version "0.1" + depends_on "#{dep_name}" + end + RUBY + Formulary.cache.delete(f_path) + + f = Formulary.factory(f_name) + fi = described_class.new(f) + + expect do + fi.forbidden_formula_check + end.to raise_error(CannotInstallFormulaError, /#{dep_name} formula was forbidden/) + end + end + specify "install fails with BuildError when a system() call fails" do ENV["HOMEBREW_TEST_NO_EXIT_CLEANUP"] = "1" ENV["FAILBALL_BUILD_ERROR"] = "1" diff --git a/Library/Homebrew/test/tap_spec.rb b/Library/Homebrew/test/tap_spec.rb index 2a87f189df..a8cafb6799 100644 --- a/Library/Homebrew/test/tap_spec.rb +++ b/Library/Homebrew/test/tap_spec.rb @@ -105,15 +105,15 @@ RSpec.describe Tap do expect do described_class.fetch("foo") - end.to raise_error(ArgumentError, /Invalid tap name/) + end.to raise_error(Tap::InvalidNameError, /Invalid tap name/) expect do described_class.fetch("homebrew/homebrew/bar") - end.to raise_error(ArgumentError, /Invalid tap name/) + end.to raise_error(Tap::InvalidNameError, /Invalid tap name/) expect do described_class.fetch("homebrew", "homebrew/baz") - end.to raise_error(ArgumentError, /Invalid tap name/) + end.to raise_error(Tap::InvalidNameError, /Invalid tap name/) end describe "::from_path" do diff --git a/docs/Manpage.md b/docs/Manpage.md index 5a2afd302e..04a674c179 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -3647,11 +3647,36 @@ command execution e.g. `$(cat file)`. *Default:* `15`. +`HOMEBREW_FORBIDDEN_CASKS` + +: A space-separated list of casks. Homebrew will refuse to install a cask if it + or any of its dependencies is on this list. + +`HOMEBREW_FORBIDDEN_FORMULAE` + +: A space-separated list of formulae. Homebrew will refuse to install a formula + or cask if it or any of its dependencies is on this list. + `HOMEBREW_FORBIDDEN_LICENSES` : A space-separated list of licenses. Homebrew will refuse to install a formula if it or any of its dependencies has a license on this list. +`HOMEBREW_FORBIDDEN_OWNER` + +: The person who has set any `HOMEBREW_FORBIDDEN_*` variables. + + *Default:* `you`. + +`HOMEBREW_FORBIDDEN_OWNER_CONTACT` + +: How to contact the `HOMEBREW_FORBIDDEN_OWNER`, if set and necessary. + +`HOMEBREW_FORBIDDEN_TAPS` + +: A space-separated list of taps. Homebrew will refuse to install a formula if + it or any of its dependencies is in a tap on this list. + `HOMEBREW_FORCE_BREWED_CA_CERTIFICATES` : If set, always use a Homebrew-installed `ca-certificates` rather than the diff --git a/manpages/brew.1 b/manpages/brew.1 index c9a7bc8bf5..7480d289e3 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -2368,9 +2368,28 @@ Output this many lines of output on formula \fBsystem\fP failures\. \fIDefault:\fP \fB15\fP\&\. .RE .TP +\fBHOMEBREW_FORBIDDEN_CASKS\fP +A space\-separated list of casks\. Homebrew will refuse to install a cask if it or any of its dependencies is on this list\. +.TP +\fBHOMEBREW_FORBIDDEN_FORMULAE\fP +A space\-separated list of formulae\. Homebrew will refuse to install a formula or cask if it or any of its dependencies is on this list\. +.TP \fBHOMEBREW_FORBIDDEN_LICENSES\fP A space\-separated list of licenses\. Homebrew will refuse to install a formula if it or any of its dependencies has a license on this list\. .TP +\fBHOMEBREW_FORBIDDEN_OWNER\fP +The person who has set any \fBHOMEBREW_FORBIDDEN_*\fP variables\. +.RS +.P +\fIDefault:\fP \fByou\fP\&\. +.RE +.TP +\fBHOMEBREW_FORBIDDEN_OWNER_CONTACT\fP +How to contact the \fBHOMEBREW_FORBIDDEN_OWNER\fP, if set and necessary\. +.TP +\fBHOMEBREW_FORBIDDEN_TAPS\fP +A space\-separated list of taps\. Homebrew will refuse to install a formula if it or any of its dependencies is in a tap on this list\. +.TP \fBHOMEBREW_FORCE_BREWED_CA_CERTIFICATES\fP If set, always use a Homebrew\-installed \fBca\-certificates\fP rather than the system version\. Automatically set if the system version is too old\. .TP