1c5f01b2fSopenharmony_ci#!/usr/bin/env python3 2c5f01b2fSopenharmony_ci 3c5f01b2fSopenharmony_ciimport contextlib 4c5f01b2fSopenharmony_ciimport logging 5c5f01b2fSopenharmony_ciimport os 6c5f01b2fSopenharmony_ciimport re 7c5f01b2fSopenharmony_ciimport shutil 8c5f01b2fSopenharmony_ciimport sys 9c5f01b2fSopenharmony_ciimport subprocess 10c5f01b2fSopenharmony_ci 11c5f01b2fSopenharmony_cifrom datetime import datetime, timedelta 12c5f01b2fSopenharmony_cifrom io import BytesIO 13c5f01b2fSopenharmony_cifrom threading import Lock, Timer 14c5f01b2fSopenharmony_ci 15c5f01b2fSopenharmony_cifrom watchdog.events import FileSystemEventHandler 16c5f01b2fSopenharmony_cifrom watchdog.observers import Observer 17c5f01b2fSopenharmony_ci 18c5f01b2fSopenharmony_cifrom http import HTTPStatus 19c5f01b2fSopenharmony_cifrom http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler 20c5f01b2fSopenharmony_ci 21c5f01b2fSopenharmony_ciCONFIG_FILE = 'serve_header.yml' 22c5f01b2fSopenharmony_ciMAKEFILE = 'Makefile' 23c5f01b2fSopenharmony_ciINCLUDE = 'include/nlohmann/' 24c5f01b2fSopenharmony_ciSINGLE_INCLUDE = 'single_include/nlohmann/' 25c5f01b2fSopenharmony_ciHEADER = 'json.hpp' 26c5f01b2fSopenharmony_ci 27c5f01b2fSopenharmony_ciDATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' 28c5f01b2fSopenharmony_ci 29c5f01b2fSopenharmony_ciJSON_VERSION_RE = re.compile(r'\s*#\s*define\s+NLOHMANN_JSON_VERSION_MAJOR\s+') 30c5f01b2fSopenharmony_ci 31c5f01b2fSopenharmony_ciclass ExitHandler(logging.StreamHandler): 32c5f01b2fSopenharmony_ci def __init__(self, level): 33c5f01b2fSopenharmony_ci """.""" 34c5f01b2fSopenharmony_ci super().__init__() 35c5f01b2fSopenharmony_ci self.level = level 36c5f01b2fSopenharmony_ci 37c5f01b2fSopenharmony_ci def emit(self, record): 38c5f01b2fSopenharmony_ci if record.levelno >= self.level: 39c5f01b2fSopenharmony_ci sys.exit(1) 40c5f01b2fSopenharmony_ci 41c5f01b2fSopenharmony_cidef is_project_root(test_dir='.'): 42c5f01b2fSopenharmony_ci makefile = os.path.join(test_dir, MAKEFILE) 43c5f01b2fSopenharmony_ci include = os.path.join(test_dir, INCLUDE) 44c5f01b2fSopenharmony_ci single_include = os.path.join(test_dir, SINGLE_INCLUDE) 45c5f01b2fSopenharmony_ci 46c5f01b2fSopenharmony_ci return (os.path.exists(makefile) 47c5f01b2fSopenharmony_ci and os.path.isfile(makefile) 48c5f01b2fSopenharmony_ci and os.path.exists(include) 49c5f01b2fSopenharmony_ci and os.path.exists(single_include)) 50c5f01b2fSopenharmony_ci 51c5f01b2fSopenharmony_ciclass DirectoryEventBucket: 52c5f01b2fSopenharmony_ci def __init__(self, callback, delay=1.2, threshold=0.8): 53c5f01b2fSopenharmony_ci """.""" 54c5f01b2fSopenharmony_ci self.delay = delay 55c5f01b2fSopenharmony_ci self.threshold = timedelta(seconds=threshold) 56c5f01b2fSopenharmony_ci self.callback = callback 57c5f01b2fSopenharmony_ci self.event_dirs = set([]) 58c5f01b2fSopenharmony_ci self.timer = None 59c5f01b2fSopenharmony_ci self.lock = Lock() 60c5f01b2fSopenharmony_ci 61c5f01b2fSopenharmony_ci def start_timer(self): 62c5f01b2fSopenharmony_ci if self.timer is None: 63c5f01b2fSopenharmony_ci self.timer = Timer(self.delay, self.process_dirs) 64c5f01b2fSopenharmony_ci self.timer.start() 65c5f01b2fSopenharmony_ci 66c5f01b2fSopenharmony_ci def process_dirs(self): 67c5f01b2fSopenharmony_ci result_dirs = [] 68c5f01b2fSopenharmony_ci event_dirs = set([]) 69c5f01b2fSopenharmony_ci with self.lock: 70c5f01b2fSopenharmony_ci self.timer = None 71c5f01b2fSopenharmony_ci while self.event_dirs: 72c5f01b2fSopenharmony_ci time, event_dir = self.event_dirs.pop() 73c5f01b2fSopenharmony_ci delta = datetime.now() - time 74c5f01b2fSopenharmony_ci if delta < self.threshold: 75c5f01b2fSopenharmony_ci event_dirs.add((time, event_dir)) 76c5f01b2fSopenharmony_ci else: 77c5f01b2fSopenharmony_ci result_dirs.append(event_dir) 78c5f01b2fSopenharmony_ci self.event_dirs = event_dirs 79c5f01b2fSopenharmony_ci if result_dirs: 80c5f01b2fSopenharmony_ci self.callback(os.path.commonpath(result_dirs)) 81c5f01b2fSopenharmony_ci if self.event_dirs: 82c5f01b2fSopenharmony_ci self.start_timer() 83c5f01b2fSopenharmony_ci 84c5f01b2fSopenharmony_ci def add_dir(self, path): 85c5f01b2fSopenharmony_ci with self.lock: 86c5f01b2fSopenharmony_ci # add path to the set of event_dirs if it is not a sibling of 87c5f01b2fSopenharmony_ci # a directory already in the set 88c5f01b2fSopenharmony_ci if not any(os.path.commonpath([path, event_dir]) == event_dir 89c5f01b2fSopenharmony_ci for (_, event_dir) in self.event_dirs): 90c5f01b2fSopenharmony_ci self.event_dirs.add((datetime.now(), path)) 91c5f01b2fSopenharmony_ci if self.timer is None: 92c5f01b2fSopenharmony_ci self.start_timer() 93c5f01b2fSopenharmony_ci 94c5f01b2fSopenharmony_ciclass WorkTree: 95c5f01b2fSopenharmony_ci make_command = 'make' 96c5f01b2fSopenharmony_ci 97c5f01b2fSopenharmony_ci def __init__(self, root_dir, tree_dir): 98c5f01b2fSopenharmony_ci """.""" 99c5f01b2fSopenharmony_ci self.root_dir = root_dir 100c5f01b2fSopenharmony_ci self.tree_dir = tree_dir 101c5f01b2fSopenharmony_ci self.rel_dir = os.path.relpath(tree_dir, root_dir) 102c5f01b2fSopenharmony_ci self.name = os.path.basename(tree_dir) 103c5f01b2fSopenharmony_ci self.include_dir = os.path.abspath(os.path.join(tree_dir, INCLUDE)) 104c5f01b2fSopenharmony_ci self.header = os.path.abspath(os.path.join(tree_dir, SINGLE_INCLUDE, HEADER)) 105c5f01b2fSopenharmony_ci self.rel_header = os.path.relpath(self.header, root_dir) 106c5f01b2fSopenharmony_ci self.dirty = True 107c5f01b2fSopenharmony_ci self.build_count = 0 108c5f01b2fSopenharmony_ci t = os.path.getmtime(self.header) 109c5f01b2fSopenharmony_ci t = datetime.fromtimestamp(t) 110c5f01b2fSopenharmony_ci self.build_time = t.strftime(DATETIME_FORMAT) 111c5f01b2fSopenharmony_ci 112c5f01b2fSopenharmony_ci def __hash__(self): 113c5f01b2fSopenharmony_ci """.""" 114c5f01b2fSopenharmony_ci return hash((self.tree_dir)) 115c5f01b2fSopenharmony_ci 116c5f01b2fSopenharmony_ci def __eq__(self, other): 117c5f01b2fSopenharmony_ci """.""" 118c5f01b2fSopenharmony_ci if not isinstance(other, type(self)): 119c5f01b2fSopenharmony_ci return NotImplemented 120c5f01b2fSopenharmony_ci return self.tree_dir == other.tree_dir 121c5f01b2fSopenharmony_ci 122c5f01b2fSopenharmony_ci def update_dirty(self, path): 123c5f01b2fSopenharmony_ci if self.dirty: 124c5f01b2fSopenharmony_ci return 125c5f01b2fSopenharmony_ci 126c5f01b2fSopenharmony_ci path = os.path.abspath(path) 127c5f01b2fSopenharmony_ci if os.path.commonpath([path, self.include_dir]) == self.include_dir: 128c5f01b2fSopenharmony_ci logging.info(f'{self.name}: working tree marked dirty') 129c5f01b2fSopenharmony_ci self.dirty = True 130c5f01b2fSopenharmony_ci 131c5f01b2fSopenharmony_ci def amalgamate_header(self): 132c5f01b2fSopenharmony_ci if not self.dirty: 133c5f01b2fSopenharmony_ci return 134c5f01b2fSopenharmony_ci 135c5f01b2fSopenharmony_ci mtime = os.path.getmtime(self.header) 136c5f01b2fSopenharmony_ci subprocess.run([WorkTree.make_command, 'amalgamate'], cwd=self.tree_dir, 137c5f01b2fSopenharmony_ci stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 138c5f01b2fSopenharmony_ci if mtime == os.path.getmtime(self.header): 139c5f01b2fSopenharmony_ci logging.info(f'{self.name}: no changes') 140c5f01b2fSopenharmony_ci else: 141c5f01b2fSopenharmony_ci self.build_count += 1 142c5f01b2fSopenharmony_ci self.build_time = datetime.now().strftime(DATETIME_FORMAT) 143c5f01b2fSopenharmony_ci logging.info(f'{self.name}: header amalgamated (build count {self.build_count})') 144c5f01b2fSopenharmony_ci 145c5f01b2fSopenharmony_ci self.dirty = False 146c5f01b2fSopenharmony_ci 147c5f01b2fSopenharmony_ciclass WorkTrees(FileSystemEventHandler): 148c5f01b2fSopenharmony_ci def __init__(self, root_dir): 149c5f01b2fSopenharmony_ci """.""" 150c5f01b2fSopenharmony_ci super().__init__() 151c5f01b2fSopenharmony_ci self.root_dir = root_dir 152c5f01b2fSopenharmony_ci self.trees = set([]) 153c5f01b2fSopenharmony_ci self.tree_lock = Lock() 154c5f01b2fSopenharmony_ci self.scan(root_dir) 155c5f01b2fSopenharmony_ci self.created_bucket = DirectoryEventBucket(self.scan) 156c5f01b2fSopenharmony_ci self.observer = Observer() 157c5f01b2fSopenharmony_ci self.observer.schedule(self, root_dir, recursive=True) 158c5f01b2fSopenharmony_ci self.observer.start() 159c5f01b2fSopenharmony_ci 160c5f01b2fSopenharmony_ci def scan(self, base_dir): 161c5f01b2fSopenharmony_ci scan_dirs = set([base_dir]) 162c5f01b2fSopenharmony_ci # recursively scan base_dir for working trees 163c5f01b2fSopenharmony_ci 164c5f01b2fSopenharmony_ci while scan_dirs: 165c5f01b2fSopenharmony_ci scan_dir = os.path.abspath(scan_dirs.pop()) 166c5f01b2fSopenharmony_ci self.scan_tree(scan_dir) 167c5f01b2fSopenharmony_ci try: 168c5f01b2fSopenharmony_ci with os.scandir(scan_dir) as dir_it: 169c5f01b2fSopenharmony_ci for entry in dir_it: 170c5f01b2fSopenharmony_ci if entry.is_dir(): 171c5f01b2fSopenharmony_ci scan_dirs.add(entry.path) 172c5f01b2fSopenharmony_ci except FileNotFoundError as e: 173c5f01b2fSopenharmony_ci logging.debug('path disappeared: %s', e) 174c5f01b2fSopenharmony_ci 175c5f01b2fSopenharmony_ci def scan_tree(self, scan_dir): 176c5f01b2fSopenharmony_ci if not is_project_root(scan_dir): 177c5f01b2fSopenharmony_ci return 178c5f01b2fSopenharmony_ci 179c5f01b2fSopenharmony_ci # skip source trees in build directories 180c5f01b2fSopenharmony_ci # this check could be enhanced 181c5f01b2fSopenharmony_ci if scan_dir.endswith('/_deps/json-src'): 182c5f01b2fSopenharmony_ci return 183c5f01b2fSopenharmony_ci 184c5f01b2fSopenharmony_ci tree = WorkTree(self.root_dir, scan_dir) 185c5f01b2fSopenharmony_ci with self.tree_lock: 186c5f01b2fSopenharmony_ci if not tree in self.trees: 187c5f01b2fSopenharmony_ci if tree.name == tree.rel_dir: 188c5f01b2fSopenharmony_ci logging.info(f'adding working tree {tree.name}') 189c5f01b2fSopenharmony_ci else: 190c5f01b2fSopenharmony_ci logging.info(f'adding working tree {tree.name} at {tree.rel_dir}') 191c5f01b2fSopenharmony_ci url = os.path.join('/', tree.rel_dir, HEADER) 192c5f01b2fSopenharmony_ci logging.info(f'{tree.name}: serving header at {url}') 193c5f01b2fSopenharmony_ci self.trees.add(tree) 194c5f01b2fSopenharmony_ci 195c5f01b2fSopenharmony_ci def rescan(self, path=None): 196c5f01b2fSopenharmony_ci if path is not None: 197c5f01b2fSopenharmony_ci path = os.path.abspath(path) 198c5f01b2fSopenharmony_ci trees = set([]) 199c5f01b2fSopenharmony_ci # check if any working trees have been removed 200c5f01b2fSopenharmony_ci with self.tree_lock: 201c5f01b2fSopenharmony_ci while self.trees: 202c5f01b2fSopenharmony_ci tree = self.trees.pop() 203c5f01b2fSopenharmony_ci if ((path is None 204c5f01b2fSopenharmony_ci or os.path.commonpath([path, tree.tree_dir]) == tree.tree_dir) 205c5f01b2fSopenharmony_ci and not is_project_root(tree.tree_dir)): 206c5f01b2fSopenharmony_ci if tree.name == tree.rel_dir: 207c5f01b2fSopenharmony_ci logging.info(f'removing working tree {tree.name}') 208c5f01b2fSopenharmony_ci else: 209c5f01b2fSopenharmony_ci logging.info(f'removing working tree {tree.name} at {tree.rel_dir}') 210c5f01b2fSopenharmony_ci else: 211c5f01b2fSopenharmony_ci trees.add(tree) 212c5f01b2fSopenharmony_ci self.trees = trees 213c5f01b2fSopenharmony_ci 214c5f01b2fSopenharmony_ci def find(self, path): 215c5f01b2fSopenharmony_ci # find working tree for a given header file path 216c5f01b2fSopenharmony_ci path = os.path.abspath(path) 217c5f01b2fSopenharmony_ci with self.tree_lock: 218c5f01b2fSopenharmony_ci for tree in self.trees: 219c5f01b2fSopenharmony_ci if path == tree.header: 220c5f01b2fSopenharmony_ci return tree 221c5f01b2fSopenharmony_ci return None 222c5f01b2fSopenharmony_ci 223c5f01b2fSopenharmony_ci def on_any_event(self, event): 224c5f01b2fSopenharmony_ci logging.debug('%s (is_dir=%s): %s', event.event_type, 225c5f01b2fSopenharmony_ci event.is_directory, event.src_path) 226c5f01b2fSopenharmony_ci path = os.path.abspath(event.src_path) 227c5f01b2fSopenharmony_ci if event.is_directory: 228c5f01b2fSopenharmony_ci if event.event_type == 'created': 229c5f01b2fSopenharmony_ci # check for new working trees 230c5f01b2fSopenharmony_ci self.created_bucket.add_dir(path) 231c5f01b2fSopenharmony_ci elif event.event_type == 'deleted': 232c5f01b2fSopenharmony_ci # check for deleted working trees 233c5f01b2fSopenharmony_ci self.rescan(path) 234c5f01b2fSopenharmony_ci elif event.event_type == 'closed': 235c5f01b2fSopenharmony_ci with self.tree_lock: 236c5f01b2fSopenharmony_ci for tree in self.trees: 237c5f01b2fSopenharmony_ci tree.update_dirty(path) 238c5f01b2fSopenharmony_ci 239c5f01b2fSopenharmony_ci def stop(self): 240c5f01b2fSopenharmony_ci self.observer.stop() 241c5f01b2fSopenharmony_ci self.observer.join() 242c5f01b2fSopenharmony_ci 243c5f01b2fSopenharmony_ciclass HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-init] 244c5f01b2fSopenharmony_ci def __init__(self, request, client_address, server): 245c5f01b2fSopenharmony_ci """.""" 246c5f01b2fSopenharmony_ci self.worktrees = server.worktrees 247c5f01b2fSopenharmony_ci self.worktree = None 248c5f01b2fSopenharmony_ci try: 249c5f01b2fSopenharmony_ci super().__init__(request, client_address, server, 250c5f01b2fSopenharmony_ci directory=server.worktrees.root_dir) 251c5f01b2fSopenharmony_ci except ConnectionResetError: 252c5f01b2fSopenharmony_ci logging.debug('connection reset by peer') 253c5f01b2fSopenharmony_ci 254c5f01b2fSopenharmony_ci def translate_path(self, path): 255c5f01b2fSopenharmony_ci path = os.path.abspath(super().translate_path(path)) 256c5f01b2fSopenharmony_ci 257c5f01b2fSopenharmony_ci # add single_include/nlohmann into path, if needed 258c5f01b2fSopenharmony_ci header = os.path.join('/', HEADER) 259c5f01b2fSopenharmony_ci header_path = os.path.join('/', SINGLE_INCLUDE, HEADER) 260c5f01b2fSopenharmony_ci if (path.endswith(header) 261c5f01b2fSopenharmony_ci and not path.endswith(header_path)): 262c5f01b2fSopenharmony_ci path = os.path.join(os.path.dirname(path), SINGLE_INCLUDE, HEADER) 263c5f01b2fSopenharmony_ci 264c5f01b2fSopenharmony_ci return path 265c5f01b2fSopenharmony_ci 266c5f01b2fSopenharmony_ci def send_head(self): 267c5f01b2fSopenharmony_ci # check if the translated path matches a working tree 268c5f01b2fSopenharmony_ci # and fullfill the request; otherwise, send 404 269c5f01b2fSopenharmony_ci path = self.translate_path(self.path) 270c5f01b2fSopenharmony_ci self.worktree = self.worktrees.find(path) 271c5f01b2fSopenharmony_ci if self.worktree is not None: 272c5f01b2fSopenharmony_ci self.worktree.amalgamate_header() 273c5f01b2fSopenharmony_ci logging.info(f'{self.worktree.name}; serving header (build count {self.worktree.build_count})') 274c5f01b2fSopenharmony_ci return super().send_head() 275c5f01b2fSopenharmony_ci logging.info(f'invalid request path: {self.path}') 276c5f01b2fSopenharmony_ci super().send_error(HTTPStatus.NOT_FOUND, 'Not Found') 277c5f01b2fSopenharmony_ci return None 278c5f01b2fSopenharmony_ci 279c5f01b2fSopenharmony_ci def send_header(self, keyword, value): 280c5f01b2fSopenharmony_ci # intercept Content-Length header; sent in copyfile later 281c5f01b2fSopenharmony_ci if keyword == 'Content-Length': 282c5f01b2fSopenharmony_ci return 283c5f01b2fSopenharmony_ci super().send_header(keyword, value) 284c5f01b2fSopenharmony_ci 285c5f01b2fSopenharmony_ci def end_headers (self): 286c5f01b2fSopenharmony_ci # intercept; called in copyfile() or indirectly 287c5f01b2fSopenharmony_ci # by send_head via super().send_error() 288c5f01b2fSopenharmony_ci pass 289c5f01b2fSopenharmony_ci 290c5f01b2fSopenharmony_ci def copyfile(self, source, outputfile): 291c5f01b2fSopenharmony_ci injected = False 292c5f01b2fSopenharmony_ci content = BytesIO() 293c5f01b2fSopenharmony_ci length = 0 294c5f01b2fSopenharmony_ci # inject build count and time into served header 295c5f01b2fSopenharmony_ci for line in source: 296c5f01b2fSopenharmony_ci line = line.decode('utf-8') 297c5f01b2fSopenharmony_ci if not injected and JSON_VERSION_RE.match(line): 298c5f01b2fSopenharmony_ci length += content.write(bytes('#define JSON_BUILD_COUNT '\ 299c5f01b2fSopenharmony_ci f'{self.worktree.build_count}\n', 'utf-8')) 300c5f01b2fSopenharmony_ci length += content.write(bytes('#define JSON_BUILD_TIME '\ 301c5f01b2fSopenharmony_ci f'"{self.worktree.build_time}"\n\n', 'utf-8')) 302c5f01b2fSopenharmony_ci injected = True 303c5f01b2fSopenharmony_ci length += content.write(bytes(line, 'utf-8')) 304c5f01b2fSopenharmony_ci 305c5f01b2fSopenharmony_ci # set content length 306c5f01b2fSopenharmony_ci super().send_header('Content-Length', length) 307c5f01b2fSopenharmony_ci # CORS header 308c5f01b2fSopenharmony_ci self.send_header('Access-Control-Allow-Origin', '*') 309c5f01b2fSopenharmony_ci # prevent caching 310c5f01b2fSopenharmony_ci self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 311c5f01b2fSopenharmony_ci self.send_header('Pragma', 'no-cache') 312c5f01b2fSopenharmony_ci self.send_header('Expires', '0') 313c5f01b2fSopenharmony_ci super().end_headers() 314c5f01b2fSopenharmony_ci 315c5f01b2fSopenharmony_ci # send the header 316c5f01b2fSopenharmony_ci content.seek(0) 317c5f01b2fSopenharmony_ci shutil.copyfileobj(content, outputfile) 318c5f01b2fSopenharmony_ci 319c5f01b2fSopenharmony_ci def log_message(self, format, *args): 320c5f01b2fSopenharmony_ci pass 321c5f01b2fSopenharmony_ci 322c5f01b2fSopenharmony_ciclass DualStackServer(ThreadingHTTPServer): 323c5f01b2fSopenharmony_ci def __init__(self, addr, worktrees): 324c5f01b2fSopenharmony_ci """.""" 325c5f01b2fSopenharmony_ci self.worktrees = worktrees 326c5f01b2fSopenharmony_ci super().__init__(addr, HeaderRequestHandler) 327c5f01b2fSopenharmony_ci 328c5f01b2fSopenharmony_ci def server_bind(self): 329c5f01b2fSopenharmony_ci # suppress exception when protocol is IPv4 330c5f01b2fSopenharmony_ci with contextlib.suppress(Exception): 331c5f01b2fSopenharmony_ci self.socket.setsockopt( 332c5f01b2fSopenharmony_ci socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 333c5f01b2fSopenharmony_ci return super().server_bind() 334c5f01b2fSopenharmony_ci 335c5f01b2fSopenharmony_ciif __name__ == '__main__': 336c5f01b2fSopenharmony_ci import argparse 337c5f01b2fSopenharmony_ci import ssl 338c5f01b2fSopenharmony_ci import socket 339c5f01b2fSopenharmony_ci import yaml 340c5f01b2fSopenharmony_ci 341c5f01b2fSopenharmony_ci # exit code 342c5f01b2fSopenharmony_ci ec = 0 343c5f01b2fSopenharmony_ci 344c5f01b2fSopenharmony_ci # setup logging 345c5f01b2fSopenharmony_ci logging.basicConfig(format='[%(asctime)s] %(levelname)s: %(message)s', 346c5f01b2fSopenharmony_ci datefmt=DATETIME_FORMAT, level=logging.INFO) 347c5f01b2fSopenharmony_ci log = logging.getLogger() 348c5f01b2fSopenharmony_ci log.addHandler(ExitHandler(logging.ERROR)) 349c5f01b2fSopenharmony_ci 350c5f01b2fSopenharmony_ci # parse command line arguments 351c5f01b2fSopenharmony_ci parser = argparse.ArgumentParser() 352c5f01b2fSopenharmony_ci parser.add_argument('--make', default='make', 353c5f01b2fSopenharmony_ci help='the make command (default: make)') 354c5f01b2fSopenharmony_ci args = parser.parse_args() 355c5f01b2fSopenharmony_ci 356c5f01b2fSopenharmony_ci # propagate the make command to use for amalgamating headers 357c5f01b2fSopenharmony_ci WorkTree.make_command = args.make 358c5f01b2fSopenharmony_ci 359c5f01b2fSopenharmony_ci worktrees = None 360c5f01b2fSopenharmony_ci try: 361c5f01b2fSopenharmony_ci # change working directory to project root 362c5f01b2fSopenharmony_ci os.chdir(os.path.realpath(os.path.join(sys.path[0], '../../'))) 363c5f01b2fSopenharmony_ci 364c5f01b2fSopenharmony_ci if not is_project_root(): 365c5f01b2fSopenharmony_ci log.error('working directory does not look like project root') 366c5f01b2fSopenharmony_ci 367c5f01b2fSopenharmony_ci # load config 368c5f01b2fSopenharmony_ci config = {} 369c5f01b2fSopenharmony_ci config_file = os.path.abspath(CONFIG_FILE) 370c5f01b2fSopenharmony_ci try: 371c5f01b2fSopenharmony_ci with open(config_file, 'r') as f: 372c5f01b2fSopenharmony_ci config = yaml.safe_load(f) 373c5f01b2fSopenharmony_ci except FileNotFoundError: 374c5f01b2fSopenharmony_ci log.info(f'cannot find configuration file: {config_file}') 375c5f01b2fSopenharmony_ci log.info('using default configuration') 376c5f01b2fSopenharmony_ci 377c5f01b2fSopenharmony_ci # find and monitor working trees 378c5f01b2fSopenharmony_ci worktrees = WorkTrees(config.get('root', '.')) 379c5f01b2fSopenharmony_ci 380c5f01b2fSopenharmony_ci # start web server 381c5f01b2fSopenharmony_ci infos = socket.getaddrinfo(config.get('bind', None), config.get('port', 8443), 382c5f01b2fSopenharmony_ci type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE) 383c5f01b2fSopenharmony_ci DualStackServer.address_family = infos[0][0] 384c5f01b2fSopenharmony_ci HeaderRequestHandler.protocol_version = 'HTTP/1.0' 385c5f01b2fSopenharmony_ci with DualStackServer(infos[0][4], worktrees) as httpd: 386c5f01b2fSopenharmony_ci scheme = 'HTTP' 387c5f01b2fSopenharmony_ci https = config.get('https', {}) 388c5f01b2fSopenharmony_ci if https.get('enabled', True): 389c5f01b2fSopenharmony_ci cert_file = https.get('cert_file', 'localhost.pem') 390c5f01b2fSopenharmony_ci key_file = https.get('key_file', 'localhost-key.pem') 391c5f01b2fSopenharmony_ci ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 392c5f01b2fSopenharmony_ci ssl_ctx.minimum_version = ssl.TLSVersion.TLSv1_2 393c5f01b2fSopenharmony_ci ssl_ctx.maximum_version = ssl.TLSVersion.MAXIMUM_SUPPORTED 394c5f01b2fSopenharmony_ci ssl_ctx.load_cert_chain(cert_file, key_file) 395c5f01b2fSopenharmony_ci httpd.socket = ssl_ctx.wrap_socket(httpd.socket, server_side=True) 396c5f01b2fSopenharmony_ci scheme = 'HTTPS' 397c5f01b2fSopenharmony_ci host, port = httpd.socket.getsockname()[:2] 398c5f01b2fSopenharmony_ci log.info(f'serving {scheme} on {host} port {port}') 399c5f01b2fSopenharmony_ci log.info('press Ctrl+C to exit') 400c5f01b2fSopenharmony_ci httpd.serve_forever() 401c5f01b2fSopenharmony_ci 402c5f01b2fSopenharmony_ci except KeyboardInterrupt: 403c5f01b2fSopenharmony_ci log.info('exiting') 404c5f01b2fSopenharmony_ci except Exception: 405c5f01b2fSopenharmony_ci ec = 1 406c5f01b2fSopenharmony_ci log.exception('an error occurred:') 407c5f01b2fSopenharmony_ci finally: 408c5f01b2fSopenharmony_ci if worktrees is not None: 409c5f01b2fSopenharmony_ci worktrees.stop() 410c5f01b2fSopenharmony_ci sys.exit(ec) 411