Created
April 4, 2011 07:58
-
-
Save vsajip/901266 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright (C) 2011 Vinay Sajip. | |
# | |
# Poor man's virtualize.py. | |
# | |
# Use with a Python executable built from the Python fork at | |
# | |
# https://bitbucket.org/vinay.sajip/pythonv/ as follows: | |
# | |
# python -m virtualize env_dir | |
# | |
# You'll need an Internet connection (needed to download distribute_setup.py). | |
# | |
# The script will change to the environment's binary directory and run | |
# | |
# ./python distribute_setup.py | |
# | |
# after which you can change to the environment's binary directory and do some | |
# easy_installs, e.g. | |
# | |
# ./easy_install setuptools-git | |
# ./easy_install ply | |
# ./easy_install Pygments | |
# ./easy_install Jinja2 | |
# ./easy_install SQLAlchemy | |
# ./easy_install coverage | |
# | |
# Note that on Windows, distributions which include C extensions (e.g. coverage) | |
# may fail due to lack of a suitable C compiler. | |
# | |
import os | |
import os.path | |
import shutil | |
import sys | |
class Context: | |
""" | |
Holds information about a current virtualisation request. | |
""" | |
pass | |
class Virtualizer: | |
""" | |
This class exists to allow virtualisation to be customised. The | |
constructor parameters determine the virtualizer's behaviour when | |
called upon to create a virtual environment. | |
By default, the virtualizer installs Distribute so that you can pip or | |
easy_install other packages into the created environment. | |
By default, the virtualizer makes the system (global) site-packages dir | |
available to the created environment. | |
:param nosite: If True, the system (global) site-packages dir is | |
not available to the environment. | |
:param nodist: If True, Distribute is not installed into the created | |
environment. | |
:param clear: If True and the target directory exists, it is deleted. | |
Otherwise, if the target directory exists, an error is | |
raised. | |
:param progress: If Distribute is installed, the progress of the | |
installation can be monitored by passing a progress | |
callable. If specified, it is called with two | |
arguments: a string indicating some progress, and a | |
context indicating where the string is coming from. | |
The context argument can have one of three values: | |
'main', indicating that it is called from virtualize() | |
itself, and 'stdout' and 'stderr', which are obtained | |
by reading lines from the output streams of a subprocess | |
which is used to install Distribute. | |
If a callable is not specified, default progress | |
information is output to sys.stderr. | |
""" | |
def __init__(self, nosite=False, clear=False, nodist=False, progress=None): | |
self.nosite = nosite | |
self.clear = clear | |
self.nodist = nodist | |
self.progress = progress | |
def reader(self, stream, context): | |
""" | |
Read lines from a subprocess' output stream and either pass to a progress | |
callable (if specified) or write progress information to sys.stderr. | |
""" | |
progress = self.progress | |
while True: | |
s = stream.readline() | |
if not s: | |
break | |
if progress is not None: | |
progress(s, context) | |
else: | |
sys.stderr.write('.') | |
sys.stderr.flush() | |
def virtualize(self, env_dir): | |
""" | |
Create a virtualized environment in a directory. | |
:param env_dir: The target directory to create an environment in. | |
""" | |
context = self.create_directories(env_dir) | |
self.create_configuration(context) | |
self.setup_python(context) | |
if not self.nodist: | |
self.install_distribute(context) | |
def create_directories(self, env_dir): | |
""" | |
Create the directories for the environment. | |
Returns a context object which holds paths in the environment, | |
for use by subsequent logic. | |
""" | |
if os.path.exists(env_dir) and not self.clear: | |
raise ValueError('Directory exists: %s' % env_dir) | |
if os.path.exists(env_dir) and self.clear: | |
shutil.rmtree(env_dir) | |
context = Context() | |
context.env_dir = env_dir | |
os.makedirs(env_dir) | |
dirname, exename = os.path.split(os.path.abspath(sys.executable)) | |
context.python_dir = dirname | |
context.python_exe = exename | |
if sys.platform == 'win32': | |
binname = 'Scripts' | |
incpath = 'Include' | |
libpath = os.path.join(env_dir, 'Lib', 'site-packages') | |
else: | |
binname = 'bin' | |
incpath = 'include' | |
libpath = os.path.join(env_dir, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages') | |
context.inc_path = path = os.path.join(env_dir, incpath) | |
os.mkdir(path) | |
os.makedirs(libpath) | |
context.bin_path = binpath = os.path.join(env_dir, binname) | |
os.mkdir(binpath) | |
return context | |
def create_configuration(self, context): | |
""" | |
Create a configuration file indicating where the environment's Python | |
was copied from, and whether the system site-packages should be made | |
available in the environment. | |
:param context: The information for the virtualisation request being | |
processed. | |
""" | |
context.cfg_path = path = os.path.join(context.env_dir, 'env.cfg') | |
with open(path, 'w', encoding='utf-8') as f: | |
f.write('home = %s\n' % context.python_dir) | |
if self.nosite: | |
f.write('include-system-site = false\n') | |
def setup_python(self, context): | |
""" | |
Set up a Python executable in the environment. | |
:param context: The information for the virtualisation request being | |
processed. | |
""" | |
binpath = context.bin_path | |
exename = context.python_exe | |
path = os.path.join(binpath, exename) | |
shutil.copyfile(sys.executable, path) | |
dirname = context.python_dir | |
if sys.platform == 'win32': | |
files = [f for f in os.listdir(dirname) if f.endswith(('.pyd', '.dll'))] | |
for f in files: | |
src = os.path.join(dirname, f) | |
dst = os.path.join(binpath, f) | |
shutil.copyfile(src, dst) | |
else: | |
os.chmod(path, 0o755) | |
dst = os.path.join(binpath, 'python') | |
os.symlink(exename, dst) | |
def install_distribute(self, context): | |
""" | |
Install Distribute in the environment. | |
:param context: The information for the virtualisation request being | |
processed. | |
""" | |
from subprocess import Popen, PIPE | |
from threading import Thread | |
from urllib.request import urlretrieve | |
url = 'http://python-distribute.org/distribute_setup.py' | |
binpath = context.bin_path | |
exename = context.python_exe | |
distpath = os.path.join(binpath, 'distribute_setup.py') | |
# Download Distribute in the env | |
urlretrieve(url, distpath) | |
progress = self.progress | |
if progress is not None: | |
progress('Installing distribute', 'main') | |
else: | |
sys.stderr.write('Installing distribute ') | |
sys.stderr.flush() | |
# Install Distribute in the env | |
os.chdir(binpath) | |
if sys.platform == 'win32': | |
args = [exename, 'distribute_setup.py'] | |
else: | |
args = [os.path.join('.', exename), 'distribute_setup.py'] | |
p = Popen(args, stdout=PIPE, stderr=PIPE) | |
t1 = Thread(target=self.reader, args=(p.stdout, 'stdout')) | |
t1.start() | |
t2 = Thread(target=self.reader, args=(p.stderr, 'stderr')) | |
t2.start() | |
p.wait() | |
t1.join() | |
t2.join() | |
if progress is not None: | |
progress('done.', 'main') | |
else: | |
sys.stderr.write('done.\n') | |
# Clean up - no longer needed | |
os.unlink(distpath) | |
# To customise, replace this factory with your own | |
virtualizer_factory = Virtualizer | |
def virtualize(env_dir, nosite=False, clear=False, nodist=False, | |
progress=None): | |
""" | |
Create a virtualized environment in a directory. | |
By default, installs Distribute so that you can pip or easy_install | |
other packages into the created environment. | |
By default, makes the system (global) site-packages dir available to | |
the created environment. | |
:param env_dir: The target directory to create an environment in. | |
:param nosite: If True, the system (global) site-packages dir is | |
not available to the environment. | |
:param nodist: If True, Distribute is not installed into the created | |
environment. | |
:param clear: If True and the target directory exists, it is deleted. | |
Otherwise, if the target directory exists, an error is | |
raised. | |
:param progress: If Distribute is installed, the progress of the | |
installation can be monitored by passing a progress | |
callable. If specified, it is called with two | |
arguments: a string indicating some progress, and a | |
context indicating where the string is coming from. | |
The context argument can have one of three values: | |
'main', indicating that it is called from virtualize() | |
itself, and 'stdout' and 'stderr', which are obtained | |
by reading lines from the output streams of a subprocess | |
which is used to install Distribute. | |
If a callable is not specified, default progress | |
information is output to sys.stderr. | |
""" | |
virtualizer = virtualizer_factory(nosite=nosite, | |
clear=clear, | |
nodist=nodist) | |
virtualizer.virtualize(env_dir) | |
def main(): | |
compatible = True | |
if sys.version_info < (3, 3): | |
compatible = False | |
elif not hasattr(sys, 'virtual_prefix'): | |
compatible = False | |
if not compatible: | |
raise ValueError('This script is only for use with ' | |
'Python 3.3 (pythonv variant)') | |
else: | |
import argparse | |
parser = argparse.ArgumentParser(description='Creates virtual Python ' | |
'environments in one or ' | |
'more target ' | |
'directories.') | |
parser.add_argument('dirs', metavar='ENV_DIR', nargs='+', | |
help='A directory to create the environment in.') | |
parser.add_argument('--no-distribute', default=False, | |
action='store_true', dest='nodist', | |
help="Don't install Distribute in the virtual " | |
"environment.") | |
parser.add_argument('--no-site-packages', default=False, | |
action='store_true', dest='nosite', | |
help="Don't give access to the global " | |
"site-packages dir to the virtual " | |
"environment.") | |
parser.add_argument('--clear', default=False, action='store_true', | |
dest='clear', help='Delete the environment ' | |
'directory if it already ' | |
'exists. If not specified and ' | |
'the directory exists, an error' | |
' is raised.') | |
options = parser.parse_args() | |
virtualizer = virtualizer_factory(nosite=options.nosite, | |
clear=options.clear, | |
nodist=options.nodist) | |
for d in options.dirs: | |
virtualizer.virtualize(d) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment