[Checkins] SVN: zc.zope3recipes/trunk/ - Implemented partial windows support

Roger Ineichen roger at projekt01.ch
Sun Jul 29 19:36:42 EDT 2007


Log message for revision 78476:
  - Implemented partial windows support
  - Added separate unit tests for windows
    and make sure that the linux test don't run on a windows box 
    because they generate different things and will generate even 
    more different things in the future
  
  Note:
  I guess the *nix tests are totaly broken on a linux box.
  This happens because we can't handle test dependencies anymore with eggs.
  The changes in zc.buildout broke all the tests in this package
  and nobody was recognizing it. I totaly dislike it that we do not have
  a trunk centric development because this will make sure that we can run 
  tests over at least a minimal set of core components. I'm sure that we
  sometimes later will switch back to a trunk development. 
  
  I really believe that development and distribution are two different 
  things we mix together right now in our development process.

Changed:
  U   zc.zope3recipes/trunk/README.txt
  A   zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt
  U   zc.zope3recipes/trunk/zc/zope3recipes/recipes.py
  U   zc.zope3recipes/trunk/zc/zope3recipes/tests.py
  A   zc.zope3recipes/trunk/zc/zope3recipes/winctl.py

-=-
Modified: zc.zope3recipes/trunk/README.txt
===================================================================
--- zc.zope3recipes/trunk/README.txt	2007-07-29 22:15:48 UTC (rev 78475)
+++ zc.zope3recipes/trunk/README.txt	2007-07-29 23:36:41 UTC (rev 78476)
@@ -10,10 +10,18 @@
 
 - Don't support package-includes
 
-Unfortunately, no Windows support at this time.
+Unfortunately, partial Windows support at this time. It works but it's alpha.
 
 .. contents::
 
+
+Trunk
+*****
+
+Added windows support. Included a custom ZDCmd and a suprocess wrapper 
+in winctl.py. Now the basic commands will also work on a windows box.
+
+
 Releases
 ********
 

