Deprecate default_formula Requirement DSL

This has been a nightmare in terms of the complexity to our dependency
system and the whack-a-mole required on bugs. If a Requirement resolves
to a Formula it should just use `depends_on "formula"` instead. This
matches the effective behaviour all users of bottles (the vast majority
of users and installs) and what we're doing in Homebrew/homebrew-core.
This commit is contained in:
Mike McQuaid 2018-01-14 13:27:43 +00:00
parent b66010605d
commit b70b5429d0
11 changed files with 24 additions and 199 deletions

View File

@ -48,9 +48,6 @@ class Build
Requirement.prune Requirement.prune
elsif req.build? && dependent != formula elsif req.build? && dependent != formula
Requirement.prune Requirement.prune
elsif req.satisfied? && (dep = req.to_dependency) && dep.installed?
deps << dep
Requirement.prune
end end
end end
end end

View File

@ -94,26 +94,14 @@ module Homebrew
if ARGV.include?("--include-requirements") if ARGV.include?("--include-requirements")
deps deps
else else
deps.map do |dep| deps.select { |dep| dep.is_a? Dependency }
if dep.is_a? Dependency
dep
elsif dep.default_formula?
dep.to_dependency
end
end.compact
end end
end end
def dep_display_name(dep) def dep_display_name(dep)
str = if dep.is_a? Requirement str = if dep.is_a? Requirement
if ARGV.include?("--include-requirements") if ARGV.include?("--include-requirements")
if dep.default_formula?
":#{dep.display_s} (#{dep_display_name(dep.to_dependency)})"
else
":#{dep.display_s}" ":#{dep.display_s}"
end
elsif dep.default_formula?
dep_display_name(dep.to_dependency)
else else
# This shouldn't happen, but we'll put something here to help debugging # This shouldn't happen, but we'll put something here to help debugging
"::#{dep.name}" "::#{dep.name}"
@ -207,7 +195,7 @@ module Homebrew
max = dependables.length - 1 max = dependables.length - 1
@dep_stack.push f.name @dep_stack.push f.name
dependables.each_with_index do |dep, i| dependables.each_with_index do |dep, i|
next if !ARGV.include?("--include-requirements") && dep.is_a?(Requirement) && !dep.default_formula? next if !ARGV.include?("--include-requirements") && dep.is_a?(Requirement)
tree_lines = if i == max tree_lines = if i == max
"└──" "└──"
else else
@ -223,9 +211,6 @@ module Homebrew
else else
"" ""
end end
if dep.is_a?(Requirement) && dep.default_formula?
recursive_deps_tree(Formulary.factory(dep.to_dependency.name), prefix + prefix_addition, true)
end
if dep.is_a? Dependency if dep.is_a? Dependency
recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_addition, true) recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_addition, true)
end end

View File

@ -113,9 +113,7 @@ module Homebrew
end end
end end
reqs.any? do |req| reqs.any? { |req| req.name == ff.name }
req.name == ff.name || [ff.name, ff.full_name].include?(req.default_formula)
end
rescue FormulaUnavailableError rescue FormulaUnavailableError
# Silently ignore this case as we don't care about things used in # Silently ignore this case as we don't care about things used in
# taps that aren't currently tapped. # taps that aren't currently tapped.

View File

@ -1486,15 +1486,10 @@ class Formula
# Returns a list of Dependency objects that are required at runtime. # Returns a list of Dependency objects that are required at runtime.
# @private # @private
def runtime_dependencies def runtime_dependencies
runtime_dependencies = recursive_dependencies do |_, dependency| recursive_dependencies do |_, dependency|
Dependency.prune if dependency.build? Dependency.prune if dependency.build?
Dependency.prune if !dependency.required? && build.without?(dependency) Dependency.prune if !dependency.required? && build.without?(dependency)
end end
runtime_requirement_deps = recursive_requirements do |_, requirement|
Requirement.prune if requirement.build?
Requirement.prune if !requirement.required? && build.without?(requirement)
end.map(&:to_dependency).compact
runtime_dependencies + runtime_requirement_deps
end end
# Returns a list of formulae depended on by this formula that aren't # Returns a list of formulae depended on by this formula that aren't
@ -1552,7 +1547,6 @@ class Formula
hsh["requirements"] = requirements.map do |req| hsh["requirements"] = requirements.map do |req|
{ {
"name" => req.name, "name" => req.name,
"default_formula" => req.default_formula,
"cask" => req.cask, "cask" => req.cask,
"download" => req.download, "download" => req.download,
} }

View File

@ -415,16 +415,6 @@ class FormulaInstaller
raise UnsatisfiedRequirements, fatals raise UnsatisfiedRequirements, fatals
end end
def install_requirement_formula?(req_dependency, req, dependent, install_bottle_for_dependent)
return false unless req_dependency
return false if req.build? && dependent.installed?
return true unless req.satisfied?
return false if req.run?
return true if build_bottle?
return true if req.satisfied_by_formula?
install_bottle_for_dependent
end
def runtime_requirements(formula) def runtime_requirements(formula)
runtime_deps = formula.runtime_dependencies.map(&:to_formula) runtime_deps = formula.runtime_dependencies.map(&:to_formula)
recursive_requirements = formula.recursive_requirements do |dependent, _| recursive_requirements = formula.recursive_requirements do |dependent, _|
@ -443,17 +433,9 @@ class FormulaInstaller
f.recursive_requirements do |dependent, req| f.recursive_requirements do |dependent, req|
build = effective_build_options_for(dependent) build = effective_build_options_for(dependent)
install_bottle_for_dependent = install_bottle_for?(dependent, build) install_bottle_for_dependent = install_bottle_for?(dependent, build)
use_default_formula = install_bottle_for_dependent || build_bottle?
req_dependency = req.to_dependency(use_default_formula: use_default_formula)
if (req.optional? || req.recommended?) && build.without?(req) if (req.optional? || req.recommended?) && build.without?(req)
Requirement.prune Requirement.prune
elsif req.build? && use_default_formula && req_dependency&.installed?
Requirement.prune
elsif install_requirement_formula?(req_dependency, req, dependent, install_bottle_for_dependent)
deps.unshift(req_dependency)
formulae.unshift(req_dependency.to_formula)
Requirement.prune
elsif req.satisfied? elsif req.satisfied?
Requirement.prune Requirement.prune
elsif !runtime_requirements.include?(req) && install_bottle_for_dependent elsif !runtime_requirements.include?(req) && install_bottle_for_dependent

View File

@ -62,8 +62,7 @@ class LinkageChecker
formula.build.without?(dep) formula.build.without?(dep)
end end
declared_deps = formula.deps.reject { |dep| filter_out.call(dep) }.map(&:name) declared_deps = formula.deps.reject { |dep| filter_out.call(dep) }.map(&:name)
declared_requirement_deps = formula.requirements.reject { |req| filter_out.call(req) }.map(&:default_formula).compact declared_dep_names = declared_deps.map { |dep| dep.split("/").last }
declared_dep_names = (declared_deps + declared_requirement_deps).map { |dep| dep.split("/").last }
undeclared_deps = @brewed_dylibs.keys.reject do |full_name| undeclared_deps = @brewed_dylibs.keys.reject do |full_name|
name = full_name.split("/").last name = full_name.split("/").last
next true if name == formula.name next true if name == formula.name

View File

