diff options
Diffstat (limited to 'Library/Homebrew/requirements/python_dependency.rb')
| -rw-r--r-- | Library/Homebrew/requirements/python_dependency.rb | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/Library/Homebrew/requirements/python_dependency.rb b/Library/Homebrew/requirements/python_dependency.rb new file mode 100644 index 000000000..194d434c7 --- /dev/null +++ b/Library/Homebrew/requirements/python_dependency.rb @@ -0,0 +1,296 @@ +require 'requirement' + +# We support Python 2.x and 3.x, either brewed or external. +# This requirement locates the correct CPython binary (no PyPy), provides +# support methods like `site_packages`, and writes our sitecustomize.py file. +# In `dependency_collector.rb`, special `:python` and `:python3` shortcuts are +# defined. You can specify a minimum version of the Python that needs to be +# present, but since not every package is ported to 3.x yet, +# `PythonInstalled("2")` is not satisfied by 3.x. +# In a formula that shall provide support for 2.x and 3.x, the idiom is: +# depends_on :python +# depends_on :python3 => :optional # or :recommended +# +# Todo: +# - Allow further options that choose: universal, framework?, brewed?... +class PythonInstalled < Requirement + attr_reader :min_version + attr_reader :if3then3 + attr_reader :site_packages + attr_accessor :site_packages + + fatal true # you can still make Python optional by `depends_on :python => :optional` + + class PythonVersion < Version + def major + to_a[0].to_s.to_i # Python's major.minor are always ints. + end + def minor + to_a[1].to_s.to_i + end + end + + def initialize(*tags) + # Extract the min_version if given. Default to python 2.X else + tags.flatten! + if /(\d+\.)*\d+/ === tags.first + @min_version = PythonVersion.new(tags.shift) + else + @min_version = PythonVersion.new("2.7") # default + end + + # often used idiom: e.g. sipdir = "share/sip" + python.if3then3 + if @min_version.major == 3 + @if3then3 = "3" + else + @if3then3 = "" + end + + # Set name according to the major version. + # The name is used to generate the options like --without-python3 + @name = "python" + @if3then3 + + # will be set later by the python_helper, because it needs the + # formula prefix to set site_packages + @site_packages = nil + + super tags + end + + # Note that during `satisfy` we still have the PATH as the user has set. + # We look for a brewed python or an external Python and store the loc of + # that binary for later usage. (See Formula#python) + satisfy :build_env => false do + @unsatisfied_because = "This formula needs #{@name}.\n" + if binary.nil? + @unsatisfied_because += "But no `#{@name}` found in your PATH! Consider to `brew install #{@name}`." + false + elsif pypy? + @unsatisfied_because += "Your #{@name} executable appears to be a PyPy, which is not supported." + false + elsif version.major != @min_version.major + @unsatisfied_because += "No Python #{@min_version.major}.x found!" + false + elsif version < @min_version + @unsatisfied_because += "Python version #{version} is too old (need at least #{@min_version})." + false + elsif @min_version.major == 2 && `python -c "import sys; print(sys.version_info.major)"`.strip == "3" + @unsatisfied_because += "Your `python` points to a Python 3.x. This is not supported." + false + else + true + end + end + + # The full path to the python or python3 executable, depending on `version`. + def binary + if brewed? + # If the python is brewed we always prefer it! + # Note, we don't support homebrew/versions/pythonXX.rb, though. + Formula.factory(@name).opt_prefix/"bin/python#{@min_version.major}" + else + p = which(@name) + raise "PythonInstalled: #{p} is not executable" if !p.nil? && !p.executable? + p + end + end + + # The python prefix (special cased for a brewed python to point into the opt_prefix) + def prefix + if brewed? + # Homebrew since a long while only supports frameworked python + HOMEBREW_PREFIX/"opt/#{name}/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}" + elsif from_osx? + # Python on OS X has been stripped off its includes (unless you install the CLT), therefore we use the MacOS.sdk. + Pathname.new("#{MacOS.sdk_path}/System/Library/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}") + else + # What Python knows about itself + Pathname.new(`#{binary} -c 'import sys;print(sys.prefix)'`.strip) + end + end + + # Get the actual x.y.z version by asking python (or python3 if @min_version>=3) + def version + @version ||= PythonVersion.new(`#{binary} -c 'import sys;print(sys.version[:5])'`.strip) + end + + # python.xy => "python2.7" is often used (and many formulae had this as `which_python`). + def xy + "python#{version.major}.#{version.minor}" + end + + # Homebrew's global site-packages. The local ones are populated by the + # python_helper method when the `prefix` of a formula is known. + def global_site_packages + HOMEBREW_PREFIX/"lib/#{xy}/site-packages" + end + + # Dir containing Python.h and others. + def incdir + if (from_osx? || brewed?) && framework? + prefix/"Headers" + else + # For all other we use Python's own standard method (works with a non-framework version, too) + Pathname.new(`#{binary} -c 'from distutils import sysconfig; print(sysconfig.get_python_inc())'`.strip) + end + end + + # Dir containing e.g. libpython2.7.dylib + def libdir + if brewed? || from_osx? + prefix/"lib/#{xy}/config" + else + Pathname.new(`#{binary} -c "from distutils import sysconfig; print(sysconfig.get_config_var('LIBPL'))"`.strip) + end + end + + # Pkgconfig (pc) files of python + def pkg_config_path + if from_osx? + # No matter if CLT-only or Xcode-only, the pc file is always here on OS X: + path = Pathname.new("/System/Library/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}/lib/pkgconfig") + path if path.exist? + else + prefix/"lib/pkgconfig" + end + end + + # Is the Python brewed (and linked)? + def brewed? + @brewed ||= begin + require 'formula' + f = Formula.factory(@name) + f.installed? && f.linked_keg.exist? + end + end + + # Is the python the one from OS X? + def from_osx? + @from_osx ||= begin + p = `#{binary} -c "import sys; print(sys.prefix)"`.strip + p.start_with?("/System/Library/Frameworks/Python.framework") + end + end + + # Is the `python` a PyPy? + def pypy? + @pypy ||= !(`#{binary} -c "import sys; print(sys.version)"`.downcase =~ /.*pypy.*/).nil? + end + + # Is this python a framework-style install (OS X only)? + def framework? + @framework ||= /Python[0-9]*\.framework/ === prefix.to_s + end + + def universal? + @universal ||= archs_for_command(binary).universal? + end + + def standard_caveats + if brewed? + "" # empty string, so we can concat this + else + <<-EOS.undent + For non-homebrew #{@name} (#{@min_version.major}.x), you need to amend your PYTHONPATH like so: + export PYTHONPATH=#{global_site_packages}:$PYTHONPATH + EOS + end + end + + def modify_build_environment + # Write our sitecustomize.py + file = global_site_packages/"sitecustomize.py" + ohai "Writing #{file}" if ARGV.verbose? || ARGV.homebrew_developer? + [".pyc", ".pyo", ".py"].map{ |f| + global_site_packages/"sitecustomize#{f}" + }.each{ |f| f.delete if f.exist? } + file.write(sitecustomize) + + # For non-system python's we add the opt_prefix/bin of python to the path. + ENV.prepend 'PATH', binary.dirname, ':' unless from_osx? + + ENV['PYTHONHOME'] = nil # to avoid fuck-ups. + ENV['PYTHONNOUSERSITE'] = '1' + # Python respects the ARCHFLAGS var if set. Shall we set them here? + # ENV['ARCHFLAGS'] = ??? # FIXME + ENV.append 'CMAKE_INCLUDE_PATH', incdir, ':' + ENV.append 'PKG_CONFIG_PATH', pkg_config_path, ':' if pkg_config_path + + # Udpate distutils.cfg (later we can remove this, but people still have + # their old brewed pythons and we have to update it here) + # Todo: If Jack's formula revisions arrive, we can get rid of this here! + if brewed? + require 'formula' + file = Formula.factory(@name).prefix/"Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}/lib/#{xy}/distutils/distutils.cfg" + ohai "Writing #{file}" if ARGV.verbose? || ARGV.homebrew_developer? + file.delete if file.exist? + file.write <<-EOF.undent + [global] + verbose=1 + [install] + force=1 + prefix=#{HOMEBREW_PREFIX} + EOF + end + end + + def sitecustomize + <<-EOF.undent + # This file is created by Homebrew and is executed on each python startup. + # Don't print from here, or else universe will collapse. + import sys + + if sys.version_info.major == #{version.major} and sys.version_info.minor == #{version.minor}: + if sys.executable.startswith('#{HOMEBREW_PREFIX}'): + # Fix 1) + # 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 can inject the + # /System's Python site-packages. People then report + # "OSError: [Errno 13] Permission denied" because pip/easy_install + # attempts to install into + # /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python + # See: https://github.com/mxcl/homebrew/issues/14712 + # Fix 2) + # Remove brewed Python's hard-coded Cellar-site-packages + sys.path = [ p for p in sys.path + if not (p.startswith('/System') or + p.startswith('#{HOMEBREW_PREFIX}/Cellar/python') and p.endswith('site-packages')) ] + # Fix 3) + # Set the sys.executable to use the opt_prefix + sys.executable = '#{HOMEBREW_PREFIX}/opt/#{name}/bin/python#{version.major}.#{version.minor}' + # Fix 4) + # Make LINKFORSHARED (and python-confing --ldflags) return the + # full path to the lib (yes, "Python" is actually the lib, not a + # dir) so that third-party software does not need to add the + # -F/#{HOMEBREW_PREFIX}/Frameworks switch. + # Assume Framework style build (default since months in brew) + try: + 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' + except: + pass # remember: don't print here. Better to fail silent. + # Fix 5) + # For all Pythons of the right major.minor version: Tell about homebrew's + # site-packages location. This is needed for Python to parse *.pth. + import site + site.addsitedir('#{global_site_packages}') + EOF + end + + def message + @unsatisfied_because + end + + def <=> other + version <=> other.version + end + + def to_s + binary.to_s + end + + def hash + to_s.hash + end +end |
