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"
|
|
|
|
end
|
|
|
|
if pth_contents.any?
|
|
|
|
(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?
|
|
|
|
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
|
|
|
|
|
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
|