Files
deepgrain-foundry/tools/serve.py
T
2026-10-24 01:07:08 -04:00

87 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Build, serve and rebuild when sources change: python3 tools/serve.py [--port 8000]."""
import argparse, errno, http.server, pathlib, subprocess, sys, threading, time
ROOT = pathlib.Path(__file__).resolve().parent.parent
DIST = ROOT / 'dist'
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=str(DIST), **kwargs)
def end_headers(self):
self.send_header('Cache-Control', 'no-store')
super().end_headers()
def log_message(self, format, *args):
pass
def build(first=False):
result = subprocess.run([sys.executable, str(ROOT / 'build.py')], cwd=ROOT,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if result.returncode:
print('build failed:\n' + result.stdout, flush=True)
elif first:
print(result.stdout, end='', flush=True)
else:
print(time.strftime('rebuilt %H:%M:%S'), flush=True)
def mtimes():
files = [ROOT / 'build.py', *(ROOT / 'src').rglob('*')]
stamps = {}
for path in files:
try:
if path.is_file(): stamps[path] = path.stat().st_mtime_ns
except FileNotFoundError:
pass
return stamps
def watch(stopped, previous):
while not stopped.wait(0.5):
current = mtimes()
if current != previous:
previous = current
build()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--port', type=int, default=8000)
args = parser.parse_args()
if not 1 <= args.port <= 65525:
parser.error('--port must be between 1 and 65525 (leaves room for ten retries)')
previous = mtimes()
build(first=True)
if not (DIST / 'index.html').is_file():
sys.exit('Cannot serve: dist/index.html does not exist. Fix the build and try again.')
for port in range(args.port, args.port + 11):
try:
server = http.server.ThreadingHTTPServer(('127.0.0.1', port), Handler)
break
except OSError as error:
if error.errno != errno.EADDRINUSE:
sys.exit(f'Cannot serve on port {port}: {error}')
else:
sys.exit(f'Cannot serve: ports {args.port}-{args.port + 10} are all busy.')
if port != args.port:
print(f'Port {args.port} was busy; using {port}.', flush=True)
print(f'Serving dist/ at http://localhost:{port} (Ctrl+C to stop)', flush=True)
stopped = threading.Event()
threading.Thread(target=watch, args=(stopped, previous), daemon=True).start()
try:
with server:
server.serve_forever()
finally:
stopped.set()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('\nServer stopped.', flush=True)