aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Library/Homebrew/language/python.rb135
-rw-r--r--Library/Homebrew/language/python_virtualenv_constants.rb2
2 files changed, 135 insertions, 2 deletions
diff --git a/Library/Homebrew/language/python.rb b/Library/Homebrew/language/python.rb
index 3c84e5362..3abe77ead 100644
--- a/Library/Homebrew/language/python.rb
+++ b/Library/Homebrew/language/python.rb
@@ -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
diff --git a/Library/Homebrew/language/python_virtualenv_constants.rb b/Library/Homebrew/language/python_virtualenv_constants.rb
new file mode 100644
index 000000000..23dbe1f72
--- /dev/null
+++ b/Library/Homebrew/language/python_virtualenv_constants.rb
@@ -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"