1695b41eeSopenharmony_ci#!/usr/bin/env python3 2695b41eeSopenharmony_ci# 3695b41eeSopenharmony_ci# Copyright 2001 Google Inc. All Rights Reserved. 4695b41eeSopenharmony_ci# 5695b41eeSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); 6695b41eeSopenharmony_ci# you may not use this file except in compliance with the License. 7695b41eeSopenharmony_ci# You may obtain a copy of the License at 8695b41eeSopenharmony_ci# 9695b41eeSopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0 10695b41eeSopenharmony_ci# 11695b41eeSopenharmony_ci# Unless required by applicable law or agreed to in writing, software 12695b41eeSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, 13695b41eeSopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14695b41eeSopenharmony_ci# See the License for the specific language governing permissions and 15695b41eeSopenharmony_ci# limitations under the License. 16695b41eeSopenharmony_ci 17695b41eeSopenharmony_ci"""Simple web server for browsing dependency graph data. 18695b41eeSopenharmony_ci 19695b41eeSopenharmony_ciThis script is inlined into the final executable and spawned by 20695b41eeSopenharmony_ciit when needed. 21695b41eeSopenharmony_ci""" 22695b41eeSopenharmony_ci 23695b41eeSopenharmony_citry: 24695b41eeSopenharmony_ci import http.server as httpserver 25695b41eeSopenharmony_ci import socketserver 26695b41eeSopenharmony_ciexcept ImportError: 27695b41eeSopenharmony_ci import BaseHTTPServer as httpserver 28695b41eeSopenharmony_ci import SocketServer as socketserver 29695b41eeSopenharmony_ciimport argparse 30695b41eeSopenharmony_ciimport os 31695b41eeSopenharmony_ciimport socket 32695b41eeSopenharmony_ciimport subprocess 33695b41eeSopenharmony_ciimport sys 34695b41eeSopenharmony_ciimport webbrowser 35695b41eeSopenharmony_ciif sys.version_info >= (3, 2): 36695b41eeSopenharmony_ci from html import escape 37695b41eeSopenharmony_cielse: 38695b41eeSopenharmony_ci from cgi import escape 39695b41eeSopenharmony_citry: 40695b41eeSopenharmony_ci from urllib.request import unquote 41695b41eeSopenharmony_ciexcept ImportError: 42695b41eeSopenharmony_ci from urllib2 import unquote 43695b41eeSopenharmony_cifrom collections import namedtuple 44695b41eeSopenharmony_ci 45695b41eeSopenharmony_ciNode = namedtuple('Node', ['inputs', 'rule', 'target', 'outputs']) 46695b41eeSopenharmony_ci 47695b41eeSopenharmony_ci# Ideally we'd allow you to navigate to a build edge or a build node, 48695b41eeSopenharmony_ci# with appropriate views for each. But there's no way to *name* a build 49695b41eeSopenharmony_ci# edge so we can only display nodes. 50695b41eeSopenharmony_ci# 51695b41eeSopenharmony_ci# For a given node, it has at most one input edge, which has n 52695b41eeSopenharmony_ci# different inputs. This becomes node.inputs. (We leave out the 53695b41eeSopenharmony_ci# outputs of the input edge due to what follows.) The node can have 54695b41eeSopenharmony_ci# multiple dependent output edges. Rather than attempting to display 55695b41eeSopenharmony_ci# those, they are summarized by taking the union of all their outputs. 56695b41eeSopenharmony_ci# 57695b41eeSopenharmony_ci# This means there's no single view that shows you all inputs and outputs 58695b41eeSopenharmony_ci# of an edge. But I think it's less confusing than alternatives. 59695b41eeSopenharmony_ci 60695b41eeSopenharmony_cidef match_strip(line, prefix): 61695b41eeSopenharmony_ci if not line.startswith(prefix): 62695b41eeSopenharmony_ci return (False, line) 63695b41eeSopenharmony_ci return (True, line[len(prefix):]) 64695b41eeSopenharmony_ci 65695b41eeSopenharmony_cidef html_escape(text): 66695b41eeSopenharmony_ci return escape(text, quote=True) 67695b41eeSopenharmony_ci 68695b41eeSopenharmony_cidef parse(text): 69695b41eeSopenharmony_ci lines = iter(text.split('\n')) 70695b41eeSopenharmony_ci 71695b41eeSopenharmony_ci target = None 72695b41eeSopenharmony_ci rule = None 73695b41eeSopenharmony_ci inputs = [] 74695b41eeSopenharmony_ci outputs = [] 75695b41eeSopenharmony_ci 76695b41eeSopenharmony_ci try: 77695b41eeSopenharmony_ci target = next(lines)[:-1] # strip trailing colon 78695b41eeSopenharmony_ci 79695b41eeSopenharmony_ci line = next(lines) 80695b41eeSopenharmony_ci (match, rule) = match_strip(line, ' input: ') 81695b41eeSopenharmony_ci if match: 82695b41eeSopenharmony_ci (match, line) = match_strip(next(lines), ' ') 83695b41eeSopenharmony_ci while match: 84695b41eeSopenharmony_ci type = None 85695b41eeSopenharmony_ci (match, line) = match_strip(line, '| ') 86695b41eeSopenharmony_ci if match: 87695b41eeSopenharmony_ci type = 'implicit' 88695b41eeSopenharmony_ci (match, line) = match_strip(line, '|| ') 89695b41eeSopenharmony_ci if match: 90695b41eeSopenharmony_ci type = 'order-only' 91695b41eeSopenharmony_ci inputs.append((line, type)) 92695b41eeSopenharmony_ci (match, line) = match_strip(next(lines), ' ') 93695b41eeSopenharmony_ci 94695b41eeSopenharmony_ci match, _ = match_strip(line, ' outputs:') 95695b41eeSopenharmony_ci if match: 96695b41eeSopenharmony_ci (match, line) = match_strip(next(lines), ' ') 97695b41eeSopenharmony_ci while match: 98695b41eeSopenharmony_ci outputs.append(line) 99695b41eeSopenharmony_ci (match, line) = match_strip(next(lines), ' ') 100695b41eeSopenharmony_ci except StopIteration: 101695b41eeSopenharmony_ci pass 102695b41eeSopenharmony_ci 103695b41eeSopenharmony_ci return Node(inputs, rule, target, outputs) 104695b41eeSopenharmony_ci 105695b41eeSopenharmony_cidef create_page(body): 106695b41eeSopenharmony_ci return '''<!DOCTYPE html> 107695b41eeSopenharmony_ci<style> 108695b41eeSopenharmony_cibody { 109695b41eeSopenharmony_ci font-family: sans; 110695b41eeSopenharmony_ci font-size: 0.8em; 111695b41eeSopenharmony_ci margin: 4ex; 112695b41eeSopenharmony_ci} 113695b41eeSopenharmony_cih1 { 114695b41eeSopenharmony_ci font-weight: normal; 115695b41eeSopenharmony_ci font-size: 140%; 116695b41eeSopenharmony_ci text-align: center; 117695b41eeSopenharmony_ci margin: 0; 118695b41eeSopenharmony_ci} 119695b41eeSopenharmony_cih2 { 120695b41eeSopenharmony_ci font-weight: normal; 121695b41eeSopenharmony_ci font-size: 120%; 122695b41eeSopenharmony_ci} 123695b41eeSopenharmony_citt { 124695b41eeSopenharmony_ci font-family: WebKitHack, monospace; 125695b41eeSopenharmony_ci white-space: nowrap; 126695b41eeSopenharmony_ci} 127695b41eeSopenharmony_ci.filelist { 128695b41eeSopenharmony_ci -webkit-columns: auto 2; 129695b41eeSopenharmony_ci} 130695b41eeSopenharmony_ci</style> 131695b41eeSopenharmony_ci''' + body 132695b41eeSopenharmony_ci 133695b41eeSopenharmony_cidef generate_html(node): 134695b41eeSopenharmony_ci document = ['<h1><tt>%s</tt></h1>' % html_escape(node.target)] 135695b41eeSopenharmony_ci 136695b41eeSopenharmony_ci if node.inputs: 137695b41eeSopenharmony_ci document.append('<h2>target is built using rule <tt>%s</tt> of</h2>' % 138695b41eeSopenharmony_ci html_escape(node.rule)) 139695b41eeSopenharmony_ci if len(node.inputs) > 0: 140695b41eeSopenharmony_ci document.append('<div class=filelist>') 141695b41eeSopenharmony_ci for input, type in sorted(node.inputs): 142695b41eeSopenharmony_ci extra = '' 143695b41eeSopenharmony_ci if type: 144695b41eeSopenharmony_ci extra = ' (%s)' % html_escape(type) 145695b41eeSopenharmony_ci document.append('<tt><a href="?%s">%s</a>%s</tt><br>' % 146695b41eeSopenharmony_ci (html_escape(input), html_escape(input), extra)) 147695b41eeSopenharmony_ci document.append('</div>') 148695b41eeSopenharmony_ci 149695b41eeSopenharmony_ci if node.outputs: 150695b41eeSopenharmony_ci document.append('<h2>dependent edges build:</h2>') 151695b41eeSopenharmony_ci document.append('<div class=filelist>') 152695b41eeSopenharmony_ci for output in sorted(node.outputs): 153695b41eeSopenharmony_ci document.append('<tt><a href="?%s">%s</a></tt><br>' % 154695b41eeSopenharmony_ci (html_escape(output), html_escape(output))) 155695b41eeSopenharmony_ci document.append('</div>') 156695b41eeSopenharmony_ci 157695b41eeSopenharmony_ci return '\n'.join(document) 158695b41eeSopenharmony_ci 159695b41eeSopenharmony_cidef ninja_dump(target): 160695b41eeSopenharmony_ci cmd = [args.ninja_command, '-f', args.f, '-t', 'query', target] 161695b41eeSopenharmony_ci proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 162695b41eeSopenharmony_ci universal_newlines=True) 163695b41eeSopenharmony_ci return proc.communicate() + (proc.returncode,) 164695b41eeSopenharmony_ci 165695b41eeSopenharmony_ciclass RequestHandler(httpserver.BaseHTTPRequestHandler): 166695b41eeSopenharmony_ci def do_GET(self): 167695b41eeSopenharmony_ci assert self.path[0] == '/' 168695b41eeSopenharmony_ci target = unquote(self.path[1:]) 169695b41eeSopenharmony_ci 170695b41eeSopenharmony_ci if target == '': 171695b41eeSopenharmony_ci self.send_response(302) 172695b41eeSopenharmony_ci self.send_header('Location', '?' + args.initial_target) 173695b41eeSopenharmony_ci self.end_headers() 174695b41eeSopenharmony_ci return 175695b41eeSopenharmony_ci 176695b41eeSopenharmony_ci if not target.startswith('?'): 177695b41eeSopenharmony_ci self.send_response(404) 178695b41eeSopenharmony_ci self.end_headers() 179695b41eeSopenharmony_ci return 180695b41eeSopenharmony_ci target = target[1:] 181695b41eeSopenharmony_ci 182695b41eeSopenharmony_ci ninja_output, ninja_error, exit_code = ninja_dump(target) 183695b41eeSopenharmony_ci if exit_code == 0: 184695b41eeSopenharmony_ci page_body = generate_html(parse(ninja_output.strip())) 185695b41eeSopenharmony_ci else: 186695b41eeSopenharmony_ci # Relay ninja's error message. 187695b41eeSopenharmony_ci page_body = '<h1><tt>%s</tt></h1>' % html_escape(ninja_error) 188695b41eeSopenharmony_ci 189695b41eeSopenharmony_ci self.send_response(200) 190695b41eeSopenharmony_ci self.end_headers() 191695b41eeSopenharmony_ci self.wfile.write(create_page(page_body).encode('utf-8')) 192695b41eeSopenharmony_ci 193695b41eeSopenharmony_ci def log_message(self, format, *args): 194695b41eeSopenharmony_ci pass # Swallow console spam. 195695b41eeSopenharmony_ci 196695b41eeSopenharmony_ciparser = argparse.ArgumentParser(prog='ninja -t browse') 197695b41eeSopenharmony_ciparser.add_argument('--port', '-p', default=8000, type=int, 198695b41eeSopenharmony_ci help='Port number to use (default %(default)d)') 199695b41eeSopenharmony_ciparser.add_argument('--hostname', '-a', default='localhost', type=str, 200695b41eeSopenharmony_ci help='Hostname to bind to (default %(default)s)') 201695b41eeSopenharmony_ciparser.add_argument('--no-browser', action='store_true', 202695b41eeSopenharmony_ci help='Do not open a webbrowser on startup.') 203695b41eeSopenharmony_ci 204695b41eeSopenharmony_ciparser.add_argument('--ninja-command', default='ninja', 205695b41eeSopenharmony_ci help='Path to ninja binary (default %(default)s)') 206695b41eeSopenharmony_ciparser.add_argument('-f', default='build.ninja', 207695b41eeSopenharmony_ci help='Path to build.ninja file (default %(default)s)') 208695b41eeSopenharmony_ciparser.add_argument('initial_target', default='all', nargs='?', 209695b41eeSopenharmony_ci help='Initial target to show (default %(default)s)') 210695b41eeSopenharmony_ci 211695b41eeSopenharmony_ciclass HTTPServer(socketserver.ThreadingMixIn, httpserver.HTTPServer): 212695b41eeSopenharmony_ci # terminate server immediately when Python exits. 213695b41eeSopenharmony_ci daemon_threads = True 214695b41eeSopenharmony_ci 215695b41eeSopenharmony_ciargs = parser.parse_args() 216695b41eeSopenharmony_ciport = args.port 217695b41eeSopenharmony_cihostname = args.hostname 218695b41eeSopenharmony_cihttpd = HTTPServer((hostname,port), RequestHandler) 219695b41eeSopenharmony_citry: 220695b41eeSopenharmony_ci if hostname == "": 221695b41eeSopenharmony_ci hostname = socket.gethostname() 222695b41eeSopenharmony_ci print('Web server running on %s:%d, ctl-C to abort...' % (hostname,port) ) 223695b41eeSopenharmony_ci print('Web server pid %d' % os.getpid(), file=sys.stderr ) 224695b41eeSopenharmony_ci if not args.no_browser: 225695b41eeSopenharmony_ci webbrowser.open_new('http://%s:%s' % (hostname, port) ) 226695b41eeSopenharmony_ci httpd.serve_forever() 227695b41eeSopenharmony_ciexcept KeyboardInterrupt: 228695b41eeSopenharmony_ci print() 229695b41eeSopenharmony_ci pass # Swallow console spam. 230695b41eeSopenharmony_ci 231695b41eeSopenharmony_ci 232