[Checkins] SVN: zc.buildout/trunk/ Added a recipe update method.

Jim Fulton jim at zope.com
Sat Oct 7 13:10:48 EDT 2006


Log message for revision 70561:
  Added a recipe update method.
  

Changed:
  U   zc.buildout/trunk/CHANGES.txt
  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/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_/src/zc/recipe/egg/api.txt
  U   zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py
  U   zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/egg.py
  U   zc.buildout/trunk/zc.recipe.testrunner/CHANGES.txt
  U   zc.buildout/trunk/zc.recipe.testrunner/src/zc/recipe/testrunner/__init__.py

-=-
Modified: zc.buildout/trunk/CHANGES.txt
===================================================================
--- zc.buildout/trunk/CHANGES.txt	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/CHANGES.txt	2006-10-07 17:10:47 UTC (rev 70561)
@@ -28,8 +28,13 @@
 Feature Changes
 ---------------
 
-Renamed the runsetup command to setup. (The old name still works.)
+- Renamed the runsetup command to setup. (The old name still works.)
 
+- Added a recipe update method. Now install is only called when a part
+  is installed for the first time, or after an uninstall. Otherwise, 
+  update is called.  For backward compatibility, recipes that don't
+  define update methiods are still supported.
+
 1.0.0b9 (2006-10-02)
 ====================
 

Modified: zc.buildout/trunk/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.py	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/src/zc/buildout/buildout.py	2006-10-07 17:10:47 UTC (rev 70561)
@@ -288,10 +288,21 @@
             for part in reversed(installed_parts):
                 if part in install_parts:
                     old_options = installed_part_options[part].copy()
-                    old_options.pop('__buildout_installed__')
+                    installed_files = old_options.pop('__buildout_installed__')
                     new_options = self.get(part)
                     if old_options == new_options:
