1#!/usr/bin/env python
2
3from __future__ import print_function
4
5"""The clang static analyzer results viewer.
6"""
7
8import sys
9import os
10import posixpath
11import threading
12import time
13try:
14    from urllib.request import urlopen
15except ImportError:
16    from urllib2 import urlopen
17import webbrowser
18
19# How long to wait for server to start.
20kSleepTimeout = .05
21kMaxSleeps = int(60 / kSleepTimeout)
22
23# Default server parameters
24
25kDefaultHost = '127.0.0.1'
26kDefaultPort = 8181
27kMaxPortsToTry = 100
28
29###
30
31
32def url_is_up(url):
33    try:
34        o = urlopen(url)
35    except IOError:
36        return False
37    o.close()
38    return True
39
40
41def start_browser(port, options):
42    import webbrowser
43
44    url = 'http://%s:%d' % (options.host, port)
45
46    # Wait for server to start...
47    if options.debug:
48        sys.stderr.write('%s: Waiting for server.' % sys.argv[0])
49        sys.stderr.flush()
50    for i in range(kMaxSleeps):
51        if url_is_up(url):
52            break
53        if options.debug:
54            sys.stderr.write('.')
55            sys.stderr.flush()
56        time.sleep(kSleepTimeout)
57    else:
58        print('WARNING: Unable to detect that server started.', file=sys.stderr)
59
60    if options.debug:
61        print('%s: Starting webbrowser...' % sys.argv[0], file=sys.stderr)
62    webbrowser.open(url)
63
64
65def run(port, options, root):
66    # Prefer to look relative to the installed binary
67    share = os.path.dirname(__file__) + "/../share/scan-view"
68    if not os.path.isdir(share):
69        # Otherwise look relative to the source
70        share = os.path.dirname(__file__) + "/../../scan-view/share"
71    sys.path.append(share)
72
73    import ScanView
74    try:
75        print('Starting scan-view at: http://%s:%d' % (options.host,
76                                                       port))
77        print('  Use Ctrl-C to exit.')
78        httpd = ScanView.create_server((options.host, port),
79                                       options, root)
80        httpd.serve_forever()
81    except KeyboardInterrupt:
82        pass
83
84
85def port_is_open(port):
86    try:
87        import socketserver
88    except ImportError:
89        import SocketServer as socketserver
90    try:
91        t = socketserver.TCPServer((kDefaultHost, port), None)
92    except:
93        return False
94    t.server_close()
95    return True
96
97
98def main():
99    import argparse
100    parser = argparse.ArgumentParser(description="The clang static analyzer "
101                                                 "results viewer.")
102    parser.add_argument("root", metavar="<results directory>", type=str)
103    parser.add_argument(
104        '--host', dest="host", default=kDefaultHost, type=str,
105        help="Host interface to listen on. (default=%s)" % kDefaultHost)
106    parser.add_argument('--port', dest="port", default=None, type=int,
107                        help="Port to listen on. (default=%s)" % kDefaultPort)
108    parser.add_argument("--debug", dest="debug", default=0,
109                        action="count",
110                        help="Print additional debugging information.")
111    parser.add_argument("--auto-reload", dest="autoReload", default=False,
112                        action="store_true",
113                        help="Automatically update module for each request.")
114    parser.add_argument("--no-browser", dest="startBrowser", default=True,
115                        action="store_false",
116                        help="Don't open a webbrowser on startup.")
117    parser.add_argument("--allow-all-hosts", dest="onlyServeLocal",
118                        default=True, action="store_false",
119                        help='Allow connections from any host (access '
120                             'restricted to "127.0.0.1" by default)')
121    args = parser.parse_args()
122
123    # Make sure this directory is in a reasonable state to view.
124    if not posixpath.exists(posixpath.join(args.root, 'index.html')):
125        parser.error('Invalid directory, analysis results not found!')
126
127    # Find an open port. We aren't particularly worried about race
128    # conditions here. Note that if the user specified a port we only
129    # use that one.
130    if args.port is not None:
131        port = args.port
132    else:
133        for i in range(kMaxPortsToTry):
134            if port_is_open(kDefaultPort + i):
135                port = kDefaultPort + i
136                break
137        else:
138            parser.error('Unable to find usable port in [%d,%d)' %
139                         (kDefaultPort, kDefaultPort+kMaxPortsToTry))
140
141    # Kick off thread to wait for server and start web browser, if
142    # requested.
143    if args.startBrowser:
144        threading.Thread(target=start_browser, args=(port, args)).start()
145
146    run(port, args, args.root)
147
148if __name__ == '__main__':
149    main()
150