[Checkins] SVN: zc.buildout/trunk/ Merged revisions 71277 to 71397 from dev branch:

Jim Fulton jim at zope.com
Mon Dec 4 16:19:41 EST 2006


Log message for revision 71398:
  Merged revisions 71277 to 71397 from dev branch:
  
  Feature Changes
  ---------------
  
  - Variable substitutions now reflect option data written by recipes.
  
  - A part referenced by a part in a parts list is now added to the parts
    list before the referencing part.  This means that you can omit
    parts from the parts list if they are referenced by other parts.
  
  - Added a develop function to the easy_install module to aid in
    creating develop eggs with custom build_ext options.
  
  - The build and develop functions in the easy_install module now
    return the path of the egg or egg link created.
  
  - Removed the limitation that parts named in the install command can
    only name configured parts.
  
  - Removed support ConfigParser-style variable substitutions
    (e.g. %(foo)s). Only the string-template style of variable
    (e.g. ${section:option}) substitutions will be supported.
    Supporting both violates "there's only one way to do it".
  
  - Deprecated the buildout-section extendedBy option.
  

Changed:
  U   zc.buildout/trunk/CHANGES.txt
  U   zc.buildout/trunk/setup.py
  U   zc.buildout/trunk/src/zc/buildout/buildout.py
  U   zc.buildout/trunk/src/zc/buildout/buildout.txt
  U   zc.buildout/trunk/src/zc/buildout/easy_install.py
  U   zc.buildout/trunk/src/zc/buildout/easy_install.txt
  U   zc.buildout/trunk/src/zc/buildout/tests.py
  U   zc.buildout/trunk/src/zc/buildout/update.txt
  U   zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt
  U   zc.buildout/trunk/zc.recipe.egg_/setup.py
  U   zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py
  U   zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py
  U   zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt
  U   zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py

-=-
Modified: zc.buildout/trunk/CHANGES.txt
===================================================================
--- zc.buildout/trunk/CHANGES.txt	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/CHANGES.txt	2006-12-04 21:19:40 UTC (rev 71398)
@@ -20,9 +20,34 @@
 Change History
 **************
 
-1.0.0b12 (2006-10-?)
+1.0.0b13 (2006-12-04)
 =====================
 
+Feature Changes
+---------------
+
+- Variable substitutions now reflect option data written by recipes.
+
+- A part referenced by a part in a parts list is now added to the parts
+  list before the referencing part.  This means that you can omit
+  parts from the parts list if they are referenced by other parts.
+
+- Added a develop function to the easy_install module to aid in
+  creating develop eggs with custom build_ext options.
+
+- The build and develop functions in the easy_install module now
+  return the path of the egg or egg link created.
+
+- Removed the limitation that parts named in the install command can
+  only name configured parts.
+
+- Removed support ConfigParser-style variable substitutions
+  (e.g. %(foo)s). Only the string-template style of variable
+  (e.g. ${section:option}) substitutions will be supported.
+  Supporting both violates "there's only one way to do it".
+
+- Deprecated the buildout-section extendedBy option.
+
 Bugs Fixed
 ----------
 

Modified: zc.buildout/trunk/setup.py
===================================================================
--- zc.buildout/trunk/setup.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/setup.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -25,6 +25,8 @@
         + '\n' +
         read('src', 'zc', 'buildout', 'testing.txt')
         + '\n' +
+        read('src', 'zc', 'buildout', 'easy_install.txt')
+        + '\n' +
         'Download\n'
         '**********************\n'
         ),

Modified: zc.buildout/trunk/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/src/zc/buildout/buildout.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -22,14 +22,22 @@
 import pprint
 import re
 import shutil
+import cStringIO
 import sys
 import tempfile
 import ConfigParser
+import UserDict
 
 import pkg_resources
 import zc.buildout
 import zc.buildout.easy_install
 
+try:
+    realpath = os.path.realpath
+except AttributeError:
+    def realpath(path):
+        return path
+
 pkg_resources_loc = pkg_resources.working_set.find(
     pkg_resources.Requirement.parse('setuptools')).location
 
@@ -42,31 +50,12 @@
     """A required section is missinh
     """
 
-class Options(dict):
+    def __str__(self):
+        return "The referenced section, %r, was not defined." % self[0]
 
-    def __init__(self, buildout, section, data):
-        self.buildout = buildout
-        self.section = section
-        super(Options, self).__init__(data)
 
-    def __getitem__(self, option):
-        try:
-            return super(Options, self).__getitem__(option)
-        except KeyError:
-            raise MissingOption("Missing option: %s:%s"
-                                % (self.section, option))
+class Buildout(UserDict.DictMixin):
 
-    # XXX need test
-    def __setitem__(self, option, value):
-        if not isinstance(value, str):
-            raise TypeError('Option values must be strings', value)
-        super(Options, self).__setitem__(option, value)
-
-    def copy(self):
-        return Options(self.buildout, self.section, self)
-
-class Buildout(dict):
-
     def __init__(self, config_file, cloptions, windows_restart=False):
         config_file = os.path.abspath(config_file)
         self._config_file = config_file
@@ -75,8 +64,6 @@
             print 'Warning: creating', config_file
             open(config_file, 'w').write('[buildout]\nparts = \n')
 
-        super(Buildout, self).__init__()
-
         # default options
         data = dict(buildout={
             'directory': os.path.dirname(config_file),
@@ -110,19 +97,9 @@
             options[option] = value
                 # The egg dire
 
-        # do substitutions
-        converted = {}
-        for section, options in data.iteritems():
-            for option, value in options.iteritems():
-                if '$' in value:
-                    value = self._dosubs(section, option, value,
-                                         data, converted, [])
-                    options[option] = value
-                converted[(section, option)] = value
-
-        # copy data into self:
-        for section, options in data.iteritems():
-            self[section] = Options(self, section, options)
+        self._raw = data
+        self._data = {}
+        self._parts = []
         
         # initialize some attrs and buildout directories.
         options = self['buildout']
@@ -140,73 +117,12 @@
 
         self._setup_logging()
 
-    def _dosubs(self, section, option, value, data, converted, seen):
-        key = section, option
-        r = converted.get(key)
-        if r is not None:
-            return r
-        if key in seen:
-            raise zc.buildout.UserError(
-                "Circular reference in substitutions.\n"
-                "We're evaluating %s\nand are referencing: %s.\n"
-                % (", ".join([":".join(k) for k in seen]),
-                   ":".join(key)
-                   )
-                )
-        seen.append(key)
-        value = '$$'.join([self._dosubs_esc(s, data, converted, seen)
-                           for s in value.split('$$')
-                           ])
-        seen.pop()
-        return value
+        offline = options.get('offline', 'false')
+        if offline not in ('true', 'false'):
+            self._error('Invalid value for offline option: %s', offline)
+        options['offline'] = offline
 
-    _template_split = re.compile('([$]{[^}]*})').split
-    _simple = re.compile('[-a-zA-Z0-9 ._]+$').match
-    _valid = re.compile('[-a-zA-Z0-9 ._]+:[-a-zA-Z0-9 ._]+$').match
-    def _dosubs_esc(self, value, data, converted, seen):
-        value = self._template_split(value)
-        subs = []
-        for ref in value[1::2]:
-            s = tuple(ref[2:-1].split(':'))
-            if not self._valid(ref):
-                if len(s) < 2:
-                    raise zc.buildout.UserError("The substitution, %s,\n"
-                                                "doesn't contain a colon."
-                                                % ref)
-                if len(s) > 2:
-                    raise zc.buildout.UserError("The substitution, %s,\n"
-                                                "has too many colons."
-                                                % ref)
-                if not self._simple(s[0]):
-                    raise zc.buildout.UserError(
-                        "The section name in substitution, %s,\n"
-                        "has invalid characters."
-                        % ref)
-                if not self._simple(s[1]):
-                    raise zc.buildout.UserError(
-                        "The option name in substitution, %s,\n"
-                        "has invalid characters."
-                        % ref)
-                
-            v = converted.get(s)
-            if v is None:
-                options = data.get(s[0])
-                if options is None:
-                    raise MissingSection(
-                        "Referenced section does not exist", s[0])
-                v = options.get(s[1])
-                if v is None:
-                    raise MissingOption("Referenced option does not exist:",
-                                        *s)
-                if '$' in v:
-                    v = self._dosubs(s[0], s[1], v, data, converted, seen)
-                    options[s[1]] = v
-                converted[s] = v
-            subs.append(v)
-        subs.append('')
 
-        return ''.join([''.join(v) for v in zip(value[::2], subs)])
-
     def _buildout_path(self, *names):
         return os.path.join(self._buildout_dir, *names)
 
@@ -268,26 +184,27 @@
         conf_parts = conf_parts and conf_parts.split() or []
         installed_parts = installed_part_options['buildout']['parts']
         installed_parts = installed_parts and installed_parts.split() or []
-
-
-        # If install_parts is given, then they must be listed in parts
-        # and we don't uninstall anything. Otherwise, we install
-        # the configured parts and uninstall anything else.
+        
         if install_parts:
-            extra = [p for p in install_parts if p not in conf_parts]
-            if extra:
-                self._error(
-                    'Invalid install parts: %s.\n'
-                    'Install parts must be listed in the configuration.',
-                    ' '.join(extra))
             uninstall_missing = False
         else:
             install_parts = conf_parts
             uninstall_missing = True
 
-        # load recipes
-        recipes = self._load_recipes(install_parts)
+        # load and initialize recipes
+        [self[part]['recipe'] for part in install_parts]
+        install_parts = self._parts
 
+        if self._log_level <= logging.DEBUG:
+            sections = list(self)
+            sections.sort()
+            print    
+            print 'Configuration data:'
+            for section in self._data:
+                _save_options(section, self[section], sys.stdout)
+            print    
+
+
         # compute new part recipe signatures
         self._compute_part_signatures(install_parts)
 
@@ -339,19 +256,20 @@
             for part in install_parts:
                 signature = self[part].pop('__buildout_signature__')
                 saved_options = self[part].copy()
+                recipe = self[part].recipe
                 if part in installed_parts:
                     self._logger.info('Updating %s', part)
                     old_options = installed_part_options[part]
                     old_installed_files = old_options['__buildout_installed__']
                     try:
-                        update = recipes[part].update
+                        update = recipe.update
                     except AttributeError:
-                        update = recipes[part].install
+                        update = recipe.install
                         self._logger.warning(
                             "The recipe for %s doesn't define an update "
                             "method. Using its install method",
                             part)
-                        
+
                     try:
                         installed_files = update()
                     except:
@@ -364,7 +282,7 @@
 
                 else:
                     self._logger.info('Installing %s', part)
-                    installed_files = recipes[part].install()
+                    installed_files = recipe.install()
                     if installed_files is None:
                         self._logger.warning(
                             "The %s install returned None.  A path or "
@@ -380,15 +298,12 @@
                               ] = '\n'.join(installed_files)
                 saved_options['__buildout_signature__'] = signature
 
-                if part not in installed_parts:
-                    installed_parts.append(part)
+                installed_parts = [p for p in installed_parts if p != part]
+                installed_parts.append(part)
 
         finally:
-            installed_part_options['buildout']['parts'] = ' '.join(
-                [p for p in conf_parts if p in installed_parts]
-                +
-                [p for p in installed_parts if p not in conf_parts] 
-            )
+            installed_part_options['buildout']['parts'] = (
+                ' '.join(installed_parts))
             installed_part_options['buildout']['installed_develop_eggs'
                                                ] = installed_develop_eggs
             
@@ -419,46 +334,8 @@
             try:
                 for setup in develop.split():
                     setup = self._buildout_path(setup)
-                    if os.path.isdir(setup):
-                        setup = os.path.join(setup, 'setup.py')
-
                     self._logger.info("Develop: %s", setup)
-
-
-                    fd, tsetup = tempfile.mkstemp()
-                    try:
-                        os.write(fd, runsetup_template % dict(
-                            setuptools=pkg_resources_loc,
-                            setupdir=os.path.dirname(setup),
-                            setup=setup,
-                            __file__ = setup,
-                            ))
-
-                        args = [
-                            zc.buildout.easy_install._safe_arg(tsetup),
-                            '-q', 'develop', '-mxN',
-                            '-f', zc.buildout.easy_install._safe_arg(
-                                ' '.join(self._links)
-                                ),
-                            '-d', zc.buildout.easy_install._safe_arg(dest),
-                            ]
-
-                        if self._log_level <= logging.DEBUG:
-                            if self._log_level == logging.DEBUG:
-                                del args[1]
-                            else:
-                                args[1] == '-v'
-                            self._logger.debug("in: %s\n%r",
-                                               os.path.dirname(setup), args)
-
-                        assert os.spawnl(
-                            os.P_WAIT, sys.executable, sys.executable,
-                            *args) == 0
-
-                    finally:
-                        os.close(fd)
-                        os.remove(tsetup)
-
+                    zc.buildout.easy_install.develop(setup, dest)
             except:
                 # if we had an error, we need to roll back changes, by
                 # removing any files we created.
@@ -490,90 +367,34 @@
                 self._logger.warning(
                     "Unexpected entry, %s, in develop-eggs directory", f)
 
-    def _load_recipes(self, parts):
-        recipes = {}
-        if not parts:
-            return recipes
-        
-        recipes_requirements = []
-        pkg_resources.working_set.add_entry(
-            self['buildout']['develop-eggs-directory'])
-        pkg_resources.working_set.add_entry(self['buildout']['eggs-directory'])
-
-        # Gather requirements
-        for part in parts:
-            options = self.get(part)
-            if options is None:
-                raise MissingSection("No section was specified for part", part)
-
-            recipe, entry = self._recipe(part, options)
-            if recipe not in recipes_requirements:
-                recipes_requirements.append(recipe)
-
-        # Install the recipe distros
-        offline = self['buildout'].get('offline', 'false')
-        if offline not in ('true', 'false'):
-            self._error('Invalid value for offline option: %s', offline)
-            
-        if offline == 'false':
-            dest = self['buildout']['eggs-directory']
-        else:
-            dest = None
-
-        ws = zc.buildout.easy_install.install(
-            recipes_requirements, dest,
-            links=self._links,
-            index=self['buildout'].get('index'),
-            path=[self['buildout']['develop-eggs-directory'],
-                  self['buildout']['eggs-directory'],
-                  ],
-            working_set=pkg_resources.working_set,
-            )
-
-        # instantiate the recipes
-        for part in parts:
-            options = self[part]
-            recipe, entry = self._recipe(part, options)
-            recipe_class = pkg_resources.load_entry_point(
-                recipe, 'zc.buildout', entry)
-            recipes[part] = recipe_class(self, part, options)
-        
-        return recipes
-
     def _compute_part_signatures(self, parts):
         # Compute recipe signature and add to options
         for part in parts:
             options = self.get(part)
             if options is None:
                 options = self[part] = {}
-            recipe, entry = self._recipe(part, options)
+            recipe, entry = _recipe(options)
             req = pkg_resources.Requirement.parse(recipe)
             sig = _dists_sig(pkg_resources.working_set.resolve([req]))
             options['__buildout_signature__'] = ' '.join(sig)
 
-    def _recipe(self, part, options):
-        recipe = options['recipe']
-        if ':' in recipe:
-            recipe, entry = recipe.split(':')
-        else:
-            entry = 'default'
-
-        return recipe, entry
-
     def _read_installed_part_options(self):
         old = self._installed_path()
         if os.path.isfile(old):
-            parser = ConfigParser.SafeConfigParser(_spacey_defaults)
+            parser = ConfigParser.RawConfigParser()
             parser.optionxform = lambda s: s
             parser.read(old)
-            return dict([
-                (section,
-                 Options(self, section,
-                         [item for item in parser.items(section)
-                          if item[0] not in _spacey_defaults]
-                         )
-                 )
-                for section in parser.sections()])
+            result = {}
+            for section in parser.sections():
+                options = {}
+                for option, value in parser.items(section):
+                    if '%(' in value:
+                        for k, v in _spacey_defaults:
+                            value = value.replace(k, v)
+                    options[option] = value
+                result[section] = Options(self, section, options)
+                        
+            return result
         else:
             return {'buildout': Options(self, 'buildout', {'parts': ''})}
 
@@ -592,7 +413,7 @@
                 
     def _install(self, part):
         options = self[part]
-        recipe, entry = self._recipe(part, options)
+        recipe, entry = _recipe(options)
         recipe_class = pkg_resources.load_entry_point(
             recipe, 'zc.buildout', entry)
         installed = recipe_class(self, part, options).install()
@@ -642,14 +463,6 @@
         root_logger.setLevel(level)
         self._log_level = level
 
-        if level <= logging.DEBUG:
-            sections = list(self)
-            sections.sort()
-            print 'Configuration data:'
-            for section in sections:
-                _save_options(section, self[section], sys.stdout)
-            print    
-
     def _maybe_upgrade(self):
         # See if buildout or setuptools need to be upgraded.
         # If they do, do the upgrade and restart the buildout process.
@@ -677,10 +490,25 @@
         if not upgraded:
             return
 
-        if (os.path.abspath(sys.argv[0])
-            != os.path.join(os.path.abspath(self['buildout']['bin-directory']),
-                            'buildout')
+        if (realpath(os.path.abspath(sys.argv[0]))
+            !=
+            realpath(
+                os.path.join(os.path.abspath(
+                                 self['buildout']['bin-directory']
+                                 ),
+                             'buildout',
+                             )
+                )
             ):
+            self._logger.debug("Running %r", realpath(sys.argv[0]))
+            self._logger.debug(
+                "Local buildout is %r",
+                realpath(
+                    os.path.join(
+                        os.path.abspath(self['buildout']['bin-directory']),
+                        'buildout')
+                    )
+                )
             self._logger.warn("Not upgrading because not running a local "
                               "buildout command")
             return
@@ -745,7 +573,7 @@
 
         fd, tsetup = tempfile.mkstemp()
         try:
-            os.write(fd, runsetup_template % dict(
+            os.write(fd, zc.buildout.easy_install.runsetup_template % dict(
                 setuptools=pkg_resources_loc,
                 setupdir=os.path.dirname(setup),
                 setup=setup,
@@ -758,21 +586,181 @@
             os.close(fd)
             os.remove(tsetup)
 
-    runsetup = setup # backward compat
+    runsetup = setup # backward compat.
+
+    def __getitem__(self, section):
+        try:
+            return self._data[section]
+        except KeyError:
+            pass
+
+        try:
+            data = self._raw[section]
+        except KeyError:
+            raise MissingSection(section)
+
+        options = Options(self, section, data)
+        self._data[section] = options
+        options._initialize()
+        return options          
+
+    def __setitem__(self, key, value):
+        raise NotImplementedError('__setitem__')
+
+    def __delitem__(self, key):
+        raise NotImplementedError('__delitem__')
+
+    def keys(self):
+        return self._raw.keys()
+
+    def __iter__(self):
+        return iter(self._raw)
+
+class Options(UserDict.DictMixin):
+
+    def __init__(self, buildout, section, data):
+        self.buildout = buildout
+        self.name = section
+        self._raw = data
+        self._data = {}
+
+    def _initialize(self):
+        # force substitutions
+        for k in self._raw:
+            self.get(k)
+
+        recipe = self.get('recipe')
+        if not recipe:
+            return
         
-runsetup_template = """
-import sys
-sys.path.insert(0, %(setuptools)r)
-import os, setuptools
+        reqs, entry = _recipe(self._data)
+        req = pkg_resources.Requirement.parse(reqs)
+        buildout = self.buildout
+        
+        if pkg_resources.working_set.find(req) is None:
+            offline = buildout['buildout']['offline'] == 'true'
+            if offline:
+                dest = None
+                path = [buildout['buildout']['develop-eggs-directory'],
+                        buildout['buildout']['eggs-directory'],
+                        ]
+            else:
+                dest = buildout['buildout']['eggs-directory']
+                path = [buildout['buildout']['develop-eggs-directory']]
+            zc.buildout.easy_install.install(
+                [reqs], dest,
+                links=buildout._links,
+                index=buildout['buildout'].get('index'),
+                path=path,
+                working_set=pkg_resources.working_set,
+                )
+            
+        recipe_class = pkg_resources.load_entry_point(
+            req.project_name, 'zc.buildout', entry)
 
-__file__ = %(__file__)r
+        self.recipe = recipe_class(buildout, self.name, self)
+        buildout._parts.append(self.name)
 
-os.chdir(%(setupdir)r)
-sys.argv[0] = %(setup)r
-execfile(%(setup)r)
-"""
+    def get(self, option, default=None, seen=None):
+        try:
+            return self._data[option]
+        except KeyError:
+            pass
 
+        v = self._raw.get(option)
+        if v is None:
+            return default
 
+        if '${' in v:
+            key = self.name, option
+            if seen is None:
+                seen = [key]
+            elif key in seen:
+                raise zc.buildout.UserError(
+                    "Circular reference in substitutions.\n"
+                    "We're evaluating %s\nand are referencing: %s.\n"
+                    % (", ".join([":".join(k) for k in seen]),
+                       ":".join(key)
+                       )
+                    )
+            else:
+                seen.append(key)
+            v = '$$'.join([self._sub(s, seen) for s in v.split('$$')])
+            seen.pop()
+
+        self._data[option] = v
+        return v
+
+    _template_split = re.compile('([$]{[^}]*})').split
+    _simple = re.compile('[-a-zA-Z0-9 ._]+$').match
+    _valid = re.compile('[-a-zA-Z0-9 ._]+:[-a-zA-Z0-9 ._]+$').match
+    def _sub(self, template, seen):
+        value = self._template_split(template)
+        subs = []
+        for ref in value[1::2]:
+            s = tuple(ref[2:-1].split(':'))
+            if not self._valid(ref):
+                if len(s) < 2:
+                    raise zc.buildout.UserError("The substitution, %s,\n"
+                                                "doesn't contain a colon."
+                                                % ref)
+                if len(s) > 2:
+                    raise zc.buildout.UserError("The substitution, %s,\n"
+                                                "has too many colons."
+                                                % ref)
+                if not self._simple(s[0]):
+                    raise zc.buildout.UserError(
+                        "The section name in substitution, %s,\n"
+                        "has invalid characters."
+                        % ref)
+                if not self._simple(s[1]):
+                    raise zc.buildout.UserError(
+                        "The option name in substitution, %s,\n"
+                        "has invalid characters."
+                        % ref)
+                
+            v = self.buildout[s[0]].get(s[1], None, seen)
+            if v is None:
+                raise MissingOption("Referenced option does not exist:", *s)
+            subs.append(v)
+        subs.append('')
+
+        return ''.join([''.join(v) for v in zip(value[::2], subs)])
+        
+    def __getitem__(self, key):
+        try:
+            return self._data[key]
+        except KeyError:
+            pass
+
+        v = self.get(key)
+        if v is None:
+            raise MissingOption("Missing option: %s:%s"
+                                % (self.name, key))
+        return v
+
+    def __setitem__(self, option, value):
+        if not isinstance(value, str):
+            raise TypeError('Option values must be strings', value)
+        self._data[option] = value
+
+    def __delitem__(self, key):
+        if key in self._raw:
+            del self._raw[key]
+            if key in self._data:
+                del self._data[key]
+        elif key in self._data:
+            del self._data[key]
+        else:
+            raise KeyError, key
+
+    def keys(self):
+        raw = self._raw
+        return list(self._raw) + [k for k in self._data if k not in raw]
+
+    def copy(self):
+        return dict([(k, self[k]) for k in self.keys()])
+
 _spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*'
                         '|'
                         '^[ \t\r\f\v]+'
@@ -780,6 +768,14 @@
                         '[ \t\r\f\v]+$'
                         )
 
+_spacey_defaults = [
+    ('%(__buildout_space__)s',   ' '),
+    ('%(__buildout_space_n__)s', '\n'),
+    ('%(__buildout_space_r__)s', '\r'),
+    ('%(__buildout_space_f__)s', '\f'),
+    ('%(__buildout_space_v__)s', '\v'),
+    ]
+
 def _quote_spacey_nl(match):
     match = match.group(0).split('\n', 1)
     result = '\n\t'.join(
@@ -794,20 +790,11 @@
         )
     return result
 
-_spacey_defaults = dict(
-    __buildout_space__   = ' ',
-    __buildout_space_r__ = '\r',
-    __buildout_space_f__ = '\f',
-    __buildout_space_v__ = '\v',
-    __buildout_space_n__ = '\n',
-    )
-
 def _save_options(section, options, f):
     print >>f, '[%s]' % section
     items = options.items()
     items.sort()
     for option, value in items:
-        value = value.replace('%', '%%')
         value = _spacey_nl.sub(_quote_spacey_nl, value)
         if value.startswith('\n\t'):
             value = '%(__buildout_space_n__)s' + value[2:]
@@ -832,7 +819,7 @@
 
     result = {}
 
-    parser = ConfigParser.SafeConfigParser()
+    parser = ConfigParser.RawConfigParser()
     parser.optionxform = lambda s: s
     parser.readfp(open(filename))
     extends = extended_by = None
@@ -850,6 +837,9 @@
             result = _update(_open(base, fname, seen), result)
 
     if extended_by:
+        self._logger.warn(
+            "The extendedBy option is deprecated.  Stop using it."
+            )
         for fname in extended_by.split():
             result = _update(result, _open(base, fname, seen))
 
@@ -887,6 +877,15 @@
             d1[section] = d2[section]
     return d1
 
+def _recipe(options):
+    recipe = options['recipe']
+    if ':' in recipe:
+        recipe, entry = recipe.split(':')
+    else:
+        entry = 'default'
+
+    return recipe, entry
+
 def _error(*message):
     sys.stderr.write('Error: ' + ' '.join(message) +'\n')
     sys.exit(1)

Modified: zc.buildout/trunk/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-12-04 21:19:40 UTC (rev 71398)
@@ -286,7 +286,7 @@
     >>> os.chdir(sample_buildout)
     >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Installing data-dir
     data-dir: Creating directory mystuff
 
@@ -335,7 +335,7 @@
     ... """)
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling data-dir
     buildout: Installing data-dir
     data-dir: Creating directory mydata
@@ -355,7 +355,7 @@
 
     >>> rmdir(sample_buildout, 'mydata')
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling data-dir
     buildout: Installing data-dir
     data-dir: Creating directory mydata
@@ -386,7 +386,7 @@
 We'll get a user error, not a traceback.
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     data-dir: Cannot create /xxx/mydata. /xxx is not a directory.
     Error: Invalid Path
 
@@ -418,9 +418,8 @@
 Variable substitutions
 ----------------------
 
-Buildout configuration files support two kinds of substitutions,
-standard ConfigParser substitutions, and string-template
-substitutions.  To illustrate this, we'll create an debug recipe to
+Buildout configuration files support variable substitution.
+To illustrate this, we'll create an debug recipe to
 allow us to see interactions with the buildout:
 
     >>> write(sample_buildout, 'recipes', 'debug.py', 
@@ -442,10 +441,9 @@
     ...     update = install
     ... """)
 
-In this example, we've used a simple base class that provides a
-boilerplate constructor.  This recipe doesn't actually create
-anything. The install method doesn't return anything, because it
-didn't create any files or directories.
+This recipe doesn't actually create anything. The install method
+doesn't return anything, because it didn't create any files or
+directories.
 
 We also have to update our setup script:
 
@@ -478,16 +476,11 @@
     ... [debug]
     ... recipe = recipes:debug
     ... File 1 = ${data-dir:path}/file
-    ... File 2 = %(File 1)s.out
-    ... File 3 = %(base)s/file3
-    ... File 4 = ${debug:File 3}/log
+    ... File 2 = ${debug:File 1}/log
     ...
     ... [data-dir]
     ... recipe = recipes:mkdir
     ... path = mydata
-    ...
-    ... [DEFAULT]
-    ... base = var
     ... """)
 
 In this example, we've used ConfigParser substitutions for file2 and
@@ -504,18 +497,18 @@
 substituted. 
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling data-dir
     buildout: Installing data-dir
     data-dir: Creating directory mydata
     buildout: Installing debug
-    File 1 mydata/file
-    File 2 mydata/file.out
-    File 3 var/file3
-    File 4 var/file3/log
-    base var
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
     recipe recipes:debug
 
+Note that the substitution of the data-dir path option reflects the
+update to the option performed by the mkdir recipe.
+
 It might seem surprising that mydata was created again.  This is
 because we changed our recipes package by adding the debug module.
 The buildout system didn't know if this module could effect the mkdir
@@ -523,14 +516,11 @@
 the buildout:
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Updating data-dir
     buildout: Updating debug
-    File 1 mydata/file
-    File 2 mydata/file.out
-    File 3 var/file3
-    File 4 var/file3/log
-    base var
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
     recipe recipes:debug
 
 We can see that mydata was not recreated.
@@ -542,23 +532,96 @@
 contain alphanumeric characters, hyphens, periods and spaces. This
 restriction might be relaxed in future releases.
 
+
+Automatic part selection and ordering
+-------------------------------------
+
+When a section with a recipe is refered to, either through variable
+substitution or by an initializing recipe, the section is treated as a
+part and added to the part list before the referencing part.  For
+example, we can leave data-dir out of the parts list:
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... develop = recipes
+    ... parts = debug
+    ... log-level = INFO
+    ...
+    ... [debug]
+    ... recipe = recipes:debug
+    ... File 1 = ${data-dir:path}/file
+    ... File 2 = ${debug:File 1}/log
+    ...
+    ... [data-dir]
+    ... recipe = recipes:mkdir
+    ... path = mydata
+    ... """)
+
+
+It will still be treated as a part:
+
+    >>> print system(buildout),
+    buildout: Develop: /sample-buildout/recipes
+    buildout: Updating data-dir
+    buildout: Updating debug
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
+    recipe recipes:debug
+
+    >>> cat('.installed.cfg') # doctest: +ELLIPSIS
+    [buildout]
+    installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
+    parts = data-dir debug
+    ...
+
+Note that the data-dir part is included *before* the debug part,
+because the debug part refers to the data-dir part.  Even if we list
+the data-dir part after the debug part, it will be included before:
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... develop = recipes
+    ... parts = debug data-dir
+    ... log-level = INFO
+    ...
+    ... [debug]
+    ... recipe = recipes:debug
+    ... File 1 = ${data-dir:path}/file
+    ... File 2 = ${debug:File 1}/log
+    ...
+    ... [data-dir]
+    ... recipe = recipes:mkdir
+    ... path = mydata
+    ... """)
+
+
+It will still be treated as a part:
+
+    >>> print system(buildout),
+    buildout: Develop: /sample-buildout/recipes
+    buildout: Updating data-dir
+    buildout: Updating debug
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
+    recipe recipes:debug
+
+    >>> cat('.installed.cfg') # doctest: +ELLIPSIS
+    [buildout]
+    installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
+    parts = data-dir debug
+    ...
+
 Multiple configuration files
 ----------------------------
 
-You can use multiple configuration files.  From your main
-configuration file, you can include other configuration files in 2
-ways:
+A configuration file can "extend" another configuration file.
+Options are read from the other configuration file if they aren't
+already defined by your configuration file.
 
-- Your configuration file can "extend" another configuration file.
-  Option are read from the other configuration file if they aren't
-  already defined by your configuration file.
-
-- Your configuration file can be "extended-by" another configuration
-  file, In this case, the options in the other configuration file
-  override options in your configuration file. 
-
-The configuration files your file extends or is extended by can extend
-or be extended by other configuration files.  The same file may be
+The configuration files your file extends can extend
+other configuration files.  The same file may be
 used more than once although, of course, cycles aren't allowed.
 
 To see how this works, we use an example:
@@ -584,7 +647,7 @@
     ... """)
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling debug
     buildout: Uninstalling data-dir
     buildout: Installing debug
@@ -598,20 +661,16 @@
 
 Here is a more elaborate example. 
 
-    >>> extensions = tmpdir('extensions')
+    >>> other = tmpdir('other')
 
     >>> write(sample_buildout, 'buildout.cfg',
     ... """
     ... [buildout]
-    ... extends = b1.cfg b2.cfg
-    ... extended-by = e1.cfg %(e2)s
+    ... extends = b1.cfg b2.cfg %(b3)s
     ...
     ... [debug]
-    ... op = %%(name)s
-    ...
-    ... [DEFAULT]
-    ... name = buildout
-    ... """ % dict(e2=os.path.join(extensions, 'e2.cfg')))
+    ... op = buildout
+    ... """ % dict(b3=os.path.join(other, 'b3.cfg')))
 
     >>> write(sample_buildout, 'b1.cfg',
     ... """
@@ -619,12 +678,8 @@
     ... extends = base.cfg
     ...
     ... [debug]
-    ... op1 = %(name)s 1
-    ... op2 = %(name)s 2
-    ... op3 = %(name)s 3
-    ...
-    ... [DEFAULT]
-    ... name = b1
+    ... op1 = b1 1
+    ... op2 = b1 2
     ... """)
 
     >>> write(sample_buildout, 'b2.cfg',
@@ -633,91 +688,58 @@
     ... extends = base.cfg
     ...
     ... [debug]
-    ... op3 = %(name)s 3
-    ... op4 = %(name)s 4
-    ... op5 = %(name)s 5
-    ...
-    ... [DEFAULT]
-    ... name = b2
+    ... op2 = b2 2
+    ... op3 = b2 3
     ... """)
 
-    >>> write(sample_buildout, 'base.cfg',
+    >>> write(other, 'b3.cfg',
     ... """
     ... [buildout]
-    ... develop = recipes
-    ... parts = debug
+    ... extends = b3base.cfg
     ...
     ... [debug]
-    ... recipe = recipes:debug
-    ... name = base
+    ... op4 = b3 4
     ... """)
 
-    >>> write(sample_buildout, 'e1.cfg',
+    >>> write(other, 'b3base.cfg',
     ... """
     ... [debug]
-    ... op1 = %(name)s 1
-    ...
-    ... [DEFAULT]
-    ... name = e1
+    ... op5 = b3base 5
     ... """)
 
-    >>> write(extensions, 'e2.cfg',
+    >>> write(sample_buildout, 'base.cfg',
     ... """
     ... [buildout]
-    ... extends = eb.cfg
-    ... extended-by = ee.cfg
-    ... """)
-
-    >>> write(extensions, 'eb.cfg',
-    ... """
-    ... [debug]
-    ... op5 = %(name)s 5
+    ... develop = recipes
+    ... parts = debug
     ...
-    ... [DEFAULT]
-    ... name = eb
-    ... """)
-
-    >>> write(extensions, 'ee.cfg',
-    ... """
     ... [debug]
-    ... op6 = %(name)s 6
-    ...
-    ... [DEFAULT]
-    ... name = ee
+    ... recipe = recipes:debug
+    ... name = base
     ... """)
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling debug
     buildout: Installing debug
-    name ee
+    name base
     op buildout
-    op1 e1 1
-    op2 b1 2
+    op1 b1 1
+    op2 b2 2
     op3 b2 3
-    op4 b2 4
-    op5 eb 5
-    op6 ee 6
+    op4 b3 4
+    op5 b3base 5
     recipe recipes:debug
 
 There are several things to note about this example:
 
-- We can name multiple files in an extends or extended-by option.
+- We can name multiple files in an extends option.
 
 - We can reference files recursively.
 
-- DEFAULT sections only directly affect the configuration file they're
-  used in, but they can have secondary effects.  For example, the name
-  option showed up in the debug section because it was defined in the
-  debug sections in several of the input files by virtue of being in
-  their DEFAULT sections.
+- Relative file names in extended options are interpreted relative to
+  the directory containing the referencing configuration file.
 
-- Relative file names in extended and extended-by options are
-  interpreted relative to the directory containing the referencing
-  configuration file.  The files eb.cfg and ee.cfg were found in the
-  extensions directory because they were referenced from a file in
-  that directory.
-
 User defaults
 -------------
 
@@ -737,17 +759,16 @@
 
     >>> os.environ['HOME'] = home
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling debug
     buildout: Installing debug
-    name ee
+    name base
     op buildout
-    op1 e1 1
-    op2 b1 2
+    op1 b1 1
+    op2 b2 2
     op3 b2 3
-    op4 b2 4
-    op5 eb 5
-    op6 ee 6
+    op4 b3 4
+    op5 b3base 5
     op7 7
     recipe recipes:debug
 
@@ -765,16 +786,13 @@
     ... [buildout]
     ... log-level = WARNING
     ... extends = b1.cfg b2.cfg
-    ... extended-by = e1.cfg
     ... """)
 
     >>> print system(buildout),
-    name e1
-    op1 e1 1
-    op2 b1 2
+    name base
+    op1 b1 1
+    op2 b2 2
     op3 b2 3
-    op4 b2 4
-    op5 b2 5
     recipe recipes:debug
 
 Command-line usage
@@ -821,7 +839,7 @@
 alternate file to store information about installed parts.
     
     >>> print system(buildout+' -c other.cfg debug:op1=foo -v'),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Installing debug
     name other
     op1 foo
@@ -834,7 +852,7 @@
 Options can also be combined in the usual Unix way, as in:
     
     >>> print system(buildout+' -vcother.cfg debug:op1=foo'),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Updating debug
     name other
     op1 foo
@@ -876,7 +894,7 @@
     ... """)
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling debug
     buildout: Installing debug
     recipe recipes:debug
@@ -898,7 +916,6 @@
     d  d2
     d  d3
     d  develop-eggs
-    -  e1.cfg
     d  eggs
     d  parts
     d  recipes
@@ -959,7 +976,7 @@
 and run the buildout specifying just d3 and d4:
 
     >>> print system(buildout+' install d3 d4'),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling d3
     buildout: Installing d3
     d3: Creating directory data3
@@ -978,7 +995,6 @@
     d  data3
     d  data4
     d  develop-eggs
-    -  e1.cfg
     d  eggs
     d  parts
     d  recipes
@@ -991,13 +1007,19 @@
     >>> cat(sample_buildout, '.installed.cfg')
     [buildout]
     installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
-    parts = debug d2 d3 d4 d1
+    parts = debug d1 d2 d3 d4
     <BLANKLINE>
     [debug]
     __buildout_installed__ = 
     __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
     recipe = recipes:debug
     <BLANKLINE>
+    [d1]
+    __buildout_installed__ = /sample-buildout/d1
+    __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
+    path = /sample-buildout/d1
+    recipe = recipes:mkdir
+    <BLANKLINE>
     [d2]
     __buildout_installed__ = /sample-buildout/d2
     __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
@@ -1015,12 +1037,6 @@
     __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
     path = /sample-buildout/data4
     recipe = recipes:mkdir
-    <BLANKLINE>
-    [d1]
-    __buildout_installed__ = /sample-buildout/d1
-    __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
-    path = /sample-buildout/d1
-    recipe = recipes:mkdir
 
 Note that the installed data for debug, d1, and d2 haven't changed,
 because we didn't install those parts and that the d1 and d2
@@ -1029,9 +1045,9 @@
 Now, if we run the buildout without the install command:
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
+    buildout: Uninstalling d2
     buildout: Uninstalling d1
-    buildout: Uninstalling d2
     buildout: Uninstalling debug
     buildout: Installing debug
     recipe recipes:debug
@@ -1055,7 +1071,6 @@
     d  data3
     d  data4
     d  develop-eggs
-    -  e1.cfg
     d  eggs
     d  parts
     d  recipes
@@ -1090,7 +1105,7 @@
     buildout: Creating directory /sample-alt/work
     buildout: Creating directory /sample-alt/basket
     buildout: Creating directory /sample-alt/developbasket
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
     buildout: Uninstalling d4
     buildout: Uninstalling d3
     buildout: Uninstalling d2
@@ -1126,7 +1141,7 @@
     buildout: Creating directory /sample-alt/parts
     buildout: Creating directory /sample-alt/eggs
     buildout: Creating directory /sample-alt/develop-eggs
-    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Develop: /sample-buildout/recipes
 
     >>> ls(alt)
     -  .installed.cfg
@@ -1162,12 +1177,11 @@
     ... parts =
     ... log-level = 25
     ... verbosity = 5
-    ... log-format = %%(levelname)s %%(message)s
+    ... log-format = %(levelname)s %(message)s
     ... """)
  
 Here, we've changed the format to include the log-level name, rather
-than the logger name.  Note that we had to double percent signs,
-because configuration options allow ConfigParser variable substitution.
+than the logger name.
 
 We've also illustrated, with a contrived example, that the log level
 can be a numeric value and that the verbosity can be specified in the
@@ -1175,7 +1189,7 @@
 level, we get a final log level of 20, which is the INFO level.
 
     >>> print system(buildout),
-    INFO Develop: /sample-buildout/recipes/setup.py
+    INFO Develop: /sample-buildout/recipes
 
 Predefined buildout options
 ---------------------------
@@ -1193,6 +1207,11 @@
     ... """)
 
     >>> print system(buildout+' -v'),
+    zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
+    zc.buildout.easy_install: We have a develop egg for zc.buildout
+    zc.buildout.easy_install: We have the best distribution that satisfies
+    setuptools
+    <BLANKLINE>
     Configuration data:
     [buildout]
     bin-directory = /sample-buildout/bin
@@ -1201,18 +1220,15 @@
     eggs-directory = /sample-buildout/eggs
     executable = /usr/local/bin/python2.3
     installed = /sample-buildout/.installed.cfg
-    log-format = %%(name)s: %%(message)s
+    log-format = %(name)s: %(message)s
     log-level = INFO
+    offline = false
     parts = 
     parts-directory = /sample-buildout/parts
     python = buildout
     verbosity = 10
     <BLANKLINE>
-    zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
-    zc.buildout.easy_install: We have a develop egg for zc.buildout
-    zc.buildout.easy_install: We have the best distribution that satisfies
-    setuptools
-
+ 
 All of these options can be overridden by configuration files or by
 command-line assignments.  We've discussed most of these options
 already, but let's review them and touch on some we haven't discussed:
@@ -1377,7 +1393,7 @@
 
     >>> os.chdir(sample_bootstrapped)
     >>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')),
-    buildout: Develop: /sample-bootstrapped/demo/setup.py
+    buildout: Develop: /sample-bootstrapped/demo
 
 Now we can add the extensions option.  We were a bit tricly and ran
 the buildout once with the demo develop egg defined but without the
@@ -1398,7 +1414,7 @@
 
     >>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')),
     ext ['buildout']
-    buildout: Develop: /sample-bootstrapped/demo/setup.py
+    buildout: Develop: /sample-bootstrapped/demo
 
 
 

Modified: zc.buildout/trunk/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -34,11 +34,14 @@
 
 url_match = re.compile('[a-z0-9+.-]+://').match
 
+setuptools_loc = pkg_resources.working_set.find(
+    pkg_resources.Requirement.parse('setuptools')
+    ).location
+
 # Include buildout and setuptools eggs in paths
 buildout_and_setuptools_path = [
+    setuptools_loc,
     pkg_resources.working_set.find(
-        pkg_resources.Requirement.parse('setuptools')).location,
-    pkg_resources.working_set.find(
         pkg_resources.Requirement.parse('zc.buildout')).location,
     ]
 
@@ -404,12 +407,15 @@
 
     dist = _satisfied(requirement, env, dest, executable, index_url, links)
     if dist is not None:
-        return dist
+        return [dist.location]
 
-    # Get an editable version of the package to a temporary directory:
-    tmp = tempfile.mkdtemp('editable')
-    tmp2 = tempfile.mkdtemp('editable')
+    undo = []
     try:
+        tmp = tempfile.mkdtemp('build')
+        undo.append(lambda : shutil.rmtree(tmp)) 
+        tmp2 = tempfile.mkdtemp('build')
+        undo.append(lambda : shutil.rmtree(tmp2))
+
         index = _get_index(executable, index_url, links)
         dist = index.fetch_distribution(requirement, tmp2, False, True)
         if dist is None:
@@ -442,13 +448,105 @@
         setuptools.command.setopt.edit_config(
             setup_cfg, dict(build_ext=build_ext))
 
-        # Now run easy_install for real:
+        tmp3 = tempfile.mkdtemp('build', dir=dest)
+        undo.append(lambda : shutil.rmtree(tmp3)) 
+
         _call_easy_install(base, env, pkg_resources.WorkingSet(),
-                           dest, links, index_url, executable, True)
+                           tmp3, links, index_url, executable, True)
+
+        return _copyeggs(tmp3, dest, '.egg', undo)
+        
     finally:
-        shutil.rmtree(tmp)
-        shutil.rmtree(tmp2)
+        undo.reverse()
+        [f() for f in undo]
+        
 
+def _rm(*paths):
+    for path in paths:
+        if os.path.isdir(path):
+            shutil.rmtree(path)
+        elif os.path.exists(path):
+            os.remove(path)
+
+def _copyeggs(src, dest, suffix, undo):
+    result = []
+    undo.append(lambda : _rm(*result))
+    for name in os.listdir(src):
+        if name.endswith(suffix):
+            new = os.path.join(dest, name)
+            _rm(new)
+            os.rename(os.path.join(src, name), new)
+            result.append(new)
+
+    assert len(result) == 1
+    undo.pop()
+    
+    return result[0]
+
+def develop(setup, dest,
+            build_ext=None,
+            executable=sys.executable):
+
+    if os.path.isdir(setup):
+        directory = setup
+        setup = os.path.join(directory, 'setup.py')
+    else:
+        directory = os.path.dirname(setup)
+        
+    undo = []
+    try:
+        if build_ext:
+            setup_cfg = os.path.join(directory, 'setup.cfg')
+            if os.path.exists(setup_cfg):
+                os.rename(setup_cfg, setup_cfg+'-develop-aside')
+                def restore_old_setup():
+                    if os.path.exists(setup_cfg):
+                        os.remove(setup_cfg)
+                    os.rename(setup_cfg+'-develop-aside', setup_cfg)
+                undo.append(restore_old_setup)
+            else:
+                open(setup_cfg, 'w')
+                undo.append(lambda: os.remove(setup_cfg))
+            setuptools.command.setopt.edit_config(
+                setup_cfg, dict(build_ext=build_ext))
+
+        fd, tsetup = tempfile.mkstemp()
+        undo.append(lambda: os.remove(tsetup))
+        undo.append(lambda: os.close(fd))
+
+        os.write(fd, runsetup_template % dict(
+            setuptools=setuptools_loc,
+            setupdir=directory,
+            setup=setup,
+            __file__ = setup,
+            ))
+
+        tmp3 = tempfile.mkdtemp('build', dir=dest)
+        undo.append(lambda : shutil.rmtree(tmp3)) 
+
+        args = [
+            zc.buildout.easy_install._safe_arg(tsetup),
+            '-q', 'develop', '-mxN',
+            '-d', _safe_arg(tmp3),
+            ]
+
+        log_level = logger.getEffectiveLevel()
+        if log_level <= logging.DEBUG:
+            if log_level == logging.DEBUG:
+                del args[1]
+            else:
+                args[1] == '-v'
+            logger.debug("in: %s\n%r", directory, args)
+
+        assert os.spawnl(os.P_WAIT, executable, executable, *args) == 0
+
+        return _copyeggs(tmp3, dest, '.egg-link', undo)
+
+    finally:
+        undo.reverse()
+        [f() for f in undo]
+            
+            
 def working_set(specs, executable, path):
     return install(specs, None, executable=executable, path=path)
 
@@ -595,6 +693,15 @@
     import code
     code.interact(banner="", local=globals())
 '''
+        
+runsetup_template = """
+import sys
+sys.path.insert(0, %(setuptools)r)
+import os, setuptools
 
+__file__ = %(__file__)r
 
-
+os.chdir(%(setupdir)r)
+sys.argv[0] = %(setup)r
+execfile(%(setup)r)
+"""

Modified: zc.buildout/trunk/src/zc/buildout/easy_install.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.txt	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.txt	2006-12-04 21:19:40 UTC (rev 71398)
@@ -1,8 +1,9 @@
-Minimal Python interface to easy_install
-========================================
+Python API for egg and script installation
+==========================================
 
-The easy_install module provides a minimal interface to the setuptools
-easy_install command that provides some additional semantics:
+The easy_install module provides some functions to provide support for
+egg and script installation.  It provides functionality at the python
+level that is similar to easy_install, with a few exceptions:
 
 - By default, we look for new packages *and* the packages that
   they depend on.  This is somewhat like (and uses) the --upgrade
@@ -21,8 +22,8 @@
 - Distutils options for building extensions can be passed.
 
 The easy_install module provides a method, install, for installing one
-or more packages and their dependencies.  The
-install function takes 2 positional arguments:
+or more packages and their dependencies.  The install function takes 2
+positional arguments:
 
 - An iterable of setuptools requirement strings for the distributions
   to be installed, and
@@ -426,18 +427,18 @@
         eggrecipedemo.main(1, 2)
 
 
-Handling custom build options for extensions
---------------------------------------------
+Handling custom build options for extensions provided in source distributions
+-----------------------------------------------------------------------------
 
 Sometimes, we need to control how extension modules are built.  The
-build method provides this level of control.  It takes a single
+build function provides this level of control.  It takes a single
 package specification, downloads a source distribution, and builds it
 with specified custom build options.
 
-The build method takes 3 positional arguments:
+The build function takes 3 positional arguments:
 
 spec
-   A package specification
+   A package specification for a source distribution
 
 dest
    A destination directory
@@ -486,9 +487,13 @@
   PyMODINIT_FUNC
   initextdemo(void)
   {
-      PyObject *d;
-      d = Py_InitModule3("extdemo", methods, "");
-      PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO));    
+      PyObject *m;
+      m = Py_InitModule3("extdemo", methods, "");
+  #ifdef TWO
+      PyModule_AddObject(m, "val", PyInt_FromLong(2));
+  #else
+      PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO));
+  #endif
   }
 
 The extension depends on a system-dependnt include file, extdemo.h,
@@ -497,9 +502,11 @@
 We'll add an include directory to our sample buildout and add the
 needed include file to it:
 
-    >>> mkdir(sample_buildout, 'include')
-    >>> open(os.path.join(sample_buildout, 'include', 'extdemo.h'), 'w').write(
-    ...    "#define EXTDEMO 42\n")
+    >>> mkdir('include')
+    >>> write('include', 'extdemo.h',
+    ... """
+    ... #define EXTDEMO 42
+    ... """)
 
 Now, we can use the build function to create an egg from the source
 distribution:
@@ -508,7 +515,10 @@
     ...   'extdemo', dest, 
     ...   {'include-dirs': os.path.join(sample_buildout, 'include')},
     ...   links=[link_server], index=link_server+'index/')
+    '/sample-install/extdemo-1.4-py2.4-unix-i686.egg'
 
+The function returns the list of eggs 
+
 Now if we look in our destination directory, we see we have an extdemo egg:
 
     >>> ls(dest)
@@ -516,3 +526,68 @@
     d  demoneeded-1.1-py2.4.egg
     d  extdemo-1.4-py2.4-unix-i686.egg
 
+Handling custom build options for extensions in develop eggs
+------------------------------------------------------------
+
+The develop function is similar to the build function, except that,
+rather than building an egg from a source directory containing a
+setup.py script.  
+
+The develop function takes 2 positional arguments:
+
+setup
+   The path to a setup script, typically named "setup.py", or a
+   directory containing a setup.py script.
+
+dest
+   The directory to install the egg link to
+
+It supports some optional keyword argument:
+
+build_ext
+   A dictionary of options to be passed to the distutils build_ext
+   command when building extensions.
+
+executable
+   A path to a Python executable.  Distributions will ne installed
+   using this executable and will be for the matching Python version.
+
+We have a local directory containing the extdemo source:
+
+    >>> ls(extdemo)
+    -  MANIFEST
+    -  MANIFEST.in
+    -  README
+    -  extdemo.c
+    -  setup.py
+
+Now, we can use the develop function to create a develop egg from the source
+distribution:
+
+    >>> zc.buildout.easy_install.develop(
+    ...   extdemo, dest, 
+    ...   {'include-dirs': os.path.join(sample_buildout, 'include')})
+    '/sample-install/extdemo.egg-link'
+
+The name of the egg link created is returned.
+
+Now if we look in our destination directory, we see we have an extdemo
+egg link:
+
+    >>> ls(dest)
+    d  demo-0.3-py2.4.egg
+    d  demoneeded-1.1-py2.4.egg
+    d  extdemo-1.4-py2.4-linux-i686.egg
+    -  extdemo.egg-link
+
+And that the source directory contains the compiled extension:
+
+    >>> ls(extdemo)
+    -  MANIFEST
+    -  MANIFEST.in
+    -  README
+    d  build
+    -  extdemo.c
+    d  extdemo.egg-info
+    -  extdemo.so
+    -  setup.py

Modified: zc.buildout/trunk/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/tests.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/src/zc/buildout/tests.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -45,7 +45,7 @@
     ... ''')
 
     >>> print system(join('bin', 'buildout')),
-    buildout: Develop: /sample-buildout/foo/setup.py
+    buildout: Develop: /sample-buildout/foo
 
     >>> ls('develop-eggs')
     -  foo.egg-link
@@ -71,10 +71,9 @@
     ... ''')
 
     >>> print system(join('bin', 'buildout')+' -v'), # doctest: +ELLIPSIS
-    Configuration data:
+    zc.buildout...
+    buildout: Develop: /sample-buildout/foo
     ...
-    buildout: Develop: /sample-buildout/foo/setup.py
-    ...
     Installed /sample-buildout/foo
     ...
 
@@ -86,7 +85,7 @@
 def buildout_error_handling():
     r"""Buildout error handling
 
-Asking for a section that doesn't exist, yields a key error:
+Asking for a section that doesn't exist, yields a missing section error:
 
     >>> import os
     >>> os.chdir(sample_buildout)
@@ -95,7 +94,7 @@
     >>> buildout['eek']
     Traceback (most recent call last):
     ...
-    KeyError: 'eek'
+    MissingSection: The referenced section, 'eek', was not defined.
 
 Asking for an option that doesn't exist, a MissingOption error is raised:
 
@@ -109,8 +108,7 @@
     >>> write(sample_buildout, 'buildout.cfg',
     ... '''
     ... [buildout]
-    ... develop = recipes
-    ... parts = data_dir debug
+    ... parts =
     ... x = ${buildout:y}
     ... y = ${buildout:z}
     ... z = ${buildout:x}
@@ -183,7 +181,7 @@
     ... ''')
 
     >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')),
-    Error: No section was specified for part x
+    Error: The referenced section, 'x', was not defined.
 
 and all parts have to have a specified recipe:
 
@@ -265,15 +263,15 @@
     >>> os.chdir(sample_buildout)
     >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
 
-    >>> print system(buildout), # doctest: +ELLIPSIS
-    buildout: Develop: ...setup.py
+    >>> print system(buildout),
+    buildout: Develop: /sample-buildout/recipes
     buildout: Installing debug
 
 If we run the buildout again, we shoudn't get a message about
 uninstalling anything because the configuration hasn't changed.
 
-    >>> print system(buildout), # doctest: +ELLIPSIS
-    buildout: Develop: ...setup.py
+    >>> print system(buildout),
+    buildout: Develop: /sample-buildout/recipes
     buildout: Updating debug
 """
 
@@ -317,20 +315,21 @@
 
     """
 
-def error_for_indefined_install_parts():
-    """
-Any parts we pass to install on the command line must be
-listed in the configuration.
+# Why?
+## def error_for_undefined_install_parts():
+##     """
+## Any parts we pass to install on the command line must be
+## listed in the configuration.
 
-    >>> print system(join('bin', 'buildout') + ' install foo'),
-    buildout: Invalid install parts: foo.
-    Install parts must be listed in the configuration.
+##     >>> print system(join('bin', 'buildout') + ' install foo'),
+##     buildout: Invalid install parts: foo.
+##     Install parts must be listed in the configuration.
 
-    >>> print system(join('bin', 'buildout') + ' install foo bar'),
-    buildout: Invalid install parts: foo bar.
-    Install parts must be listed in the configuration.
+##     >>> print system(join('bin', 'buildout') + ' install foo bar'),
+##     buildout: Invalid install parts: foo bar.
+##     Install parts must be listed in the configuration.
     
-    """
+##     """
 
 
 bootstrap_py = os.path.join(
@@ -515,7 +514,7 @@
     ... """)
 
     >>> print system(join('bin', 'buildout')),
-    buildout: Develop: /sample-buildout/foo/setup.py
+    buildout: Develop: /sample-buildout/foo
 
     >>> ls('develop-eggs')
     -  foox.egg-link
@@ -536,8 +535,8 @@
     ... """)
 
     >>> print system(join('bin', 'buildout')),
-    buildout: Develop: /sample-buildout/foo/setup.py
-    buildout: Develop: /sample-buildout/bar/setup.py
+    buildout: Develop: /sample-buildout/foo
+    buildout: Develop: /sample-buildout/bar
 
     >>> ls('develop-eggs')
     -  foox.egg-link
@@ -552,7 +551,7 @@
     ... parts =
     ... """)
     >>> print system(join('bin', 'buildout')),
-    buildout: Develop: /sample-buildout/bar/setup.py
+    buildout: Develop: /sample-buildout/bar
 
 It is gone
 
@@ -611,7 +610,7 @@
     ... """)
 
     >>> print system(join('bin', 'buildout')),
-    buildout: Develop: /sample-buildout/foo/setup.py
+    buildout: Develop: /sample-buildout/foo
 
 Now, if we generate a working set using the egg link, we will get a warning
 and we will get setuptools included in the working set.
@@ -659,7 +658,6 @@
     ...     ])]
     ['foox', 'setuptools']
 
-
     >>> print handler,
 
 We get the same behavior if the it is a depedency that uses a
@@ -682,8 +680,8 @@
     ... """)
 
     >>> print system(join('bin', 'buildout')),
-    buildout: Develop: /sample-buildout/foo/setup.py
-    buildout: Develop: /sample-buildout/bar/setup.py
+    buildout: Develop: /sample-buildout/foo
+    buildout: Develop: /sample-buildout/bar
 
     >>> [dist.project_name
     ...  for dist in zc.buildout.easy_install.working_set(
@@ -703,7 +701,53 @@
     >>> handler.uninstall()
 
     '''
+
+def develop_preserves_existing_setup_cfg():
+    """
     
+See "Handling custom build options for extensions in develop eggs" in
+easy_install.txt.  This will be very similar except that we'll have an
+existing setup.cfg:
+
+    >>> write(extdemo, "setup.cfg",
+    ... '''
+    ... # sampe cfg file
+    ...
+    ... [foo]
+    ... bar = 1
+    ...
+    ... [build_ext]
+    ... define = X,Y
+    ... ''')
+
+    >>> mkdir('include')
+    >>> write('include', 'extdemo.h',
+    ... '''
+    ... #define EXTDEMO 42
+    ... ''')
+
+    >>> dest = tmpdir('dest')
+    >>> zc.buildout.easy_install.develop(
+    ...   extdemo, dest, 
+    ...   {'include-dirs': os.path.join(sample_buildout, 'include')})
+    '/tmp/tmp7AFYXv/_TEST_/dest/extdemo.egg-link'
+
+    >>> ls(dest)
+    -  extdemo.egg-link
+
+    >>> cat(extdemo, "setup.cfg")
+    <BLANKLINE>
+    # sampe cfg file
+    <BLANKLINE>
+    [foo]
+    bar = 1
+    <BLANKLINE>
+    [build_ext]
+    define = X,Y
+
+"""
+    
+    
 def create_sample_eggs(test, executable=sys.executable):
     write = test.globs['write']
     dest = test.globs['sample_eggs']
@@ -762,9 +806,13 @@
 PyMODINIT_FUNC
 initextdemo(void)
 {
-    PyObject *d;
-    d = Py_InitModule3("extdemo", methods, "");
-    PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO));    
+    PyObject *m;
+    m = Py_InitModule3("extdemo", methods, "");
+#ifdef TWO
+    PyModule_AddObject(m, "val", PyInt_FromLong(2));
+#else
+    PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO));
+#endif
 }
 """
 
@@ -778,8 +826,8 @@
 """
 
 def add_source_dist(test):
