[Checkins] SVN: z3c.recipe.winservice/trunk/ New feature: ``runscript`` and ``parameters``.

Adam Groszer agroszer at gmail.com
Tue Oct 19 08:26:53 EDT 2010


Log message for revision 117754:
  New feature: ``runscript`` and ``parameters``.
  More docs, tests. Some fixes. See CHANGES.txt

Changed:
  U   z3c.recipe.winservice/trunk/CHANGES.txt
  U   z3c.recipe.winservice/trunk/README.txt
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/tests.py
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in

-=-
Modified: z3c.recipe.winservice/trunk/CHANGES.txt
===================================================================
--- z3c.recipe.winservice/trunk/CHANGES.txt	2010-10-19 08:53:56 UTC (rev 117753)
+++ z3c.recipe.winservice/trunk/CHANGES.txt	2010-10-19 12:26:52 UTC (rev 117754)
@@ -5,9 +5,20 @@
 Version 0.7.0 (unreleased)
 --------------------------
 
+- New feature: ``runscript`` and ``parameters``.
+  With those, this recipe is now capable of making any executable or
+  python script a service.
+
+- Added more docs and tests.
+
+- Remove compiled winservice.pyc and pyo if they exist.
+
+- Most folder calculation moved to the recipe installer.
+  winservice.py gets constants, that makes it easier to see what happens.
+
 - Updated tests to be more tolerant to different Python versions.
 
-- Using Python's ``doctest`` module instead of depreacted
+- Using Python's ``doctest`` module instead of deprecated
   ``zope.testing.doctest``.
 
 Version 0.6.3 (2009-11-02)

Modified: z3c.recipe.winservice/trunk/README.txt
===================================================================
--- z3c.recipe.winservice/trunk/README.txt	2010-10-19 08:53:56 UTC (rev 117753)
+++ z3c.recipe.winservice/trunk/README.txt	2010-10-19 12:26:52 UTC (rev 117754)
@@ -1,2 +1,49 @@
-The ``z3c.recipe.winservice:service`` generates a service scripts and 
-configuration files for starting a egg based Zope 3 setup as a windows service.
+=====================
+z3c.recipe.winservice
+=====================
+
+This recipe offers windows service installation support.
+
+The 'service' recipe installes the required scripts and files which can be
+used to install a windows service.
+
+Using the ``runscript`` option it is able to make any executable a service.
+
+
+Options
+*******
+
+The 'service' recipe accepts the following options:
+
+name
+  The windows service name option.
+
+description
+  The windows service description option.
+
+runzope
+  The script name which gets run by the winservice.
+  If the script name contains any path, it's taken as it is, otherwise the
+  buildout bin folder is prepended. winservice will check on install if this
+  script exists.
+  The install takes care of adding ``-script.py`` if necessary.
+  This script can get setup for exmaple with the z3c.recipe.dev.app recipe.
+
+runscript
+  The script (.py) or executable (.exe) name to be run by the winservice.
+  The value will get NO treatment, you need to pass an exact specification.
+  winservice will check on install if this script/exe exists.
+  Use this option OR runzope, but never both.
+
+parameters
+  This value will get passed to the script (runzope or runscript) as a
+  parameter. The value will get NO treatment, you need to take care of adding
+  any quotes if necessary.
+
+debug
+  Adding this option to the recipe wraps the whole script to run into
+  a catch all except that logs the exception to the windows event log.
+  This is good for debugging weird exceptions that occur before the Zope
+  logging system is in place.
+  This does not work if runscript is an executable.
+

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt	2010-10-19 08:53:56 UTC (rev 117753)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt	2010-10-19 12:26:52 UTC (rev 117754)
@@ -1,420 +1,659 @@
 =====================
-Z3 development recipe
+z3c.recipe.winservice
 =====================
 
-z3c.recipe.winservice
----------------------
+This recipe offers windows service installation support.
 
-This Zope 3 recipes offers windows service installation support.
 
-The 'service' recipe installes the required scripts and files whihc can be
-used for install a windwos service.
-
-
-Options
-*******
-
-The 'service' recipe accepts the following options:
-
-name
-  The windows service name option.
-
-description
-  The windows service description option.
-
-runzope
-  The script name which get used by the winservice. This script must exist in
-  the bin folder and before we run this recipe exist. This script can get setup
-  with the z3c.recipe.dev.app recipe.
-
-
 Test
 ****
 
 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('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')
-  ... ''')
+    >>> mkdir('demo2')
+    >>> write('demo2', 'setup.py',
+    ... '''
+    ... from setuptools import setup
+    ... setup(name = 'demo2', install_requires='demo1')
+    ... ''')
 
 We also need to setup an application. This is normaly done with the app recipe
 defined in z3c.recipe.dev:
 
-  >>> write('bin', 'app-script.py',
-  ... '''
-  ... dummy start script
-  ... ''')
+    >>> write('bin', 'app-script.py',
+    ... '''
+    ... dummy start script
+    ... ''')
 
 
 Now check if the setup was correct. This is true if we have already the
 buildout files and our fake app-script.py installed:
 
-  >>> ls('bin')
-  -  app-script.py
-  -  buildout-script.py
-  -  buildout.exe
+    >>> ls('bin')
+    -  app-script.py
+    -  buildout-script.py
+    -  buildout.exe
 
 We'll create a `buildout.cfg` file that defines our winservice configuration:
 
-  >>> write('buildout.cfg',
-  ... '''
-  ... [buildout]
-  ... develop = demo1 demo2
-  ... parts = winservice
-  ...
-  ... [winservice]
-  ... recipe = z3c.recipe.winservice:service
-  ... name = Zope 3 Windows Service
-  ... description = Zope 3 Windows Service description
-  ... runzope = app
-  ...
-  ... ''' % globals())
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = winservice
+    ...
+    ... [winservice]
+    ... recipe = z3c.recipe.winservice:service
+    ... name = Zope 3 Windows Service
+    ... description = Zope 3 Windows Service description
+    ... runzope = app
+    ...
+    ... ''' % 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 winservice.
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Installing winservice.
 
 
 The bin folder contains the windows service installer script:
 
