import os, re, sys from urlparse import urlparse from os.path import ( join, dirname, normpath, abspath, expanduser, isfile ) from subprocess import Popen, PIPE SVN_WORKINGDIR_CHANGED = re.compile('[ADUCM] ').match SVN_ADDED = re.compile('A ').match def abs_normpath(path, *parts): """Return an absoloute, normalized, path. If parts are supplied the are joined. Both path and parts are filtered through `expanduser`. The returned string is always an aboslout path. """ if parts: return normpath( join(abspath(expanduser(path)), expanduser(join(*parts))) ) else: return normpath( join(abspath(expanduser(path))) ) class CommandError(Exception): """Raised if any subprocess exits with non zero status.""" def svn_files(location, svnbin='svn'): """List all the files that are known to svn in ``location``.""" argv=[svnbin, 'status', '-v', location] proc=Popen(argv, stdout=PIPE, stderr=PIPE) stdout,stderr=proc.communicate() if proc.returncode: raise CommandError(stdout, stderr) files = [ln.split()[-1] for ln in stdout.split('\n') if ln ] return files def svn_co(location, revision, url, svnbin='svn'): """Checkout a working copy to the directory `location`.""" argv=[svnbin, 'co', '-r', revision, url, location] proc=Popen(argv, stdout=PIPE, stderr=PIPE) stdout,stderr=proc.communicate() if proc.returncode: raise CommandError(stdout, stderr) added=[] for ln in stdout: if SVN_ADDED(ln): ent = ln.split(None, 1)[-1] if isfile(ent): added.append(ent) return added def svn_up(location, revision, svnbin='svn'): """Update a working copy to the directory `location`.""" argv=[svnbin, 'up', '-r', revision] cwd = os.getcwd() changed = [] try: os.chdir(location) proc=Popen(argv, stdout=PIPE, stderr=PIPE) stdout,stderr=proc.communicate() if proc.returncode: raise CommandError(stdout, stderr) for ln in stdout: if SVN_WORKINGDIR_CHANGED(ln): changed.append(join(location, ln.split(None, 1)[-1])) return changed finally: os.chdir(cwd) class Recipe: """A zc.buildout recipe that wraps svn_co and svn_up.""" def __init__(self, buildout, name, options): self.buildout = buildout self.name = name self.options = options self.checkout_cfg_section = options.get('checkout-cfg-section', buildout.get( 'checkout-cfg-section', 'checkout-defaults' ) ) co = self.checkout_options = buildout.get( self.checkout_cfg_section, buildout['buildout'] ) co_key = self.checkout_option_key = co.get( 'checkout-basedir-option', 'checkout-basedir' ) cbd = co.get(co_key) if cbd is None: url = options['url'] urlparts = urlparse(url) netloc = urlparts[1] if (netloc and netloc in buildout and co_key in buildout['buildout'][netloc]): cbd = buildout['buildout'][netloc][co_key] if cbd is None: cbd = join(buildout['buildout']['parts-directory'], name) self.checkout_basedir = cbd self.svn = co.setdefault('SVN', 'svn') self.abs_checkout_basedir = abs_normpath(cbd) self.url = options['url'] self.revision = options.get('revision', 'HEAD') self.location = options.setdefault('location', name) self.compile_ext = options.get('compile_ext', '') self.newest = ( buildout['buildout'].get('offline', 'false') == 'false' and buildout['buildout'].get('newest', 'true') == 'true' ) def buildout_loc_absloc(self): return (join(self.checkout_basedir, self.location), join(self.abs_checkout_basedir, self.location) ) def update(self): """Update the checkout. Does nothing if buildout is in offline mode. """ loc, absloc = self.buildout_loc_absloc() try: os.makedirs(absloc) except OSError: pass if not self.newest: return () svn_up(absloc, self.revision, svnbin=self.svn) return () def install(self): """Checkout a working copy. Fails if buildout is running in offline mode. """ loc, absloc = self.buildout_loc_absloc() try: os.makedirs(absloc) except OSError: pass svn_co(absloc, self.revision, self.url, svnbin=self.svn) return () def keep_working_copy(name, _): print 'keeping ' + name