147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
# Autoreloading launcher.
|
|
# Borrowed from Peter Hunt and the CherryPy project (http://www.cherrypy.org).
|
|
# Some taken from Ian Bicking's Paste (http://pythonpaste.org/).
|
|
#
|
|
# Portions copyright (c) 2004, CherryPy Team (team@cherrypy.org)
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without modification,
|
|
# are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
# * Neither the name of the CherryPy Team nor the names of its contributors
|
|
# may be used to endorse or promote products derived from this software
|
|
# without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import os, sys, time, signal
|
|
|
|
try:
|
|
from django.utils.six.moves import _thread as thread
|
|
except ImportError:
|
|
from django.utils.six.moves import _dummy_thread as thread
|
|
|
|
# This import does nothing, but it's necessary to avoid some race conditions
|
|
# in the threading module. See http://code.djangoproject.com/ticket/2330 .
|
|
try:
|
|
import threading
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
import termios
|
|
except ImportError:
|
|
termios = None
|
|
|
|
RUN_RELOADER = True
|
|
|
|
_mtimes = {}
|
|
_win = (sys.platform == "win32")
|
|
|
|
def code_changed():
|
|
global _mtimes, _win
|
|
filenames = [getattr(m, "__file__", None) for m in sys.modules.values()]
|
|
for filename in filter(None, filenames):
|
|
if filename.endswith(".pyc") or filename.endswith(".pyo"):
|
|
filename = filename[:-1]
|
|
if filename.endswith("$py.class"):
|
|
filename = filename[:-9] + ".py"
|
|
if not os.path.exists(filename):
|
|
continue # File might be in an egg, so it can't be reloaded.
|
|
stat = os.stat(filename)
|
|
mtime = stat.st_mtime
|
|
if _win:
|
|
mtime -= stat.st_ctime
|
|
if filename not in _mtimes:
|
|
_mtimes[filename] = mtime
|
|
continue
|
|
if mtime != _mtimes[filename]:
|
|
_mtimes = {}
|
|
return True
|
|
return False
|
|
|
|
def ensure_echo_on():
|
|
if termios:
|
|
fd = sys.stdin
|
|
if fd.isatty():
|
|
attr_list = termios.tcgetattr(fd)
|
|
if not attr_list[3] & termios.ECHO:
|
|
attr_list[3] |= termios.ECHO
|
|
if hasattr(signal, 'SIGTTOU'):
|
|
old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
|
|
else:
|
|
old_handler = None
|
|
termios.tcsetattr(fd, termios.TCSANOW, attr_list)
|
|
if old_handler is not None:
|
|
signal.signal(signal.SIGTTOU, old_handler)
|
|
|
|
def reloader_thread():
|
|
ensure_echo_on()
|
|
while RUN_RELOADER:
|
|
if code_changed():
|
|
sys.exit(3) # force reload
|
|
time.sleep(1)
|
|
|
|
def restart_with_reloader():
|
|
while True:
|
|
args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv
|
|
if sys.platform == "win32":
|
|
args = ['"%s"' % arg for arg in args]
|
|
new_environ = os.environ.copy()
|
|
new_environ["RUN_MAIN"] = 'true'
|
|
exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
|
|
if exit_code != 3:
|
|
return exit_code
|
|
|
|
def python_reloader(main_func, args, kwargs):
|
|
if os.environ.get("RUN_MAIN") == "true":
|
|
thread.start_new_thread(main_func, args, kwargs)
|
|
try:
|
|
reloader_thread()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
else:
|
|
try:
|
|
exit_code = restart_with_reloader()
|
|
if exit_code < 0:
|
|
os.kill(os.getpid(), -exit_code)
|
|
else:
|
|
sys.exit(exit_code)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
def jython_reloader(main_func, args, kwargs):
|
|
from _systemrestart import SystemRestart
|
|
thread.start_new_thread(main_func, args)
|
|
while True:
|
|
if code_changed():
|
|
raise SystemRestart
|
|
time.sleep(1)
|
|
|
|
|
|
def main(main_func, args=None, kwargs=None):
|
|
if args is None:
|
|
args = ()
|
|
if kwargs is None:
|
|
kwargs = {}
|
|
if sys.platform.startswith('java'):
|
|
reloader = jython_reloader
|
|
else:
|
|
reloader = python_reloader
|
|
reloader(main_func, args, kwargs)
|
|
|