3 # Copyright 2001 Google Inc. All Rights Reserved.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Simple web server for browsing dependency graph data.
19 This script is inlined into the final executable and spawned by
23 from __future__ import print_function
26 import http.server as httpserver
28 import BaseHTTPServer as httpserver
35 from collections import namedtuple
37 Node = namedtuple('Node', ['inputs', 'rule', 'target', 'outputs'])
39 # Ideally we'd allow you to navigate to a build edge or a build node,
40 # with appropriate views for each. But there's no way to *name* a build
41 # edge so we can only display nodes.
43 # For a given node, it has at most one input edge, which has n
44 # different inputs. This becomes node.inputs. (We leave out the
45 # outputs of the input edge due to what follows.) The node can have
46 # multiple dependent output edges. Rather than attempting to display
47 # those, they are summarized by taking the union of all their outputs.
49 # This means there's no single view that shows you all inputs and outputs
50 # of an edge. But I think it's less confusing than alternatives.
52 def match_strip(line, prefix):
53 if not line.startswith(prefix):
55 return (True, line[len(prefix):])
58 lines = iter(text.split('\n'))
66 target = next(lines)[:-1] # strip trailing colon
69 (match, rule) = match_strip(line, ' input: ')
71 (match, line) = match_strip(next(lines), ' ')
74 (match, line) = match_strip(line, '| ')
77 (match, line) = match_strip(line, '|| ')
80 inputs.append((line, type))
81 (match, line) = match_strip(next(lines), ' ')
83 match, _ = match_strip(line, ' outputs:')
85 (match, line) = match_strip(next(lines), ' ')
88 (match, line) = match_strip(next(lines), ' ')
92 return Node(inputs, rule, target, outputs)
94 def create_page(body):
95 return '''<!DOCTYPE html>
113 font-family: WebKitHack, monospace;
117 -webkit-columns: auto 2;
122 def generate_html(node):
123 document = ['<h1><tt>%s</tt></h1>' % node.target]
126 document.append('<h2>target is built using rule <tt>%s</tt> of</h2>' %
128 if len(node.inputs) > 0:
129 document.append('<div class=filelist>')
130 for input, type in sorted(node.inputs):
133 extra = ' (%s)' % type
134 document.append('<tt><a href="?%s">%s</a>%s</tt><br>' %
135 (input, input, extra))
136 document.append('</div>')
139 document.append('<h2>dependent edges build:</h2>')
140 document.append('<div class=filelist>')
141 for output in sorted(node.outputs):
142 document.append('<tt><a href="?%s">%s</a></tt><br>' %
144 document.append('</div>')
146 return '\n'.join(document)
148 def ninja_dump(target):
149 proc = subprocess.Popen([sys.argv[1], '-t', 'query', target],
150 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
151 universal_newlines=True)
152 return proc.communicate() + (proc.returncode,)
154 class RequestHandler(httpserver.BaseHTTPRequestHandler):
156 assert self.path[0] == '/'
157 target = urllib2.unquote(self.path[1:])
160 self.send_response(302)
161 self.send_header('Location', '?' + sys.argv[2])
165 if not target.startswith('?'):
166 self.send_response(404)
171 ninja_output, ninja_error, exit_code = ninja_dump(target)
173 page_body = generate_html(parse(ninja_output.strip()))
175 # Relay ninja's error message.
176 page_body = '<h1><tt>%s</tt></h1>' % ninja_error
178 self.send_response(200)
180 self.wfile.write(create_page(page_body).encode('utf-8'))
182 def log_message(self, format, *args):
183 pass # Swallow console spam.
185 port = int(os.getenv("PORT", '8000'))
186 httpd = httpserver.HTTPServer(('',port), RequestHandler)
188 hostname = socket.gethostname()
189 print('Web server running on %s:%d, ctl-C to abort...' % (hostname,port) )
190 print('Web server pid %d' % os.getpid(), file=sys.stderr )
191 webbrowser.open_new('http://%s:%s' % (hostname, port) )
192 httpd.serve_forever()
193 except KeyboardInterrupt:
195 pass # Swallow console spam.