-    import tarfile
-    tmp = tempfile.mkdtemp('test-sdist')
+    
+    tmp = test.globs['extdemo'] = test.globs['tmpdir']('extdemo')
     write = test.globs['write']
     try:
         write(tmp, 'extdemo.c', extdemo_c);
@@ -930,7 +978,7 @@
                ]),
             ),
         doctest.DocTestSuite(
-            setUp=zc.buildout.testing.buildoutSetUp,
+            setUp=easy_install_SetUp,
             tearDown=zc.buildout.testing.buildoutTearDown,
             checker=renormalizing.RENormalizing([
                zc.buildout.testing.normalize_path,

Modified: zc.buildout/trunk/src/zc/buildout/update.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/update.txt	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/src/zc/buildout/update.txt	2006-12-04 21:19:40 UTC (rev 71398)
@@ -74,7 +74,7 @@
       zc.buildout version 99.99,
       setuptools version 99.99;
     restarting.
-    buildout: Develop: /sample-buildout/showversions/setup.py
+    buildout: Develop: /sample-buildout/showversions
     buildout: Installing show-versions
     zc.buildout 99.99
     setuptools 99.99
@@ -122,7 +122,7 @@
       zc.buildout version 1.0.0,
       setuptools version 0.6;
     restarting.
-    buildout: Develop: /sample-buildout/showversions/setup.py
+    buildout: Develop: /sample-buildout/showversions
     buildout: Updating show-versions
     zc.buildout 1.0.0
     setuptools 0.6
@@ -143,7 +143,7 @@
     ... """ % dict(new_releases=new_releases))
 
     >>> print system(buildout),
-    buildout: Develop: /sample-buildout/showversions/setup.py
+    buildout: Develop: /sample-buildout/showversions
     buildout: Updating show-versions
     zc.buildout 1.0.0
     setuptools 0.6
@@ -174,4 +174,3 @@
     buildout: Not upgrading because not running a local buildout command
 
     >>> ls('bin')
-

Modified: zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt	2006-12-04 21:19:40 UTC (rev 71398)
@@ -8,6 +8,27 @@
 Change History
 **************
 
+1.0.0b3 (2006-12-04)
+====================
+
+Feature Changes
+---------------
+
+- Added a develop recipe for creating develop eggs.
+
+  This is useful to:
+
+  - Specify custom extension building options,
+
+  - Specify a version of Python to use, and to
+
+  - Cause develop eggs to be created after other parts.
+
+- The develop and build recipes now return the paths created, so that 
+  created eggs or egg links are removed when a part is removed (or
+  changed).
+
+
 1.0.0b2 (2006-10-16)
 ====================
 

Modified: zc.buildout/trunk/zc.recipe.egg_/setup.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/setup.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/zc.recipe.egg_/setup.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -42,7 +42,9 @@
     tests_require = ['zope.testing'],
     test_suite = name+'.tests.test_suite',
     entry_points = {'zc.buildout': ['default = %s:Egg' % name,
+                                    'script = %s:Egg' % name,
                                     'custom = %s:Custom' % name,
+                                    'develop = %s:Develop' % name,
                                     ]
                     },
     zip_safe=False,

Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -1,2 +1,2 @@
 from zc.recipe.egg.egg import Egg
-from zc.recipe.egg.custom import Custom
+from zc.recipe.egg.custom import Custom, Develop

Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -19,12 +19,27 @@
 import os, re, zipfile
 import zc.buildout.easy_install
 
-class Custom:
+class Base:
 
     def __init__(self, buildout, name, options):
-        self.buildout = buildout
-        self.name = name
-        self.options = options
+        self.name, self.options = name, options
+
+        options['_d'] = buildout['buildout']['develop-eggs-directory']
+
+        python = options.get('python', buildout['buildout']['python'])
+        options['executable'] = buildout[python]['executable']
+
+        self.build_ext = build_ext(buildout, options)
+
+
+    def update(self):
+        return self.install()
+
+class Custom(Base):
+
+    def __init__(self, buildout, name, options):
+        Base.__init__(self, buildout, name, options)
+
         links = options.get('find-links',
                             buildout['buildout'].get('find-links'))
         if links:
@@ -39,47 +54,66 @@
             options['index'] = index
         self.index = index
 
-        options['_b'] = buildout['buildout']['bin-directory']
         options['_e'] = buildout['buildout']['eggs-directory']
-        options['_d'] = buildout['buildout']['develop-eggs-directory']
 
         assert options.get('unzip') in ('true', 'false', None)
 
-        python = options.get('python', buildout['buildout']['python'])
-        options['executable'] = buildout[python]['executable']
+        if buildout['buildout'].get('offline') == 'true':
+            self.install = lambda: ()
 
-        build_ext = {}
-        for be_option in ('include-dirs', 'library-dirs', 'rpath'):
-            value = options.get(be_option)
-            if value is None:
-                continue
-            value = [
-                os.path.join(
-                    buildout['buildout']['directory'],
-                    v.strip()
-                    )
-                for v in value.strip().split('\n')
-                if v.strip()
-            ]
-            build_ext[be_option] = ':'.join(value)
-            options[be_option] = ':'.join(value)
-        self.build_ext = build_ext
-
     def install(self):
-        if self.buildout['buildout'].get('offline') == 'true':
-            return ()
         options = self.options
         distribution = options.get('eggs', self.name).strip()
-        build_ext = dict([
-            (k, options[k])
-            for k in ('include-dirs', 'library-dirs', 'rpath')
-            if k in options
-            ])
-        zc.buildout.easy_install.build(
+        return zc.buildout.easy_install.build(
             distribution, options['_d'], self.build_ext,
             self.links, self.index, options['executable'], [options['_e']],
             )
         
-        return ()
+class Develop(Base):
 
-    update = install
+    def __init__(self, buildout, name, options):
+        Base.__init__(self, buildout, name, options)
+        options['setup'] = os.path.join(buildout['buildout']['directory'],
+                                        options['setup'])
+
+    def install(self):
+        options = self.options
+        return zc.buildout.easy_install.develop(
+            options['setup'], options['_d'], self.build_ext,
+            options['executable'],
+            )
+        
+
+def build_ext(buildout, options):
+    result = {}
+    for be_option in ('include-dirs', 'library-dirs', 'rpath'):
+        value = options.get(be_option)
+        if value is None:
+            continue
+        value = [
+            os.path.join(
+                buildout['buildout']['directory'],
+                v.strip()
+                )
+            for v in value.strip().split('\n')
+            if v.strip()
+        ]
+        result[be_option] = os.pathsep.join(value)
+        options[be_option] = os.pathsep.join(value)
+
+    swig = options.get('swig')
+    if swig:
+        options['swig'] = result['swig'] = os.path.join(
+            buildout['buildout']['directory'],
+            swig,
+            )
+
+    for be_option in ('define', 'undef', 'libraries', 'link-objects',
+                      'debug', 'force', 'compiler', 'swig-cpp', 'swig-opts',
+                      ):
+        value = options.get(be_option)
+        if value is None:
+            continue
+        result[be_option] = value
+
+    return result

Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt	2006-12-04 21:19:40 UTC (rev 71398)
@@ -1,5 +1,5 @@
-Custon eggs
-===========
+Creating eggs with extensions neededing custom build settings
+=============================================================
 
 Sometimes, It's necessary to provide extra control over how an egg is
 created.  This is commonly true for eggs with extension modules that
@@ -20,6 +20,42 @@
    A new-line separated list of directories to search for dynamic libraries
    at run time.
 
+define
+   A comma-separated list of names of C preprocessor variables to
+   define.
+
+undef
+   A comman separated list of names of C preprocessor variables to
+   undefine.
+
+libraries
+   The name of an additional library to link with.  Due to limitations
+   in distutils and desprite the option name, only a single library
+   can be specified.
+
+link-objects
+   The name of an link object to link afainst.  Due to limitations
+   in distutils and desprite the option name, only a single link object
+   can be specified.
+
+debug
+   Compile/link with debugging information
+
+force
+   Forcibly build everything (ignore file timestamps)
+
+compiler
+   Specify the compiler type
+
+swig
+   The path to the swig executable
+
+swig-cpp           
+   Make SWIG create C++ files (default is C)
+
+swig-opts
+   List of SWIG command line options
+
 In addition, the following options can be used to specify the egg:
 
 egg
@@ -57,9 +93,13 @@
   PyMODINIT_FUNC
   initextdemo(void)
   {
-      PyObject *d;
-      d = Py_InitModule3("extdemo", methods, "");
-      PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO));    
+      PyObject *m;
+      m = Py_InitModule3("extdemo", methods, "");
+  #ifdef TWO
+      PyModule_AddObject(m, "val", PyInt_FromLong(2));
+  #else
+      PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO));
+  #endif
   }
 
 The extension depends on a system-dependnt include file, extdemo.h,
@@ -71,10 +111,11 @@
 We have a sample buildout that we'll add an include directory to with
 the necessary include file:
 
-    >>> mkdir(sample_buildout, 'include')
-    >>> import os
-    >>> open(os.path.join(sample_buildout, 'include', 'extdemo.h'), 'w').write(
-    ...    "#define EXTDEMO 42\n")
+    >>> mkdir('include')
+    >>> write('include', 'extdemo.h',
+    ... """
+    ... #define EXTDEMO 42
+    ... """)
 
 We'll also update the buildout configuration file to define a part for
 the egg:
@@ -91,8 +132,7 @@
     ... include-dirs = include
     ... """ % dict(server=link_server))
 
-    >>> os.chdir(sample_buildout)
-    >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
+    >>> buildout = join('bin', 'buildout')
 
     >>> print system(buildout),
     buildout: Installing extdemo
@@ -113,3 +153,177 @@
 dependencies or scripts for a custom egg, define another part and use
 the zc.recipe.egg recipe, listing the custom egg as one of the eggs to
 be installed.  The zc.recipe.egg recipe will use the installed egg.
+
+Let's define a script that uses out ext demo:
+
+    >>> mkdir('demo')
+    >>> write('demo', 'demo.py',
+    ... """
+    ... import extdemo
+    ... def main():
+    ...     print extdemo.val
+    ... """)
+
+    >>> write('demo', 'setup.py',
+    ... """
+    ... from setuptools import setup
+    ... setup(name='demo')
+    ... """)
+
+
+    >>> write('buildout.cfg',
+    ... """
+    ... [buildout]
+    ... develop = demo
+    ... parts = extdemo demo
+    ...
+    ... [extdemo]
+    ... recipe = zc.recipe.egg:custom
+    ... find-links = %(server)s
+    ... index = %(server)s/index
+    ... include-dirs = include
+    ...
+    ... [demo]
+    ... recipe = zc.recipe.egg
+    ... eggs = demo 
+    ...        extdemo
+    ... entry-points = demo=demo:main
+    ... """ % dict(server=link_server))
+
+    >>> print system(buildout),
+    buildout: Develop: /sample-buildout/demo
+    buildout: Updating extdemo
+    buildout: Installing demo
+
+When we run the script, we'll 42 printed:
+
+    >>> print system(join('bin', 'demo')),
+    42
+
+Controlling develop-egg generation
+==================================
+
+If you want to provide custom build options for a develop egg, you can
+use the develop recipe.  The recipe has the following options:
+
+path
+   The path to a setup script or directory containing a startup
+   script. This is required.
+
+include-dirs
+   A new-line separated list of directories to search for include
+   files.
+
+library-dirs
+   A new-line separated list of directories to search for libraries
+   to link with.
+
+rpath
+   A new-line separated list of directories to search for dynamic libraries
+   at run time.
+
+define
+   A comma-separated list of names of C preprocessor variables to
+   define.
+
+undef
+   A comman separated list of names of C preprocessor variables to
+   undefine.
+
+libraries
+   The name of an additional library to link with.  Due to limitations
+   in distutils and desprite the option name, only a single library
+   can be specified.
+
+link-objects
+   The name of an link object to link afainst.  Due to limitations
+   in distutils and desprite the option name, only a single link object
+   can be specified.
+
+debug
+   Compile/link with debugging information
+
+force
+   Forcibly build everything (ignore file timestamps)
+
+compiler
+   Specify the compiler type
+
+swig
+   The path to the swig executable
+
+swig-cpp           
+   Make SWIG create C++ files (default is C)
+
+swig-opts
+   List of SWIG command line options
+
+python
+   The name of a section to get the Python executable from.
+   If not specified, then the buildout python option is used.  The
+   Python executable is found in the executable option of the named
+   section. 
+
+To illustrate this, we'll use a directory containing the extdemo
+example from the earlier section:
+
+    >>> ls(extdemo)
+    -  MANIFEST
+    -  MANIFEST.in
+    -  README
+    -  extdemo.c
+    -  setup.py
+
+    >>> write('buildout.cfg',
+    ... """
+    ... [buildout]
+    ... develop = demo
+    ... parts = extdemo demo
+    ...
+    ... [extdemo]
+    ... setup = %(extdemo)s
+    ... recipe = zc.recipe.egg:develop
+    ... include-dirs = include
+    ... define = TWO
+    ...
+    ... [demo]
+    ... recipe = zc.recipe.egg
+    ... eggs = demo 
+    ...        extdemo
+    ... entry-points = demo=demo:main
+    ... """ % dict(extdemo=extdemo))
+
+Note that we added a define option to cause the preprocessor variable
+TWO to be defined.  This will cause the module-variable, 'val', to be
+set with a value of 2.
+
+    >>> print system(buildout),
+    buildout: Develop: /tmp/tmpCXjRps/_TEST_/sample-buildout/demo
+    buildout: Uninstalling extdemo
+    buildout: Installing extdemo
+    buildout: Updating demo
+
+Our develop-eggs now includes an egg link for extdemo:
+
+    >>> ls('develop-eggs')
+    -  demo.egg-link
+    -  extdemo.egg-link
+    -  zc.recipe.egg.egg-link
+
+and the extdemo now has a built extension:
+
+    >>> ls(extdemo)
+    -  MANIFEST
+    -  MANIFEST.in
+    -  README
+    d  build
+    -  extdemo.c
+    d  extdemo.egg-info
+    -  extdemo.so
+    -  setup.py
+
+Because develop eggs take precedence over non-develop eggs, the demo
+script will use the new develop egg:
+
+    >>> print system(join('bin', 'demo')),
+    2

Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py	2006-12-04 21:10:17 UTC (rev 71397)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py	2006-12-04 21:19:40 UTC (rev 71398)
@@ -78,6 +78,7 @@
             'custom.txt',
             setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
             checker=renormalizing.RENormalizing([
+               zc.buildout.testing.normalize_path,
                (re.compile("(d  ((ext)?demo(needed)?|other)"
                            "-\d[.]\d-py)\d[.]\d(-\S+)?[.]egg"),
                 '\\1V.V.egg'),



More information about the Checkins mailing list