 a8566c9848
			
		
	
	
		a8566c9848
		
	
	
	
	
		
			
			`any?` is not the opposite of `empty?`. Besides the case that `[false, nil].any?` will return false, `any?`(O(n)) has much worse performance than `empty?`(O(1)).
		
			
				
	
	
		
			247 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| require "utils"
 | |
| require "language/python_virtualenv_constants"
 | |
| 
 | |
| module Language
 | |
|   module Python
 | |
|     def self.major_minor_version(python)
 | |
|       version = /\d\.\d/.match `#{python} --version 2>&1`
 | |
|       return unless version
 | |
|       Version.create(version.to_s)
 | |
|     end
 | |
| 
 | |
|     def self.homebrew_site_packages(version = "2.7")
 | |
|       HOMEBREW_PREFIX/"lib/python#{version}/site-packages"
 | |
|     end
 | |
| 
 | |
|     def self.each_python(build, &block)
 | |
|       original_pythonpath = ENV["PYTHONPATH"]
 | |
|       ["python", "python3"].each do |python|
 | |
|         next if build.without? python
 | |
|         version = major_minor_version python
 | |
|         ENV["PYTHONPATH"] = if Formulary.factory(python).installed?
 | |
|           nil
 | |
|         else
 | |
|           homebrew_site_packages(version)
 | |
|         end
 | |
|         block.call python, version if block
 | |
|       end
 | |
|       ENV["PYTHONPATH"] = original_pythonpath
 | |
|     end
 | |
| 
 | |
|     def self.reads_brewed_pth_files?(python)
 | |
|       version = major_minor_version python
 | |
|       return unless homebrew_site_packages(version).directory?
 | |
|       return unless homebrew_site_packages(version).writable_real?
 | |
|       probe_file = homebrew_site_packages(version)/"homebrew-pth-probe.pth"
 | |
|       begin
 | |
|         probe_file.atomic_write("import site; site.homebrew_was_here = True")
 | |
|         quiet_system python, "-c", "import site; assert(site.homebrew_was_here)"
 | |
|       ensure
 | |
|         probe_file.unlink if probe_file.exist?
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def self.user_site_packages(python)
 | |
|       Pathname.new(`#{python} -c "import site; print(site.getusersitepackages())"`.chomp)
 | |
|     end
 | |
| 
 | |
|     def self.in_sys_path?(python, path)
 | |
|       script = <<-EOS.undent
 | |
|         import os, sys
 | |
|         [os.path.realpath(p) for p in sys.path].index(os.path.realpath("#{path}"))
 | |
|       EOS
 | |
|       quiet_system python, "-c", script
 | |
|     end
 | |
| 
 | |
|     # deprecated; use system "python", *setup_install_args(prefix) instead
 | |
|     def self.setup_install(python, prefix, *args)
 | |
|       opoo <<-EOS.undent
 | |
|         Language::Python.setup_install is deprecated.
 | |
|         If you are a formula author, please use
 | |
|           system "python", *Language::Python.setup_install_args(prefix)
 | |
|         instead.
 | |
|       EOS
 | |
| 
 | |
|       # force-import setuptools, which monkey-patches distutils, to make
 | |
|       # sure that we always call a setuptools setup.py. trick borrowed from pip:
 | |
|       # https://github.com/pypa/pip/blob/043af83/pip/req/req_install.py#L743-L780
 | |
|       shim = <<-EOS.undent
 | |
|         import setuptools, tokenize
 | |
|         __file__ = 'setup.py'
 | |
|         exec(compile(getattr(tokenize, 'open', open)(__file__).read()
 | |
|           .replace('\\r\\n', '\\n'), __file__, 'exec'))
 | |
|       EOS
 | |
|       args += %w[--single-version-externally-managed --record=installed.txt]
 | |
|       args << "--prefix=#{prefix}"
 | |
|       system python, "-c", shim, "install", *args
 | |
|     end
 | |
| 
 | |