-                        continue
+                        # The options are the same, but are all of the
+                        # installed files still there?  If not, we should
+                        # reinstall.
+                        if not installed_files:
+                            continue
+                        for f in installed_files.split('\n'):
+                            if not os.path.exists(self._buildout_path(f)):
+                                break
+                        else:
+                            continue
+
+                    # output debugging info
                     for k in old_options:
                         if k not in new_options:
                             self._logger.debug("Part: %s, dropped option %s",
@@ -305,6 +316,7 @@
                         if k not in old_options:
                             self._logger.debug("Part: %s, new option %s",
                                                part, k)
+
                 elif not uninstall_missing:
                     continue
 
@@ -316,17 +328,52 @@
 
             # install new parts
             for part in install_parts:
-                self._logger.info('Installing %s', part)
-                installed_part_options[part] = self[part].copy()
-                del self[part]['__buildout_signature__']
-                installed_files = recipes[part].install() or ()
+                signature = self[part].pop('__buildout_signature__')
+                saved_options = self[part].copy()
+                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
+                    except AttributeError:
+                        update = recipes[part].install
+                        self._logger.warning(
+                            "The recipe for %s doesn't define an update "
+                            "method. Using it's install method",
+                            part)
+                        
+                    try:
+                        installed_files = update()
+                    except:
+                        installed_parts.remove(part)
+                        self._uninstall(old_installed_files)
+                        raise
+                    
+                    if installed_files is None:
+                        installed_files = old_installed_files.split('\n')
+
+                else:
+                    self._logger.info('Installing %s', part)
+                    installed_files = recipes[part].install()
+                    if installed_files is None:
+                        self._logger.warning(
+                            "The %s install returned None.  A path or "
+                            "iterable os paths should be returned.",
+                            part)
+                        installed_files = ()
+                    
                 if isinstance(installed_files, str):
                     installed_files = [installed_files]
-                installed_part_options[part]['__buildout_installed__'] = (
-                    '\n'.join(installed_files)
-                    )
+
+                installed_part_options[part] = saved_options
+                saved_options['__buildout_installed__'
+                              ] = '\n'.join(installed_files)
+                saved_options['__buildout_signature__'] = signature
+
                 if part not in installed_parts:
                     installed_parts.append(part)
+
         finally:
             installed_part_options['buildout']['parts'] = ' '.join(
                 [p for p in conf_parts if p in installed_parts]
@@ -475,7 +522,9 @@
         return self._buildout_path(self['buildout']['installed'])
 
     def _uninstall(self, installed):
-        for f in installed.split():
+        for f in installed.split('\n'):
+            if not f:
+                continue
             f = self._buildout_path(f)
             if os.path.isdir(f):
                 shutil.rmtree(f)

Modified: zc.buildout/trunk/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-10-07 17:10:47 UTC (rev 70561)
@@ -14,9 +14,8 @@
 configuration files and recipes.  There are three ways to set up the
 buildout software and create a buildout instance:
 
-1. Install the zc.buildout egg with easy_install `easy_install
-   <http://peak.telecommunity.com/DevCenter/EasyInstall>`_ and use the
-   buildout script installed in a Python scripts area.
+1. Install the zc.buildout egg with easy_install and use the buildout
+   script installed in a Python scripts area.
 
 2. Use the buildout bootstrap script to create a buildout that
    includes both the setuptools and zc.buildout eggs.  This allows you
@@ -31,7 +30,7 @@
 Often, a software project will be managed in a software repository,
 such as a subversion repository, that includes some software source
 directories, buildout configuration files, and a copy of the buildout
-bootstrap script,  To work on the project, one would check out the
+bootstrap script.  To work on the project, one would check out the
 project from the repository and run the bootstrap script which
 installs setuptools and zc.buildout into the checkout as well as any
 parts defined.
@@ -92,15 +91,24 @@
 almost anything, such as a Python package, a program, a directory, or
 even a configuration file.  
 
+Recipes
+-------
+
 A part is created by a recipe.  Recipes are always installed as Python
 eggs. They can be downloaded from a package server, such as the
-Python Package Index, or they can be developed as part of a project.
+Python Package Index, or they can be developed as part of a project
+using a "develop" egg.  
+
+A develop egg is a special kind of egg that gets installed as an "egg
+link" that contains the name of a source directory.  Develop eggs
+don't have to be packaged for distribution to be used and can be
+modified in place, which is especially useful while they are being
+developed.
+
 Let's create a recipe as part of the sample project.  We'll create a
-recipe for creating directories.  
+recipe for creating directories.  First, we'll create a recipes source
+directory for our local recipes:
 
-First, we'll create a recipes directory for
-our local recipes:
-
     >>> mkdir(sample_buildout, 'recipes')
 
 and then we'll create a source file for our mkdir recipe:
@@ -128,56 +136,81 @@
     ...
     ...     def install(self):
     ...         path = self.options['path']
-    ...         if not os.path.isdir(path):
-    ...             logging.getLogger(self.name).info(
-    ...                 'Creating directory %s', os.path.basename(path))
-    ...             os.mkdir(path)
+    ...         logging.getLogger(self.name).info(
+    ...             'Creating directory %s', os.path.basename(path))
+    ...         os.mkdir(path)
     ...         return path
+    ...
+    ...     def update(self):
+    ...         pass
     ... """)
 
-The recipe defines a constructor that takes a buildout object, a part
-name, and an options dictionary. It saves them in instance attributes.
+Currently, recipes must define 3 methods [#future_recipe_methods]_:
 
-If the path is relative, we'll interpret it as relative to the
-buildout directory.  The buildout object passed in is a mapping from
-section name to a mapping of options for that section. The buildout
-directory is available as the directory option of the buildout
-section.  We normalize the path and save it back into the options
-directory.  
+- a constructor,
 
-Any time we use data from another section, it is important to reflect
-that data in the recipe's options when the recipe is constructed.
+- an install method, and
 
+- an update method.
+
+The constructor is responsible for updating a parts options to reflect
+data read from other sections.  The buildout system keeps track of
+whether a part specification has changed.  A part specification has
+changed if it's options, after ajusting for data read from other
+sections, has changed, or if the recipe has changed.  Only the options
+for the part are considered.  If data are read from other sections,
+then that information has to be reflected in the parts options.  In
+the Mkdir example, the given path is interpreted relative to the
+buildout directory, and data from the buildout directory is read.  The
+path option is updated to reflect this.  If the directory option was
+changed in the buildout sections, we would know to update parts
+created using the mkdir recipe using relative path names.
+
 When buildout is run, it saves configuration data for installed parts
-in a file named installed.cfg.  In subsequent runs, it compares
-part-configuration data stored in the installed.cfg file and the
+in a file named ".installed.cfg".  In subsequent runs, it compares
+part-configuration data stored in the .installed.cfg file and the
 part-configuration data loaded from the configuration files as
 modified by recipe constructors to decide if the configuration of a
 part has changed. If the configuration has changed, or if the recipe
-has changed, then the part is uninstalled before reinstalling it.  The
+has changed, then the part is uninstalled and reinstalled.  The
 buildout only looks at the part's options, so any data used to
 configure the part needs to be reflected in the part's options.  It is
 the job of a recipe constructor to make sure that the options include
-all rel event data.
+all relevent data.
 
 Of course, parts are also uninstalled if they are no-longer used.
 
+The recipe defines a constructor that takes a buildout object, a part
+name, and an options dictionary. It saves them in instance attributes.
+If the path is relative, we'll interpret it as relative to the
+buildout directory.  The buildout object passed in is a mapping from
+section name to a mapping of options for that section. The buildout
+directory is available as the directory option of the buildout
+section.  We normalize the path and save it back into the options
+directory.  
+
 The install method is responsible for creating the part.  In this
-case, we need the path of the directory to create.  We'll use a
-path option from our options dictionary.
+case, we need the path of the directory to create.  We'll use a path
+option from our options dictionary.  The install method logs what it's
+doing using the Python logging call.  We return the path that we
+installed.  If the part is uninstalled or reinstalled, then the path
+returned will be removed by the buildout machinery.  A recipe install
+method is expected to return a string, or an iterable of strings
+containing paths to be removed if a part is uninstalled.  For most
+recipes, this is all of the uninstall support needed.
 
-The install method logs what it's doing using the Python logging call.
+The update method is responsible for updating an already installed
+part.  An empty method is often provided, as in this example, if parts
+can't be updated.  An update method can return None, a string, or an
+iterable of strings.  If a string or iterable of strings is returned,
+then the saved list of paths to be uninstalled is updated with the new
+information. 
 
-We return the path that we installed.  If the part is uninstalled or
-reinstalled, then the path returned will be removed by the buildout
-machinery.  A recipe install method is expected to return None, a
-string, or an iterable of strings containing paths to be removed if a
-part is uninstalled.  For most recipes, this is all of the uninstall
-support needed.  A recipe can provide custom uninstall support as will
-be described later.
-
 We need to provide packaging information so that our recipe can be
-installed as an egg.  We need to define a setup script for this:
+installed as a develop egg. The minimum information we need to specify
+[#packaging_info]_ is a name.  For recipes, we also need to define the
+names of the recipe classes as entry points.  Packaging information is
+provided via a setup.py script:
 
     >>> write(sample_buildout, 'recipes', 'setup.py',
     ... """
@@ -189,21 +222,11 @@
     ...     )
     ... """)
 
-This setup script is incomplete.  It doesn't describe what is to be 
-included in a distribution.  This is fine if we never actually create
-a distribution. If recipes are going to be used only internally in a
-buildout, then we needn't include distribution information.  If we
-wanted to use the same recipes in multiple buildouts, then we'd need
-to include proper distribution data.  To find out more about creating
-distributions, see the setuptools documentation.
-
 Our setup script defines an entry point. Entry points provide
 a way for an egg to define the services it provides.  Here we've said
-that we define a zc.buildout entry point named default.  Recipe
+that we define a zc.buildout entry point named mkdir.  Recipe
 classes must be exposed as entry points in the zc.buildout group.  we
-give entry points names within the group.  The name "default" is
-somewhat special because it allows a recipe to be referenced using a
-package name without naming an entry point.
+give entry points names within the group.
 
 We also need a README.txt for our recipes to avoid an annoying warning
 from distutils, on which setuptools and zc.buildout are based:
@@ -326,7 +349,16 @@
     d  parts
     d  recipes
 
+If any of the files or directories created by a recipe are removed,
+the part will be reinstalled:
 
+    >>> rmdir(sample_buildout, 'mydata')
+    >>> print system(buildout),
+    buildout: Develop: /sample-buildout/recipes/setup.py
+    buildout: Uninstalling data-dir
+    buildout: Installing data-dir
+    data-dir: Creating directory mydata
+
 Error reporting
 ---------------
 
@@ -404,6 +436,9 @@
     ...         items.sort()
     ...         for option, value in items:
     ...             print option, value
+    ...         return ()
+    ...
+    ...     update = install
     ... """)
 
 In this example, we've used a simple base class that provides a
@@ -488,8 +523,8 @@
 
     >>> print system(buildout),
     buildout: Develop: /sample-buildout/recipes/setup.py
-    buildout: Installing data-dir
-    buildout: Installing debug
+    buildout: Updating data-dir
+    buildout: Updating debug
     File 1 mydata/file
     File 2 mydata/file.out
     File 3 var/file3
@@ -799,7 +834,7 @@
     
     >>> print system(buildout+' -vcother.cfg debug:op1=foo'),
     buildout: Develop: /sample-buildout/recipes/setup.py
-    buildout: Installing debug
+    buildout: Updating debug
     name other
     op1 foo
     recipe recipes:debug
@@ -1000,8 +1035,8 @@
     x 1
     buildout: Installing d2
     d2: Creating directory data2
-    buildout: Installing d3
-    buildout: Installing d4
+    buildout: Updating d3
+    buildout: Updating d4
 
 We see the output of the debug recipe and that data2 was created.  We
 also see that d1 and d2 have gone away:
@@ -1346,3 +1381,13 @@
     ext ['buildout']
     buildout: Develop: /sample-bootstrapped/demo/setup.py
 
+
+
+.. [#future_recipe_methods] In the future, additional mathods may be
+       added. Older recipes with fewer methods will still be
+       supported.
+
+.. [#packaging_info] If we wanted to create a distribution from this
+       package, we would need specify much more information.  See the
+       `setuptools documentation
+       <http://peak.telecommunity.com/DevCenter/setuptools>`_.

Modified: zc.buildout/trunk/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/tests.py	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/src/zc/buildout/tests.py	2006-10-07 17:10:47 UTC (rev 70561)
@@ -178,6 +178,8 @@
     ...     def install(self):
     ...         open('t', 'w').write('t')
     ...         return 't'
+    ...
+    ...     update = install
     ... ''')
 
 
@@ -214,7 +216,7 @@
 
     >>> print system(buildout), # doctest: +ELLIPSIS
     buildout: Develop: ...setup.py
-    buildout: Installing debug
+    buildout: Updating debug
 """
 
 
@@ -277,22 +279,22 @@
 <BLANKLINE>
   -q
 <BLANKLINE>
-     Deccreaae the level of verbosity.  This option can be used multiple times.
+     Decrease the level of verbosity.  This option can be used multiple times.
 <BLANKLINE>
   -c config_file
 <BLANKLINE>
      Specify the path to the buildout configuration file to be used.
-     This defaults to the file named"buildout.cfg" in the current
-     working directory. 
+     This defaults to the file named "buildout.cfg" in the current
+     working directory.
 <BLANKLINE>
 Assignments are of the form: section:option=value and are used to
-provide configuration options that override those givem in the
+provide configuration options that override those given in the
 configuration file.  For example, to run the buildout in offline mode,
 use buildout:offline=true.
 <BLANKLINE>
 Options and assignments can be interspersed.
 <BLANKLINE>
-Commmonds:
+Commands:
 <BLANKLINE>
   install [parts]
 <BLANKLINE>
@@ -324,22 +326,22 @@
 <BLANKLINE>
   -q
 <BLANKLINE>
-     Deccreaae the level of verbosity.  This option can be used multiple times.
+     Decrease the level of verbosity.  This option can be used multiple times.
 <BLANKLINE>
   -c config_file
 <BLANKLINE>
      Specify the path to the buildout configuration file to be used.
-     This defaults to the file named"buildout.cfg" in the current
-     working directory. 
+     This defaults to the file named "buildout.cfg" in the current
+     working directory.
 <BLANKLINE>
 Assignments are of the form: section:option=value and are used to
-provide configuration options that override those givem in the
+provide configuration options that override those given in the
 configuration file.  For example, to run the buildout in offline mode,
 use buildout:offline=true.
 <BLANKLINE>
 Options and assignments can be interspersed.
 <BLANKLINE>
-Commmonds:
+Commands:
 <BLANKLINE>
   install [parts]
 <BLANKLINE>

Modified: zc.buildout/trunk/src/zc/buildout/update.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/update.txt	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/src/zc/buildout/update.txt	2006-10-07 17:10:47 UTC (rev 70561)
@@ -44,6 +44,7 @@
     ...         for project in 'zc.buildout', 'setuptools':
     ...             req = pkg_resources.Requirement.parse(project)
     ...             print project, pkg_resources.working_set.find(req).version
+    ...         return ()
     ... """)
 
 

Modified: zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/zc.recipe.egg_/CHANGES.txt	2006-10-07 17:10:47 UTC (rev 70561)
@@ -8,6 +8,8 @@
 Change History
 **************
 
+Updated to work with zc.buildout 1.0.0b10.
+
 1.0.0b1
 =======
 

Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/api.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/api.txt	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/api.txt	2006-10-07 17:10:47 UTC (rev 70561)
@@ -38,6 +38,9 @@
     ...         for d in ws:
     ...             print d
     ...         print 'extra paths:', self.egg.extra_paths
+    ...         return ()
+    ...
+    ...     update = install
     ... """)
 
 Here we instantiated the egg recipe in the constructor, saving it in

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-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py	2006-10-07 17:10:47 UTC (rev 70561)
@@ -67,7 +67,7 @@
 
     def install(self):
         if self.buildout['buildout'].get('offline') == 'true':
-            return
+            return ()
         options = self.options
         distribution = options.get('eggs', self.name).strip()
         build_ext = dict([
@@ -80,3 +80,6 @@
             self.links, self.index, options['executable'], [options['_e']],
             )
         
+        return ()
+
+    update = install

Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/egg.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/egg.py	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/egg.py	2006-10-07 17:10:47 UTC (rev 70561)
@@ -119,3 +119,6 @@
                 interpreter=options.get('interpreter'),
                 )
 
+        return ()
+
+    update = install

Modified: zc.buildout/trunk/zc.recipe.testrunner/CHANGES.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.testrunner/CHANGES.txt	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/zc.recipe.testrunner/CHANGES.txt	2006-10-07 17:10:47 UTC (rev 70561)
@@ -2,6 +2,8 @@
 Change History
 **************
 
+Updated to work with zc.buildout 1.0.0b10.
+
 1.0.0b2
 =======
 

Modified: zc.buildout/trunk/zc.recipe.testrunner/src/zc/recipe/testrunner/__init__.py
===================================================================
--- zc.buildout/trunk/zc.recipe.testrunner/src/zc/recipe/testrunner/__init__.py	2006-10-07 12:09:20 UTC (rev 70560)
+++ zc.buildout/trunk/zc.recipe.testrunner/src/zc/recipe/testrunner/__init__.py	2006-10-07 17:10:47 UTC (rev 70561)
@@ -54,6 +54,8 @@
                 )),
             )
 
+    update = install
+
 arg_template = """[
   '--test-path', %(TESTPATH)s,
   ]"""



More information about the Checkins mailing list