Added: zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt	                        (rev 0)
+++ zc.zope3recipes/trunk/zc/zope3recipes/WINDOWS.txt	2007-07-29 23:36:41 UTC (rev 78476)
@@ -0,0 +1,1690 @@
+=============
+Zope3 Recipes
+=============
+
+This documentation describes the windows installation. See README for more
+general information.
+
+The 'application' recipe accepts the following options:
+
+site.zcml
+  The contents of site.zcml.
+
+eggs
+  The names of one or more eggs, with their dependencies that should
+  be included in the Python path of the generated scripts.
+
+
+Lets define some (bogus) eggs that we can use in our application:
+
+    >>> mkdir('demo1')
+    >>> write('demo1', 'setup.py',
+    ... '''
+    ... from setuptools import setup
+    ... setup(name = 'demo1')
+    ... ''')
+
+    >>> mkdir('demo2')
+    >>> write('demo2', 'setup.py',
+    ... '''
+    ... from setuptools import setup
+    ... setup(name = 'demo2', install_requires='demo1')
+    ... ''')
+
+We'll create a buildout.cfg file that defines our application:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = myapp
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:application
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ... ''' % globals())
+
+Now, Let's run the buildout and see what we get:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+The runzope script runs the Web server:
+
+    >>> cat('parts', 'myapp', 'runzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      ]
+    <BLANKLINE>
+    import zope.app.twisted.main
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zope.app.twisted.main.main()
+
+Here, unlike the above example the location path is not included
+in sys.path .  Similarly debugzope script is also changed:
+
+    >>> cat('parts', 'myapp', 'debugzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      '/zope3recipes',
+      ]
+    <BLANKLINE>
+    import zope.app.twisted.main
+    <BLANKLINE>
+    <BLANKLINE>
+    import zc.zope3recipes.debugzope
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zc.zope3recipes.debugzope.debug(main_module=zope.app.twisted.main)
+
+
+Building Zope 3 Applications (from Zope 3 checkouts/tarballs)
+=============================================================
+
+The 'app' recipe works much like the 'application' recipe.  It takes
+the same configuration options plus the following one:
+
+zope3
+  The name of a section defining a location option that gives the
+  location of a Zope installation.  This can be either a checkout or a
+  distribution.  If the location has a lib/python subdirectory, it is
+  treated as a distribution, otherwise, it must have a src
+  subdirectory and will be treated as a checkout. This option defaults
+  to "zope3".  And if location is empty, the application will run solely
+  from eggs.
+
+Let's look at an example.  We'll make a faux zope installation:
+
+    >>> zope3 = tmpdir('zope3')
+    >>> mkdir(zope3, 'src')
+
+Now we'll create a buildout.cfg file that defines our application:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = myapp
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ... ''' % globals())
+
+Note that our site.zcml file is very small.  It expect the application
+zcml to define almost everything.  In fact, a site.zcml file will often
+include just a single include directive.  We don't need to include the
+surrounding configure element, unless we want a namespace other than
+the zope namespace.  A configure directive will be included for us.
+
+Let's run the buildout and see what we get:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling myapp.
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+A directory is created in the parts directory for our application files:
+
+    >>> ls('parts')
+    d  myapp
+
+    >>> ls('parts', 'myapp')
+    -  debugzope-script.py
+    -  debugzope.exe
+    -  runzope-script.py
+    -  runzope.exe
+    -  site.zcml
+
+We get 3 files, two scripts and a site.zcml file.  The site.zcml file
+is just what we had in the buildout configuration:
+
+    >>> cat('parts', 'myapp', 'site.zcml')
+    <configure xmlns='http://namespaces.zope.org/zope'
+               xmlns:meta="http://namespaces.zope.org/meta"
+               >
+    <include package="demo2" />
+    <principal
+    id="zope.manager"
+    title="Manager"
+    login="jim"
+    password_manager="SHA1"
+    password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    />
+    <grant
+    role="zope.Manager"
+    principal="zope.manager"
+    />
+    </configure>
+
+Unfortunately, the leading whitespace is stripped from the
+configuration file lines.  This is a consequence of the way
+ConfigParser works.
+
+The runzope script runs the Web server:
+
+    >>> cat('parts', 'myapp', 'runzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      '/zope3/src',
+      ]
+    <BLANKLINE>
+    import zope.app.twisted.main
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zope.app.twisted.main.main()
+
+It includes in it's path the eggs we specified in the configuration
+file, along with their dependencies. Note that we haven't specified a
+configuration file.  When runzope is run, a -C option must be used to
+provide a configuration file.  -X options can also be provided to
+override configuration file options.
+
+The debugzope script provides access to the object system.  When
+debugzope is run, a -C option must be used to provide a configuration
+file.  -X options can also be provided to override configuration file
+options.  If run without any additional arguments, then an interactive
+interpreter will be started with databases specified in the
+configuration file opened and with the variable root set to the
+application root object.  The debugger variable is set to a Zope 3
+debugger.  If additional arguments are provided, then the first
+argument should be a script name and the remaining arguments are
+script arguments.  The script will be run with the root and debugger
+variables available as global variables.
+
+..
+
+    >>> cat('parts', 'myapp', 'debugzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      '/zope3/src',
+      '/zope3recipes',
+      ]
+    <BLANKLINE>
+    import zope.app.twisted.main
+    <BLANKLINE>
+    <BLANKLINE>
+    import zc.zope3recipes.debugzope
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zc.zope3recipes.debugzope.debug(main_module=zope.app.twisted.main)
+
+Note that the runzope shown above uses the default, twisted-based
+server components.  It's possible to specify which set of server
+components is used: the "servers" setting can be set to either
+"zserver" or "twisted".  For the application, this affects the runzope
+script; we'll see additional differences when we create instances of
+the application.
+
+Let's continue to use the twisted servers, but make the selection
+explicit:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = myapp
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... servers = twisted
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Updating myapp.
+
+Note that this is recognized as not being a change to the
+configuration; the messages say that myapp was updated, not
+uninstalled and then re-installed.
+
+The runzope script generated is identical to what we saw before:
+
+    >>> cat('parts', 'myapp', 'runzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      '/zope3/src',
+      ]
+    <BLANKLINE>
+    import zope.app.twisted.main
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zope.app.twisted.main.main()
+
+We can also specify the ZServer servers explicitly:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = myapp
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... servers = zserver
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling myapp.
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+The part has been re-installed, and the runzope script generated is
+different now.  Note that the main() function is imported from a
+different package this time:
+
+    >>> cat('parts', 'myapp', 'runzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      '/zope3/src',
+      ]
+    <BLANKLINE>
+    import zope.app.server.main
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zope.app.server.main.main()
+
+The debugzope script has also been modified to take this into account.
+
+    >>> cat('parts', 'myapp', 'debugzope')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/demo2',
+      '/sample-buildout/demo1',
+      '/zope3/src',
+      '/zope3recipes',
+      ]
+    <BLANKLINE>
+    import zope.app.server.main
+    <BLANKLINE>
+    <BLANKLINE>
+    import zc.zope3recipes.debugzope
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zc.zope3recipes.debugzope.debug(main_module=zope.app.server.main)
+
+
+Legacy Functional Testing Support
+---------------------------------
+
+Zope 3's functional testing support is based on zope.testing test
+layers. There is a default functional test layer that older functional
+tests use.  This layer loads the default configuration for the Zope
+application server.  It exists to provide support for older functional
+tests that were written before layers were added to the testing
+infrastructure.   The default testing layer has a number of
+disadvantages:
+
+- It loads configurations for a large number of packages.  This has
+  the potential to introduce testing dependency on all of these
+  packages.
+
+- It required a ftesting.zcml file and makes assumptions about where
+  that file is.  In particular, it assumes a location relative to the
+  current working directory when the test is run.
+
+Newer software and maintained software should use their own functional
+testing layers that use test-configuration files defined in packages.
+
+To support older packages that use the default layer, a ftesting.zcml
+option is provided.  If it is used, then the contents of the option
+are written to a ftesting.zcml file in the application.  In addition,
+an ftesting-base.zcml file is written that includes configuration
+traditionally found in a Zope 3 ftesting-base.zcml excluding reference
+to package-includes.
+
+If we modify our buildout to include an ftesting.zcml option:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = myapp
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... ftesting.zcml =
+    ...    <meta:provides feature="devmode" />
+    ...    <include file="ftesting-base.zcml" />
+    ...    <includeOverrides package="demo2" />
+    ... eggs = demo2
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling myapp.
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+
+We'll get ftesting.zcml files and ftesting-base.zcml files created in
+the application:
+
+    >>> cat('parts', 'myapp', 'ftesting.zcml')
+    <configure xmlns='http://namespaces.zope.org/zope'
+               xmlns:meta="http://namespaces.zope.org/meta"
+               >
+    <BLANKLINE>
+    <meta:provides feature="devmode" />
+    <include file="ftesting-base.zcml" />
+    <includeOverrides package="demo2" />
+    </configure>
+
+    >>> cat('parts', 'myapp', 'ftesting-base.zcml')
+    <BLANKLINE>
+    <configure
+       xmlns="http://namespaces.zope.org/zope"
+       i18n_domain="zope"
+       >
+      <include package="zope.app" />
+      <include package="zope.app" file="ftesting.zcml" />
+      <include package="zope.app.securitypolicy" file="meta.zcml" />
+      <include package="zope.app.securitypolicy" />
+      <securityPolicy
+        component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+      <role id="zope.Anonymous" title="Everybody"
+                     description="All users have this role implicitly" />
+      <role id="zope.Manager" title="Site Manager" />
+      <role id="zope.Member" title="Site Member" />
+      <grant permission="zope.View"
+                      role="zope.Anonymous" />
+      <grant permission="zope.app.dublincore.view"
+                      role="zope.Anonymous" />
+      <grantAll role="zope.Manager" />
+      <include package="zope.app.securitypolicy.tests"
+               file="functional.zcml" />
+      <unauthenticatedPrincipal
+          id="zope.anybody"
+          title="Unauthenticated User"
+          />
+      <unauthenticatedGroup
+        id="zope.Anybody"
+        title="Unauthenticated Users"
+        />
+      <authenticatedGroup
+        id="zope.Authenticated"
+        title="Authenticated Users"
+        />
+      <everybodyGroup
+        id="zope.Everybody"
+        title="All Users"
+        />
+      <principal
+          id="zope.mgr"
+          title="Manager"
+          login="mgr"
+          password="mgrpw" />
+      <principal
+          id="zope.globalmgr"
+          title="Manager"
+          login="globalmgr"
+          password="globalmgrpw" />
+      <grant role="zope.Manager" principal="zope.globalmgr" />
+    </configure>
+
+Defining Zope3 instances
+========================
+
+Having defined an application, we can define one or more instances of
+the application.  We do this using the zc.zope3recipes instance
+recipe.  The instance recipe has 2 modes, a development and a
+production mode.  We'll start with the development mode.  In
+development mode, a part directory will be created for each instance
+containing the instance's configuration files. This directory will
+also contain run-time files created by the instances, such as log
+files or zdaemon socket files.
+
+When defining an instance, we need to specify a zope.conf file.  The
+recipe can do most of the work for us.  Let's look at a a basic
+example:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+The application option names an application part.  The application
+part will be used to determine the location of the site.zcml file and
+the name of the control script to run.
+
+We specified a zope.conf option which contains a start at our final
+zope.conf file.  The recipe will add some bits we leave out.  The one
+thing we really need to have is a database definition.  We simply
+include the zconfig option from the database section, which we provide
+as a file storage part using the zc.recipe.filestorage recipe.  The
+filestorage recipe will create a directory to hold our database and
+compute a zconfig option that we can use in our instance section.
+
+Note that we've replaced the myapp part with the instance part.  The
+myapp part will be included by virtue of the reference from the
+instance part.
+
+Let's run the buildout, and see what we get:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling myapp.
+    Installing database.
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+    Installing instance.
+    Generated script '/sample-buildout/bin/instance'.
+
+We see that the database and myapp parts were included by virtue of
+being referenced from the instance part.
+
+We get new directories for our database and instance:
+
+    >>> ls('parts')
+    d  database
+    d  instance
+    d  myapp
+
+The instance directory contains zdaemon.conf and zope.conf files:
+
+    >>> ls('parts', 'instance')
+    -  zdaemon.conf
+    -  zope.conf
+
+Let's look at the zope.conf file that was generated:
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8080
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /sample-buildout/parts/instance/access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+This uses the twisted server types, since that's the default
+configuration for Zope 3.  If we specify use of the ZServer servers,
+the names of the server types are adjusted appropriately:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... servers = zserver
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Uninstalling myapp.
+    Updating database.
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+    Installing instance.
+
+
+The generated zope.conf file now uses the ZServer server components
+instead:
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8080
+      type WSGI-HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /sample-buildout/parts/instance/access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+The Twisted-based servers can also be specified explicitly:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... servers = twisted
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Uninstalling myapp.
+    Updating database.
+    Installing myapp.
+    Generated script '/sample-buildout/parts/myapp/runzope'.
+    Generated script '/sample-buildout/parts/myapp/debugzope'.
+    Installing instance.
+
+The generated zope.conf file now uses the Twisted server components
+once more:
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8080
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /sample-buildout/parts/instance/access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+It includes the database definition that we provided in the zope.conf
+option.  It has a site-definition option that names the site.zcml file
+from our application directory.
+
+We didn't specify any server or logging ZConfig sections, so some were
+generated for us.
+
+Note that, by default, the event-log output goes to standard output.
+We'll say more about that when we talk about the zdaemon
+configuration later.
+
+If we specify a server section ourselves:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ...    <server>
+    ...        type PostmortemDebuggingHTTP
+    ...        address 8080
+    ...    </server>
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Updating database.
+    Updating myapp.
+    Installing instance.
+
+Then the section (or sections) we provide will be used and new ones
+won't be added:
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8080
+      type PostmortemDebuggingHTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /sample-buildout/parts/instance/access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+If we just want to specify alternate ports or addresses, we can use
+the address option which accepts zero or more address specifications:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ... address = 8081 foo.com:8082
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Updating database.
+    Updating myapp.
+    Installing instance.
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8081
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <server>
+      address foo.com:8082
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /sample-buildout/parts/instance/access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+We can specify our own accesslog and eventlog configuration.  For
+example, to send the event-log output to a file and suppress the
+access log:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ...    <eventlog>
+    ...      <logfile>
+    ...        path ${buildout:parts-directory}/instance/event.log
+    ...        formatter zope.exceptions.log.Formatter
+    ...      </logfile>
+    ...    </eventlog>
+    ...    <accesslog>
+    ...    </accesslog>
+    ...
+    ... address = 8081
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Updating database.
+    Updating myapp.
+    Installing instance.
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path /sample-buildout/parts/instance/event.log
+      </logfile>
+    </eventlog>
+    <BLANKLINE>
+    <accesslog>
+    </accesslog>
+    <BLANKLINE>
+    <server>
+      address 8081
+      type HTTP
+    </server>
+
+Let's look at the zdaemon.conf file:
+
+    >>> cat('parts', 'instance', 'zdaemon.conf')
+    <runner>
+      daemon on
+      directory /sample-buildout/parts/instance
+      program /sample-buildout/parts/myapp/runzope -C /sample-buildout/parts/instance/zope.conf
+      socket-name /sample-buildout/parts/instance/zdaemon.sock
+      transcript /sample-buildout/parts/instance/z3.log
+    </runner>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        path /sample-buildout/parts/instance/z3.log
+      </logfile>
+    </eventlog>
+
+Here we see a fairly ordinary zdaemon.conf file.  The program option
+refers to the runzope script in our application directory.  The socket
+file, used for communication between the zdaemon command-line script
+and the zademon manager is placed in the instance directory.
+
+If you want to override any part of the generated zdaemon output,
+simply provide a zdaemon.conf option in your instance section:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ... address = 8081
+    ... zdaemon.conf =
+    ...     <runner>
+    ...       daemon off
+    ...       socket-name /sample-buildout/parts/instance/sock
+    ...       transcript /dev/null
+    ...     </runner>
+    ...     <eventlog>
+    ...     </eventlog>
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Updating database.
+    Updating myapp.
+    Installing instance.
+
+    >>> cat('parts', 'instance', 'zdaemon.conf')
+    <runner>
+      daemon off
+      directory /sample-buildout/parts/instance
+      program /sample-buildout/parts/myapp/runzope -C /sample-buildout/parts/instance/zope.conf
+      socket-name /sample-buildout/parts/instance/sock
+      transcript /dev/null
+    </runner>
+    <BLANKLINE>
+    <eventlog>
+    </eventlog>
+
+In addition to the configuration files, a control script is generated
+in the buildout bin directory:
+
+    >>> ls('bin')
+    -  buildout-script.py
+    -  buildout.exe
+    -  instance-script.py
+    -  instance.exe
+
+..
+
+    >>> cat('bin', 'instance')
+    #!/usr/local/bin/python2.4
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/eggs/zdaemon-2.0-py2.4.egg',
+      '/sample-buildout/eggs/setuptools-0.6-py2.4.egg',
+      '/sample-buildout/eggs/ZConfig-2.3-py2.4.egg',
+      '/zope3recipes',
+      ]
+    <BLANKLINE>
+    import zc.zope3recipes.winctl
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zc.zope3recipes.winctl.main([
+            '/sample-buildout/parts/myapp/debugzope',
+            '/sample-buildout/parts/instance/zope.conf',
+            '-C', '/sample-buildout/parts/instance/zdaemon.conf',
+            ]+sys.argv[1:]
+            )
+
+Some configuration sections can include a key multiple times; the ZEO
+client section works this way.  When a key is given multiple times,
+all values are included in the resulting configuration in the order in
+which they're give in the input::
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf =
+    ...     <zodb>
+    ...       <zeoclient>
+    ...         server 127.0.0.1:8001
+    ...         server 127.0.0.1:8002
+    ...       </zeoclient>
+    ...     </zodb>
+    ... address = 8081
+    ... zdaemon.conf =
+    ...     <runner>
+    ...       daemon off
+    ...       socket-name /sample-buildout/parts/instance/sock
+    ...       transcript /dev/null
+    ...     </runner>
+    ...     <eventlog>
+    ...     </eventlog>
+    ...
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Uninstalling database.
+    Updating myapp.
+    Installing instance.
+
+    >>> cat('parts', 'instance', 'zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <zeoclient>
+        server 127.0.0.1:8001
+        server 127.0.0.1:8002
+      </zeoclient>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8081
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /sample-buildout/parts/instance/access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+
+Log files
+---------
+
+The log file settings deserver some explanation.  The Zope event log
+only captures output from logging calls.  In particular, it doesn't
+capture startup errors written to standard error.  The zdaemon
+transcript log is very useful for capturing this output.  Without it,
+error written to standard error are lost when running as a daemon.
+The default Zope 3 configuration in the past was to write the Zope
+access and event log output to both files and standard output and to
+define a transcript log.  This had the effect that the transcript
+duplicated the contents of the event log and access logs, in addition
+to capturing other output.  This was space inefficient.
+
+This recipe's approach is to combine the zope and zdaemon event-log
+information as well as Zope error output into a single log file.  We
+do this by directing Zope's event log to standard output, where it is
+useful when running Zope in foreground mode and where it can be
+captured by the zdaemon transcript log.
+
+Unix Deployments
+----------------
+
+The instance recipe provides support for Unix deployments, as provided
+by the zc.recipe.deployment recipe.  A deployment part defines a number of
+options used by the instance recipe:
+
+etc-directory
+    The name of the directory where configuration files should be
+    placed.  This defaults to /etc/NAME, where NAME is the deployment
+    name.
+
+log-directory
+    The name of the directory where application instances should write
+    their log files.  This defaults to /var/log/NAME, where NAME is
+    the deployment name.
+
+run-directory
+    The name of the directory where application instances should put
+    their run-time files such as pid files and inter-process
+    communication socket files.  This defaults to /var/run/NAME, where
+    NAME is the deployment name.
+
+rc-directory
+    The name of the directory where run-control scripts should be
+    installed.
+
+user
+    The name of a user that processes should run as.
+
+The deployment recipe has to be run as root for various reasons, but
+we can create a faux deployment by providing a section with the needed
+data. Let's update our configuration to use a deployment.  We'll first
+create a faux installation root:
+
+    >>> root = tmpdir('root')
+    >>> mkdir(root, 'etc')
+    >>> mkdir(root, 'etc', 'myapp-run')
+    >>> mkdir(root, 'etc', 'init.d')
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ... address = 8081
+    ... deployment = myapp-run
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ...
+    ... [myapp-run]
+    ... etc-directory = %(root)s/etc/myapp-run
+    ... rc-directory = %(root)s/etc/init.d
+    ... log-directory = %(root)s/var/log/myapp-run
+    ... run-directory = %(root)s/var/run/myapp-run
+    ... user = zope
+    ... ''' % globals())
+
+Here we've added a deployment section, myapp-run, and added a
+deployment option to our instance part telling the instance recipe to
+use the deployment.  If we rerun the buildout:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Installing database.
+    Updating myapp.
+    Installing instance.
+    Generated script '/root/etc/init.d/myapp-run-instance'.
+
+The installer files will move.  We'll no-longer have the instance part:
+
+    >>> ls('parts')
+    d  database
+    d  myapp
+
+or the control script:
+
+    >>> ls('bin')
+    -  buildout-script.py
+    -  buildout.exe
+    -  instance-script.py
+    -  instance.exe
+
+Rather, we'll get our configuration files in the /etc/myapp-run directory:
+
+    >>> ls(root, 'etc', 'myapp-run')
+    -  instance-zdaemon.conf
+    -  instance-zope.conf
+
+Note that the instance name was added as a prefix to the file names,
+since we'll typically have additional instances in the deployment.
+
+The control script is in the init.d directory:
+
+    >>> ls(root, 'etc', 'init.d')
+    -  myapp-run-instance-script.py
+    -  myapp-run-instance.exe
+
+Note that the deployment name is added as a prefix of the control
+script name.
+
+The configuration files have changed to reflect the deployment
+locations:
+
+    >>> cat(root, 'etc', 'myapp-run', 'instance-zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8081
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /root/var/log/myapp-run/instance-access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+    >>> cat(root, 'etc', 'myapp-run', 'instance-zdaemon.conf')
+    <runner>
+      daemon on
+      directory /root/var/run/myapp-run
+      program /sample-buildout/parts/myapp/runzope -C /root/etc/myapp-run/instance-zope.conf
+      socket-name /root/var/run/myapp-run/instance-zdaemon.sock
+      transcript /root/var/log/myapp-run/instance-z3.log
+      user zope
+    </runner>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        path /root/var/log/myapp-run/instance-z3.log
+      </logfile>
+    </eventlog>
+
+Defining multiple similar instances
+-----------------------------------
+
+Often you want to define multiple instances that differ only by one or
+two options (e.g. an address).  The extends option lets you name a
+section from which default options should be loaded.  Any options in
+the source section not defined in the extending section are added to
+the extending section.
+
+Let's update our buildout to add a new instance:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = instance instance2
+    ...
+    ... [zope3]
+    ... location = %(zope3)s
+    ...
+    ... [myapp]
+    ... recipe = zc.zope3recipes:app
+    ... site.zcml = <include package="demo2" />
+    ...             <principal
+    ...                 id="zope.manager"
+    ...                 title="Manager"
+    ...                 login="jim"
+    ...                 password_manager="SHA1"
+    ...                 password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
+    ...                 />
+    ...             <grant
+    ...                 role="zope.Manager"
+    ...                 principal="zope.manager"
+    ...                 />
+    ... eggs = demo2
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = ${database:zconfig}
+    ... address = 8081
+    ... deployment = myapp-run
+    ...
+    ... [instance2]
+    ... recipe = zc.zope3recipes:instance
+    ... extends = instance
+    ... address = 8082
+    ...
+    ... [database]
+    ... recipe = zc.recipe.filestorage
+    ...
+    ... [myapp-run]
+    ... etc-directory = %(root)s/etc/myapp-run
+    ... rc-directory = %(root)s/etc/init.d
+    ... log-directory = %(root)s/var/log/myapp-run
+    ... run-directory = %(root)s/var/run/myapp-run
+    ... user = zope
+    ... ''' % globals())
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling instance.
+    Updating database.
+    Updating myapp.
+    Installing instance.
+    Installing instance2.
+    Generated script '/root/etc/init.d/myapp-run-instance2'.
+
+Now, we have the new instance configuration files:
+
+    >>> ls(root, 'etc', 'myapp-run')
+    -  instance-zdaemon.conf
+    -  instance-zope.conf
+    -  instance2-zdaemon.conf
+    -  instance2-zope.conf
+
+    >>> cat(root, 'etc', 'myapp-run', 'instance2-zope.conf')
+    site-definition /sample-buildout/parts/myapp/site.zcml
+    <BLANKLINE>
+    <zodb>
+      <filestorage>
+        path /sample-buildout/parts/database/Data.fs
+      </filestorage>
+    </zodb>
+    <BLANKLINE>
+    <server>
+      address 8082
+      type HTTP
+    </server>
+    <BLANKLINE>
+    <accesslog>
+      <logfile>
+        path /root/var/log/myapp-run/instance2-access.log
+      </logfile>
+    </accesslog>
+    <BLANKLINE>
+    <eventlog>
+      <logfile>
+        formatter zope.exceptions.log.Formatter
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+
+test_winctl
+-----------
+
+The winctl script is an extended version of zdaemon that provides an
+extra command, run.  Let's create a buildout that installs it as an
+ordinary script:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = winctl
+    ...
+    ... [winctl]
+    ... recipe = zc.recipe.egg
+    ... eggs = zc.zope3recipes
+    ...        zdaemon
+    ... entry-points = winctl=zc.zope3recipes.winctl:main
+    ... scripts = winctl
+    ... ''')
+
+    >>> print system(join('bin', 'buildout')),
+    Uninstalling instance2.
+    Uninstalling instance.
+    Uninstalling myapp.
+    Uninstalling database.
+    Installing winctl.
+    Generated script '/sample-buildout/bin/winctl'.
+
+We'll create a configuration file:
+
+    >>> write('doecho.bat', 'echo %*')
+
+    >>> write('conf',
+    ... '''
+    ... <runner>
+    ...   program doecho.bat hi
+    ... </runner>
+    ... ''')
+
+The configuration doesn't matter much. :)
+
+Unlike a normal zdaemon script, we have to pass two extra arguments, a
+script to run the zope debugger with, and the name of a zope
+configuration file. For demonstration purposes, we'll just use echo.
+
+confPath = os.path.abspath(conf)
+
+    >>> conf = join(sample_buildout, 'conf')
+    >>> echo = join(sample_buildout, 'doecho.bat')
+    >>> cmd = '%s %s zope.conf -C%s fg there' % (join('bin', 'winctl'), echo, conf)
+    >>> print system(cmd) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+    <BLANKLINE>
+    /sample-buildout>echo hi there
+    hi there
+    Zope3 started in forground:  doecho.bat hi there
+    <BLANKLINE>
+
+Notice:
+
+  - The first 2 arguments were ignored.
+
+  - It got the program, 'echo.bat hi', from the configuration file.
+
+  - We ran the program in the foreground, passing the extra argument, there.
+
+Now, if we use the run command, it will run the script we passed as
+the first argument:
+
+    >>> cmd = '%s %s zope.conf -C%s run there' % (join('bin', 'winctl'), echo, conf)
+    >>> print system(cmd) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+    <BLANKLINE>
+    /sample-buildout>echo -C zope.conf there
+    -C zope.conf there
+    Debug Zope3:  /sample-buildout/doecho.bat -C zope.conf there
+    Zope3 started in debug mode, pid=...
+    <BLANKLINE>
+
+debug is another name for run:
+
+    >>> cmd = '%s %s zope.conf -C%s debug there' % (join('bin', 'winctl'), echo, conf)
+    >>> print system(cmd) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+    <BLANKLINE>
+    /sample-buildout>echo -C zope.conf there
+    -C zope.conf there
+    Debug Zope3:  /sample-buildout/doecho.bat -C zope.conf there
+    Zope3 started in debug mode, pid=...
+    <BLANKLINE>
+
+test_sane_errors_from_recipe
+----------------------------
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = instance
+    ...
+    ... [myapp]
+    ... location = foo
+    ... ;; Note that 'servers' has a default value when the
+    ... ;; application recipe is involved.
+    ... servers = twisted
+    ...
+    ... [instance]
+    ... recipe = zc.zope3recipes:instance
+    ... application = myapp
+    ... zope.conf = 
+    ... ''')
+
+    >>> print system(join('bin', 'buildout')),
+    Couldn't find index page for 'zc.recipe.egg' (maybe misspelled?)
+    Uninstalling winctl.
+    Installing instance.
+    While:
+      Installing instance.
+    Error: No database sections have been defined.

