Log message for revision 29975: Major service enhancements. Service cleanly shuts down child, and if child fails the tail of the process output (which generally contains a traceback) is written to the event log. Minor tweaks to the Windows build 'clean' process and documentation tweaks. Don't kill the service if we can't write to the event log
Changed: U Zope/trunk/doc/INSTALL.txt A Zope/trunk/doc/WINDOWS.txt U Zope/trunk/inst/Makefile.win.in U Zope/trunk/inst/configure.py U Zope/trunk/lib/python/nt_svcutils/service.py U Zope/trunk/skel/bin/runzope.bat.in U Zope/trunk/skel/bin/zopeservice.py.in U Zope/trunk/utilities/mkzopeinstance.py -=- Modified: Zope/trunk/doc/INSTALL.txt =================================================================== --- Zope/trunk/doc/INSTALL.txt 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/doc/INSTALL.txt 2005-04-14 02:00:15 UTC (rev 29975) @@ -3,6 +3,9 @@ Welcome to Zope! This document describes building and installing Zope on UNIX and Linux. + + See WINDOWS.txt for information about Windows. See the PLATFORMS + directory for notes about various other platforms. System requirements when building from source Added: Zope/trunk/doc/WINDOWS.txt =================================================================== --- Zope/trunk/doc/WINDOWS.txt 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/doc/WINDOWS.txt 2005-04-14 02:00:15 UTC (rev 29975) @@ -0,0 +1,69 @@ +How to build and install Zope from source code on Windows. +---------------------------------------------------------- +These instructions appear to work for 2.7 and the trunk: + +* Ensure you have the correct MSVC version installed for the + version of Python you will be using. + +* Install (or build from sources) Python + http://www.python.org + +* Install (or build from sources) the Python for Windows extensions + http://sourceforge.net/projects/pywin32/ + +* Unpack the Zope source distribution or pull from CVS. Change + to that directory. + +* Execute: + % python.exe inst\configure.py + It should say something like: + > + > - Zope top-level binary directory will be c:\Zope-2.7. + > - Makefile written. + > + > Next, run the Visual C++ batch file "VCVARS32.bat" and then "nmake". + + (run 'configure.py --help' to see how to change things) + +* 'makefile' will have ben created. As instructed, execute 'nmake'. + If the build succeeds, the last message printed should be: + > Zope built. Next, do 'nmake install'. + +* As instructed, execute 'nmake install'. A few warnings will be generated, + but they can be ignored. The last message in the build process should be: + > Zope binaries installed successfully. + +* If you are running from CVS, the build may fail: + See http://collector.zope.org/Zope/1530 + > running install_data + > error: can't copy 'version.txt': no matching files + > NMAKE : fatal error U1077: '"e:\src\python-2.3-cvs\pcbuild\python.exe"' : return code '0x1' + > Stop. + + If you see this error, edit setup.py and comment the line referencing + 'version.txt' + +* Zope itself has now been installed. We need to create an instance. Run: + % python.exe {install_path}\bin\mkzopeinstance.py + + We will be prompted, via the console, for the instance directory and + username/password for the admin user. + +* We are now ready to start zope. Run: + % {zope_instance}\run_zope.bat. + Zope should start with nice log messages being printed to + stdout. When Zope is ready, you should see: + > ------ + > 2004-10-13T12:27:58 INFO(0) Zope Ready to handle requests + + Press Ctrl+C to stop this instance of the server. + +* Optionally, install as a Windows service. Execute: + % python {zope_instance}\zope_service.py + to see the valid options. You may want something like: + % python {zope_instance}\zope_service.py --startup=auto install + + Once installed, it can be started any number of ways: + - python {zope_instance}\zope_service.py start + - Control Panel + - net start service_short_name (eg, "net start Zope_-1227678699" Property changes on: Zope/trunk/doc/WINDOWS.txt ___________________________________________________________________ Name: svn:eol-style + native Modified: Zope/trunk/inst/Makefile.win.in =================================================================== --- Zope/trunk/inst/Makefile.win.in 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/inst/Makefile.win.in 2005-04-14 02:00:15 UTC (rev 29975) @@ -31,15 +31,15 @@ XCOPY=xcopy /i /s /e /y COPY=copy -.PHONY: clean install build unbuild -.PHONY: default - default: build # default: The default step (invoked when make is called without a target) @ echo. @ echo Zope built. Next, do 'nmake install'. - @ echo + @ echo. +.PHONY: clean install build unbuild +.PHONY: default + # build: Do whatever 'setup.py build' implies build: $(PYTHON) "$(BASE_DIR)\setup.py" \ @@ -47,7 +47,7 @@ # unbuild: Remove the build directory (undo the make build step) unbuild: - $(RMRF) $(BUILD_BASE) + -$(RMRF) $(BUILD_BASE) # install: Install a software home. install: build @@ -62,7 +62,7 @@ # the source directory for good measure. clean: unbuild $(CD) "$(BASE_DIR) - $(RM) /s *.pyc *.pyo *.dll *.o *.obj *.pyd + -$(RM) /s *.pyc *.pyo *.dll *.o *.obj *.pyd Modified: Zope/trunk/inst/configure.py =================================================================== --- Zope/trunk/inst/configure.py 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/inst/configure.py 2005-04-14 02:00:15 UTC (rev 29975) @@ -23,7 +23,7 @@ if sys.platform == 'win32': PREFIX = 'c:\\Zope-' + versions.ZOPE_MAJOR_VERSION IN_MAKEFILE = 'Makefile.win.in' - MAKE_COMMAND='the Visual C++ batch file "VCVARS32.bat" and then "nmake build"' + MAKE_COMMAND='the Visual C++ batch file "VCVARS32.bat" and then "nmake"' else: PREFIX = '/opt/Zope-' + versions.ZOPE_MAJOR_VERSION IN_MAKEFILE = 'Makefile.in' Modified: Zope/trunk/lib/python/nt_svcutils/service.py =================================================================== --- Zope/trunk/lib/python/nt_svcutils/service.py 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/lib/python/nt_svcutils/service.py 2005-04-14 02:00:15 UTC (rev 29975) @@ -14,19 +14,10 @@ """Windows Services installer/controller for Zope/ZEO/ZRS instance homes""" -import msvcrt -import win32api -import win32con -import win32event -import win32file -import win32pipe -import win32process -import win32security -import win32service -import win32serviceutil -import pywintypes -import time -import os +import sys, os, time, threading, signal +import win32api, win32event, win32file, win32pipe, win32process, win32security +import win32service, win32serviceutil, servicemanager +import pywintypes, winerror, win32con # the max seconds we're allowed to spend backing off BACKOFF_MAX = 300 @@ -37,6 +28,15 @@ # a dead process) BACKOFF_INITIAL_INTERVAL = 5 +# We execute a new thread that captures the tail of the output from our child +# process. If the child fails, it is written to the event log. +# This process is unconditional, and the output is never written to disk +# (except obviously via the event log entry) +# Size of the blocks we read from the child process's output. +CHILDCAPTURE_BLOCK_SIZE = 80 +# The number of BLOCKSIZE blocks we keep as process output. +CHILDCAPTURE_MAX_BLOCKS = 200 + class Service(win32serviceutil.ServiceFramework): """Base class for a Windows Server to manage an external process. @@ -47,47 +47,40 @@ """ # The PythonService model requires that an actual on-disk class declaration - # represent a single service. Thus, the below definition of start_cmd, + # represent a single service. Thus, the definitions below for the instance # 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. - + # each instance. + # The values below are just examples. _svc_name_ = r'Zope-Instance' _svc_display_name_ = r'Zope instance at C:\Zope-Instance' - start_cmd = ( - r'"C:\Program Files\Zope-2.7.0-a1\bin\python.exe" ' - r'"C:\Program Files\Zope-2.7.0-a1\lib\python\Zope\Startup\run.py" ' - r'-C "C:\Zope-Instance\etc\zope.conf"' - ) + process_runner = r'C:\Program Files\Zope-2.7.0-a1\bin\python.exe' + process_args = r'{path_to}\run.py -C {path_to}\zope.conf' + evtlog_name = 'Zope' - # If capture_io is True, then log_file must be the path of a file - # that the controlled process's stdout and stderr will be written to. - # The I/O capture is immature. It does not handle buffering in the - # controlled process or sensible interleaving of output between - # stdout and stderr. It is intended primarily as a stopgap when - # the controlled process produces critical output that can't be - # written to a log file using mechanism inside that process. - capture_io = False - log_file = None - def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) + # Just say "Zope", instead of "Zope_-xxxxx" + try: + servicemanager.SetEventSourceName(self.evtlog_name) + except AttributeError: + # old pywin32 - that's ok. + pass # 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) + # We create it inheritable so we can pass it to the child process, so + # it too can act on the stop event. + sa = win32security.SECURITY_ATTRIBUTES() + sa.bInheritHandle = True + self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None) + self.redirect_thread = None + def SvcStop(self): # Before we do anything, tell the SCM we are starting the stop process. self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self.onStop() - # 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. + # Set the stop event - the main loop takes care of termination. win32event.SetEvent(self.hWaitStop) def onStop(self): @@ -96,34 +89,39 @@ def createProcess(self, cmd): self.start_time = time.time() - if self.capture_io: - self.log = open(self.log_file, "ab") - return self.createProcessCaptureIO(cmd) - else: - return win32process.CreateProcess( - None, cmd, None, None, 0, 0, None, None, - win32process.STARTUPINFO()), None + return self.createProcessCaptureIO(cmd) def logmsg(self, event): # log a service event using servicemanager.LogMsg - from servicemanager import LogMsg, EVENTLOG_INFORMATION_TYPE - LogMsg(EVENTLOG_INFORMATION_TYPE, event, - (self._svc_name_, " (%s)" % self._svc_display_name_)) + try: + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + event, + (self._svc_name_, + " (%s)" % self._svc_display_name_)) + except win32api.error, details: + # Failed to write a log entry - most likely problem is + # that the event log is full. We don't want this to kill us + print "FAILED to write INFO event", event, ":", details + def _dolog(self, func, msg): + try: + fullmsg = "%s (%s): %s" % \ + (self._svc_name_, self._svc_display_name_, msg) + func(fullmsg) + except win32api.error, details: + # Failed to write a log entry - most likely problem is + # that the event log is full. We don't want this to kill us + print "FAILED to write event log entry:", details + print msg + def info(self, s): - from servicemanager import LogInfoMsg - LogInfoMsg("%s (%s): %s" % - (self._svc_name_, self._svc_display_name_, s)) + self._dolog(servicemanager.LogInfoMsg, s) def warning(self, s): - from servicemanager import LogWarningMsg - LogWarningMsg("%s (%s): %s" % - (self._svc_name_, self._svc_display_name_, s)) + self._dolog(servicemanager.LogWarningMsg, s) def error(self, s): - from servicemanager import LogErrorMsg - LogErrorMsg("%s (%s): %s" % - (self._svc_name_, self._svc_display_name_, s)) + self._dolog(servicemanager.LogErrorMsg, s) def SvcDoRun(self): # indicate to Zope that the process is daemon managed (restartable) @@ -150,20 +148,79 @@ # the cumulative backoff seconds counter self.backoff_cumulative = 0 - import servicemanager self.logmsg(servicemanager.PYS_SERVICE_STARTED) - + while 1: - info, handles = self.createProcess(self.start_cmd) + # We pass *this* file and the handle as the first 2 params, then + # the 'normal' startup args. + # See the bottom of this script for how that is handled. + cmd = '"%s" %s' % (self.process_runner, self.process_args) + info = self.createProcess(cmd) + # info is (hProcess, hThread, pid, tid) self.hZope = info[0] # process handle # XXX why the test before the log message? if self.backoff_interval > BACKOFF_INITIAL_INTERVAL: self.info("created process") - if not (self.run(handles) and self.checkRestart()): + if not (self.run() and self.checkRestart()): break + + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + # Stop the child process by opening the special named event. + # We give it 90 seconds to shutdown normally. If that doesn't + # stop things, we give it 30 seconds to do a "fast" shutdown. + # After that, we just knock it on the head. + winver = sys.getwindowsversion() + for sig, timeout in ((signal.SIGINT, 30), (signal.SIGTERM, 10)): + event_name = "Zope-%d-%d" % (info[2], sig) + # sys.getwindowsversion() -> major, minor, build, platform_id, ver_string + # for platform_id, 2==VER_PLATFORM_WIN32_NT + if winver[0] >= 5 and winver[3] == 2: + event_name = "Global\\" + event_name + try: + he = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0, + event_name) + except win32event.error, details: + if details[0] == winerror.ERROR_FILE_NOT_FOUND: + # process already dead! + break + # no other expected error - report it. + self.warning("Failed to open child shutdown event %s" + % (event_name,)) + continue + + win32event.SetEvent(he) + # It should be shutting down now - wait for termination, reporting + # progress as we go. + for i in range(timeout): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + rc = win32event.WaitForSingleObject(self.hZope, 3000) + if rc == win32event.WAIT_OBJECT_0: + break + # Process terminated - no need to try harder. + if rc == win32event.WAIT_OBJECT_0: + break + + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + # If necessary, kill it + if win32process.GetExitCodeProcess(self.hZope)==win32con.STILL_ACTIVE: + win32api.TerminateProcess(self.hZope, 3) + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + + # Wait for the redirect thread - it should have died as the remote + # process terminated. + # As we are shutting down, we do the join with a little more care, + # reporting progress as we wait (even though we never will <wink>) + if self.redirect_thread is not None: + for i in range(5): + self.redirect_thread.join(1) + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + if not self.redirect_thread.isAlive(): + break + else: + self.warning("Redirect thread did not stop!") self.logmsg(servicemanager.PYS_SERVICE_STOPPED) - def run(self, handles): + def run(self): """Monitor the daemon process. Returns True if the service should continue running and @@ -171,63 +228,35 @@ the process exited unexpectedly and the caller should restart it. """ - keep_running = True - # Assume that the controlled program isn't expecting anything - # on stdin. - if handles: - handles[0].Close() - - if handles: - waitfor = [self.hWaitStop, self.hZope, handles[1], handles[2]] + rc = win32event.WaitForMultipleObjects([self.hWaitStop, self.hZope], + 0, # bWaitAll + win32event.INFINITE) + if rc == win32event.WAIT_OBJECT_0: + # user sent a stop service request + self.SvcStop() + keep_running = False + elif rc == win32event.WAIT_OBJECT_0 + 1: + # user did not send a service stop request, but + # the process died; this may be an error condition + status = win32process.GetExitCodeProcess(self.hZope) + # exit status 0 means the user caused a clean shutdown, + # presumably via the web interface. Any other status + # is an error that gets written to the event log. + if status != 0: + # This should never block - the child process terminating + # has closed the redirection pipe, so our thread dies. + self.redirect_thread.join(5) + if self.redirect_thread.isAlive(): + self.warning("Redirect thread did not stop!") + self.warning("process terminated with exit code %d.\n%s" \ + % (status, "".join(self.captured_blocks))) + keep_running = status != 0 else: - waitfor = [self.hWaitStop, self.hZope] - while 1: - rc = win32event.WaitForMultipleObjects(waitfor, 0, - win32event.INFINITE) - if rc == win32event.WAIT_OBJECT_0: - # user sent a stop service request - self.SvcStop() - keep_running = False - break - elif rc == win32event.WAIT_OBJECT_0 + 1: - # user did not send a service stop request, but - # the process died; this may be an error condition - status = win32process.GetExitCodeProcess(self.hZope) - # exit status 0 means the user caused a clean shutdown, - # presumably via the web interface - keep_running = status != 0 - break - else: - i = rc - win32event.WAIT_OBJECT_0 - if not self.redirect(waitfor[i]): - del waitfor[i] - if handles: - handles[1].Close() - handles[2].Close() + # No other valid return codes. + assert 0, rc return keep_running - def redirect(self, handle): - # This call will block until 80 bytes of output are ready. - # If the controlled program is buffering its I/O, it's - # possible for this to take a long time. Don't know if - # there is a better solution. - try: - ec, data = win32file.ReadFile(handle, 80) - except pywintypes.error, err: - # 109 means that the pipe was closed by the controlled - # process. Other errors might have similarly inocuous - # explanations, but we haven't run into them yet. - if err[0] != 109: - self.warning("Error reading output from process: %s" % err) - return False - # In the absence of overlapped I/O, the Python win32api - # turns all error codes into exceptions. - assert ec == 0 - self.log.write(data) - self.log.flush() - return True - def checkRestart(self): # this was an abormal shutdown. if self.backoff_cumulative > BACKOFF_MAX: @@ -239,43 +268,77 @@ if time.time() - self.start_time > BACKOFF_CLEAR_TIME: self.backoff_interval = BACKOFF_INITIAL_INTERVAL self.backoff_cumulative = 0 - # XXX 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(self.backoff_interval) + # sleep for our backoff, but still respond to stop requests. + if win32event.WAIT_OBJECT_0 == \ + win32event.WaitForSingleObject(self.hWaitStop, + self.backoff_interval * 1000): + return False self.backoff_cumulative += self.backoff_interval self.backoff_interval *= 2 return True def createProcessCaptureIO(self, cmd): - stdin = self.newPipe() - stdout = self.newPipe() - stderr = self.newPipe() + hInputRead, hInputWriteTemp = self.newPipe() + hOutReadTemp, hOutWrite = self.newPipe() + pid = win32api.GetCurrentProcess() + # This one is duplicated as inheritable. + hErrWrite = win32api.DuplicateHandle(pid, hOutWrite, pid, 0, 1, + win32con.DUPLICATE_SAME_ACCESS) + # These are non-inheritable duplicates. + hOutRead = self.dup(hOutReadTemp) + hInputWrite = self.dup(hInputWriteTemp) + # dup() closed hOutReadTemp, hInputWriteTemp + si = win32process.STARTUPINFO() - si.hStdInput = stdin[0] - si.hStdOutput = stdout[1] - si.hStdError = stderr[1] - si.dwFlags = (win32process.STARTF_USESTDHANDLES - | win32process.STARTF_USESHOWWINDOW) + si.hStdInput = hInputRead + si.hStdOutput = hOutWrite + si.hStdError = hErrWrite + si.dwFlags = win32process.STARTF_USESTDHANDLES | \ + win32process.STARTF_USESHOWWINDOW si.wShowWindow = win32con.SW_HIDE - c_stdin = self.dup(stdin[1]) - c_stdout = self.dup(stdout[0]) - c_stderr = self.dup(stderr[0]) - # pass True to allow handles to be inherited. Inheritance is # problematic in general, but should work in the controlled # circumstances of a service process. - info = win32process.CreateProcess(None, cmd, None, None, True, 0, - None, None, si) - stdin[0].Close() - stdout[1].Close() - stderr[1].Close() + create_flags = win32process.CREATE_NEW_CONSOLE + info = win32process.CreateProcess(None, cmd, None, None, True, + create_flags, None, None, si) + # (NOTE: these really aren't necessary for Python - they are closed + # as soon as they are collected) + hOutWrite.Close() + hErrWrite.Close() + hInputRead.Close() + # We don't use stdin + hInputWrite.Close() - return info, (c_stdin, c_stdout, c_stderr) + # start a thread collecting output + t = threading.Thread(target=self.redirectCaptureThread, + args = (hOutRead,)) + t.start() + self.redirect_thread = t + return info + def redirectCaptureThread(self, handle): + # Only one of these running at a time, and handling both stdout and + # stderr on a single handle. The read data is never referenced until + # the thread dies - so no need for locks around self.captured_blocks. + self.captured_blocks = [] + #self.info("Redirect thread starting") + while 1: + try: + ec, data = win32file.ReadFile(handle, CHILDCAPTURE_BLOCK_SIZE) + except pywintypes.error, err: + # ERROR_BROKEN_PIPE means the child process closed the + # handle - ie, it terminated. + if err[0] != winerror.ERROR_BROKEN_PIPE: + self.warning("Error reading output from process: %s" % err) + break + self.captured_blocks.append(data) + del self.captured_blocks[CHILDCAPTURE_MAX_BLOCKS:] + handle.Close() + #self.info("Redirect capture thread terminating") + def newPipe(self): sa = win32security.SECURITY_ATTRIBUTES() sa.bInheritHandle = True @@ -291,6 +354,8 @@ pipe.Close() return dup +# Real __main__ bootstrap code is in the instance's service module. if __name__ == '__main__': - win32serviceutil.HandleCommandLine(Service) - + print "This is a framework module - you don't run it directly." + print "See your $SOFTWARE_HOME\bin directory for the service script." + sys.exit(1) Modified: Zope/trunk/skel/bin/runzope.bat.in =================================================================== --- Zope/trunk/skel/bin/runzope.bat.in 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/skel/bin/runzope.bat.in 2005-04-14 02:00:15 UTC (rev 29975) @@ -4,5 +4,5 @@ @set SOFTWARE_HOME=<<SOFTWARE_HOME>> @set CONFIG_FILE=<<INSTANCE_HOME>>\etc\zope.conf @set PYTHONPATH=%SOFTWARE_HOME% [EMAIL PROTECTED] ZOPE_RUN=%SOFTWARE_HOME%\Zope\Startup\run.py [EMAIL PROTECTED] ZOPE_RUN=%SOFTWARE_HOME%\Zope2\Startup\run.py "%PYTHON%" "%ZOPE_RUN%" -C "%CONFIG_FILE%" %1 %2 %3 %4 %5 %6 %7 Modified: Zope/trunk/skel/bin/zopeservice.py.in =================================================================== --- Zope/trunk/skel/bin/zopeservice.py.in 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/skel/bin/zopeservice.py.in 2005-04-14 02:00:15 UTC (rev 29975) @@ -38,8 +38,9 @@ install : Installs the service - update : Updates the service, use this when you change - the service class implementation + update : Updates the service. Use this if you change any + configuration settings and need the service to be + re-registered. remove : Removes the service @@ -53,13 +54,9 @@ debug : Runs the service in debug mode - You can view the usage options by running ntservice.py without any + You can view the usage options by running this module without any arguments. - Note: you may have to register the Python service program first, - - win32\PythonService.exe /register - Starting Zope Start Zope by clicking the 'start' button in the services control @@ -74,41 +71,62 @@ Event logging - Zope events are logged to the NT application event log. Use the - event viewer to keep track of Zope events. + Service related events (such as startup, shutdown, or errors executing + the Zope process) are logged to the NT application event log. Use the + event viewer to see these events. -Note: to successfully run this script, the Zope software home needs to be on -the PYTHONPATH. + Zope Events are still written to the Zope event logs. + """ +import sys, os -import os.path -from os.path import dirname as dn -import sys - # these are replacements from mkzopeinstance -PYTHONW = r'<<PYTHONW>>' +PYTHON = r'<<PYTHON>>' SOFTWARE_HOME=r'<<SOFTWARE_HOME>>' INSTANCE_HOME = r'<<INSTANCE_HOME>>' ZOPE_HOME = r'<<ZOPE_HOME>>' -ZOPE_RUN = r'%s\Zope\Startup\run.py' % SOFTWARE_HOME +ZOPE_RUN = r'%s\Zope2\Startup\run.py' % SOFTWARE_HOME CONFIG_FILE= os.path.join(INSTANCE_HOME, 'etc', 'zope.conf') PYTHONSERVICE_EXE=r'%s\bin\PythonService.exe' % ZOPE_HOME -sys.path.insert(0, SOFTWARE_HOME) -sys.path.insert(1, os.path.join(SOFTWARE_HOME, 'third_party', 'docutils')) -sys.path.insert(2, os.path.join(SOFTWARE_HOME, 'third_party', 'docutils', 'extras')) +# Setup the environment, so sub-processes see these variables +for check_dir in (os.path.join(SOFTWARE_HOME, 'third_party', 'docutils', 'extras'), + os.path.join(SOFTWARE_HOME, 'third_party', 'docutils'), + SOFTWARE_HOME, + ): + parts = os.environ.get("PYTHONPATH", "").split(os.pathsep) + if check_dir not in parts: + parts = filter(None, [check_dir] + parts) + os.environ["PYTHONPATH"] = os.pathsep.join(parts) +os.environ["INSTANCE_HOME"] = INSTANCE_HOME + +# Ensure SOFTWARE_HOME is on our current sys.path so we can import the +# nt_svcutils package. Note we don't need the docutils dirs in sys.path, as +# only Zope itself (our child process) uses it, and that happens via +# PYTHONPATH +if SOFTWARE_HOME not in sys.path: + sys.path.insert(0, SOFTWARE_HOME) + from nt_svcutils.service import Service servicename = 'Zope_%s' % str(hash(INSTANCE_HOME.lower())) class InstanceService(Service): - start_cmd = '"%s" "%s" -C "%s"' % (PYTHONW, ZOPE_RUN, CONFIG_FILE) _svc_name_ = servicename _svc_display_name_ = 'Zope instance at %s' % INSTANCE_HOME - _exe_name_ = PYTHONSERVICE_EXE + # _svc_description_ can also be set (but what to say isn't clear!) + # If the exe we expect is not there, let the service framework search + # for it. This will be true for people running from source builds and + # relying on pre-installed pythonservice.exe. + # Note this is only used at install time, not runtime. + if os.path.isfile(PYTHONSERVICE_EXE): + _exe_name_ = PYTHONSERVICE_EXE + process_runner = PYTHON + process_args = '"%s" -C "%s"' % (ZOPE_RUN, CONFIG_FILE) + if __name__ == '__main__': import win32serviceutil win32serviceutil.HandleCommandLine(InstanceService) Modified: Zope/trunk/utilities/mkzopeinstance.py =================================================================== --- Zope/trunk/utilities/mkzopeinstance.py 2005-04-14 01:57:47 UTC (rev 29974) +++ Zope/trunk/utilities/mkzopeinstance.py 2005-04-14 02:00:15 UTC (rev 29975) @@ -93,9 +93,11 @@ user, password = get_inituser() # we need to distinguish between python.exe and pythonw.exe under - # Windows in order to make Zope run using python.exe when run in a - # console window and pythonw.exe when run as a service, so we do a bit - # of sniffing here. + # Windows. Zope is always run using 'python.exe' (even for services), + # however, it may be installed via pythonw.exe (as a sub-process of an + # installer). Thus, sys.executable may not be the executable we use. + # We still provide both PYTHON and PYTHONW, but PYTHONW should never + # need be used. psplit = os.path.split(sys.executable) exedir = os.path.join(*psplit[:-1]) pythonexe = os.path.join(exedir, 'python.exe') _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins