Python module deps, ext. python fix and 10.6 fix
* Fixes Homebrew/homebrew#20572 by tweaking the logic that decides which python is used by the `python` object inside a formula. There was a bug when on 10.6 there is no Python 2.7 but a :recommended Python was still treated as being available. * Use the user's PATH when looking for an external Python. Until now only brewed or OS X system's python have been found by `depends_on :python`. But now we support any Python in PATH (e.g. pyenv's python). * Further, instead of handling python modules and import tests in LanguageModuleDependency, these are now handled by: depends_on :python => 'numpy' # for example The old style depends_on 'numpy' => :python is still supported and is only an alias for the newer style (only for :python, the other languages are not altered by this commit). The reasoning is that if a formula requires a python module, it basically also needs python itself - and further that specific version of python has to provide the module. So the `PythonInstalled` is the natural place to check for the availability of a python module. Using a python module and other tags like :optional or :recommended is done like so: depends_on :python => [:optional, 'numpy'] Specifying another PyPi (Python Package index) name than the module import name is seldom used but supported, too: depends_on :python => ['enchant'=>'pyenchant'] A last note: For clarity, you can define multiple depends_on statements with different modules to be importable.`
This commit is contained in:
parent
c5f9f42e51
commit
a3a0146d7c
@ -18,7 +18,7 @@ require 'set'
|
|||||||
class DependencyCollector
|
class DependencyCollector
|
||||||
# Define the languages that we can handle as external dependencies.
|
# Define the languages that we can handle as external dependencies.
|
||||||
LANGUAGE_MODULES = Set[
|
LANGUAGE_MODULES = Set[
|
||||||
:chicken, :jruby, :lua, :node, :ocaml, :perl, :python, :python2, :python3, :rbx, :ruby
|
:chicken, :jruby, :lua, :node, :ocaml, :perl, :python, :rbx, :ruby
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
attr_reader :deps, :requirements
|
attr_reader :deps, :requirements
|
||||||
@ -68,6 +68,9 @@ class DependencyCollector
|
|||||||
if tags.empty?
|
if tags.empty?
|
||||||
Dependency.new(spec, tags)
|
Dependency.new(spec, tags)
|
||||||
elsif (tag = tags.first) && LANGUAGE_MODULES.include?(tag)
|
elsif (tag = tags.first) && LANGUAGE_MODULES.include?(tag)
|
||||||
|
# Next line only for legacy support of `depends_on 'module' => :python`
|
||||||
|
# It should be replaced by `depends_on :python => 'module'`
|
||||||
|
return PythonInstalled.new("2", spec, *tags) if tag == :python
|
||||||
LanguageModuleDependency.new(tag, spec)
|
LanguageModuleDependency.new(tag, spec)
|
||||||
else
|
else
|
||||||
Dependency.new(spec, tags)
|
Dependency.new(spec, tags)
|
||||||
@ -94,9 +97,8 @@ class DependencyCollector
|
|||||||
when :clt then CLTDependency.new(tags)
|
when :clt then CLTDependency.new(tags)
|
||||||
when :arch then ArchRequirement.new(tags)
|
when :arch then ArchRequirement.new(tags)
|
||||||
when :hg then MercurialDependency.new(tags)
|
when :hg then MercurialDependency.new(tags)
|
||||||
when :python then PythonInstalled.new(tags)
|
when :python, :python2 then PythonInstalled.new("2", *tags)
|
||||||
when :python2 then PythonInstalled.new("2", tags)
|
when :python3 then PythonInstalled.new("3", *tags)
|
||||||
when :python3 then PythonInstalled.new("3", tags)
|
|
||||||
# Tiger's ld is too old to properly link some software
|
# Tiger's ld is too old to properly link some software
|
||||||
when :ld64 then LD64Dependency.new if MacOS.version < :leopard
|
when :ld64 then LD64Dependency.new if MacOS.version < :leopard
|
||||||
else
|
else
|
||||||
|
@ -29,25 +29,30 @@ def python_helper(options={:allowed_major_versions => [2, 3]}, &block)
|
|||||||
if python_reqs.empty?
|
if python_reqs.empty?
|
||||||
raise "If you use python in the formula, you have to add `depends_on :python` (or :python3)!"
|
raise "If you use python in the formula, you have to add `depends_on :python` (or :python3)!"
|
||||||
end
|
end
|
||||||
# Now select those that are satisfied and matching the version.major
|
# Now select those that are satisfied and matching the version.major and
|
||||||
python_reqs = python_reqs.select do |p|
|
# check that no two python binaries are the same (which could be the case
|
||||||
p.satisfied? &&
|
# because more than one `depends_on :python => 'module_name' may be present).
|
||||||
options[:allowed_major_versions].include?(p.version.major) &&
|
filtered_python_reqs = []
|
||||||
if p.optional? || p.recommended?
|
while !python_reqs.empty?
|
||||||
self.build.with?(p.name)
|
py = python_reqs.shift
|
||||||
else
|
# this is ulgy but Ruby 1.8 has no `uniq! { }`
|
||||||
true
|
if !filtered_python_reqs.map{ |fpr| fpr.binary }.include?(py.binary) &&
|
||||||
|
py.satisfied? &&
|
||||||
|
options[:allowed_major_versions].include?(py.version.major) &&
|
||||||
|
self.build.with?(py.name) || !(py.optional? || py.recommended?)
|
||||||
|
then
|
||||||
|
filtered_python_reqs << py
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow to use an else-branch like so: `if python do ... end; else ... end`
|
# Allow to use an else-branch like so: `if python do ... end; else ... end`
|
||||||
return false if python_reqs.empty?
|
return false if filtered_python_reqs.empty?
|
||||||
|
|
||||||
# Sort by version, so the older 2.x will be used first and if no
|
# Sort by version, so the older 2.x will be used first and if no
|
||||||
# block_given? then 2.x is preferred because it is returned.
|
# block_given? then 2.x is preferred because it is returned.
|
||||||
# Further note, having 3.x last allows us to run `2to3 --write .`
|
# Further note, having 3.x last allows us to run `2to3 --write .`
|
||||||
# which modifies the sources in-place (for some packages that need this).
|
# which modifies the sources in-place (for some packages that need this).
|
||||||
python_reqs.sort_by{ |py| py.version }.map do |py|
|
filtered_python_reqs.sort_by{ |py| py.version }.map do |py|
|
||||||
# Now is the time to set the site_packages to the correct value
|
# Now is the time to set the site_packages to the correct value
|
||||||
py.site_packages = lib/py.xy/'site-packages'
|
py.site_packages = lib/py.xy/'site-packages'
|
||||||
if !block_given?
|
if !block_given?
|
||||||
|
@ -31,13 +31,12 @@ class PythonInstalled < Requirement
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(*tags)
|
def initialize(version="2.6", *tags )
|
||||||
# Extract the min_version if given. Default to python 2.X else
|
# Extract the min_version if given. Default to python 2.X else
|
||||||
tags.flatten!
|
if /(\d+\.)*\d+/ === version.to_s
|
||||||
if /(\d+\.)*\d+/ === tags.first
|
@min_version = PythonVersion.new(version)
|
||||||
@min_version = PythonVersion.new(tags.shift)
|
|
||||||
else
|
else
|
||||||
@min_version = PythonVersion.new("2.6") # default
|
raise "Invalid version specification for Python: '#{version}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
# often used idiom: e.g. sipdir = "share/sip" + python.if3then3
|
# often used idiom: e.g. sipdir = "share/sip" + python.if3then3
|
||||||
@ -51,6 +50,18 @@ class PythonInstalled < Requirement
|
|||||||
# The name is used to generate the options like --without-python3
|
# The name is used to generate the options like --without-python3
|
||||||
@name = "python" + @if3then3
|
@name = "python" + @if3then3
|
||||||
|
|
||||||
|
# Check if any python modules should be importable. We use a hash to store
|
||||||
|
# the corresponding name on PyPi "<import_name>" => "<name_on_PyPi>".
|
||||||
|
# Example: `depends_on :python => ['enchant' => 'pyenchant']
|
||||||
|
@imports = {}
|
||||||
|
tags.each do |tag|
|
||||||
|
if tag.kind_of? String
|
||||||
|
@imports[tag] = tag
|
||||||
|
elsif tag.kind_of? Hash
|
||||||
|
@imports.merge!(tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# will be set later by the python_helper, because it needs the
|
# will be set later by the python_helper, because it needs the
|
||||||
# formula prefix to set site_packages
|
# formula prefix to set site_packages
|
||||||
@site_packages = nil
|
@site_packages = nil
|
||||||
@ -78,10 +89,29 @@ class PythonInstalled < Requirement
|
|||||||
elsif @min_version.major == 2 && `python -c "import sys; print(sys.version_info[0])"`.strip == "3"
|
elsif @min_version.major == 2 && `python -c "import sys; print(sys.version_info[0])"`.strip == "3"
|
||||||
@unsatisfied_because += "Your `python` points to a Python 3.x. This is not supported."
|
@unsatisfied_because += "Your `python` points to a Python 3.x. This is not supported."
|
||||||
else
|
else
|
||||||
true
|
@imports.keys.map do |module_name|
|
||||||
|
if not importable? module_name
|
||||||
|
@unsatisfied_because += "Unsatisfied dependency: #{module_name}\n"
|
||||||
|
@unsatisfied_because += "OS X System's " if from_osx?
|
||||||
|
@unsatisfied_because += "Brewed " if brewed?
|
||||||
|
@unsatisfied_because += "External " unless brewed? || from_osx?
|
||||||
|
@unsatisfied_because += "Python cannot `import #{module_name}`. Install with:\n "
|
||||||
|
unless importable? 'pip'
|
||||||
|
@unsatisfied_because += "sudo easy_install pip\n "
|
||||||
|
end
|
||||||
|
@unsatisfied_because += "pip-#{version.major}.#{version.minor} install #{@imports[module_name]}"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end.all? # all given `module_name`s have to be `importable?`
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def importable? module_name
|
||||||
|
quiet_system(binary, "-c", "import #{module_name}")
|
||||||
|
end
|
||||||
|
|
||||||
# The full path to the python or python3 executable, depending on `version`.
|
# The full path to the python or python3 executable, depending on `version`.
|
||||||
def binary
|
def binary
|
||||||
@binary ||= begin
|
@binary ||= begin
|
||||||
@ -90,8 +120,15 @@ class PythonInstalled < Requirement
|
|||||||
# Note, we don't support homebrew/versions/pythonXX.rb, though.
|
# Note, we don't support homebrew/versions/pythonXX.rb, though.
|
||||||
Formula.factory(@name).opt_prefix/"bin/python#{@min_version.major}"
|
Formula.factory(@name).opt_prefix/"bin/python#{@min_version.major}"
|
||||||
else
|
else
|
||||||
# This should find at least system python, because /usr/bin is in PATH:
|
begin
|
||||||
which(@name)
|
# Using the ORIGINAL_PATHS here because in superenv, the user
|
||||||
|
# installed external Python is not visible otherwise.
|
||||||
|
tmp_PATH = ENV['PATH']
|
||||||
|
ENV['PATH'] = ORIGINAL_PATHS.join(':')
|
||||||
|
which(@name)
|
||||||
|
ensure
|
||||||
|
ENV['PATH'] = tmp_PATH
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -257,7 +294,7 @@ class PythonInstalled < Requirement
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info[0] == #{version.major} and sys.version_info[1] == #{version.minor}:
|
if sys.version_info[0] == #{version.major} and sys.version_info[1] == #{version.minor}:
|
||||||
if sys.executable.startswith('#{HOMEBREW_PREFIX}'):
|
if sys.executable.startswith('#{HOMEBREW_PREFIX}/opt/python'):
|
||||||
# Fix 1)
|
# Fix 1)
|
||||||
# A setuptools.pth and/or easy-install.pth sitting either in
|
# A setuptools.pth and/or easy-install.pth sitting either in
|
||||||
# /Library/Python/2.7/site-packages or in
|
# /Library/Python/2.7/site-packages or in
|
||||||
@ -285,7 +322,7 @@ class PythonInstalled < Requirement
|
|||||||
from _sysconfigdata import build_time_vars
|
from _sysconfigdata import build_time_vars
|
||||||
build_time_vars['LINKFORSHARED'] = '-u _PyMac_Error #{HOMEBREW_PREFIX}/opt/#{name}/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}/Python'
|
build_time_vars['LINKFORSHARED'] = '-u _PyMac_Error #{HOMEBREW_PREFIX}/opt/#{name}/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}/Python'
|
||||||
except:
|
except:
|
||||||
pass # remember: don't print here. Better to fail silent.
|
pass # remember: don't print here. Better to fail silently.
|
||||||
# Fix 5)
|
# Fix 5)
|
||||||
# For all Pythons of the right major.minor version: Tell about homebrew's
|
# For all Pythons of the right major.minor version: Tell about homebrew's
|
||||||
# site-packages location. This is needed for Python to parse *.pth.
|
# site-packages location. This is needed for Python to parse *.pth.
|
||||||
@ -307,6 +344,10 @@ class PythonInstalled < Requirement
|
|||||||
end
|
end
|
||||||
|
|
||||||
def hash
|
def hash
|
||||||
to_s.hash
|
# Requirements are a ComparableSet. So we define our identity by the
|
||||||
|
# selected python binary plus the @imports in order to support multiple:
|
||||||
|
# depends_on :python => 'module1'
|
||||||
|
# depends_on :python => 'module2'
|
||||||
|
(binary.to_s+@imports.to_s).hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user