-  >>> ls('bin')
-  -  app-script.py
-  -  buildout-script.py
-  -  buildout.exe
-  -  winservice.py
+    >>> ls('bin')
+    -  app-script.py
+    -  buildout-script.py
+    -  buildout.exe
+    -  winservice.py
 
 The winservice.py contains the service setup for our zope windows service:
 
-  >>> cat('bin', 'winservice.py')
-  ##############################################################################
-  #
-  # Copyright (c) 2008 Zope Foundation 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.
-  #
-  ##############################################################################
-  """A Zope Windows NT service frontend.
-  <BLANKLINE>
-  Usage:
-  <BLANKLINE>
-    Installation
-  <BLANKLINE>
-      You can manually install, uninstall the service from the commandline.
-  <BLANKLINE>
-        python bin\winservice.py [options] install|update|remove|start [...]
-             |stop|restart [...]|debug [...]
-  <BLANKLINE>
-      Options for 'install' and 'update' commands only:
-  <BLANKLINE>
-       --username domain\username : The Username the service is to run
-                                    under
-  <BLANKLINE>
-       --password password : The password for the username
-  <BLANKLINE>
-       --startup [manual|auto|disabled] : How the service starts,
-                                          default = manual
-  <BLANKLINE>
-      Commands
-  <BLANKLINE>
-        install : Installs the service
-  <BLANKLINE>
-        update : Updates the service, use this when you change
-                 the service class implementation
-  <BLANKLINE>
-        remove : Removes the service
-  <BLANKLINE>
-        start : Starts the service, this can also be done from the
-                services control panel
-  <BLANKLINE>
-        stop : Stops the service, this can also be done from the
-               services control panel
-  <BLANKLINE>
-        restart : Restarts the service
-  <BLANKLINE>
-        debug : Runs the service in debug mode
-  <BLANKLINE>
-      You can view the usage options by running ntservice.py without any
-      arguments.
-  <BLANKLINE>
-      Note: you may have to register the Python service program first,
-  <BLANKLINE>
-        win32\PythonService.exe /register
-  <BLANKLINE>
-    Starting Zope
-  <BLANKLINE>
-      Start Zope by clicking the 'start' button in the services control
-      panel. You can set Zope to automatically start at boot time by
-      choosing 'Auto' startup by clicking the 'statup' button.
-  <BLANKLINE>
-    Stopping Zope
-  <BLANKLINE>
-      Stop Zope by clicking the 'stop' button in the services control
-      panel. You can also stop Zope through the web by going to the
-      Zope control panel and by clicking 'Shutdown'.
-  <BLANKLINE>
-    Event logging
-  <BLANKLINE>
-      Zope events are logged to the NT application event log. Use the
-      event viewer to keep track of Zope events.
-  <BLANKLINE>
-  """
-  <BLANKLINE>
-  import sys, os, time
-  import pywintypes
-  import win32serviceutil
-  import win32service
-  import win32event
-  import win32process
-  <BLANKLINE>
-  # these are replacements from winservice recipe
-  PYTHON = r'...'
-  PYTHONDIR = os.path.split(PYTHON)[0]
-  PYTHONSERVICE_EXE = r'%s\Lib\site-packages\win32\pythonservice.exe' % PYTHONDIR
-  TOSTART = r'/sample-buildout/bin/app-script.py'
-  SERVICE_NAME = '...sample_buildout_bin_app_script_py'
-  SERVICE_DISPLAY_NAME = r'Zope 3 Windows Service'
-  SERVICE_DESCRIPTION = r'Zope 3 Windows Service description'
-  INSTANCE_HOME = os.path.dirname(os.path.dirname(TOSTART))
-  <BLANKLINE>
-  <BLANKLINE>
-  # the max seconds we're allowed to spend backing off
-  BACKOFF_MAX = 300
-  # if the process runs successfully for more than BACKOFF_CLEAR_TIME
-  # seconds, we reset the backoff stats to their initial values
-  BACKOFF_CLEAR_TIME = 30
-  # the initial backoff interval (the amount of time we wait to restart
-  # a dead process)
-  BACKOFF_INITIAL_INTERVAL = 5
-  <BLANKLINE>
-  <BLANKLINE>
-  class NullOutput:
-      """A stdout / stderr replacement that discards everything."""
-  <BLANKLINE>
-      def noop(self, *args, **kw):
-          pass
-  <BLANKLINE>
-      write = writelines = close = seek = flush = truncate = noop
-  <BLANKLINE>
-      def __iter__(self):
-          return self
-  <BLANKLINE>
-      def next(self):
-          raise StopIteration
-  <BLANKLINE>
-      def isatty(self):
-          return False
-  <BLANKLINE>
-      def tell(self):
-          return 0
-  <BLANKLINE>
-      def read(self, *args, **kw):
-          return ''
-  <BLANKLINE>
-      readline = read
-  <BLANKLINE>
-      def readlines(self, *args, **kw):
-          return []
-  <BLANKLINE>
-  <BLANKLINE>
-  class Zope3Service(win32serviceutil.ServiceFramework):
-      """ A class representing a Windows NT service that can manage an
-      instance-home-based Zope/ZEO/ZRS processes """
-  <BLANKLINE>
-      # The PythonService model requires that an actual on-disk class declaration
-      # represent a single service.  Thus, the below definition of start_cmd,
-      # must be overridden in a subclass in a file within the instance home for
-      # each instance.  The below-defined start_cmd (and _svc_display_name_
-      # and _svc_name_) are just examples.
-  <BLANKLINE>
-      _svc_name_ = SERVICE_NAME
-      _svc_display_name_ = SERVICE_DISPLAY_NAME
-      _svc_description_ = SERVICE_DESCRIPTION
-  <BLANKLINE>
-      _exe_name_ = PYTHONSERVICE_EXE
-      start_cmd = ''
-  <BLANKLINE>
-      def __init__(self, args):
-          if not os.path.exists(PYTHON):
-              raise OSError("%s does not exist" % PYTHON)
-          if not os.path.exists(TOSTART):
-              raise OSError("%s does not exist" % TOSTART)
-  <BLANKLINE>
-          self.start_cmd = '"%s" "%s"' % (PYTHON, TOSTART)
-  <BLANKLINE>
-          win32serviceutil.ServiceFramework.__init__(self, args)
-          # Create an event which we will use to wait on.
-          # The "service stop" request will set this event.
-          self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
-          self.redirectOutput()
-  <BLANKLINE>
-      def redirectOutput(self):
-          sys.stdout.close()
-          sys.stderr.close()
-          sys.stdout = NullOutput()
-          sys.stderr = NullOutput()
-  <BLANKLINE>
-      def SvcStop(self):
-          # Before we do anything, tell the SCM we are starting the stop process.
-          self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-  <BLANKLINE>
-          # TODO:  This TerminateProcess call doesn't make much sense:  it's
-          # doing a hard kill _first_, never giving the process a chance to
-          # shut down cleanly.  Compare to current Zope2 service code, which
-          # uses Windows events to give the process a chance to shut down
-          # cleanly, doing a hard kill only if that doesn't succeed.
-  <BLANKLINE>
-          # stop the process if necessary
-          try:
-              win32process.TerminateProcess(self.hZope, 0)
-          except pywintypes.error:
-              # the process may already have been terminated
-              pass
-          # And set my event.
-          win32event.SetEvent(self.hWaitStop)
-  <BLANKLINE>
-      # SvcStop only gets triggered when the user explictly stops (or restarts)
-      # the service.  To shut the service down cleanly when Windows is shutting
-      # down, we also need to hook SvcShutdown.
-      SvcShutdown = SvcStop
-  <BLANKLINE>
-      def createProcess(self, cmd):
-          #need to set current dir to INSTANCE_HOME otherwise pkg_resources will
-          #be pissed (in a combination with paster)
-          return win32process.CreateProcess(
-              None, cmd, None, None, 0, 0, None,
-              INSTANCE_HOME,
-              win32process.STARTUPINFO())
-  <BLANKLINE>
-      def SvcDoRun(self):
-          # indicate to Zope that the process is daemon managed (restartable)
-          os.environ['ZMANAGED'] = '1'
-  <BLANKLINE>
-          # daemon behavior:  we want to to restart the process if it
-          # dies, but if it dies too many times, we need to give up.
-  <BLANKLINE>
-          # we use a simple backoff algorithm to determine whether
-          # we should try to restart a dead process:  for each
-          # time the process dies unexpectedly, we wait some number of
-          # seconds to restart it, as determined by the backoff interval,
-          # which doubles each time the process dies.  if we exceed
-          # BACKOFF_MAX seconds in cumulative backoff time, we give up.
-          # at any time if we successfully run the process for more thab
-          # BACKOFF_CLEAR_TIME seconds, the backoff stats are reset.
-  <BLANKLINE>
-          # the initial number of seconds between process start attempts
-          backoff_interval = BACKOFF_INITIAL_INTERVAL
-          # the cumulative backoff seconds counter
-          backoff_cumulative = 0
-  <BLANKLINE>
-          import servicemanager
-  <BLANKLINE>
-          # log a service started message
-          servicemanager.LogMsg(
-              servicemanager.EVENTLOG_INFORMATION_TYPE,
-              servicemanager.PYS_SERVICE_STARTED,
-              (self._svc_name_, ' (%s)' % self._svc_display_name_))
-  <BLANKLINE>
-          while 1:
-              start_time = time.time()
-              info = self.createProcess(self.start_cmd)
-              self.hZope = info[0] # the pid
-              if backoff_interval > BACKOFF_INITIAL_INTERVAL:
-                  # if we're in a backoff state, log a message about
-                  # starting a new process
-                  servicemanager.LogInfoMsg(
-                      '%s (%s): recovering from died process, new process '
-                      'started' % (self._svc_name_, self._svc_display_name_)
-                      )
-              rc = win32event.WaitForMultipleObjects(
-                  (self.hWaitStop, self.hZope), 0, win32event.INFINITE)
-              if rc == win32event.WAIT_OBJECT_0:
-                  # user sent a stop service request
-                  self.SvcStop()
-                  break
-              else:
-                  # user did not send a service stop request, but
-                  # the process died; this may be an error condition
-                  status = win32process.GetExitCodeProcess(self.hZope)
-                  if status == 0:
-                      # the user shut the process down from the web
-                      # interface (or it otherwise exited cleanly)
-                      break
-                  else:
-                      # this was an abormal shutdown.  if we can, we want to
-                      # restart the process but if it seems hopeless,
-                      # don't restart an infinite number of times.
-                      if backoff_cumulative > BACKOFF_MAX:
-                          # it's hopeless
-                          servicemanager.LogErrorMsg(
-                            '%s (%s): process could not be restarted due to max '
-                            'restart attempts exceeded' % (
-                              self._svc_display_name_, self._svc_name_
-                            ))
-                          self.SvcStop()
-                          break
-                      servicemanager.LogWarningMsg(
-                         '%s (%s): process died unexpectedly.  Will attempt '
-                         'restart after %s seconds.' % (
-                              self._svc_name_, self._svc_display_name_,
-                              backoff_interval
-                              )
-                         )
-                      # if BACKOFF_CLEAR_TIME seconds have elapsed since we last
-                      # started the process, reset the backoff interval
-                      # and the cumulative backoff time to their original
-                      # states
-                      if time.time() - start_time > BACKOFF_CLEAR_TIME:
-                          backoff_interval = BACKOFF_INITIAL_INTERVAL
-                          backoff_cumulative = 0
-                      # we sleep for the backoff interval.  since this is async
-                      # code, it would be better done by sending and
-                      # catching a timed event (a service
-                      # stop request will need to wait for us to stop sleeping),
-                      # but this works well enough for me.
-                      time.sleep(backoff_interval)
-                      # update backoff_cumulative with the time we spent
-                      # backing off.
-                      backoff_cumulative = backoff_cumulative + backoff_interval
-                      # bump the backoff interval up by 2* the last interval
-                      backoff_interval = backoff_interval * 2
-  <BLANKLINE>
-                      # loop and try to restart the process
-  <BLANKLINE>
-          # log a service stopped message
-          servicemanager.LogMsg(
-              servicemanager.EVENTLOG_INFORMATION_TYPE,
-              servicemanager.PYS_SERVICE_STOPPED,
-              (self._svc_name_, ' (%s) ' % self._svc_display_name_))
-  <BLANKLINE>
-  <BLANKLINE>
-  if __name__ == '__main__':
-      import win32serviceutil
-      if os.path.exists(PYTHONSERVICE_EXE):
-          # This ensures that pythonservice.exe is registered...
-          os.system('"%s" -register' % PYTHONSERVICE_EXE)
-      win32serviceutil.HandleCommandLine(Zope3Service)
+    >>> cat('bin', 'winservice.py')
+    ##############################################################################
+    #
+    # Copyright (c) 2008 Zope Foundation 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.
+    #
+    ##############################################################################
+    """A Zope Windows NT service frontend.
+    <BLANKLINE>
+    Usage:
+    <BLANKLINE>
+      Installation
+    <BLANKLINE>
+        You can manually install, uninstall the service from the commandline.
+    <BLANKLINE>
+          python bin\winservice.py [options] install|update|remove|start [...]
+               |stop|restart [...]|debug [...]
+    <BLANKLINE>
+        Options for 'install' and 'update' commands only:
+    <BLANKLINE>
+         --username domain\username : The Username the service is to run
+                                      under
+    <BLANKLINE>
+         --password password : The password for the username
+    <BLANKLINE>
+         --startup [manual|auto|disabled] : How the service starts,
+                                            default = manual
+    <BLANKLINE>
+        Commands
+    <BLANKLINE>
+          install : Installs the service
+    <BLANKLINE>
+          update : Updates the service, use this when you change
+                   the service class implementation
+    <BLANKLINE>
+          remove : Removes the service
+    <BLANKLINE>
+          start : Starts the service, this can also be done from the
+                  services control panel
+    <BLANKLINE>
+          stop : Stops the service, this can also be done from the
+                 services control panel
+    <BLANKLINE>
+          restart : Restarts the service
+    <BLANKLINE>
+          debug : Runs the service in debug mode
+    <BLANKLINE>
+        You can view the usage options by running ntservice.py without any
+        arguments.
+    <BLANKLINE>
+        Note: you may have to register the Python service program first,
+    <BLANKLINE>
+          win32\PythonService.exe /register
+    <BLANKLINE>
+      Starting Zope
+    <BLANKLINE>
+        Start Zope by clicking the 'start' button in the services control
+        panel. You can set Zope to automatically start at boot time by
+        choosing 'Auto' startup by clicking the 'statup' button.
+    <BLANKLINE>
+      Stopping Zope
+    <BLANKLINE>
+        Stop Zope by clicking the 'stop' button in the services control
+        panel. You can also stop Zope through the web by going to the
+        Zope control panel and by clicking 'Shutdown'.
+    <BLANKLINE>
+      Event logging
+    <BLANKLINE>
+        Zope events are logged to the NT application event log. Use the
+        event viewer to keep track of Zope events.
+    <BLANKLINE>
+    """
+    <BLANKLINE>
+    import sys, os, time
+    import pywintypes
+    import win32serviceutil
+    import win32service
+    import win32event
+    import win32process
+    <BLANKLINE>
+    # these are replacements from winservice recipe
+    PYTHONSERVICE_EXE = r'...\Lib\site-packages\win32\pythonservice.exe'
+    PYTHON = r'...\python.exe'
+    TOSTART = r'/sample-buildout/bin/app-script.py'
+    PARAMETERS = r''
+    SERVICE_NAME = '...sample_buildout_bin_app_script_py'
+    SERVICE_DISPLAY_NAME = r'Zope 3 Windows Service'
+    SERVICE_DESCRIPTION = r'Zope 3 Windows Service description'
+    INSTANCE_HOME = r'/sample-buildout'
+    <BLANKLINE>
+    <BLANKLINE>
+    # the max seconds we're allowed to spend backing off
+    BACKOFF_MAX = 300
+    # if the process runs successfully for more than BACKOFF_CLEAR_TIME
+    # seconds, we reset the backoff stats to their initial values
+    BACKOFF_CLEAR_TIME = 30
+    # the initial backoff interval (the amount of time we wait to restart
+    # a dead process)
+    BACKOFF_INITIAL_INTERVAL = 5
+    <BLANKLINE>
+    <BLANKLINE>
+    class NullOutput:
+        """A stdout / stderr replacement that discards everything."""
+    <BLANKLINE>
+        def noop(self, *args, **kw):
+            pass
+    <BLANKLINE>
+        write = writelines = close = seek = flush = truncate = noop
+    <BLANKLINE>
+        def __iter__(self):
+            return self
+    <BLANKLINE>
+        def next(self):
+            raise StopIteration
+    <BLANKLINE>
+        def isatty(self):
+            return False
+    <BLANKLINE>
+        def tell(self):
+            return 0
+    <BLANKLINE>
+        def read(self, *args, **kw):
+            return ''
+    <BLANKLINE>
+        readline = read
+    <BLANKLINE>
+        def readlines(self, *args, **kw):
+            return []
+    <BLANKLINE>
+    <BLANKLINE>
+    class Zope3Service(win32serviceutil.ServiceFramework):
+        """ A class representing a Windows NT service that can manage an
+        instance-home-based Zope/ZEO/ZRS processes """
+    <BLANKLINE>
+        # The PythonService model requires that an actual on-disk class declaration
+        # represent a single service.  Thus, the below definition of start_cmd,
+        # must be overridden in a subclass in a file within the instance home for
+        # each instance.  The below-defined start_cmd (and _svc_display_name_
+        # and _svc_name_) are just examples.
+    <BLANKLINE>
+        _svc_name_ = SERVICE_NAME
+        _svc_display_name_ = SERVICE_DISPLAY_NAME
+        _svc_description_ = SERVICE_DESCRIPTION
+    <BLANKLINE>
+        _exe_name_ = PYTHONSERVICE_EXE
+        start_cmd = ''
+    <BLANKLINE>
+        def __init__(self, args):
+            cmdLine = ''
+            if PYTHON:
+                if not os.path.exists(PYTHON):
+                    raise OSError("%s does not exist" % PYTHON)
+                cmdLine += '"%s" ' % PYTHON
+            if not os.path.exists(TOSTART):
+                raise OSError("%s does not exist" % TOSTART)
+            cmdLine += '"%s" ' % TOSTART
+    <BLANKLINE>
+            if PARAMETERS:
+                cmdLine += PARAMETERS
+    <BLANKLINE>
+            self.start_cmd = cmdLine
+    <BLANKLINE>
+            win32serviceutil.ServiceFramework.__init__(self, args)
+            # Create an event which we will use to wait on.
+            # The "service stop" request will set this event.
+            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
+            self.redirectOutput()
+    <BLANKLINE>
+        def redirectOutput(self):
+            sys.stdout.close()
+            sys.stderr.close()
+            sys.stdout = NullOutput()
+            sys.stderr = NullOutput()
+    <BLANKLINE>
+        def SvcStop(self):
+            # Before we do anything, tell the SCM we are starting the stop process.
+            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+    <BLANKLINE>
+            # TODO:  This TerminateProcess call doesn't make much sense:  it's
+            # doing a hard kill _first_, never giving the process a chance to
+            # shut down cleanly.  Compare to current Zope2 service code, which
+            # uses Windows events to give the process a chance to shut down
+            # cleanly, doing a hard kill only if that doesn't succeed.
+    <BLANKLINE>
+            # stop the process if necessary
+            try:
+                win32process.TerminateProcess(self.hZope, 0)
+            except pywintypes.error:
+                # the process may already have been terminated
+                pass
+            # And set my event.
+            win32event.SetEvent(self.hWaitStop)
+    <BLANKLINE>
+        # SvcStop only gets triggered when the user explictly stops (or restarts)
+        # the service.  To shut the service down cleanly when Windows is shutting
+        # down, we also need to hook SvcShutdown.
+        SvcShutdown = SvcStop
+    <BLANKLINE>
+        def createProcess(self, cmd):
+            #need to set current dir to INSTANCE_HOME otherwise pkg_resources will
+            #be pissed (in a combination with paster)
+            return win32process.CreateProcess(
+                None, cmd, None, None, 0, 0, None,
+                INSTANCE_HOME,
+                win32process.STARTUPINFO())
+    <BLANKLINE>
+        def SvcDoRun(self):
+            # indicate to Zope that the process is daemon managed (restartable)
+            os.environ['ZMANAGED'] = '1'
+    <BLANKLINE>
+            # daemon behavior:  we want to to restart the process if it
+            # dies, but if it dies too many times, we need to give up.
+    <BLANKLINE>
+            # we use a simple backoff algorithm to determine whether
+            # we should try to restart a dead process:  for each
+            # time the process dies unexpectedly, we wait some number of
+            # seconds to restart it, as determined by the backoff interval,
+            # which doubles each time the process dies.  if we exceed
+            # BACKOFF_MAX seconds in cumulative backoff time, we give up.
+            # at any time if we successfully run the process for more thab
+            # BACKOFF_CLEAR_TIME seconds, the backoff stats are reset.
+    <BLANKLINE>
+            # the initial number of seconds between process start attempts
+            backoff_interval = BACKOFF_INITIAL_INTERVAL
+            # the cumulative backoff seconds counter
+            backoff_cumulative = 0
+    <BLANKLINE>
+            import servicemanager
+    <BLANKLINE>
+            # log a service started message
+            servicemanager.LogMsg(
+                servicemanager.EVENTLOG_INFORMATION_TYPE,
+                servicemanager.PYS_SERVICE_STARTED,
+                (self._svc_name_, ' (%s)' % self._svc_display_name_))
+    <BLANKLINE>
+            while 1:
+                start_time = time.time()
+                info = self.createProcess(self.start_cmd)
+                self.hZope = info[0] # the pid
+                if backoff_interval > BACKOFF_INITIAL_INTERVAL:
+                    # if we're in a backoff state, log a message about
+                    # starting a new process
+                    servicemanager.LogInfoMsg(
+                        '%s (%s): recovering from died process, new process '
+                        'started' % (self._svc_name_, self._svc_display_name_)
+                        )
+                rc = win32event.WaitForMultipleObjects(
+                    (self.hWaitStop, self.hZope), 0, win32event.INFINITE)
+                if rc == win32event.WAIT_OBJECT_0:
+                    # user sent a stop service request
+                    self.SvcStop()
+                    break
+                else:
+                    # user did not send a service stop request, but
+                    # the process died; this may be an error condition
+                    status = win32process.GetExitCodeProcess(self.hZope)
+                    if status == 0:
+                        # the user shut the process down from the web
+                        # interface (or it otherwise exited cleanly)
+                        break
+                    else:
+                        # this was an abormal shutdown.  if we can, we want to
+                        # restart the process but if it seems hopeless,
+                        # don't restart an infinite number of times.
+                        if backoff_cumulative > BACKOFF_MAX:
+                            # it's hopeless
+                            servicemanager.LogErrorMsg(
+                              '%s (%s): process could not be restarted due to max '
+                              'restart attempts exceeded' % (
+                                self._svc_display_name_, self._svc_name_
+                              ))
+                            self.SvcStop()
+                            break
+                        servicemanager.LogWarningMsg(
+                           '%s (%s): process died unexpectedly.  Will attempt '
+                           'restart after %s seconds.' % (
+                                self._svc_name_, self._svc_display_name_,
+                                backoff_interval
+                                )
+                           )
+                        # if BACKOFF_CLEAR_TIME seconds have elapsed since we last
+                        # started the process, reset the backoff interval
+                        # and the cumulative backoff time to their original
+                        # states
+                        if time.time() - start_time > BACKOFF_CLEAR_TIME:
+                            backoff_interval = BACKOFF_INITIAL_INTERVAL
+                            backoff_cumulative = 0
+                        # we sleep for the backoff interval.  since this is async
+                        # code, it would be better done by sending and
+                        # catching a timed event (a service
+                        # stop request will need to wait for us to stop sleeping),
+                        # but this works well enough for me.
+                        time.sleep(backoff_interval)
+                        # update backoff_cumulative with the time we spent
+                        # backing off.
+                        backoff_cumulative = backoff_cumulative + backoff_interval
+                        # bump the backoff interval up by 2* the last interval
+                        backoff_interval = backoff_interval * 2
+    <BLANKLINE>
+                        # loop and try to restart the process
+    <BLANKLINE>
+            # log a service stopped message
+            servicemanager.LogMsg(
+                servicemanager.EVENTLOG_INFORMATION_TYPE,
+                servicemanager.PYS_SERVICE_STOPPED,
+                (self._svc_name_, ' (%s) ' % self._svc_display_name_))
+    <BLANKLINE>
+    <BLANKLINE>
+    if __name__ == '__main__':
+        import win32serviceutil
+        if os.path.exists(PYTHONSERVICE_EXE):
+            # This ensures that pythonservice.exe is registered...
+            os.system('"%s" -register' % PYTHONSERVICE_EXE)
+        win32serviceutil.HandleCommandLine(Zope3Service)
 
+Parameters for the script
+-------------------------
 
+Let's add now parameters to the script:
 
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = winservice
+    ...
+    ... [winservice]
+    ... recipe = z3c.recipe.winservice:service
+    ... name = Zope 3 Windows Service
+    ... description = Zope 3 Windows Service description
+    ... runzope = app
+    ... parameters = -C zope.conf
+    ...
+    ... ''' % globals())
+
+Let's buildout:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling winservice.
+    Installing winservice.
+
+The bin folder contains the windows service installer script:
+
+    >>> ls('bin')
+    -  app-script.py
+    -  buildout-script.py
+    -  buildout.exe
+    -  winservice.py
+
+PARAMETERS gets filled in:
+
+    >>> cat('bin', 'winservice.py')
+    ##############################################################################
+    ...
+    # these are replacements from winservice recipe
+    PYTHONSERVICE_EXE = r'...\Lib\site-packages\win32\pythonservice.exe'
+    PYTHON = r'...\python.exe'
+    TOSTART = r'/sample-buildout/bin/app-script.py'
+    PARAMETERS = r'-C zope.conf'
+    SERVICE_NAME = '...sample_buildout_bin_app_script_py'
+    SERVICE_DISPLAY_NAME = r'Zope 3 Windows Service'
+    SERVICE_DESCRIPTION = r'Zope 3 Windows Service description'
+    INSTANCE_HOME = r'/sample-buildout'
+    ...
+
+A script not in ``bin``
+-----------------------
+
+We can have the script in a different folder than ``bin``:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = winservice
+    ...
+    ... [winservice]
+    ... recipe = z3c.recipe.winservice:service
+    ... name = Zope 3 Windows Service
+    ... description = Zope 3 Windows Service description
+    ... runzope = parts\zope-app\zope-dev
+    ...
+    ... ''' % globals())
+
+Let's buildout:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling winservice.
+    Installing winservice.
+    While:
+      Installing winservice.
+    Error: App start script parts\zope-app\zope-dev-script.py does not exist.
+
+Oops, the script does not exist yet.
+
+    >>> import os
+    >>> os.mkdir('parts/zope-app')
+
+    >>> write('parts', 'zope-app', 'zope-dev-script.py',
+    ... '''
+    ... other dummy app script
+    ... ''')
+
+Buildout again:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Installing winservice.
+
+The bin folder contains the windows service installer script:
+
+    >>> ls('bin')
+    -  app-script.py
+    -  buildout-script.py
+    -  buildout.exe
+    -  winservice.py
+
+TOSTART respects the folder given in runzope:
+
+    >>> cat('bin', 'winservice.py')
+    ##############################################################################
+    ...
+    PYTHONSERVICE_EXE = r'...\Lib\site-packages\win32\pythonservice.exe'
+    PYTHON = r'...\python.exe'
+    TOSTART = r'parts\zope-app\zope-dev-script.py'
+    PARAMETERS = r''
+    SERVICE_NAME = 'parts_zope_app_zope_dev_script_py'
+    SERVICE_DISPLAY_NAME = r'Zope 3 Windows Service'
+    SERVICE_DESCRIPTION = r'Zope 3 Windows Service description'
+    INSTANCE_HOME = r'/sample-buildout'
+    ...
+
+
+``runscript`` executable
+-------------------------
+
+Let's use now the ``runscript`` option:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = winservice
+    ...
+    ... [winservice]
+    ... recipe = z3c.recipe.winservice:service
+    ... name = Zope 3 Windows Service
+    ... description = Zope 3 Windows Service description
+    ... runscript = parts\exe\some.exe
+    ... parameters = -C zope.conf
+    ...
+    ... ''' % globals())
+
+Let's buildout:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling winservice.
+    Installing winservice.
+    While:
+      Installing winservice.
+    Error: App start script parts\exe\some.exe does not exist.
+
+Darn, the executable must exist.
+Try again:
+
+    >>> import os
+    >>> os.mkdir('parts/exe')
+
+    >>> write('parts', 'exe', 'some.exe',
+    ... '''
+    ... dummy executable
+    ... ''')
+
+Buildout again:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Installing winservice.
+
+The bin folder contains the windows service installer script:
+
+    >>> ls('bin')
+    -  app-script.py
+    -  buildout-script.py
+    -  buildout.exe
+    -  winservice.py
+
+PYTHON gets blanked, because this is an executable,
+TOSTART gets filled with the right executable,
+PARAMETERS is still as before.
+
+    >>> cat('bin', 'winservice.py')
+    ##############################################################################
+    ...
+    # these are replacements from winservice recipe
+    PYTHONSERVICE_EXE = r'...\Lib\site-packages\win32\pythonservice.exe'
+    PYTHON = r''
+    TOSTART = r'parts\exe\some.exe'
+    PARAMETERS = r'-C zope.conf'
+    SERVICE_NAME = 'parts_exe_some_exe'
+    SERVICE_DISPLAY_NAME = r'Zope 3 Windows Service'
+    SERVICE_DESCRIPTION = r'Zope 3 Windows Service description'
+    INSTANCE_HOME = r'/sample-buildout'
+    ...
+
+``runscript`` python script
+----------------------------
+
+Let's use now the ``runscript`` option:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = winservice
+    ...
+    ... [winservice]
+    ... recipe = z3c.recipe.winservice:service
+    ... name = Zope 3 Windows Service
+    ... description = Zope 3 Windows Service description
+    ... runscript = parts\exe\some.py
+    ... parameters = -C zope.conf
+    ...
+    ... ''' % globals())
+
+    >>> write('parts', 'exe', 'some.py',
+    ... '''
+    ... dummy python script
+    ... ''')
+
+Let's buildout:
+
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling winservice.
+    Installing winservice.
+
+The bin folder contains the windows service installer script:
+
+    >>> ls('bin')
+    -  app-script.py
+    -  buildout-script.py
+    -  buildout.exe
+    -  winservice.py
+
+PYTHON is back, because this is a script,
+TOSTART gets filled with the right python script,
+PARAMETERS is still as before.
+
+    >>> cat('bin', 'winservice.py')
+    ##############################################################################
+    ...
+    # these are replacements from winservice recipe
+    PYTHONSERVICE_EXE = r'...\Lib\site-packages\win32\pythonservice.exe'
+    PYTHON = r'...\python.exe'
+    TOSTART = r'parts\exe\some.py'
+    PARAMETERS = r'-C zope.conf'
+    SERVICE_NAME = 'parts_exe_some_py'
+    SERVICE_DISPLAY_NAME = r'Zope 3 Windows Service'
+    SERVICE_DESCRIPTION = r'Zope 3 Windows Service description'
+    INSTANCE_HOME = r'/sample-buildout'
+    ...
+
 Debug
 -----
 
