diff --git a/Library/Homebrew/dependency_collector.rb b/Library/Homebrew/dependency_collector.rb index 91bcb85a34..50dffaab6f 100644 --- a/Library/Homebrew/dependency_collector.rb +++ b/Library/Homebrew/dependency_collector.rb @@ -18,7 +18,7 @@ require 'set' class DependencyCollector # Define the languages that we can handle as external dependencies. LANGUAGE_MODULES = Set[ - :chicken, :jruby, :lua, :node, :ocaml, :perl, :python, :python2, :python3, :rbx, :ruby + :chicken, :jruby, :lua, :node, :ocaml, :perl, :python, :rbx, :ruby ].freeze attr_reader :deps, :requirements @@ -68,6 +68,9 @@ 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 PythonInstalled.new("2", spec, *tags) if tag == :python LanguageModuleDependency.new(tag, spec) else Dependency.new(spec, tags) @@ -94,9 +97,8 @@ class DependencyCollector when :clt then CLTDependency.new(tags) when :arch then ArchRequirement.new(tags) when :hg then MercurialDependency.new(tags) - when :python then PythonInstalled.new(tags) - when :python2 then PythonInstalled.new("2", tags) - when :python3 then PythonInstalled.new("3", tags) + when :python, :python2 then PythonInstalled.new("2", *tags) + when :python3 then PythonInstalled.new("3", *tags) # Tiger's ld is too old to properly link some software when :ld64 then LD64Dependency.new if MacOS.version < :leopard else diff --git a/Library/Homebrew/python_helper.rb b/Library/Homebrew/python_helper.rb index dcb698c9d0..3db59b334e 100644 --- a/Library/Homebrew/python_helper.rb +++ b/Library/Homebrew/python_helper.rb @@ -29,25 +29,30 @@ def python_helper(options={:allowed_major_versions => [2, 3]}, &block) 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 - python_reqs = python_reqs.select do |p| - p.satisfied? && - options[:allowed_major_versions].include?(p.version.major) && - if p.optional? || p.recommended? - self.build.with?(p.name) - else - true + # 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 = [] + while !python_reqs.empty? + py = python_reqs.shift + # this is ulgy but Ruby 1.8 has no `uniq! { }` + 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 # 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 # 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). - 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 py.site_packages = lib/py.xy/'site-packages' if !block_given? diff --git a/Library/Homebrew/requirements/python_dependency.rb b/Library/Homebrew/requirements/python_dependency.rb index c3753d8139..610bc1c6b9 100644 --- a/Library/Homebrew/requirements/python_dependency.rb +++ b/Library/Homebrew/requirements/python_dependency.rb @@ -31,13 +31,12 @@ class PythonInstalled < Requirement end end - def initialize(*tags) + def initialize(version="2.6", *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) + if /(\d+\.)*\d+/ === version.to_s + @min_version = PythonVersion.new(version) else - @min_version = PythonVersion.new("2.6") # default + raise "Invalid version specification for Python: '#{version}'" end # 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 @name = "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 + elsif tag.kind_of? Hash + @imports.merge!(tag) + end + end + # will be set later by the python_helper, because it needs the # formula prefix to set site_packages @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" @unsatisfied_because += "Your `python` points to a Python 3.x. This is not supported." 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 + 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 @@ -90,8 +120,15 @@ class PythonInstalled < Requirement # Note, we don't support homebrew/versions/pythonXX.rb, though. Formula.factory(@name).opt_prefix/"bin/python#{@min_version.major}" else - # This should find at least system python, because /usr/bin is in PATH: - which(@name) + begin + # 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 @@ -257,7 +294,7 @@ class PythonInstalled < Requirement import sys 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) # A setuptools.pth and/or easy-install.pth sitting either in # /Library/Python/2.7/site-packages or in @@ -285,7 +322,7 @@ class PythonInstalled < Requirement 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. + pass # remember: don't print here. Better to fail silently. # 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. @@ -307,6 +344,10 @@ class PythonInstalled < Requirement end 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