@ -9,13 +9,11 @@ require "build_environment"
class Requirement class Requirement
include Dependable include Dependable
attr_reader :tags, :name, :cask, :download, :default_formula attr_reader :tags, :name, :cask, :download
def initialize(tags = []) def initialize(tags = [])
@default_formula = self.class.default_formula
@cask ||= self.class.cask @cask ||= self.class.cask
@download ||= self.class.download @download ||= self.class.download
@formula = nil
tags.each do |tag| tags.each do |tag|
next unless tag.is_a? Hash next unless tag.is_a? Hash
@cask ||= tag[:cask] @cask ||= tag[:cask]
@ -56,12 +54,6 @@ class Requirement
result = self.class.satisfy.yielder { |p| instance_eval(&p) } result = self.class.satisfy.yielder { |p| instance_eval(&p) }
@satisfied_result = result @satisfied_result = result
return false unless result return false unless result
if parent = satisfied_result_parent
parent.to_s =~ %r{(#{Regexp.escape(HOMEBREW_CELLAR)}|#{Regexp.escape(HOMEBREW_PREFIX)}/opt)/([\w+-.@]+)}
@formula = Regexp.last_match(2)
end
true true
end end
@ -71,10 +63,6 @@ class Requirement
self.class.fatal || false self.class.fatal || false
end end
def default_formula?
self.class.default_formula || false
end
def satisfied_result_parent def satisfied_result_parent
return unless @satisfied_result.is_a?(Pathname) return unless @satisfied_result.is_a?(Pathname)
parent = @satisfied_result.resolved_path.parent parent = @satisfied_result.resolved_path.parent
@ -124,24 +112,6 @@ class Requirement
"#<#{self.class.name}: #{name.inspect} #{tags.inspect}>" "#<#{self.class.name}: #{name.inspect} #{tags.inspect}>"
end end
def formula
@formula || self.class.default_formula
end
def satisfied_by_formula?
!@formula.nil?
end
def to_dependency(use_default_formula: false)
if use_default_formula && default_formula?
Dependency.new(self.class.default_formula, tags, method(:modify_build_environment), name)
elsif formula =~ HOMEBREW_TAP_FORMULA_REGEX
TapDependency.new(formula, tags, method(:modify_build_environment), name)
elsif formula
Dependency.new(formula, tags, method(:modify_build_environment), name)
end
end
def display_s def display_s
name name
end end
@ -167,8 +137,11 @@ class Requirement
include BuildEnvironment::DSL include BuildEnvironment::DSL
attr_reader :env_proc, :build attr_reader :env_proc, :build
attr_rw :fatal, :default_formula attr_rw :fatal, :cask, :download
attr_rw :cask, :download
def default_formula(val = nil)
# odeprecated "Requirement.default_formula"
end
def satisfy(options = nil, &block) def satisfy(options = nil, &block)
return @satisfied if options.nil? && !block_given? return @satisfied if options.nil? && !block_given?

View File

@ -132,64 +132,4 @@ describe FormulaInstaller do
fi.check_install_sanity fi.check_install_sanity
}.to raise_error(CannotInstallFormulaError) }.to raise_error(CannotInstallFormulaError)
end end
describe "#install_requirement_formula?", :needs_compat do
before do
@requirement = Python3Requirement.new
@requirement_dependency = @requirement.to_dependency
@install_bottle_for_dependent = false
allow(@requirement).to receive(:satisfied?).and_return(satisfied?)
allow(@requirement).to receive(:satisfied_by_formula?).and_return(satisfied_by_formula?)
allow(@requirement).to receive(:build?).and_return(build?)
@dependent = formula do
url "foo"
version "0.1"
depends_on :python3
end
allow(@dependent).to receive(:installed?).and_return(installed?)
@fi = FormulaInstaller.new(@dependent)
end
subject { @fi.install_requirement_formula?(@requirement_dependency, @requirement, @dependent, @install_bottle_for_dependent) }
context "it returns false when requirement is satisfied" do
let(:satisfied?) { true }
let(:satisfied_by_formula?) { false }
let(:build?) { false }
let(:installed?) { false }
it { is_expected.to be false }
end
context "it returns false when requirement is satisfied but default formula is installed" do
let(:satisfied?) { true }
let(:satisfied_by_formula?) { false }
let(:build?) { false }
let(:installed?) { false }
it { is_expected.to be false }
end
context "it returns false when requirement is :build and dependent is installed" do
let(:satisfied?) { false }
let(:satisfied_by_formula?) { false }
let(:build?) { true }
let(:installed?) { true }
it { is_expected.to be false }
end
context "it returns true when requirement isn't satisfied" do
let(:satisfied?) { false }
let(:satisfied_by_formula?) { false }
let(:build?) { false }
let(:installed?) { false }
it { is_expected.to be true }
end
context "it returns true when requirement is satisfied by a formula" do
let(:satisfied?) { true }
let(:satisfied_by_formula?) { true }
let(:build?) { false }
let(:installed?) { false }
it { is_expected.to be true }
end
end
end end