Modified: zc.zope3recipes/trunk/zc/zope3recipes/recipes.py
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/recipes.py	2007-07-29 22:15:48 UTC (rev 78475)
+++ zc.zope3recipes/trunk/zc/zope3recipes/recipes.py	2007-07-29 23:36:41 UTC (rev 78476)
@@ -14,7 +14,7 @@
 """Collected Zope 3 recipes
 """
 
-import os, shutil
+import os, sys, shutil
 import zc.buildout
 import zc.recipe.egg
 import pkg_resources
@@ -30,6 +30,10 @@
     'zserver': ('zope.app.server.main',  'WSGI-HTTP'),
     }
 
+WIN = False
+if sys.platform[:3].lower() == "win":
+    WIN = True
+
 class Application(object):
     
     def __init__(self, buildout, name, options):
@@ -292,22 +296,40 @@
             open(zope_conf_path, 'w').write(str(zope_conf))
             open(zdaemon_conf_path, 'w').write(str(zdaemon_conf))
 
-            zc.buildout.easy_install.scripts(
-                [(rc, 'zc.zope3recipes.ctl', 'main')],
-                ws, options['executable'], options['bin-directory'],
-                extra_paths = [this_loc],
-                arguments = ('['
-                             '\n        %r,'
-                             '\n        %r,'
-                             '\n        %r, %r,'
-                             '\n        ]+sys.argv[1:]'
-                             '\n        '
-                             % (os.path.join(app_loc, 'debugzope'),
-                                zope_conf_path,
-                                '-C', zdaemon_conf_path,
-                                )
-                             ),
-                )
+            if WIN:
+                zc.buildout.easy_install.scripts(
+                    [(rc, 'zc.zope3recipes.winctl', 'main')],
+                    ws, options['executable'], options['bin-directory'],
+                    extra_paths = [this_loc],
+                    arguments = ('['
+                                 '\n        %r,'
+                                 '\n        %r,'
+                                 '\n        %r, %r,'
+                                 '\n        ]+sys.argv[1:]'
+                                 '\n        '
+                                 % (os.path.join(app_loc, 'debugzope'),
+                                    zope_conf_path,
+                                    '-C', zdaemon_conf_path,
+                                    )
+                                 ),
+                    )
+            else:
+                zc.buildout.easy_install.scripts(
+                    [(rc, 'zc.zope3recipes.ctl', 'main')],
+                    ws, options['executable'], options['bin-directory'],
+                    extra_paths = [this_loc],
+                    arguments = ('['
+                                 '\n        %r,'
+                                 '\n        %r,'
+                                 '\n        %r, %r,'
+                                 '\n        ]+sys.argv[1:]'
+                                 '\n        '
+                                 % (os.path.join(app_loc, 'debugzope'),
+                                    zope_conf_path,
+                                    '-C', zdaemon_conf_path,
+                                    )
+                                 ),
+                    )
 
             return creating
 