|     def self.setup_install_args(prefix)
 | |
|       shim = <<-EOS.undent
 | |
|         import setuptools, tokenize
 | |
|         __file__ = 'setup.py'
 | |
|         exec(compile(getattr(tokenize, 'open', open)(__file__).read()
 | |
|           .replace('\\r\\n', '\\n'), __file__, 'exec'))
 | |
|       EOS
 | |
|       %W[
 | |
|         -c
 | |
|         #{shim}
 | |
|         --no-user-cfg
 | |
|         install
 | |
|         --prefix=#{prefix}
 | |
|         --single-version-externally-managed
 | |
|         --record=installed.txt
 | |
|       ]
 | |
|     end
 | |
| 
 | |
|     def self.package_available?(python, module_name)
 | |
|       quiet_system python, "-c", "import #{module_name}"
 | |
|     end
 | |
| 
 | |
|     # Mixin module for {Formula} adding virtualenv support features.
 | |
|     module Virtualenv
 | |
|       def self.included(base)
 | |
|         base.class_eval do
 | |
|           resource "homebrew-virtualenv" do
 | |
|             url PYTHON_VIRTUALENV_URL
 | |
|             sha256 PYTHON_VIRTUALENV_SHA256
 | |
|           end
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Instantiates, creates, and yields a {Virtualenv} object for use from
 | |
|       # Formula#install, which provides helper methods for instantiating and
 | |
|       # installing packages into a Python virtualenv.
 | |
|       # @param venv_root [Pathname, String] the path to the root of the virtualenv
 | |
|       #   (often `libexec/"venv"`)
 | |
|       # @param python [String] which interpreter to use (e.g. "python"
 | |
|       #   or "python3")
 | |
|       # @param formula [Formula] the active Formula
 | |
|       # @return [Virtualenv] a {Virtualenv} instance
 | |
|       def virtualenv_create(venv_root, python = "python", formula = self)
 | |
|         ENV.refurbish_args
 | |
|         venv = Virtualenv.new formula, venv_root, python
 | |
|         venv.create
 | |
| 
 | |
|         # Find any Python bindings provided by recursive dependencies
 | |
|         formula_deps = formula.recursive_dependencies
 | |
|         xy = Language::Python.major_minor_version python
 | |
|         pth_contents = formula_deps.map do |d|
 | |
|           next if d.build?
 | |
|           dep_site_packages = Formula[d.name].opt_lib/"python#{xy}/site-packages"
 | |
|           next unless dep_site_packages.exist?
 | |
|           "import site; site.addsitedir('#{dep_site_packages}')\n"
 | |
|         end.compact
 | |
|         unless pth_contents.empty?
 | |
|           (venv_root/"lib/python#{xy}/site-packages/homebrew_deps.pth").write pth_contents.join
 | |
|         end
 | |
| 
 | |
|         venv
 | |
|       end
 | |
| 
 | |
|       # Helper method for the common case of installing a Python application.
 | |
|       # Creates a virtualenv in `libexec`, installs all `resource`s defined
 | |
|       # on the formula, and then installs the formula.
 | |
|       def virtualenv_install_with_resources
 | |
|         venv = virtualenv_create(libexec)
 | |
|         venv.pip_install resources
 | |
|         venv.pip_install_and_link buildpath
 | |
|         venv
 | |
|       end
 | |
| 
 | |
|       # Convenience wrapper for creating and installing packages into Python
 | |
|       # virtualenvs.
 | |
|       class Virtualenv
 | |
|         # Initializes a Virtualenv instance. This does not create the virtualenv
 | |
|         # on disk; {#create} does that.
 | |
|         # @param formula [Formula] the active Formula
 | |
|         # @param venv_root [Pathname, String] the path to the root of the
 | |
|         #   virtualenv
 | |
|         # @param python [String] which interpreter to use; i.e. "python" or
 | |
|         #   "python3"
 | |
|         def initialize(formula, venv_root, python)
 | |
|           @formula = formula
 | |