View File

@ -2,7 +2,6 @@ require "extend/ENV"
require "requirement" require "requirement"
describe Requirement do describe Requirement do
alias_matcher :have_a_default_formula, :be_a_default_formula
alias_matcher :be_a_build_requirement, :be_a_build alias_matcher :be_a_build_requirement, :be_a_build
subject { klass.new } subject { klass.new }
@ -173,60 +172,6 @@ describe Requirement do
its(:option_names) { are_expected.to eq(["foo"]) } its(:option_names) { are_expected.to eq(["foo"]) }
end end
describe "#default_formula?" do
context "#default_formula specified" do
let(:klass) do
Class.new(described_class) do
default_formula "foo"
end
end
it { is_expected.to have_a_default_formula }
end
context "#default_formula omitted" do
it { is_expected.not_to have_a_default_formula }
end
end
describe "#to_dependency" do
let(:klass) do
Class.new(described_class) do
default_formula "foo"
end
end
it "returns a Dependency for its default Formula" do
expect(subject.to_dependency).to eq(Dependency.new("foo"))
end
context "#modify_build_environment" do
context "with error" do
let(:klass) do
Class.new(described_class) do
class ModifyBuildEnvironmentError < StandardError; end
default_formula "foo"
satisfy do
true
end
env do
raise ModifyBuildEnvironmentError
end
end
end
it "raises an error" do
expect {
subject.to_dependency.modify_build_environment
}.to raise_error(klass.const_get(:ModifyBuildEnvironmentError))
end
end
end
end
describe "#modify_build_environment" do describe "#modify_build_environment" do
context "without env proc" do context "without env proc" do
let(:klass) { Class.new(described_class) } let(:klass) { Class.new(described_class) }

View File

@ -0,0 +1,11 @@
# Building Against Non-Homebrew Dependencies
## History
Originally Homebrew was a build-from-source package manager and all user environment variables and non-Homebrew-installed software were available to builds. Since then Homebrew added `Requirement`s to specify dependencies on non-Homebrew software (such as those provided by `brew cask` like X11/XQuartz), the `superenv` build system to strip out unspecified dependencies, environment filtering to stop the user environment leaking into Homebrew builds and `default_formula` to specify that a `Requirement` can be satisifed by a particular formula.
As Homebrew became primarily a binary package manager, most users were fulfilling `Requirement`s with the `default_formula`, not with arbitrary alternatives. To improve quality and reduce variation, Homebrew now exclusively supports using the default formula, as an ordinary dependency, and no longer supports using arbitrary alternatives.
## Today
If you wish to build against custom non-Homebrew dependencies that are provided by Homebrew (e.g. a non-Homebrew, non-macOS `ruby`) then you must [create and maintain your own tap](How-to-Create-and-Maintain-a-Tap.md) as these formulae will not be accepted in Homebrew/homebrew-core. Once you have done that you can specify `env :std` in the formula which will allow a e.g. `which ruby` to access your existing `PATH` variable and allow compilation to link against this Ruby.

View File

@ -33,6 +33,7 @@
- [Python for Formula Authors](Python-for-Formula-Authors.md) - [Python for Formula Authors](Python-for-Formula-Authors.md)
- [Migrating A Formula To A Tap](Migrating-A-Formula-To-A-Tap.md) - [Migrating A Formula To A Tap](Migrating-A-Formula-To-A-Tap.md)
- [Rename A Formula](Rename-A-Formula.md) - [Rename A Formula](Rename-A-Formula.md)
- [Building Against Non-Homebrew Dependencies](Building-Against-Non-Homebrew-Dependencies.md)
- [How To Create (And Maintain) A Tap](How-to-Create-and-Maintain-a-Tap.md) - [How To Create (And Maintain) A Tap](How-to-Create-and-Maintain-a-Tap.md)
- [Brew Test Bot](Brew-Test-Bot.md) - [Brew Test Bot](Brew-Test-Bot.md)
- [Prose Style Guidelines](Prose-Style-Guidelines.md) - [Prose Style Guidelines](Prose-Style-Guidelines.md)