@@ -430,60 +669,60 @@
 
 We can enable the ``debug`` option:
 
-  >>> write('buildout.cfg',
-  ... '''
-  ... [buildout]
-  ... develop = demo1 demo2
-  ... parts = winservice
-  ...
-  ... [winservice]
-  ... recipe = z3c.recipe.winservice:service
-  ... name = Zope 3 Windows Service
-  ... description = Zope 3 Windows Service description
-  ... runzope = app
-  ... debug = true
-  ...
-  ... ''' % globals())
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... develop = demo1 demo2
+    ... parts = winservice
+    ...
+    ... [winservice]
+    ... recipe = z3c.recipe.winservice:service
+    ... name = Zope 3 Windows Service
+    ... description = Zope 3 Windows Service description
+    ... runzope = app
+    ... debug = true
+    ...
+    ... ''' % 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'
-  Uninstalling winservice.
-  Installing winservice.
+    >>> print system(join('bin', 'buildout')),
+    Develop: '/sample-buildout/demo1'
+    Develop: '/sample-buildout/demo2'
+    Uninstalling winservice.
+    Installing winservice.
 
 
 The bin folder contains the windows service installer script:
 
-  >>> ls('bin')
-  -  app-script.py
-  -  app-servicedebug.py
-  -  buildout-script.py
-  -  buildout.exe
-  -  winservice.py
+    >>> ls('bin')
+    -  app-script.py
+    -  app-servicedebug.py
+    -  buildout-script.py
+    -  buildout.exe
+    -  winservice.py
 
 The winservice.py file gets changed according to the new script name:
 
-  >>> cat('bin', 'winservice.py')
-  ##############################################################################
-  ...TOSTART = r'/sample-buildout/bin/app-servicedebug.py'...
-  ...SERVICE_NAME = '...bin_app_script_py'...
+    >>> cat('bin', 'winservice.py')
+    ##############################################################################
+    ...TOSTART = r'/sample-buildout/bin/app-servicedebug.py'...
+    ...SERVICE_NAME = '...bin_app_script_py'...
 
 The debug script contains a bare catch-all and a logger:
 
-  >>> cat('bin', 'app-servicedebug.py')
-  <BLANKLINE>
-  def exceptionlogger():
-      import servicemanager
-      import traceback
-      servicemanager.LogErrorMsg("Script %s had an exception: %s" % (
-        __file__, traceback.format_exc()
-      ))
-  <BLANKLINE>
-  try:
-  <BLANKLINE>
-      dummy start script
-  <BLANKLINE>
-  except Exception, e:
-      exceptionlogger()
+    >>> cat('bin', 'app-servicedebug.py')
+    <BLANKLINE>
+    def exceptionlogger():
+        import servicemanager
+        import traceback
+        servicemanager.LogErrorMsg("Script %s had an exception: %s" % (
+          __file__, traceback.format_exc()
+        ))
+    <BLANKLINE>
+    try:
+    <BLANKLINE>
+        dummy start script
+    <BLANKLINE>
+    except Exception, e:
+        exceptionlogger()

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py	2010-10-19 08:53:56 UTC (rev 117753)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py	2010-10-19 12:26:52 UTC (rev 117754)
@@ -54,6 +54,16 @@
             shutil.copymode(srcFile, script)
             shutil.copystat(srcFile, script)
 
+            #get rid of compiled versions
+            try:
+                os.unlink(os.path.splitext(script)[0]+'.pyc')
+            except OSError:
+                pass
+            try:
+                os.unlink(os.path.splitext(script)[0]+'.pyo')
+            except OSError:
+                pass
+
     generated.append(script)
 
     return generated
@@ -75,6 +85,17 @@
         shutil.copymode(srcFile, dstFile)
         shutil.copystat(srcFile, dstFile)
 
+        #get rid of compiled versions
+        try:
+            os.unlink(os.path.splitext(dstFile)[0]+'.pyc')
+        except OSError:
+            pass
+
+        try:
+            os.unlink(os.path.splitext(dstFile)[0]+'.pyo')
+        except OSError:
+            pass
+
     generated.append(dstFile)
 
     return generated
@@ -107,17 +128,33 @@
         # setup start script
         binDir = self.buildout['buildout']['bin-directory']
         runzope = options.get('runzope')
+        runscript = options.get('runscript')
 
         # fix script name
-        if not runzope:
+        if not runzope and not runscript:
             raise zc.buildout.UserError(
-                'Missing runzope option in winservice recipe.')
-        if not runzope.endswith('-script.py'):
-            if runzope.endswith('.py'):
-                runzope = runzope[:3]
-            runzope = '%s-script.py' % runzope
+                'Missing `runzope` or `runscript` option in the recipe.')
 
-        self.runScript = os.path.join(binDir, runzope)
+        if runzope and runscript:
+            raise zc.buildout.UserError(
+                'Only one of `runzope` or `runscript` allowed in the recipe.')
+
+        if runzope:
+            #old-ish way
+            if not runzope.endswith('-script.py'):
+                if runzope.endswith('.py'):
+                    runzope = runzope[:3]
+                runzope = '%s-script.py' % runzope
+
+            if '/' in runzope or '\\' in runzope:
+                #don't add the bin folder if there's already a folder
+                self.runScript = runzope
+            else:
+                self.runScript = os.path.join(binDir, runzope)
+        else:
+            #new-ish way, just don't touch runscript
+            self.runScript = runscript
+
         options['serviceName'] = self.getServiceName()
 
     def getServiceName(self):
@@ -151,28 +188,47 @@
         displayName = options.get('name', defaultName)
         serviceName = options['serviceName']
         description = options.get('description', defaultDescription)
+        parameters = options.get('parameters', '')
+        python = self.executable
+        pythondir = os.path.split(python)[0]
+        #this is dumb... but stays unless someone figures something better
+        pythonservice_exe = r'%s\Lib\site-packages\win32\pythonservice.exe' % pythondir
+        instance_home = self.buildout['buildout']['directory']
 
+        # raise exeption if the service exe is not here now
+        if not os.path.exists(pythonservice_exe):
+            raise zc.buildout.UserError(
+                'Python service %s does not exist.'  %
+                    pythonservice_exe)
+
         generated = []
 
-        if options.get('debug'):
+        if options.get('debug') and self.runScript.lower().endswith('.py'):
             serviceScript = self.runScript.replace(
                 '-script.py', '-servicedebug.py')
             generated += patchScript(self.runScript, serviceScript)
         else:
             serviceScript = self.runScript
 
+        if serviceScript.lower().endswith('.exe'):
+            #if the script is an exe, no need to run it with python
+            python = ''
+
         self.winServiceVars = [
-            ("<<PYTHON>>", self.executable),
+            ("<<PYTHON>>", python),
             ("<<RUNZOPE>>", serviceScript),
+            ("<<PYTHONSERVICE_EXE>>", pythonservice_exe),
             ("<<SERVICE_NAME>>", serviceName),
             ("<<SERVICE_DISPLAY_NAME>>", displayName),
             ("<<SERVICE_DESCRIPTION>>", description),
+            ("<<PARAMETERS>>", parameters),
+            ("<<INSTANCE_HOME>>", instance_home),
             ]
 
         # raise exeption if the app script is not here now
         if not os.path.exists(serviceScript):
             raise zc.buildout.UserError(
-                'App start script %s does not exist in bin folder.'  %
+                'App start script %s does not exist.'  %
                     self.runScript)
 
         # get templates

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/tests.py
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/tests.py	2010-10-19 08:53:56 UTC (rev 117753)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/tests.py	2010-10-19 12:26:52 UTC (rev 117754)
@@ -66,8 +66,7 @@
             setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
             optionflags=(
                 doctest.NORMALIZE_WHITESPACE|
-                doctest.ELLIPSIS|
-                doctest.REPORT_NDIFF),
+                doctest.ELLIPSIS),
             checker=checker),
         )
 

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in	2010-10-19 08:53:56 UTC (rev 117753)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in	2010-10-19 12:26:52 UTC (rev 117754)
@@ -85,14 +85,14 @@
 import win32process
 
 # these are replacements from winservice recipe
+PYTHONSERVICE_EXE = r'<<PYTHONSERVICE_EXE>>'
 PYTHON = r'<<PYTHON>>'
-PYTHONDIR = os.path.split(PYTHON)[0]
-PYTHONSERVICE_EXE = r'%s\Lib\site-packages\win32\pythonservice.exe' % PYTHONDIR
 TOSTART = r'<<RUNZOPE>>'
+PARAMETERS = r'<<PARAMETERS>>'
 SERVICE_NAME = '<<SERVICE_NAME>>'
 SERVICE_DISPLAY_NAME = r'<<SERVICE_DISPLAY_NAME>>'
 SERVICE_DESCRIPTION = r'<<SERVICE_DESCRIPTION>>'
-INSTANCE_HOME = os.path.dirname(os.path.dirname(TOSTART))
+INSTANCE_HOME = r'<<INSTANCE_HOME>>'
 
 
 # the max seconds we're allowed to spend backing off
@@ -152,13 +152,20 @@
     start_cmd = ''
 
     def __init__(self, args):
-        if not os.path.exists(PYTHON):
-            raise OSError("%s does not exist" % PYTHON)
+        cmdLine = ''
+        if PYTHON:
+            if not os.path.exists(PYTHON):
+                raise OSError("%s does not exist" % PYTHON)
+            cmdLine += '"%s" ' % PYTHON
         if not os.path.exists(TOSTART):
             raise OSError("%s does not exist" % TOSTART)
+        cmdLine += '"%s" ' % TOSTART
 
-        self.start_cmd = '"%s" "%s"' % (PYTHON, TOSTART)
+        if PARAMETERS:
+            cmdLine += PARAMETERS
 
+        self.start_cmd = cmdLine
+
         win32serviceutil.ServiceFramework.__init__(self, args)
         # Create an event which we will use to wait on.
         # The "service stop" request will set this event.



More information about the checkins mailing list