|           @venv_root = Pathname.new(venv_root)
 | |
|           @python = python
 | |
|         end
 | |
| 
 | |
|         # Obtains a copy of the virtualenv library and creates a new virtualenv
 | |
|         # on disk.
 | |
|         # @return [void]
 | |
|         def create
 | |
|           return if (@venv_root/"bin/python").exist?
 | |
| 
 | |
|           @formula.resource("homebrew-virtualenv").stage do |stage|
 | |
|             old_pythonpath = ENV.delete "PYTHONPATH"
 | |
|             begin
 | |
|               xy = Language::Python.major_minor_version(@python)
 | |
|               staging = Pathname.new(stage.staging.tmpdir)
 | |
|               ENV.prepend_create_path "PYTHONPATH", staging/"target/lib/python#{xy}/site-packages"
 | |
|               @formula.system @python, *Language::Python.setup_install_args(staging/"target")
 | |
|               @formula.system @python, "-s", staging/"target/bin/virtualenv", "-p", @python, @venv_root
 | |
|             ensure
 | |
|               ENV["PYTHONPATH"] = old_pythonpath
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           # Robustify symlinks to survive python3 patch upgrades
 | |
|           @venv_root.find do |f|
 | |
|             next unless f.symlink?
 | |
|             if (rp = f.realpath.to_s).start_with? HOMEBREW_CELLAR
 | |
|               python = rp.include?("python3") ? "python3" : "python"
 | |
|               new_target = rp.sub %r{#{HOMEBREW_CELLAR}/#{python}/[^/]+}, Formula[python].opt_prefix
 | |
|               f.unlink
 | |
|               f.make_symlink new_target
 | |
|             end
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         # Installs packages represented by `targets` into the virtualenv.
 | |
|         # @param targets [String, Pathname, Resource,
 | |
|         #   Array<String, Pathname, Resource>] (A) token(s) passed to pip
 | |
|         #   representing the object to be installed. This can be a directory
 | |
|         #   containing a setup.py, a {Resource} which will be staged and
 | |
|         #   installed, or a package identifier to be fetched from PyPI.
 | |
|         #   Multiline strings are allowed and treated as though they represent
 | |
|         #   the contents of a `requirements.txt`.
 | |
|         # @return [void]
 | |
|         def pip_install(targets)
 | |
|           targets = [targets] unless targets.is_a? Array
 | |
|           targets.each do |t|
 | |
|             if t.respond_to? :stage
 | |
|               next if t.name == "homebrew-virtualenv"
 | |
|               t.stage { do_install Pathname.pwd }
 | |
|             else
 | |
|               t = t.lines.map(&:strip) if t.respond_to?(:lines) && t =~ /\n/
 | |
|               do_install t
 | |
|             end
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         # Installs packages represented by `targets` into the virtualenv, but
 | |
|         #   unlike {#pip_install} also links new scripts to {Formula#bin}.
 | |
|         # @param (see #pip_install)
 | |
|         # @return (see #pip_install)
 | |
|         def pip_install_and_link(targets)
 | |
|           bin_before = Dir[@venv_root/"bin/*"].to_set
 | |
| 
 | |
|           pip_install(targets)
 | |
| 
 | |
|           bin_after = Dir[@venv_root/"bin/*"].to_set
 | |
|           bin_to_link = (bin_after - bin_before).to_a
 | |
|           @formula.bin.install_symlink(bin_to_link)
 | |
|         end
 | |
| 
 | |
|         private
 | |
| 
 | |
|         def do_install(targets)
 | |
|           targets = [targets] unless targets.is_a? Array
 | |
|           @formula.system @venv_root/"bin/pip", "install",
 | |
|                           "-v", "--no-deps", "--no-binary", ":all:",
 | |
|                           "--ignore-installed", *targets
 | |
|         end
 | |
|       end # class Virtualenv
 | |
|     end # module Virtualenv
 | |
|   end # module Python
 | |
| end # module Language
 |