# HG changeset patch # User Jeff Hammel # Date 1310134844 25200 # Node ID 1c7da6388dd9dd877415f119eb227440c24083ee initial commit of carton diff -r 000000000000 -r 1c7da6388dd9 carton.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/carton.py Fri Jul 08 07:20:44 2011 -0700 @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +""" +make a self-extracting virtualenv from directories or URLs +of packages + +To package up all files in a virtualenvs source directory (e.g.): + +python path/to/carton.py mozmill mozmill/src/* + +This will create a self-extracting file, `mozmill.py`, that will unfold +a virtualenv +""" + +# imports +import os +import sys +import tarfile +import tempfile +import urllib2 +from optparse import OptionParser +from StringIO import StringIO + +# global variables +usage = "%prog [options] environment_name directory|url [...]" +virtualenv_url = 'http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.6.1.tar.gz' +template = """#!/usr/bin/env python + +import os +import shutil +import subprocess +import sys +import tarfile +import tempfile +from optparse import OptionParser +from StringIO import StringIO + +try: + call = subprocess.check_call +except AttributeError: + # old python; boo :( + call = subprocess.call + +# virtualenv name +ENV='''%(ENV)s''' + +# packed files +VIRTUAL_ENV='''%(VIRTUAL_ENV)s'''.decode('base64') +PACKAGE_SOURCES='''%(PACKAGE_SOURCES)s'''.decode('base64') + +# unpack virtualenv +tempdir = tempfile.mkdtemp() +buffer = StringIO() +buffer.write(VIRTUAL_ENV) +buffer.seek(0) +tf = tarfile.open(mode='r', fileobj=buffer) +tf.extractall(tempdir) + +# find the virtualenv +for root, dirs, files in os.walk(tempdir): + if 'virtualenv.py' in files: + virtualenv = os.path.join(root, 'virtualenv.py') + break +else: + raise Exception("virtualenv.py not found in " + tempdir) +print virtualenv + +# create the virtualenv +call([sys.executable, virtualenv, ENV]) + +# unpack the sources +srcdir = os.path.join(ENV, 'src') +os.mkdir(srcdir) +buffer = StringIO() +buffer.write(PACKAGE_SOURCES) +buffer.seek(0) +tf = tarfile.open(mode='r', fileobj=buffer) +tf.extractall(srcdir) + +# find the bin/scripts directory +for i in ('bin', 'Scripts'): + scripts_dir = os.path.abspath(os.path.join(ENV, i)) + if os.path.exists(scripts_dir): + break +else: + raise Exception("Scripts directory not found in " + ENV) + +# find the virtualenv's python +for i in ('python', 'python.exe'): + python = os.path.join(scripts_dir, i) + if os.path.exists(python): + break +else: + raise Exception("python not found in " + scripts_dir) + +# activate the virtualenv +#activate = os.path.join(scripts_dir, 'activate_this.py') +#assert os.path.exists(activate), activate + " does not exist" +#execfile(activate, dict(__file__=activate)) + +# setup the sources for development +for i in os.listdir(srcdir): + subdir = os.path.join(srcdir, i) + if os.path.exists(os.path.join(srcdir, i, 'setup.py')): + call([python, 'setup.py', 'develop'], cwd=subdir) + +# cleanup tempdir +# shutil.rmtree(tempdir) + +# TODO: +# - add carton to the virtualenv (!) +# - add virtualenv to the virtualenv (!) +""" + +def isURL(path): + return path.startswith('http://') or path.startswith('https://') + +def main(args=sys.argv[1:]): + + # parse CLI arguments + parser = OptionParser(usage=usage, description=__doc__) + parser.add_option('-o', dest='outfile', + help="specify outfile; otherwise it will come from environment_name") + parser.add_option('--virtualenv', dest='virtualenv', + help="use virtualenv URL") + options, args = parser.parse_args(args) + if len(args) < 2: + parser.print_usage() + parser.exit() + environment = args[0] + if environment.endswith('.py'): + # stop on .py; will add it in later + environment = environment[:-3] + sources = args[1:] + + # tar up the sources + tempdir = tempfile.mkdtemp() + source_buffer = StringIO() + sources_tar = tarfile.open(mode="w:gz", fileobj=source_buffer) + for source in sources: + + if isURL(source): + # remote tarball + raise NotImplementedError + else: + # local directory or tarball + sources_tar.add(source, arcname=os.path.basename(source)) + # could use git, hg, etc repos. but probably shouldn't + sources_tar.close() + + # tar up virtualenv if not available + if options.virtualenv: + if isURL(options.virtualenv): + globals()['VIRTUAL_ENV'] = urllib2.urlopen(options.virtualenv).read() + else: + assert os.path.exists(options.virtualenv) + if os.path.isdir(options.virtualenv): + raise NotImplementedError("Hypothetically you should be able to use a local directory or tarball, but I haven't done this yet") + else: + # assert a tarfile + assert tarfile.is_tarfile(options.virtualenv), "%s must be a tar file" % options.virtualenv + globals()['VIRTUAL_ENV'] = file(options.virtualenv).read() + else: + globals()['VIRTUAL_ENV'] = urllib2.urlopen(virtualenv_url).read() + # TODO: used the below hashed value of VIRTUAL_ENV if set + # (set that with another file) + + # interpolate "template" -> output + outfile = options.outfile + if outfile is None: + outfile = environment + '.py' + variables = {'VIRTUAL_ENV': VIRTUAL_ENV.encode('base64'), + 'ENV': environment, + 'PACKAGE_SOURCES': source_buffer.getvalue().encode('base64')} + f = file(outfile, 'w') + f.write(template % variables) + f.close() + try: + os.chmod(outfile, 0755) + except: + # you probably don't have os.chmod + pass + +VIRTUAL_ENV = """""" + +if __name__ == '__main__': + main()