summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xsrc/browse.py130
-rw-r--r--src/ninja.cc42
2 files changed, 171 insertions, 1 deletions
diff --git a/src/browse.py b/src/browse.py
new file mode 100755
index 0000000..0d586bf
--- /dev/null
+++ b/src/browse.py
@@ -0,0 +1,130 @@
+#!/usr/bin/python
+
+"""Simple web server for browsing dependency graph data.
+
+This script is inlined into the final executable and spawned by
+it when needed.
+"""
+
+import BaseHTTPServer
+import subprocess
+import sys
+import webbrowser
+
+def match_strip(prefix, line):
+ assert line.startswith(prefix)
+ return line[len(prefix):]
+
+def parse(text):
+ lines = text.split('\n')
+ node = lines.pop(0)
+ node = node[:-1] # strip trailing colon
+
+ input = []
+ if lines and lines[0].startswith(' input:'):
+ input.append(match_strip(' input: ', lines.pop(0)))
+ while lines and lines[0].startswith(' '):
+ input.append(lines.pop(0).strip())
+
+ outputs = []
+ while lines:
+ output = []
+ output.append(match_strip(' output: ', lines.pop(0)))
+ while lines and lines[0].startswith(' '):
+ output.append(lines.pop(0).strip())
+ outputs.append(output)
+
+ return (node, input, outputs)
+
+def generate_html(data):
+ node, input, outputs = data
+ print '''<!DOCTYPE html>
+<style>
+body {
+ font-family: sans;
+ font-size: 0.8em;
+ margin: 4ex;
+}
+h1 {
+ font-weight: normal;
+ text-align: center;
+ margin: 0;
+}
+h2 {
+ font-weight: normal;
+}
+tt {
+ font-family: WebKitHack, monospace;
+}
+</style>'''
+ print '<table><tr><td colspan=3>'
+ print '<h1>%s</h1>' % node
+ print '</td></tr>'
+
+ print '<tr><td valign=top>'
+ print '<h2>input</h2>'
+ if input:
+ print '<p><tt>%s</tt>:</p>' % input[0]
+ print '<ul>'
+ for i in input[1:]:
+ print '<li><tt><a href="?%s">%s</a></tt></li>' % (i, i)
+ print '</ul>'
+ print '</td>'
+ print '<td width=50>&nbsp;</td>'
+
+ print '<td valign=top>'
+ print '<h2>outputs</h2>'
+ for output in outputs:
+ print '<p><tt>%s</tt>:</p>' % output[0]
+ print '<ul>'
+ for i in output[1:]:
+ print '<li><tt><a href="?%s">%s</a></tt></li>' % (i, i)
+ print '</ul>'
+ print '</td></tr></table>'
+
+def ninja_dump(target):
+ proc = subprocess.Popen(['./ninja', '-q', target], stdout=subprocess.PIPE)
+ return proc.communicate()[0]
+
+class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def do_GET(self):
+ assert self.path[0] == '/'
+ target = self.path[1:]
+
+ if target == '':
+ self.send_response(302)
+ self.send_header('Location', '?' + sys.argv[1])
+ self.end_headers()
+ return
+
+ if not target.startswith('?'):
+ self.send_response(404)
+ self.end_headers()
+ return
+ target = target[1:]
+
+ input = ninja_dump(target)
+
+ self.send_response(200)
+ self.end_headers()
+ stdout = sys.stdout
+ sys.stdout = self.wfile
+ try:
+ generate_html(parse(input.strip()))
+ finally:
+ sys.stdout = stdout
+
+ def log_message(self, format, *args):
+ pass # Swallow console spam.
+
+port = 8000
+httpd = BaseHTTPServer.HTTPServer(('',port), RequestHandler)
+try:
+ print 'Web server running on port %d...' % port
+ webbrowser.open_new('http://localhost:%s' % port)
+ httpd.serve_forever()
+except KeyboardInterrupt:
+ print
+ pass # Swallow console spam.
+
+
diff --git a/src/ninja.cc b/src/ninja.cc
index 0c77489..d273bea 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -10,6 +10,17 @@
#include "graphviz.h"
+// Import browse.py as binary data.
+asm(
+".data\n"
+"browse_data_begin:\n"
+".incbin \"src/browse.py\"\n"
+"browse_data_end:\n"
+);
+// Declare the symbols defined above.
+extern const char browse_data_begin[];
+extern const char browse_data_end[];
+
option options[] = {
{ "help", no_argument, NULL, 'h' },
{ }
@@ -25,6 +36,7 @@ void usage() {
" -n dry run (don't run commands but pretend they succeeded)\n"
" -v show all command lines\n"
" -q show inputs/outputs of target (query mode)\n"
+" -b browse dependency graph of target in a web browser\n"
);
}
@@ -39,9 +51,10 @@ int main(int argc, char** argv) {
const char* input_file = "build.ninja";
bool graph = false;
bool query = false;
+ bool browse = false;
int opt;
- while ((opt = getopt_long(argc, argv, "ghi:nvq", options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "bghi:nvq", options, NULL)) != -1) {
switch (opt) {
case 'g':
graph = true;
@@ -58,6 +71,9 @@ int main(int argc, char** argv) {
case 'q':
query = true;
break;
+ case 'b':
+ browse = true;
+ break;
case 'h':
default:
usage();
@@ -123,6 +139,30 @@ int main(int argc, char** argv) {
return 0;
}
+ if (browse) {
+ // Create a temporary file, dump the Python code into it, and
+ // delete the file, keeping our open handle to it.
+ char tmpl[] = "browsepy-XXXXXX";
+ int fd = mkstemp(tmpl);
+ unlink(tmpl);
+ const int browse_data_len = browse_data_end - browse_data_begin;
+ int len = write(fd, browse_data_begin, browse_data_len);
+ if (len < browse_data_len) {
+ perror("write");
+ return 1;
+ }
+
+ // exec Python, telling it to use our script file.
+ const char* command[] = {
+ "python", "/proc/self/fd/3", argv[0], NULL
+ };
+ execvp(command[0], (char**)command);
+
+ // If we get here, the exec failed.
+ printf("ERROR: Failed to spawn python for graph browsing, aborting.\n");
+ return 1;
+ }
+
const char* kLogPath = ".ninja_log";
if (!state.build_log_->Load(kLogPath, &err)) {
fprintf(stderr, "error loading build log: %s\n", err.c_str());