Package trac :: Package util :: Module autoreload

Source Code for Module trac.util.autoreload

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2006-2020 Edgewall Software 
  4  # All rights reserved. 
  5  # 
  6  # This software is licensed as described in the file COPYING, which 
  7  # you should have received as part of this distribution. The terms 
  8  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
  9  # 
 10  # This software consists of voluntary contributions made by many 
 11  # individuals. For the exact contribution history, see the revision 
 12  # history and logs, available at https://trac.edgewall.org/log/. 
 13   
 14  import os 
 15  import sys 
 16  import threading 
 17  import time 
 18  import traceback 
 19   
 20  _SLEEP_TIME = 1 
 21   
22 -def _reloader_thread(modification_callback, loop_callback):
23 """When this function is run from the main thread, it will force other 24 threads to exit when any modules currently loaded change. 25 26 :param modification_callback: a function taking a single argument, 27 the modified file, which is called 28 every time a modification is 29 detected 30 31 :param loop_callback: a function taking no arguments, which is 32 called after every modification check 33 34 """ 35 mtimes = {} 36 while True: 37 for filename in filter(None, [getattr(module, '__file__', None) 38 for module in sys.modules.values()]): 39 while not os.path.isfile(filename): # Probably in an egg or zip file 40 filename = os.path.dirname(filename) 41 if not filename: 42 break 43 if not filename: # Couldn't map to physical file, so just ignore 44 continue 45 46 if filename.endswith(('.pyc', '.pyo')): 47 filename = filename[:-1] 48 49 if not os.path.isfile(filename): 50 # Compiled file for non-existant source 51 continue 52 53 mtime = os.stat(filename).st_mtime 54 if filename not in mtimes: 55 mtimes[filename] = mtime 56 continue 57 if mtime != mtimes[filename]: 58 modification_callback(filename) 59 sys.exit(3) 60 loop_callback() 61 time.sleep(_SLEEP_TIME)
62
63 -def _restart_with_reloader():
64 is_win32 = sys.platform == 'win32' 65 if is_win32: 66 can_exec = lambda path: os.path.isfile(path) and \ 67 os.path.normpath(path).endswith('.exe') 68 else: 69 can_exec = lambda path: os.access(path, os.X_OK) 70 71 if os.path.isfile(sys.argv[0]): 72 args = sys.argv if can_exec(sys.argv[0]) else \ 73 [sys.executable] + sys.argv 74 elif is_win32 and can_exec(sys.argv[0] + '.exe'): 75 args = [sys.argv[0] + '.exe'] + sys.argv[1:] 76 elif os.path.isfile(sys.argv[0] + '-script.py'): 77 args = [sys.executable, sys.argv[0] + '-script.py'] + sys.argv[1:] 78 else: 79 args = [sys.executable] + sys.argv 80 path = args[0] 81 if is_win32: 82 args = ['"%s"' % arg for arg in args] 83 new_environ = os.environ.copy() 84 new_environ['RUN_MAIN'] = 'true' 85 86 while True: 87 # This call reinvokes ourself and goes into the other branch of main as 88 # a new process. 89 exit_code = os.spawnve(os.P_WAIT, path, args, new_environ) 90 if exit_code != 3: 91 return exit_code
92
93 -def main(func, modification_callback, *args, **kwargs):
94 """Run the given function and restart any time modules are changed.""" 95 if os.environ.get('RUN_MAIN'): 96 exit_code = [] 97 def main_thread(): 98 try: 99 func(*args, **kwargs) 100 exit_code.append(None) 101 except SystemExit as e: 102 exit_code.append(e.code) 103 except: 104 traceback.print_exception(*sys.exc_info()) 105 exit_code.append(1)
106 def check_exit(): 107 if exit_code: 108 sys.exit(exit_code[0]) 109 # Lanch the actual program as a child thread 110 thread = threading.Thread(target=main_thread, name='Main thread') 111 thread.setDaemon(True) 112 thread.start() 113 try: 114 # Now wait for a file modification and quit 115 _reloader_thread(modification_callback, check_exit) 116 except KeyboardInterrupt: 117 pass 118 else: 119 # Initial invocation just waits around restarting this executable 120 try: 121 sys.exit(_restart_with_reloader()) 122 except KeyboardInterrupt: 123 pass 124