Add helper class for Python virtualenvs

This commit is contained in:
Tim D. Smith 2016-07-22 23:02:52 -07:00
parent 4174bf147c
commit 2783adec4a
2 changed files with 135 additions and 2 deletions

View File

@ -1,4 +1,5 @@
require "utils"
require "language/python_virtualenv_constants"
module Language
module Python
@ -96,5 +97,135 @@ module Language
def self.package_available?(python, module_name)
quiet_system python, "-c", "import #{module_name}"
end
end
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)
venv = Virtualenv.new formula, venv_root, python
venv.create
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.link_scripts(bin) { venv.pip_install 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
# Compares the venv bin directory before and after executing a block,
# and symlinks any new scripts into `destination`.
# Use like: venv.link_scripts(bin) { venv.pip_install my_package }
# @param destination [Pathname, String] Destination into which new
# scripts should be linked.
# @return [void]
def link_scripts(destination)
bin_before = Dir[@venv_root/"bin/*"].to_set
yield
bin_after = Dir[@venv_root/"bin/*"].to_set
destination = Pathname.new(destination)
destination.install_symlink((bin_after - bin_before).to_a)
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:",
*targets
end
end # class Virtualenv
end # module Virtualenv
end # module Python
end # module Language

View File

@ -0,0 +1,2 @@
PYTHON_VIRTUALENV_URL = "https://files.pythonhosted.org/packages/5c/79/5dae7494b9f5ed061cff9a8ab8d6e1f02db352f3facf907d9eb614fb80e9/virtualenv-15.0.2.tar.gz"
PYTHON_VIRTUALENV_SHA256 = "fab40f32d9ad298fba04a260f3073505a16d52539a84843cf8c8369d4fd17167"