# Copyright (c) 2007-2008 Infrae. All rights reserved. # $Id: Common.py 33243 2009-01-29 10:59:47Z sylvain $ from sets import Set # For python 2.3 compatibility import os.path import os import re import zc.buildout def ignoredFile(file): """Return true if the file should be ignored while checking for added/changed/modified files. """ for suffix in ['.pyc', '.pyo', '.egg-info']: if file.endswith(suffix): return True return False def reportInvalidFiles(path, name, badfiles): """Report invalid files. """ badfiles = [file for file in badfiles if not ignoredFile(file)] if not badfiles: return raise ValueError("""\ In '%s': local modifications detected while uninstalling %r: Uninstall aborted! Please check for local modifications and make sure these are checked in. If you sure that these modifications can be ignored, remove the checkout manually: rm -rf %s Or if applicable, add the file to the 'svn:ignore' property of the file's container directory. Alternatively, add an ignore glob pattern to your subversion client's 'global-ignores' configuration variable. """ % (path, name, """ rm -rf """.join(badfiles))) def checkExistPath(path, warning=True): """Check that a path exist. """ status = os.path.exists(path) if not status and warning: print "-------- WARNING --------" print "Directory %s have been removed." % os.path.abspath(path) print "Changes might be lost." print "-------- WARNING --------" return status def checkAddedPaths(location, urls): """Check that no path have been added to that location. """ current_paths = Set([os.path.join(location, s) for s in os.listdir(location)]) recipe_paths = Set(urls.keys()) added_paths = list(current_paths - recipe_paths) for path in added_paths[:]: if path.endswith('.svn'): added_paths.remove(path) if added_paths: msg = "New path have been added to the location: %s." raise ValueError(msg % ', '.join(added_paths)) def prepareURLs(location, urls): """Given a list of urls/path, and a location, prepare a list of tuple with url, full path. """ def prepareEntry(line): link, path = line.split() return os.path.join(location, path), link return dict([prepareEntry(l) for l in urls.splitlines() if l.strip()]) def extractNames(urls): """Return just the target names of the urls (used for egg names)""" def extractName(line): link, name = line.split() return name return [extractName(line) for line in urls.splitlines() if line.strip()] class BaseRecipe(object): """infrae.subversion recipe. Base class. """ def __init__(self, buildout, name, options): self.buildout = buildout self.name = name self.options = options # location is overridable if desired. location = options.get('location', None) if location: self.location = os.path.abspath(os.path.join( buildout['buildout']['directory'], location)) else: self.location = os.path.join( buildout['buildout']['parts-directory'], self.name) options['location'] = self.location self.revisions = {} # Store revision information for each link self.updated = [] # Store updated links self.urls = prepareURLs(self.location, options['urls']) self.export = options.get('export') self.offline = buildout['buildout'].get('offline', 'false') == 'true' self.eggify = options.get('as_eggs', False) self.eggs = self.eggify and extractNames(options['urls']) or [] self.newest = ( not self.offline and buildout['buildout'].get('newest', 'true') == 'true' ) self.verbose = buildout['buildout'].get('verbosity', 0) self.warning = not (options.get('no_warnings', 'false') == 'true') def _exportInformationToOptions(self): """Export revision and changed information to options. Options can only contains strings. """ if self.options.get('export_info', False): self.options['updated'] = str('\n'.join(self.updated)) str_revisions = ['%s %s' % r for r in self.revisions.items() if r[1]] self.options['revisions'] = str('\n'.join(sorted(str_revisions))) # Always export egg list self.options['eggs'] = '\n'.join(sorted(self.eggs)) def _updateAllRevisionInformation(self): """Update all revision information for defined urls. """ for path, link in self.urls.items(): if os.path.exists(path): self._updateRevisionInformation(link, path) def _updateRevisionInformation(self, link, revision): """Update revision information on a path. """ old_revision = self.revisions.get(link, None) self.revisions[link] = revision if not (old_revision is None): self.updated.append(link) def _updatePath(self, link, path): """Update a single path. """ raise NotImplementedError def _updateAllPaths(self): """Update the checkouts. """ ignore = self.options.get('ignore_updates', False) or self.export num_release = re.compile('.*@[0-9]+$') for path, link in self.urls.items(): if not checkExistPath(path, warning=self.warning): if self.verbose: print "Entry %s missing, checkout a new version ..." % link self._installPath(link, path) continue if ignore: continue if num_release.match(link): if self.verbose: print "Given num release for %s, skipping." % link continue if self.verbose: print "Updating %s" % path self._updatePath(link, path) def update(self): """Update the recipe. Does not update SVN path if the buildout is in offline mode, but still eggify and export information. """ if self.newest: self._updateAllPaths() if self.eggify: self._eggify() self._exportInformationToOptions() return self.location def _installPath(self, link, path): """Checkout a single entry. """ raise NotImplementedError def _installPathVerbose(self, link, path): """Checkout a single entry with verbose. """ if self.verbose: print "%s %s to %s" % (self.export and 'Export' or 'Fetch', link, path) self._installPath(link, path) def _eggify(self): """Install everything as development eggs if eggs=true""" if self.eggify: target = self.buildout['buildout']['develop-eggs-directory'] for path in self.urls.keys(): # If we update the recipe, and we don't have newest, # and that some path have been deleted, all of them # might not be there. if checkExistPath(path, warning=self.warning): zc.buildout.easy_install.develop(path, target) def install(self): """Checkout the checkouts. Fails if buildout is running in offline mode. """ for path, link in self.urls.items(): self._installPathVerbose(link, path) installed = [self.location] if self.eggify: self._eggify() # And also return the develop-eggs/*.egg-link files that are # ours so that an uninstall automatically zaps them. dev_dir = self.buildout['buildout']['develop-eggs-directory'] egg_links = ['%s.egg-link' % egg for egg in self.eggs] egg_links = [os.path.join(dev_dir, link) for link in egg_links] installed += egg_links self._exportInformationToOptions() return installed