From 0c9412113041f4f33cf907f6f97af057c904fb06 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 22 Jan 2011 16:39:06 -0800 Subject: [PATCH] add browser mode --- src/browse.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ninja.cc | 42 ++++++++++++++++++- 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100755 src/browse.py 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 ''' +''' + print '' + + print '' + print '' + + print '
' + print '

%s

' % node + print '
' + print '

input

' + if input: + print '

%s:

' % input[0] + print '
    ' + for i in input[1:]: + print '
  • %s
  • ' % (i, i) + print '
' + print '
 ' + print '

outputs

' + for output in outputs: + print '

%s:

' % output[0] + print '
    ' + for i in output[1:]: + print '
  • %s
  • ' % (i, i) + print '
' + print '
' + +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()); -- 2.7.4