diff --git a/Library/Homebrew/dependency_collector.rb b/Library/Homebrew/dependency_collector.rb index b3b1f52e1e..0e3feac8e0 100644 --- a/Library/Homebrew/dependency_collector.rb +++ b/Library/Homebrew/dependency_collector.rb @@ -74,9 +74,6 @@ class DependencyCollector if tags.empty? Dependency.new(spec, tags) 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 PythonDependency.new("2", Array(spec)) if tag == :python LanguageModuleDependency.new(tag, spec) else Dependency.new(spec, tags) @@ -105,8 +102,9 @@ class DependencyCollector when :clt then CLTDependency.new(tags) when :arch then ArchRequirement.new(tags) when :hg then MercurialDependency.new(tags) - when :python, :python2 then PythonDependency.new("2", tags) - when :python3 then PythonDependency.new("3", tags) + # python2 is deprecated + when :python, :python2 then PythonDependency.new(tags) + when :python3 then Python3Dependency.new(tags) # Tiger's ld is too old to properly link some software when :ld64 then LD64Dependency.new if MacOS.version < :leopard when :ant then ant_dep(spec, tags) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index a35b286339..a2b9c8fdb9 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -340,24 +340,14 @@ class Formula ] end - # Install python bindings inside of a block given to this method and/or - # call python so: `system python, "setup.py", "install", "--prefix=#{prefix}" - # Note that there are no quotation marks around python! - # - def python(options={:allowed_major_versions => [2, 3]}, &block) - require 'python_helper' - python_helper(options, &block) - end - - # Explicitly only execute the block for 2.x (if a python 2.x is available) - def python2 &block - python(:allowed_major_versions => [2], &block) - end - - # Explicitly only execute the block for 3.x (if a python 3.x is available) - def python3 &block - python(:allowed_major_versions => [3], &block) + # Deprecated + def python(options={}, &block) + opoo 'Formula#python is deprecated and will go away shortly.' + block.call if block_given? + PythonDependency.new end + alias_method :python2, :python + alias_method :python3, :python # Generates a formula's ruby class name from a formula's name def self.class_s name diff --git a/Library/Homebrew/python_helper.rb b/Library/Homebrew/python_helper.rb deleted file mode 100644 index a8389cee7e..0000000000 --- a/Library/Homebrew/python_helper.rb +++ /dev/null @@ -1,90 +0,0 @@ -# This helper method is used in the Formula class when the user calls -# `python`, `python2` or `python3`. - -# This method has a dual nature. For one, it takes a &block and sets up -# the ENV such that a Python, as defined in the requirements, is the default. -# If there are multiple `PythonDependency` requirements, the block is evaluated -# once for each Python. This makes it possible to easily support 2.x and -# 3.x Python bindings without code duplication in formulae. -# If you need to special case stuff, set :allowed_major_versions. -# Second, inside the block, a formula author may call this method to access -# certain convienience methods for the currently selected Python, e.g. -# `python.site_packages`. -# This method should be executed in the context of the formula, so that -# prefix is defined. Note, that this method will set @current_python to be -# able to refer to the current python if a block is executed for 2.x and 3.x. -def python_helper(options={:allowed_major_versions => [2, 3]}, &block) - if !block_given? and !@current_python.nil? - # We are already inside of a `python do ... end` block, so just return - # the current_python or nil if the version.major is not allowed. - if options[:allowed_major_versions].include?(@current_python.version.major) - return @current_python - else - return nil - end - end - - # Look for PythonDependency requirements for this formula: - python_reqs = requirements.select{ |r| r.kind_of?(PythonDependency) } - if python_reqs.empty? - raise "If you use python in the formula, you have to add `depends_on :python` (or :python3)!" - end - # Now select those that are satisfied and matching the version.major and - # check that no two python binaries are the same (which could be the case - # because more than one `depends_on :python => 'module_name' may be present). - filtered_python_reqs = [] - python_reqs.each do |py| - next if filtered_python_reqs.any? { |req| req.binary == py.binary } - next unless py.satisfied? - next unless options[:allowed_major_versions].include?(py.version.major) - next if (py.optional? || py.recommended?) && build.without?(py.name) - filtered_python_reqs << py - end - - # Allow to use an else-branch like so: `if python do ... end; else ... end`. - return nil if filtered_python_reqs.empty? - - # 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. - # Further note, having 3.x last allows us to run `2to3 --write .` - # which modifies the sources in-place (for some packages that need this). - filtered_python_reqs.sort_by{ |py| py.version }.map do |py| - # Now is the time to `site_packages` to the correct value in the Cellar. - py.site_packages = lib/py.xy/'site-packages' - py.private_site_packages = libexec/"lib"/py.xy/"site-packages" - return py if !block_given? - - puts "brew: Python block (#{py.binary})..." if ARGV.verbose? && ARGV.debug? - # Ensure env changes are only temporary: - old_env = ENV.to_hash - begin - # In order to install into the `Cellar`, the dir must exist and be in the - # `PYTHONPATH`. This will be executed in the context of the formula and - # lib points to the `HOMEBREW_PREFIX/Cellar///lib`. - mkdir_p py.site_packages - ENV.append_path 'PYTHONPATH', py.site_packages - # Allow to --prefix=libexec if a private site-packages is needed. - mkdir_p py.private_site_packages - ENV.append_path 'PYTHONPATH', py.private_site_packages - puts "brew: PYTHONPATH=#{ENV['PYTHONPATH']}" if ARGV.verbose? - ENV['PYTHON'] = py.binary - ENV.prepend_path 'CMAKE_INCLUDE_PATH', py.incdir - ENV.prepend_path 'PKG_CONFIG_PATH', py.pkg_config_path if py.pkg_config_path - ENV.prepend_path 'PATH', py.binary.dirname unless py.from_osx? - #Note: Don't set LDFLAGS to point to the Python.framework, because - # it breaks builds (for example scipy.) - - # Track the state of the currently selected python for this block, - # so if this python_helper is called again _inside_ the block, - # we can just return the right python. - @current_python = py - res = instance_eval(&block) - @current_python = nil - res - ensure - # Clean up if the private_site_packages has not been used. - py.private_site_packages.rmdir if py.private_site_packages.children.empty? - ENV.replace(old_env) - end - end -end diff --git a/Library/Homebrew/requirements/python_dependency.rb b/Library/Homebrew/requirements/python_dependency.rb index 0b9fa8bad3..aee42d9f66 100644 --- a/Library/Homebrew/requirements/python_dependency.rb +++ b/Library/Homebrew/requirements/python_dependency.rb @@ -1,365 +1,42 @@ 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, -# `PythonDependency("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 PythonDependency < Requirement - attr_reader :min_version - attr_reader :if3then3 - attr_reader :imports - attr_reader :python - attr_accessor :site_packages - attr_accessor :private_site_packages - attr_writer :binary # The python.rb formula needs to set the binary + fatal true - fatal true # you can still make Python optional by `depends_on :python => :optional` - - class PythonVersion < Version - def major - tokens[0].to_s.to_i # Python's major.minor are always ints. - end - def minor - tokens[1].to_s.to_i - end - end - - def initialize(default_version="2.6", tags=[]) - # Extract the min_version if given. Default to default_version else - if /(\d+\.)*\d+/ === tags.first.to_s - @min_version = PythonVersion.new(tags.shift) - else - @min_version = PythonVersion.new(default_version) - end - - # often used idiom: e.g. sipdir = "share/sip#{python.if3then3}" - if @min_version.major == 3 - @if3then3 = "3" - else - @if3then3 = "" - end - - @python = "python"+@if3then3 - - # Check if any python modules should be importable. We use a hash to store - # the corresponding name on PyPi "" => "". - # Example: `depends_on :python => ['enchant' => 'pyenchant'] - @imports = {} - tags.each do |tag| - if tag.kind_of? String - @imports[tag] = tag # if the module name is the same as the PyPi name - elsif tag.kind_of? Hash - @imports.merge!(tag) - end - end - - # Set name according to the major version and optionally python modules: - # Used to generate the options like --without-python3, --with-python-numpy - @name = "python#{@if3then3}" - @name += "-#{@imports.values*'-'}" unless @imports.empty? - - # will be set later by the python_helper, because it needs the - # formula prefix to set site_packages - @site_packages = nil - @private_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 - begin - # Unset the PYTHONPATH during these tests, but later ensure it is restored. - pythonpath = ENV['PYTHONPATH'] - ENV['PYTHONPATH'] = nil - @unsatisfied_because = '' - if binary.nil? || !binary.executable? - @unsatisfied_because += "No `#{@python}` found in your PATH. To install with Homebrew use `brew install #{@python}`." - false - elsif pypy? - @unsatisfied_because += "Your #{@python} 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 in your PATH! --> `brew install #{@python}`?" - 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[0])"`.strip == "3" - @unsatisfied_because += "Your `python` points to Python 3.x; this is not supported." - false - else - @imports.keys.all? do |module_name| - if importable? module_name - true - else - @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 " - @unsatisfied_because += "sudo easy_install pip\n " unless importable? 'pip' - @unsatisfied_because += "pip-#{version.major}.#{version.minor} install #{@imports[module_name]}" - false - end - end - end - ensure - ENV['PYTHONPATH'] = pythonpath - 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`. - def binary - @binary ||= begin - if brewed? - # If the python is brewed we always prefer it! - # Note, we don't support homebrew/versions/pythonXX.rb, though. - Formula.factory(@python).opt_prefix/"bin/python#{@min_version.major}" - else - which(@python) - end - 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/#{python}/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}" - elsif from_osx? and MacOS.version < :mavericks - # Python on OS X before Mavericks 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 (just `site_packages`) are - # populated by the python_helperg 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? - if @min_version.major == 3 - prefix/"lib/#{xy}/config-#{version.major}.#{version.minor}m" - else - prefix/"lib/#{xy}/config" - end - 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 brewed Python installed? - def brewed? - @brewed ||= begin - require 'formula' - Formula.factory(@python).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 - - def framework - # We return the path to Frameworks and not the 'Python.framework', because - # the latter is (sadly) the same for 2.x and 3.x. - if prefix.to_s =~ /^(.*\/Frameworks)\/(Python\.framework).*$/ - @framework = $1 - end - end - def framework?; not framework.nil? 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 #{@python} (#{@min_version.major}.x), you need to amend your PYTHONPATH like so: - export PYTHONPATH=#{global_site_packages}:$PYTHONPATH - EOS - end + which python_binary end def modify_build_environment - # Most methods fail if we don't have a binary. - return if binary.nil? - - ENV['PYTHONHOME'] = nil # to avoid fuck-ups. - ENV['PYTHONPATH'] = nil # first unset, because global_site_packages may fail - ENV['PYTHONPATH'] = if brewed? then nil; else global_site_packages.to_s; end - - # For non-system python's we add the opt_prefix/bin of python to the path. - ENV.prepend_path 'PATH', binary.dirname unless from_osx? - ENV.append_path 'CMAKE_INCLUDE_PATH', incdir - ENV.append_path 'PKG_CONFIG_PATH', pkg_config_path if pkg_config_path - # We don't set the -F#{framework} here, because if Python 2.x and 3.x are - # used, `Python.framework` is ambiguous. However, in the `python do` block - # we can set LDFLAGS+="-F#{framework}" because only one is temporarily set. - - # Write our sitecustomize.py - file = global_site_packages/"sitecustomize.py" - ohai "Writing #{file}" if ARGV.verbose? && ARGV.debug? - - %w{.pyc .pyo .py}.each do |ext| - f = global_site_packages/"sitecustomize#{ext}" - f.unlink if f.exist? - end - - file.write(sitecustomize) - - # 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(@python).opt_prefix/"Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}/lib/#{xy}/distutils/distutils.cfg" - ohai "Writing #{file}" if ARGV.verbose? && ARGV.debug? - 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 python command line scripts may fail! - # - import os - import sys - - if sys.version_info[0] != #{version.major}: - # This can only happen if the user has set the PYTHONPATH for 3.x and run Python 2.x or vice versa. - # Every Python looks at the PYTHONPATH variable and we can't fix it here in sitecustomize.py, - # because the PYTHONPATH is evaluated after the sitecustomize.py. Many modules (e.g. PyQt4) are - # built only for a specific version of Python and will fail with cryptic error messages. - # In the end this means: Don't set the PYTHONPATH permanently if you use different Python versions. - exit('Your PYTHONPATH points to a site-packages dir for Python #{version.major}.x but you are running Python ' + - str(sys.version_info[0]) + '.x!\\n PYTHONPATH is currently: "' + str(os.environ['PYTHONPATH']) + '"\\n' + - ' You should `unset PYTHONPATH` to fix this.') - else: - # Only do this for a brewed python: - opt_executable = '#{HOMEBREW_PREFIX}/opt/#{python}/bin/#{xy}' - if os.path.realpath(sys.executable) == os.path.realpath(opt_executable): - # Remove /System site-packages, and the Cellar site-packages - # which we moved to lib/pythonX.Y/site-packages. Further, remove - # HOMEBREW_PREFIX/lib/python because we later addsitedir(...). - sys.path = [ p for p in sys.path - if (not p.startswith('/System') and - not p.startswith('#{HOMEBREW_PREFIX}/lib/python') and - not (p.startswith('#{HOMEBREW_PREFIX}/Cellar/python') and p.endswith('site-packages'))) ] - - # LINKFORSHARED (and python-config --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/#{python}/Frameworks/Python.framework/Versions/#{version.major}.#{version.minor}/Python' - except: - pass # remember: don't print here. Better to fail silently. - - # Set the sys.executable to use the opt_prefix - sys.executable = opt_executable - - # Tell about homebrew's site-packages location. - # This is needed for Python to parse *.pth. - import site - site.addsitedir('#{HOMEBREW_PREFIX}/lib/#{xy}/site-packages') - EOF - end - - def message - @unsatisfied_because - end - - def <=> other - version <=> other.version + ENV['PYTHONPATH'] = "#{HOMEBREW_PREFIX}/lib/python2.7/site-packages" end + # Deprecated def to_s - binary.to_s + python_binary end - # Objects of this class are used to represent dependencies on Python and - # dependencies on Python modules. Both are already included in `name` - def eql?(other) - instance_of?(other.class) && name == other.name + protected + + def python_binary + 'python' end - def hash - name.hash + def system_python? + which(python_binary).to_s == "/usr/bin/python" + end +end + +class Python3Dependency < PythonDependency + default_formula 'python3' + + protected + + def python_binary + 'python3' + end + + def system_python? + false end end