Modified: zc.zope3recipes/trunk/zc/zope3recipes/tests.py
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/tests.py	2007-07-29 22:15:48 UTC (rev 78475)
+++ zc.zope3recipes/trunk/zc/zope3recipes/tests.py	2007-07-29 23:36:41 UTC (rev 78476)
@@ -140,19 +140,22 @@
     ),
     ])
 
+
 def test_suite():
-    return unittest.TestSuite((
-        doctest.DocTestSuite(
+    suite = unittest.TestSuite()
+    if sys.platform[:3].lower() == "win":
+        suite.addTest(doctest.DocFileSuite('WINDOWS.txt',
             setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
-            checker=checker,
-            ),
-        doctest.DocFileSuite(
-            'README.txt',
+            checker=checker))
+    else:
+        suite.addTest(doctest.DocTestSuite(
             setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
-            checker=checker,
-            ),
-        
-        ))
+            checker=checker))
+        suite.addTest(doctest.DocFileSuite('README.txt',
+            setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
+            checker=checker))
 
+    return suite
+
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Added: zc.zope3recipes/trunk/zc/zope3recipes/winctl.py
===================================================================
--- zc.zope3recipes/trunk/zc/zope3recipes/winctl.py	                        (rev 0)
+++ zc.zope3recipes/trunk/zc/zope3recipes/winctl.py	2007-07-29 23:36:41 UTC (rev 78476)
@@ -0,0 +1,336 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Top-level controller for 'zopectl'.
+"""
+
+import os, sys
+import socket
+import subprocess
+import errno
+import zdaemon.zdctl
+from zdaemon.zdctl import ZDCmd
+from zdaemon.zdoptions import ZDOptions
+from ZConfig.components.logger.handlers import FileHandlerFactory
+from ZConfig.datatypes import existing_dirpath
+
+if sys.platform[:3].lower() == "win":
+    import win32api
+    import win32com.client
+    import win32process
+    from win32file import ReadFile, WriteFile
+    from win32pipe import PeekNamedPipe
+    import msvcrt
+    import select
+
+
+def getChildrenPidsOfPid(pid):
+    """Returns the children pids of a pid"""
+    wmi = win32com.client.GetObject('winmgmts:')
+    children = wmi.ExecQuery('Select * from win32_process where ParentProcessId=%s' % pid)
+    pids = []
+    for proc in children:
+        pids.append(proc.Properties_('ProcessId'))
+    return pids
+
+
+def getDaemonProcess(pid):
+    """Returns the daemon proces."""
+    wmi = win32com.client.GetObject('winmgmts:')
+    children = wmi.ExecQuery('Select * from win32_process where ProcessId=%s' % pid)
+    pids = []
+    for proc in children:
+        pids.append(proc.Properties_('ProcessId'))
+    if len(pids) == 1:
+        return pids[0]
+    return None
+
+
+def getZopeScriptProcess(pid):
+    """Returns the daemon proces."""
+    wmi = win32com.client.GetObject('winmgmts:')
+    children = wmi.ExecQuery('Select * from win32_process where ParentProcessId=%s' % pid)
+    pids = []
+    for proc in children:
+        pids.append(proc.Properties_('ProcessId'))
+    if len(pids) == 1:
+        return pids[0]
+    return None
+
+
+def kill(pid):
+    """kill function for Win32"""
+    handle = win32api.OpenProcess(1, 0, pid)
+    win32api.TerminateProcess(handle, 0)
+    win32api.CloseHandle(handle)
+
+
+def killAll(pid):
+    """Kill runzope and the python process started by runzope."""
+    pids = getChildrenPidsOfPid(pid)
+    for pid in pids:
+        kill(pid)
+
+
+class Popen(subprocess.Popen):
+    def recv(self, maxsize=None):
+        return self._recv('stdout', maxsize)
+    
+    def recv_err(self, maxsize=None):
+        return self._recv('stderr', maxsize)
+
+    def send_recv(self, input='', maxsize=None):
+        return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
+
+    def get_conn_maxsize(self, which, maxsize):
+        if maxsize is None:
+            maxsize = 1024
+        elif maxsize < 1:
+            maxsize = 1
+        return getattr(self, which), maxsize
+    
+    def _close(self, which):
+        getattr(self, which).close()
+        setattr(self, which, None)
+
+    def send(self, input):
+        if not self.stdin:
+            return None
+
+        try:
+            x = msvcrt.get_osfhandle(self.stdin.fileno())
+            (errCode, written) = WriteFile(x, input)
+        except ValueError:
+            return self._close('stdin')
+        except (subprocess.pywintypes.error, Exception), why:
+            if why[0] in (109, errno.ESHUTDOWN):
+                return self._close('stdin')
+            raise
+
+        return written
+
+    def _recv(self, which, maxsize):
+        conn, maxsize = self.get_conn_maxsize(which, maxsize)
+        if conn is None:
+            return None
+        
+        try:
+            x = msvcrt.get_osfhandle(conn.fileno())
+            (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
+            if maxsize < nAvail:
+                nAvail = maxsize
+            if nAvail > 0:
+                (errCode, read) = ReadFile(x, nAvail, None)
+        except ValueError:
+            return self._close(which)
+        except (subprocess.pywintypes.error, Exception), why:
+            if why[0] in (109, errno.ESHUTDOWN):
+                return self._close(which)
+            raise
+        
+        if self.universal_newlines:
+            read = self._translate_newlines(read)
+        return read
+
+
+# TODO: implement a win service script and add install and remove methods for
+# the service.
+# Also implement start and stop methods controlling the windows service daemon
+# if the service is installed. Use the allready defined methods if no service
+# is installed.
+
+#class ZopeCtlOptions(ZDOptions):
+#    """Zope controller options."""
+#
+#    def realize(self, *args, **kwds):
+#        ZopeCtlOptions.realize(self, *args, **kwds)
+#
+#        # Add the path to the zopeservice.py script, which is needed for
+#        # some of the Windows specific commands
+#        servicescript = os.path.join(self.directory, 'bin', 'zopeservice.py')
+#        self.servicescript = '"%s" %s' % (self.python, servicescript)
+
+
+class ZopectlCmd(zdaemon.zdctl.ZDCmd):
+    """Manages Zope start and stop etc. 
+    
+    This implementation uses a subprocess for execute the given python script. 
+
+    There is also a windows service daemon which can get installed with the 
+    install and remove  methods. If a windows service is installed, the 
+    controller dispatches the start and stop commands to the service. If no 
+    service is installed, we use the subprocess instead.
+    """
+
+    zd_up = 0
+    zd_pid = 0
+    zd_status = None
+    proc = None
+
+    def do_install(self, arg):
+        """Install the windows service."""
+        #program = "%s install" % self.options.servicescript
+        #print program
+        #subprocess.Popen(program)
+        print "Not implemented right now"
+
+    def help_install(self):
+        print "install -- Installs Zope3 as a Windows service."
+        print "Not implemented right now"
+
+    def do_remove(self, arg):
+        """Remove the windows service."""
+        #program = "%s remove" % self.options.servicescript
+        #print program
+        #subprocess.Popen(program)
+        print "Not implemented right now"
+
+    def help_remove(self):
+        print "remove -- Removes the Zope3 Windows service."
+        print "Not implemented right now"
+
+    def do_debug(self, arg):
+        # Start the process
+        if self.zd_pid:
+            print "Zope3 already running; pid=%d" % self.zd_pid
+            return
+        args  = " ".join(self.options.args[1:])
+        cmds = [self._debugzope, '-C', self._zope_conf, args]
+        program = " ".join(cmds)
+        print "Debug Zope3: ", program
+        self.proc = Popen(program)
+        self.zd_pid = self.proc.pid
+        self.zd_up = 1
+        self.awhile(lambda: self.zd_pid,
+                    "Zope3 started in debug mode, pid=%(zd_pid)d")
+
+    def help_debug(self):
+        print "debug -- Initialize the Zope application, providing a"
+        print "         debugger object at an interactive Python prompt."
+
+    do_run = do_debug
+
+    def help_run(self):
+        print "run <script> [args] -- run a Python script with the Zope "
+        print "                       environment set up.  The script has "
+        print "                       'root' exposed as the root container."
+
+    def send_action(self, action):
+        """Dispatch actions to subprocess."""
+        try:
+            self.proc.send(action + "\n")
+            response = ""
+            while 1:
+                data = self.proc.recv(1000)
+                if not data:
+                    break
+                response += data
+            return response
+        except (subprocess.pywintypes.error, Exception):
+            return None
+
+    def get_status(self):
+        if not self.zd_pid:
+            return None
+        proc = getDaemonProcess(self.zd_pid)
+        if proc is not None:
+            self.zd_up = 1
+        proc = getZopeScriptProcess(self.zd_pid)
+        if proc is not None:
+            self.zd_status = "Zope3 is running"
+            return self.zd_status
+        return None
+
+    def do_stop(self, arg):
+        # Stop the Windows process
+        if not self.zd_pid:
+            print "Zope3 is not running"
+            return
+        
+        killAll(self.zd_pid)
+        self.zd_up = 0
+        self.zd_pid = 0
+        cpid = win32process.GetCurrentProcessId()
+        self.awhile(lambda: not getChildrenPidsOfPid(cpid), "Zope3 stopped")
+
+    def do_kill(self, arg):
+        self.do_stop(arg)
+
+    def do_restart(self, arg):
+        pid = self.zd_pid
+        if self.zd_pid:
+            self.do_stop(arg)
+            self.do_start(arg)
+        else:
+            self.do_start(arg)
+        self.awhile(lambda: self.zd_pid not in (0, pid),
+                    "Zope3 restarted, pid=%(zd_pid)d")
+
+    def show_options(self):
+        print "winctl options:"
+        print "configfile:  ", repr(self.options.configfile)
+        print "python:      ", repr(self.options.python)
+        print "program:     ", repr(self.options.program)
+        print "user:        ", repr(self.options.user)
+        print "directory:   ", repr(self.options.directory)
+        print "logfile:     ", repr(self.options.logfile)
+
+    def do_start(self, arg):
+        # Start the process
+        if self.zd_pid:
+            print "Zope3 already running; pid=%d" % self.zd_pid
+            return
+        program = " ".join(self.options.program)
+        print "Starting Zope3: ", program
+        self.proc = Popen(program)
+        self.zd_pid = self.proc.pid
+        self.zd_up = 1
+        self.awhile(lambda: self.zd_pid,
+                    "Zope3 started, pid=%(zd_pid)d")
+
+    def do_fg(self, arg):
+        self.do_foreground(arg)
+
+    def help_fg(self):
+        self.help_foreground()
+
+    def do_foreground(self, arg):
+        # Start the process
+        if self.zd_pid:
+            print "To run the Zope3 in the foreground, please stop it first."
+            return
+
+        program = self.options.program + self.options.args[1:]
+        program = " ".join(program)
+        sys.stdout.flush()
+        try:
+            subprocess.call(program)
+            print "Zope3 started in forground: ", program
+        except KeyboardInterrupt:
+            print
+
+    def help_foreground(self):
+        print "foreground -- Run the program in the forground."
+        print "fg -- an alias for foreground."
+        print "Not supported on windows will call start"
+
+
+def main(args=None):
+    if args is None:
+        args = sys.argv[1:]
+    
+    class Cmd(ZopectlCmd):
+        _debugzope = args.pop(0)
+        _zope_conf = args.pop(0)
+
+    zdaemon.zdctl.main(args, None, Cmd)



More information about the Checkins mailing list