| 
									
										
										
										
											2016-07-09 11:58:43 +02:00
										 |  |  | require "utils" | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  | require "language/python_virtualenv_constants" | 
					
						
							| 
									
										
										
										
											2014-03-07 18:03:54 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | module Language | 
					
						
							|  |  |  |   module Python | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.major_minor_version(python) | 
					
						
							| 
									
										
										
										
											2014-03-07 18:03:54 +00:00
										 |  |  |       version = /\d\.\d/.match `#{python} --version 2>&1` | 
					
						
							|  |  |  |       return unless version | 
					
						
							| 
									
										
										
										
											2016-07-11 16:09:35 +03:00
										 |  |  |       Version.create(version.to_s) | 
					
						
							| 
									
										
										
										
											2014-03-07 18:03:54 +00:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.homebrew_site_packages(version = "2.7") | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |       HOMEBREW_PREFIX/"lib/python#{version}/site-packages" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.each_python(build, &block) | 
					
						
							| 
									
										
										
										
											2014-03-07 18:03:54 +00:00
										 |  |  |       original_pythonpath = ENV["PYTHONPATH"] | 
					
						
							|  |  |  |       ["python", "python3"].each do |python| | 
					
						
							|  |  |  |         next if build.without? python | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |         version = major_minor_version python | 
					
						
							| 
									
										
										
										
											2014-06-22 15:00:15 -05:00
										 |  |  |         ENV["PYTHONPATH"] = if Formulary.factory(python).installed? | 
					
						
							| 
									
										
										
										
											2014-03-07 18:03:54 +00:00
										 |  |  |           nil | 
					
						
							|  |  |  |         else | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |           homebrew_site_packages(version) | 
					
						
							| 
									
										
										
										
											2014-03-07 18:03:54 +00:00
										 |  |  |         end | 
					
						
							|  |  |  |         block.call python, version if block | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |       ENV["PYTHONPATH"] = original_pythonpath | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.reads_brewed_pth_files?(python) | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |       version = major_minor_version python | 
					
						
							|  |  |  |       return unless homebrew_site_packages(version).directory? | 
					
						
							| 
									
										
										
										
											2014-11-05 19:37:24 -08:00
										 |  |  |       return unless homebrew_site_packages(version).writable_real? | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |       probe_file = homebrew_site_packages(version)/"homebrew-pth-probe.pth" | 
					
						
							| 
									
										
										
										
											2014-11-05 19:37:24 -08:00
										 |  |  |       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 | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.user_site_packages(python) | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |       Pathname.new(`#{python} -c "import site; print(site.getusersitepackages())"`.chomp) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.in_sys_path?(python, path) | 
					
						
							| 
									
										
										
										
											2014-10-19 13:47:55 -07:00
										 |  |  |       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 | 
					
						
							| 
									
										
										
										
											2014-11-06 22:25:11 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-09 23:17:11 -08:00
										 |  |  |     # deprecated; use system "python", *setup_install_args(prefix) instead | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.setup_install(python, prefix, *args) | 
					
						
							| 
									
										
										
										
											2014-12-10 09:10:44 -08:00
										 |  |  |       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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-11-06 22:25:11 -08:00
										 |  |  |       # 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 | 
					
						
							| 
									
										
										
										
											2014-12-09 23:17:11 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.setup_install_args(prefix) | 
					
						
							| 
									
										
										
										
											2014-12-09 23:17:11 -08:00
										 |  |  |       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} | 
					
						
							| 
									
										
										
										
											2015-01-08 16:43:40 -08:00
										 |  |  |         --no-user-cfg | 
					
						
							| 
									
										
										
										
											2014-12-09 23:17:11 -08:00
										 |  |  |         install | 
					
						
							|  |  |  |         --prefix=#{prefix} | 
					
						
							|  |  |  |         --single-version-externally-managed | 
					
						
							|  |  |  |         --record=installed.txt | 
					
						
							|  |  |  |       ] | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2015-03-02 21:44:35 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     def self.package_available?(python, module_name) | 
					
						
							| 
									
										
										
										
											2015-03-02 21:44:35 -08:00
										 |  |  |       quiet_system python, "-c", "import #{module_name}" | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # 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) | 
					
						
							| 
									
										
										
										
											2016-07-29 08:45:26 -07:00
										 |  |  |         ENV.refurbish_args | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |         venv = Virtualenv.new formula, venv_root, python | 
					
						
							|  |  |  |         venv.create | 
					
						
							| 
									
										
										
										
											2016-07-31 11:59:30 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # 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" | 
					
						
							| 
									
										
										
										
											2016-08-05 22:01:32 +08:00
										 |  |  |         end.compact | 
					
						
							|  |  |  |         unless pth_contents.empty? | 
					
						
							| 
									
										
										
										
											2016-07-31 11:59:30 -07:00
										 |  |  |           (venv_root/"lib/python#{xy}/site-packages/homebrew_deps.pth").write pth_contents.join | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2016-08-02 22:37:15 +02:00
										 |  |  |         venv.pip_install_and_link buildpath | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |         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? | 
					
						
							| 
									
										
										
										
											2016-09-10 10:38:35 +01:00
										 |  |  |             next unless (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 | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |           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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-02 22:37:15 +02:00
										 |  |  |         # 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) | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |           bin_before = Dir[@venv_root/"bin/*"].to_set | 
					
						
							| 
									
										
										
										
											2016-08-02 22:37:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           pip_install(targets) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |           bin_after = Dir[@venv_root/"bin/*"].to_set | 
					
						
							| 
									
										
										
										
											2016-08-02 22:37:15 +02:00
										 |  |  |           bin_to_link = (bin_after - bin_before).to_a | 
					
						
							|  |  |  |           @formula.bin.install_symlink(bin_to_link) | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def do_install(targets) | 
					
						
							|  |  |  |           targets = [targets] unless targets.is_a? Array | 
					
						
							|  |  |  |           @formula.system @venv_root/"bin/pip", "install", | 
					
						
							| 
									
										
										
										
											2016-07-31 11:59:30 -07:00
										 |  |  |                           "-v", "--no-deps", "--no-binary", ":all:", | 
					
						
							|  |  |  |                           "--ignore-installed", *targets | 
					
						
							| 
									
										
										
										
											2016-07-22 23:02:52 -07:00
										 |  |  |         end | 
					
						
							|  |  |  |       end # class Virtualenv | 
					
						
							|  |  |  |     end # module Virtualenv | 
					
						
							|  |  |  |   end # module Python | 
					
						
							|  |  |  | end # module Language |