aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/requirements/python_dependency.rb
diff options
context:
space:
mode:
authorSamuel John2013-01-21 10:33:56 +0100
committerSamuel John2013-06-03 17:29:43 +0200
commit0b50110107ea2998e65011ec31ce45931b446dab (patch)
tree9f28d410bcd3ac3bbd547bc1220919dbc8e5c39d /Library/Homebrew/requirements/python_dependency.rb
parent35c46b417c781864d1b772ed7f8b77504605f4ad (diff)
downloadhomebrew-0b50110107ea2998e65011ec31ce45931b446dab.tar.bz2
Python 2.x and 3.x support
New `depends_on :python` Dependency. New `depends_on :python3` Dependency. To avoid having multiple formulae with endings -py2 and -py3, we will handle support for different pythons (2.x vs. 3.x) in the same formula. Further brewed vs. external python will be transparently supported. The formula also gets a new object `python`, which is false if no Python is available or the user has disabled it. Otherwise it is defined and provides several support methods: python.site_packages # the site-packages in the formula's Cellar python.global_site_packages python.binary # the full path to the python binary python.prefix python.version python.version.major python.version.minor python.xy # => e.g. "python2.7" python.incdir # includes of python python.libdir # the python dylib library python.pkg_config_path # used internally by brew python.from_osx? python.framework? python.universal? python.pypy? python.standard_caveats # Text to set PYTHONPATH for python.from_osx? python.if3then3 # => "" for 2.x and to "3" for 3.x. Further, to avoid code duplication, `python` takes an optional block that is run twice if the formula defines depends_on :python AND :python3. python do system python, 'setup.py', "--prefix=#{prefix}" end Read more in the Homebrew wiki.
Diffstat (limited to 'Library/Homebrew/requirements/python_dependency.rb')
-rw-r--r--Library/Homebrew/requirements/python_dependency.rb296
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