Android Build Tools
This commit is contained in:
@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Methods for reporting bugs."""
|
||||
|
||||
import subprocess, sys, os
|
||||
|
||||
__all__ = ["ReportFailure", "BugReport", "getReporters"]
|
||||
|
||||
#
|
||||
|
||||
|
||||
class ReportFailure(Exception):
|
||||
"""Generic exception for failures in bug reporting."""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
# Collect information about a bug.
|
||||
|
||||
|
||||
class BugReport(object):
|
||||
def __init__(self, title, description, files):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.files = files
|
||||
|
||||
|
||||
# Reporter interfaces.
|
||||
|
||||
import os
|
||||
|
||||
import email, mimetypes, smtplib
|
||||
from email import encoders
|
||||
from email.message import Message
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
# ===------------------------------------------------------------------------===#
|
||||
# ReporterParameter
|
||||
# ===------------------------------------------------------------------------===#
|
||||
|
||||
|
||||
class ReporterParameter(object):
|
||||
def __init__(self, n):
|
||||
self.name = n
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def getValue(self, r, bugtype, getConfigOption):
|
||||
return getConfigOption(r.getName(), self.getName())
|
||||
|
||||
def saveConfigValue(self):
|
||||
return True
|
||||
|
||||
|
||||
class TextParameter(ReporterParameter):
|
||||
def getHTML(self, r, bugtype, getConfigOption):
|
||||
return """\
|
||||
<tr>
|
||||
<td class="form_clabel">%s:</td>
|
||||
<td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
|
||||
</tr>""" % (
|
||||
self.getName(),
|
||||
r.getName(),
|
||||
self.getName(),
|
||||
self.getValue(r, bugtype, getConfigOption),
|
||||
)
|
||||
|
||||
|
||||
class SelectionParameter(ReporterParameter):
|
||||
def __init__(self, n, values):
|
||||
ReporterParameter.__init__(self, n)
|
||||
self.values = values
|
||||
|
||||
def getHTML(self, r, bugtype, getConfigOption):
|
||||
default = self.getValue(r, bugtype, getConfigOption)
|
||||
return """\
|
||||
<tr>
|
||||
<td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
|
||||
%s
|
||||
</select></td>""" % (
|
||||
self.getName(),
|
||||
r.getName(),
|
||||
self.getName(),
|
||||
"\n".join(
|
||||
[
|
||||
"""\
|
||||
<option value="%s"%s>%s</option>"""
|
||||
% (o[0], o[0] == default and ' selected="selected"' or "", o[1])
|
||||
for o in self.values
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# ===------------------------------------------------------------------------===#
|
||||
# Reporters
|
||||
# ===------------------------------------------------------------------------===#
|
||||
|
||||
|
||||
class EmailReporter(object):
|
||||
def getName(self):
|
||||
return "Email"
|
||||
|
||||
def getParameters(self):
|
||||
return [TextParameter(x) for x in ["To", "From", "SMTP Server", "SMTP Port"]]
|
||||
|
||||
# Lifted from python email module examples.
|
||||
def attachFile(self, outer, path):
|
||||
# Guess the content type based on the file's extension. Encoding
|
||||
# will be ignored, although we should check for simple things like
|
||||
# gzip'd or compressed files.
|
||||
ctype, encoding = mimetypes.guess_type(path)
|
||||
if ctype is None or encoding is not None:
|
||||
# No guess could be made, or the file is encoded (compressed), so
|
||||
# use a generic bag-of-bits type.
|
||||
ctype = "application/octet-stream"
|
||||
maintype, subtype = ctype.split("/", 1)
|
||||
if maintype == "text":
|
||||
fp = open(path)
|
||||
# Note: we should handle calculating the charset
|
||||
msg = MIMEText(fp.read(), _subtype=subtype)
|
||||
fp.close()
|
||||
else:
|
||||
fp = open(path, "rb")
|
||||
msg = MIMEBase(maintype, subtype)
|
||||
msg.set_payload(fp.read())
|
||||
fp.close()
|
||||
# Encode the payload using Base64
|
||||
encoders.encode_base64(msg)
|
||||
# Set the filename parameter
|
||||
msg.add_header(
|
||||
"Content-Disposition", "attachment", filename=os.path.basename(path)
|
||||
)
|
||||
outer.attach(msg)
|
||||
|
||||
def fileReport(self, report, parameters):
|
||||
mainMsg = """\
|
||||
BUG REPORT
|
||||
---
|
||||
Title: %s
|
||||
Description: %s
|
||||
""" % (
|
||||
report.title,
|
||||
report.description,
|
||||
)
|
||||
|
||||
if not parameters.get("To"):
|
||||
raise ReportFailure('No "To" address specified.')
|
||||
if not parameters.get("From"):
|
||||
raise ReportFailure('No "From" address specified.')
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg["Subject"] = "BUG REPORT: %s" % (report.title)
|
||||
# FIXME: Get config parameters
|
||||
msg["To"] = parameters.get("To")
|
||||
msg["From"] = parameters.get("From")
|
||||
msg.preamble = mainMsg
|
||||
|
||||
msg.attach(MIMEText(mainMsg, _subtype="text/plain"))
|
||||
for file in report.files:
|
||||
self.attachFile(msg, file)
|
||||
|
||||
try:
|
||||
s = smtplib.SMTP(
|
||||
host=parameters.get("SMTP Server"), port=parameters.get("SMTP Port")
|
||||
)
|
||||
s.sendmail(msg["From"], msg["To"], msg.as_string())
|
||||
s.close()
|
||||
except:
|
||||
raise ReportFailure("Unable to send message via SMTP.")
|
||||
|
||||
return "Message sent!"
|
||||
|
||||
|
||||
class BugzillaReporter(object):
|
||||
def getName(self):
|
||||
return "Bugzilla"
|
||||
|
||||
def getParameters(self):
|
||||
return [TextParameter(x) for x in ["URL", "Product"]]
|
||||
|
||||
def fileReport(self, report, parameters):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class RadarClassificationParameter(SelectionParameter):
|
||||
def __init__(self):
|
||||
SelectionParameter.__init__(
|
||||
self,
|
||||
"Classification",
|
||||
[
|
||||
["1", "Security"],
|
||||
["2", "Crash/Hang/Data Loss"],
|
||||
["3", "Performance"],
|
||||
["4", "UI/Usability"],
|
||||
["6", "Serious Bug"],
|
||||
["7", "Other"],
|
||||
],
|
||||
)
|
||||
|
||||
def saveConfigValue(self):
|
||||
return False
|
||||
|
||||
def getValue(self, r, bugtype, getConfigOption):
|
||||
if bugtype.find("leak") != -1:
|
||||
return "3"
|
||||
elif bugtype.find("dereference") != -1:
|
||||
return "2"
|
||||
elif bugtype.find("missing ivar release") != -1:
|
||||
return "3"
|
||||
else:
|
||||
return "7"
|
||||
|
||||
|
||||
###
|
||||
|
||||
|
||||
def getReporters():
|
||||
reporters = []
|
||||
reporters.append(EmailReporter())
|
||||
return reporters
|
||||
@ -0,0 +1,859 @@
|
||||
from __future__ import print_function
|
||||
|
||||
try:
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
except ImportError:
|
||||
from BaseHTTPServer import HTTPServer
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
from urllib import unquote
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
import posixpath
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
from io import StringIO, BytesIO
|
||||
else:
|
||||
from io import BytesIO, BytesIO as StringIO
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
import socket
|
||||
import itertools
|
||||
|
||||
import Reporter
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
###
|
||||
# Various patterns matched or replaced by server.
|
||||
|
||||
kReportFileRE = re.compile("(.*/)?report-(.*)\\.html")
|
||||
|
||||
kBugKeyValueRE = re.compile("<!-- BUG([^ ]*) (.*) -->")
|
||||
|
||||
# <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->
|
||||
|
||||
kReportCrashEntryRE = re.compile("<!-- REPORTPROBLEM (.*?)-->")
|
||||
kReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')
|
||||
|
||||
kReportReplacements = []
|
||||
|
||||
# Add custom javascript.
|
||||
kReportReplacements.append(
|
||||
(
|
||||
re.compile("<!-- SUMMARYENDHEAD -->"),
|
||||
"""\
|
||||
<script language="javascript" type="text/javascript">
|
||||
function load(url) {
|
||||
if (window.XMLHttpRequest) {
|
||||
req = new XMLHttpRequest();
|
||||
} else if (window.ActiveXObject) {
|
||||
req = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
if (req != undefined) {
|
||||
req.open("GET", url, true);
|
||||
req.send("");
|
||||
}
|
||||
}
|
||||
</script>""",
|
||||
)
|
||||
)
|
||||
|
||||
# Insert additional columns.
|
||||
kReportReplacements.append((re.compile("<!-- REPORTBUGCOL -->"), "<td></td><td></td>"))
|
||||
|
||||
# Insert report bug and open file links.
|
||||
kReportReplacements.append(
|
||||
(
|
||||
re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'),
|
||||
(
|
||||
'<td class="Button"><a href="report/\\1">Report Bug</a></td>'
|
||||
+ '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>'
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
kReportReplacements.append(
|
||||
(
|
||||
re.compile("<!-- REPORTHEADER -->"),
|
||||
'<h3><a href="/">Summary</a> > Report %(report)s</h3>',
|
||||
)
|
||||
)
|
||||
|
||||
kReportReplacements.append(
|
||||
(
|
||||
re.compile("<!-- REPORTSUMMARYEXTRA -->"),
|
||||
'<td class="Button"><a href="report/%(report)s">Report Bug</a></td>',
|
||||
)
|
||||
)
|
||||
|
||||
# Insert report crashes link.
|
||||
|
||||
# Disabled for the time being until we decide exactly when this should
|
||||
# be enabled. Also the radar reporter needs to be fixed to report
|
||||
# multiple files.
|
||||
|
||||
# kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'),
|
||||
# '<br>These files will automatically be attached to ' +
|
||||
# 'reports filed here: <a href="report_crashes">Report Crashes</a>.'))
|
||||
|
||||
###
|
||||
# Other simple parameters
|
||||
|
||||
kShare = posixpath.join(posixpath.dirname(__file__), "../share/scan-view")
|
||||
kConfigPath = os.path.expanduser("~/.scanview.cfg")
|
||||
|
||||
###
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
__all__ = ["create_server"]
|
||||
|
||||
|
||||
class ReporterThread(threading.Thread):
|
||||
def __init__(self, report, reporter, parameters, server):
|
||||
threading.Thread.__init__(self)
|
||||
self.report = report
|
||||
self.server = server
|
||||
self.reporter = reporter
|
||||
self.parameters = parameters
|
||||
self.success = False
|
||||
self.status = None
|
||||
|
||||
def run(self):
|
||||
result = None
|
||||
try:
|
||||
if self.server.options.debug:
|
||||
print("%s: SERVER: submitting bug." % (sys.argv[0],), file=sys.stderr)
|
||||
self.status = self.reporter.fileReport(self.report, self.parameters)
|
||||
self.success = True
|
||||
time.sleep(3)
|
||||
if self.server.options.debug:
|
||||
print(
|
||||
"%s: SERVER: submission complete." % (sys.argv[0],), file=sys.stderr
|
||||
)
|
||||
except Reporter.ReportFailure as e:
|
||||
self.status = e.value
|
||||
except Exception as e:
|
||||
s = StringIO()
|
||||
import traceback
|
||||
|
||||
print("<b>Unhandled Exception</b><br><pre>", file=s)
|
||||
traceback.print_exc(file=s)
|
||||
print("</pre>", file=s)
|
||||
self.status = s.getvalue()
|
||||
|
||||
|
||||
class ScanViewServer(HTTPServer):
|
||||
def __init__(self, address, handler, root, reporters, options):
|
||||
HTTPServer.__init__(self, address, handler)
|
||||
self.root = root
|
||||
self.reporters = reporters
|
||||
self.options = options
|
||||
self.halted = False
|
||||
self.config = None
|
||||
self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
self.config = configparser.RawConfigParser()
|
||||
|
||||
# Add defaults
|
||||
self.config.add_section("ScanView")
|
||||
for r in self.reporters:
|
||||
self.config.add_section(r.getName())
|
||||
for p in r.getParameters():
|
||||
if p.saveConfigValue():
|
||||
self.config.set(r.getName(), p.getName(), "")
|
||||
|
||||
# Ignore parse errors
|
||||
try:
|
||||
self.config.read([kConfigPath])
|
||||
except:
|
||||
pass
|
||||
|
||||
# Save on exit
|
||||
import atexit
|
||||
|
||||
atexit.register(lambda: self.save_config())
|
||||
|
||||
def save_config(self):
|
||||
# Ignore errors (only called on exit).
|
||||
try:
|
||||
f = open(kConfigPath, "w")
|
||||
self.config.write(f)
|
||||
f.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
def halt(self):
|
||||
self.halted = True
|
||||
if self.options.debug:
|
||||
print("%s: SERVER: halting." % (sys.argv[0],), file=sys.stderr)
|
||||
|
||||
def serve_forever(self):
|
||||
while not self.halted:
|
||||
if self.options.debug > 1:
|
||||
print("%s: SERVER: waiting..." % (sys.argv[0],), file=sys.stderr)
|
||||
try:
|
||||
self.handle_request()
|
||||
except OSError as e:
|
||||
print("OSError", e.errno)
|
||||
|
||||
def finish_request(self, request, client_address):
|
||||
if self.options.autoReload:
|
||||
import ScanView
|
||||
|
||||
self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
|
||||
HTTPServer.finish_request(self, request, client_address)
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
# Ignore socket errors
|
||||
info = sys.exc_info()
|
||||
if info and isinstance(info[1], socket.error):
|
||||
if self.options.debug > 1:
|
||||
print(
|
||||
"%s: SERVER: ignored socket error." % (sys.argv[0],),
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
HTTPServer.handle_error(self, request, client_address)
|
||||
|
||||
|
||||
# Borrowed from Quixote, with simplifications.
|
||||
def parse_query(qs, fields=None):
|
||||
if fields is None:
|
||||
fields = {}
|
||||
for chunk in (_f for _f in qs.split("&") if _f):
|
||||
if "=" not in chunk:
|
||||
name = chunk
|
||||
value = ""
|
||||
else:
|
||||
name, value = chunk.split("=", 1)
|
||||
name = unquote(name.replace("+", " "))
|
||||
value = unquote(value.replace("+", " "))
|
||||
item = fields.get(name)
|
||||
if item is None:
|
||||
fields[name] = [value]
|
||||
else:
|
||||
item.append(value)
|
||||
return fields
|
||||
|
||||
|
||||
class ScanViewRequestHandler(SimpleHTTPRequestHandler):
|
||||
server_version = "ScanViewServer/" + __version__
|
||||
dynamic_mtime = time.time()
|
||||
|
||||
def do_HEAD(self):
|
||||
try:
|
||||
SimpleHTTPRequestHandler.do_HEAD(self)
|
||||
except Exception as e:
|
||||
self.handle_exception(e)
|
||||
|
||||
def do_GET(self):
|
||||
try:
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
except Exception as e:
|
||||
self.handle_exception(e)
|
||||
|
||||
def do_POST(self):
|
||||
"""Serve a POST request."""
|
||||
try:
|
||||
length = self.headers.getheader("content-length") or "0"
|
||||
try:
|
||||
length = int(length)
|
||||
except:
|
||||
length = 0
|
||||
content = self.rfile.read(length)
|
||||
fields = parse_query(content)
|
||||
f = self.send_head(fields)
|
||||
if f:
|
||||
self.copyfile(f, self.wfile)
|
||||
f.close()
|
||||
except Exception as e:
|
||||
self.handle_exception(e)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
if self.server.options.debug:
|
||||
sys.stderr.write(
|
||||
"%s: SERVER: %s - - [%s] %s\n"
|
||||
% (
|
||||
sys.argv[0],
|
||||
self.address_string(),
|
||||
self.log_date_time_string(),
|
||||
format % args,
|
||||
)
|
||||
)
|
||||
|
||||
def load_report(self, report):
|
||||
path = os.path.join(self.server.root, "report-%s.html" % report)
|
||||
data = open(path).read()
|
||||
keys = {}
|
||||
for item in kBugKeyValueRE.finditer(data):
|
||||
k, v = item.groups()
|
||||
keys[k] = v
|
||||
return keys
|
||||
|
||||
def load_crashes(self):
|
||||
path = posixpath.join(self.server.root, "index.html")
|
||||
data = open(path).read()
|
||||
problems = []
|
||||
for item in kReportCrashEntryRE.finditer(data):
|
||||
fieldData = item.group(1)
|
||||
fields = dict(
|
||||
[i.groups() for i in kReportCrashEntryKeyValueRE.finditer(fieldData)]
|
||||
)
|
||||
problems.append(fields)
|
||||
return problems
|
||||
|
||||
def handle_exception(self, exc):
|
||||
import traceback
|
||||
|
||||
s = StringIO()
|
||||
print("INTERNAL ERROR\n", file=s)
|
||||
traceback.print_exc(file=s)
|
||||
f = self.send_string(s.getvalue(), "text/plain")
|
||||
if f:
|
||||
self.copyfile(f, self.wfile)
|
||||
f.close()
|
||||
|
||||
def get_scalar_field(self, name):
|
||||
if name in self.fields:
|
||||
return self.fields[name][0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def submit_bug(self, c):
|
||||
title = self.get_scalar_field("title")
|
||||
description = self.get_scalar_field("description")
|
||||
report = self.get_scalar_field("report")
|
||||
reporterIndex = self.get_scalar_field("reporter")
|
||||
files = []
|
||||
for fileID in self.fields.get("files", []):
|
||||
try:
|
||||
i = int(fileID)
|
||||
except:
|
||||
i = None
|
||||
if i is None or i < 0 or i >= len(c.files):
|
||||
return (False, "Invalid file ID")
|
||||
files.append(c.files[i])
|
||||
|
||||
if not title:
|
||||
return (False, "Missing title.")
|
||||
if not description:
|
||||
return (False, "Missing description.")
|
||||
try:
|
||||
reporterIndex = int(reporterIndex)
|
||||
except:
|
||||
return (False, "Invalid report method.")
|
||||
|
||||
# Get the reporter and parameters.
|
||||
reporter = self.server.reporters[reporterIndex]
|
||||
parameters = {}
|
||||
for o in reporter.getParameters():
|
||||
name = "%s_%s" % (reporter.getName(), o.getName())
|
||||
if name not in self.fields:
|
||||
return (
|
||||
False,
|
||||
'Missing field "%s" for %s report method.'
|
||||
% (name, reporter.getName()),
|
||||
)
|
||||
parameters[o.getName()] = self.get_scalar_field(name)
|
||||
|
||||
# Update config defaults.
|
||||
if report != "None":
|
||||
self.server.config.set("ScanView", "reporter", reporterIndex)
|
||||
for o in reporter.getParameters():
|
||||
if o.saveConfigValue():
|
||||
name = o.getName()
|
||||
self.server.config.set(reporter.getName(), name, parameters[name])
|
||||
|
||||
# Create the report.
|
||||
bug = Reporter.BugReport(title, description, files)
|
||||
|
||||
# Kick off a reporting thread.
|
||||
t = ReporterThread(bug, reporter, parameters, self.server)
|
||||
t.start()
|
||||
|
||||
# Wait for thread to die...
|
||||
while t.isAlive():
|
||||
time.sleep(0.25)
|
||||
submitStatus = t.status
|
||||
|
||||
return (t.success, t.status)
|
||||
|
||||
def send_report_submit(self):
|
||||
report = self.get_scalar_field("report")
|
||||
c = self.get_report_context(report)
|
||||
if c.reportSource is None:
|
||||
reportingFor = "Report Crashes > "
|
||||
fileBug = (
|
||||
"""\
|
||||
<a href="/report_crashes">File Bug</a> > """
|
||||
% locals()
|
||||
)
|
||||
else:
|
||||
reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource, report)
|
||||
fileBug = '<a href="/report/%s">File Bug</a> > ' % report
|
||||
title = self.get_scalar_field("title")
|
||||
description = self.get_scalar_field("description")
|
||||
|
||||
res, message = self.submit_bug(c)
|
||||
|
||||
if res:
|
||||
statusClass = "SubmitOk"
|
||||
statusName = "Succeeded"
|
||||
else:
|
||||
statusClass = "SubmitFail"
|
||||
statusName = "Failed"
|
||||
|
||||
result = (
|
||||
"""
|
||||
<head>
|
||||
<title>Bug Submission</title>
|
||||
<link rel="stylesheet" type="text/css" href="/scanview.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h3>
|
||||
<a href="/">Summary</a> >
|
||||
%(reportingFor)s
|
||||
%(fileBug)s
|
||||
Submit</h3>
|
||||
<form name="form" action="">
|
||||
<table class="form">
|
||||
<tr><td>
|
||||
<table class="form_group">
|
||||
<tr>
|
||||
<td class="form_clabel">Title:</td>
|
||||
<td class="form_value">
|
||||
<input type="text" name="title" size="50" value="%(title)s" disabled>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="form_label">Description:</td>
|
||||
<td class="form_value">
|
||||
<textarea rows="10" cols="80" name="description" disabled>
|
||||
%(description)s
|
||||
</textarea>
|
||||
</td>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
||||
<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
|
||||
%(message)s
|
||||
<p>
|
||||
<hr>
|
||||
<a href="/">Return to Summary</a>
|
||||
</body>
|
||||
</html>"""
|
||||
% locals()
|
||||
)
|
||||
return self.send_string(result)
|
||||
|
||||
def send_open_report(self, report):
|
||||
try:
|
||||
keys = self.load_report(report)
|
||||
except IOError:
|
||||
return self.send_error(400, "Invalid report.")
|
||||
|
||||
file = keys.get("FILE")
|
||||
if not file or not posixpath.exists(file):
|
||||
return self.send_error(400, 'File does not exist: "%s"' % file)
|
||||
|
||||
import startfile
|
||||
|
||||
if self.server.options.debug:
|
||||
print('%s: SERVER: opening "%s"' % (sys.argv[0], file), file=sys.stderr)
|
||||
|
||||
status = startfile.open(file)
|
||||
if status:
|
||||
res = 'Opened: "%s"' % file
|
||||
else:
|
||||
res = 'Open failed: "%s"' % file
|
||||
|
||||
return self.send_string(res, "text/plain")
|
||||
|
||||
def get_report_context(self, report):
|
||||
class Context(object):
|
||||
pass
|
||||
|
||||
if report is None or report == "None":
|
||||
data = self.load_crashes()
|
||||
# Don't allow empty reports.
|
||||
if not data:
|
||||
raise ValueError("No crashes detected!")
|
||||
c = Context()
|
||||
c.title = "clang static analyzer failures"
|
||||
|
||||
stderrSummary = ""
|
||||
for item in data:
|
||||
if "stderr" in item:
|
||||
path = posixpath.join(self.server.root, item["stderr"])
|
||||
if os.path.exists(path):
|
||||
lns = itertools.islice(open(path), 0, 10)
|
||||
stderrSummary += "%s\n--\n%s" % (
|
||||
item.get("src", "<unknown>"),
|
||||
"".join(lns),
|
||||
)
|
||||
|
||||
c.description = """\
|
||||
The clang static analyzer failed on these inputs:
|
||||
%s
|
||||
|
||||
STDERR Summary
|
||||
--------------
|
||||
%s
|
||||
""" % (
|
||||
"\n".join([item.get("src", "<unknown>") for item in data]),
|
||||
stderrSummary,
|
||||
)
|
||||
c.reportSource = None
|
||||
c.navMarkup = "Report Crashes > "
|
||||
c.files = []
|
||||
for item in data:
|
||||
c.files.append(item.get("src", ""))
|
||||
c.files.append(posixpath.join(self.server.root, item.get("file", "")))
|
||||
c.files.append(
|
||||
posixpath.join(self.server.root, item.get("clangfile", ""))
|
||||
)
|
||||
c.files.append(posixpath.join(self.server.root, item.get("stderr", "")))
|
||||
c.files.append(posixpath.join(self.server.root, item.get("info", "")))
|
||||
# Just in case something failed, ignore files which don't
|
||||
# exist.
|
||||
c.files = [f for f in c.files if os.path.exists(f) and os.path.isfile(f)]
|
||||
else:
|
||||
# Check that this is a valid report.
|
||||
path = posixpath.join(self.server.root, "report-%s.html" % report)
|
||||
if not posixpath.exists(path):
|
||||
raise ValueError("Invalid report ID")
|
||||
keys = self.load_report(report)
|
||||
c = Context()
|
||||
c.title = keys.get("DESC", "clang error (unrecognized")
|
||||
c.description = """\
|
||||
Bug reported by the clang static analyzer.
|
||||
|
||||
Description: %s
|
||||
File: %s
|
||||
Line: %s
|
||||
""" % (
|
||||
c.title,
|
||||
keys.get("FILE", "<unknown>"),
|
||||
keys.get("LINE", "<unknown>"),
|
||||
)
|
||||
c.reportSource = "report-%s.html" % report
|
||||
c.navMarkup = """<a href="/%s">Report %s</a> > """ % (
|
||||
c.reportSource,
|
||||
report,
|
||||
)
|
||||
|
||||
c.files = [path]
|
||||
return c
|
||||
|
||||
def send_report(self, report, configOverrides=None):
|
||||
def getConfigOption(section, field):
|
||||
if (
|
||||
configOverrides is not None
|
||||
and section in configOverrides
|
||||
and field in configOverrides[section]
|
||||
):
|
||||
return configOverrides[section][field]
|
||||
return self.server.config.get(section, field)
|
||||
|
||||
# report is None is used for crashes
|
||||
try:
|
||||
c = self.get_report_context(report)
|
||||
except ValueError as e:
|
||||
return self.send_error(400, e.message)
|
||||
|
||||
title = c.title
|
||||
description = c.description
|
||||
reportingFor = c.navMarkup
|
||||
if c.reportSource is None:
|
||||
extraIFrame = ""
|
||||
else:
|
||||
extraIFrame = """\
|
||||
<iframe src="/%s" width="100%%" height="40%%"
|
||||
scrolling="auto" frameborder="1">
|
||||
<a href="/%s">View Bug Report</a>
|
||||
</iframe>""" % (
|
||||
c.reportSource,
|
||||
c.reportSource,
|
||||
)
|
||||
|
||||
reporterSelections = []
|
||||
reporterOptions = []
|
||||
|
||||
try:
|
||||
active = int(getConfigOption("ScanView", "reporter"))
|
||||
except:
|
||||
active = 0
|
||||
for i, r in enumerate(self.server.reporters):
|
||||
selected = i == active
|
||||
if selected:
|
||||
selectedStr = " selected"
|
||||
else:
|
||||
selectedStr = ""
|
||||
reporterSelections.append(
|
||||
'<option value="%d"%s>%s</option>' % (i, selectedStr, r.getName())
|
||||
)
|
||||
options = "\n".join(
|
||||
[o.getHTML(r, title, getConfigOption) for o in r.getParameters()]
|
||||
)
|
||||
display = ("none", "")[selected]
|
||||
reporterOptions.append(
|
||||
"""\
|
||||
<tr id="%sReporterOptions" style="display:%s">
|
||||
<td class="form_label">%s Options</td>
|
||||
<td class="form_value">
|
||||
<table class="form_inner_group">
|
||||
%s
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
"""
|
||||
% (r.getName(), display, r.getName(), options)
|
||||
)
|
||||
reporterSelections = "\n".join(reporterSelections)
|
||||
reporterOptionsDivs = "\n".join(reporterOptions)
|
||||
reportersArray = "[%s]" % (
|
||||
",".join([repr(r.getName()) for r in self.server.reporters])
|
||||
)
|
||||
|
||||
if c.files:
|
||||
fieldSize = min(5, len(c.files))
|
||||
attachFileOptions = "\n".join(
|
||||
[
|
||||
"""\
|
||||
<option value="%d" selected>%s</option>"""
|
||||
% (i, v)
|
||||
for i, v in enumerate(c.files)
|
||||
]
|
||||
)
|
||||
attachFileRow = """\
|
||||
<tr>
|
||||
<td class="form_label">Attach:</td>
|
||||
<td class="form_value">
|
||||
<select style="width:100%%" name="files" multiple size=%d>
|
||||
%s
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
""" % (
|
||||
min(5, len(c.files)),
|
||||
attachFileOptions,
|
||||
)
|
||||
else:
|
||||
attachFileRow = ""
|
||||
|
||||
result = (
|
||||
"""<html>
|
||||
<head>
|
||||
<title>File Bug</title>
|
||||
<link rel="stylesheet" type="text/css" href="/scanview.css" />
|
||||
</head>
|
||||
<script language="javascript" type="text/javascript">
|
||||
var reporters = %(reportersArray)s;
|
||||
function updateReporterOptions() {
|
||||
index = document.getElementById('reporter').selectedIndex;
|
||||
for (var i=0; i < reporters.length; ++i) {
|
||||
o = document.getElementById(reporters[i] + "ReporterOptions");
|
||||
if (i == index) {
|
||||
o.style.display = "";
|
||||
} else {
|
||||
o.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<body onLoad="updateReporterOptions()">
|
||||
<h3>
|
||||
<a href="/">Summary</a> >
|
||||
%(reportingFor)s
|
||||
File Bug</h3>
|
||||
<form name="form" action="/report_submit" method="post">
|
||||
<input type="hidden" name="report" value="%(report)s">
|
||||
|
||||
<table class="form">
|
||||
<tr><td>
|
||||
<table class="form_group">
|
||||
<tr>
|
||||
<td class="form_clabel">Title:</td>
|
||||
<td class="form_value">
|
||||
<input type="text" name="title" size="50" value="%(title)s">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="form_label">Description:</td>
|
||||
<td class="form_value">
|
||||
<textarea rows="10" cols="80" name="description">
|
||||
%(description)s
|
||||
</textarea>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
%(attachFileRow)s
|
||||
|
||||
</table>
|
||||
<br>
|
||||
<table class="form_group">
|
||||
<tr>
|
||||
<td class="form_clabel">Method:</td>
|
||||
<td class="form_value">
|
||||
<select id="reporter" name="reporter" onChange="updateReporterOptions()">
|
||||
%(reporterSelections)s
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
%(reporterOptionsDivs)s
|
||||
</table>
|
||||
<br>
|
||||
</td></tr>
|
||||
<tr><td class="form_submit">
|
||||
<input align="right" type="submit" name="Submit" value="Submit">
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
%(extraIFrame)s
|
||||
|
||||
</body>
|
||||
</html>"""
|
||||
% locals()
|
||||
)
|
||||
|
||||
return self.send_string(result)
|
||||
|
||||
def send_head(self, fields=None):
|
||||
if self.server.options.onlyServeLocal and self.client_address[0] != "127.0.0.1":
|
||||
return self.send_error(401, "Unauthorized host.")
|
||||
|
||||
if fields is None:
|
||||
fields = {}
|
||||
self.fields = fields
|
||||
|
||||
o = urlparse(self.path)
|
||||
self.fields = parse_query(o.query, fields)
|
||||
path = posixpath.normpath(unquote(o.path))
|
||||
|
||||
# Split the components and strip the root prefix.
|
||||
components = path.split("/")[1:]
|
||||
|
||||
# Special case some top-level entries.
|
||||
if components:
|
||||
name = components[0]
|
||||
if len(components) == 2:
|
||||
if name == "report":
|
||||
return self.send_report(components[1])
|
||||
elif name == "open":
|
||||
return self.send_open_report(components[1])
|
||||
elif len(components) == 1:
|
||||
if name == "quit":
|
||||
self.server.halt()
|
||||
return self.send_string("Goodbye.", "text/plain")
|
||||
elif name == "report_submit":
|
||||
return self.send_report_submit()
|
||||
elif name == "report_crashes":
|
||||
overrides = {"ScanView": {}, "Radar": {}, "Email": {}}
|
||||
for i, r in enumerate(self.server.reporters):
|
||||
if r.getName() == "Radar":
|
||||
overrides["ScanView"]["reporter"] = i
|
||||
break
|
||||
overrides["Radar"]["Component"] = "llvm - checker"
|
||||
overrides["Radar"]["Component Version"] = "X"
|
||||
return self.send_report(None, overrides)
|
||||
elif name == "favicon.ico":
|
||||
return self.send_path(posixpath.join(kShare, "bugcatcher.ico"))
|
||||
|
||||
# Match directory entries.
|
||||
if components[-1] == "":
|
||||
components[-1] = "index.html"
|
||||
|
||||
relpath = "/".join(components)
|
||||
path = posixpath.join(self.server.root, relpath)
|
||||
|
||||
if self.server.options.debug > 1:
|
||||
print(
|
||||
'%s: SERVER: sending path "%s"' % (sys.argv[0], path), file=sys.stderr
|
||||
)
|
||||
return self.send_path(path)
|
||||
|
||||
def send_404(self):
|
||||
self.send_error(404, "File not found")
|
||||
return None
|
||||
|
||||
def send_path(self, path):
|
||||
# If the requested path is outside the root directory, do not open it
|
||||
rel = os.path.abspath(path)
|
||||
if not rel.startswith(os.path.abspath(self.server.root)):
|
||||
return self.send_404()
|
||||
|
||||
ctype = self.guess_type(path)
|
||||
if ctype.startswith("text/"):
|
||||
# Patch file instead
|
||||
return self.send_patched_file(path, ctype)
|
||||
else:
|
||||
mode = "rb"
|
||||
try:
|
||||
f = open(path, mode)
|
||||
except IOError:
|
||||
return self.send_404()
|
||||
return self.send_file(f, ctype)
|
||||
|
||||
def send_file(self, f, ctype):
|
||||
# Patch files to add links, but skip binary files.
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", ctype)
|
||||
fs = os.fstat(f.fileno())
|
||||
self.send_header("Content-Length", str(fs[6]))
|
||||
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
|
||||
self.end_headers()
|
||||
return f
|
||||
|
||||
def send_string(self, s, ctype="text/html", headers=True, mtime=None):
|
||||
encoded_s = s.encode("utf-8")
|
||||
if headers:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", ctype)
|
||||
self.send_header("Content-Length", str(len(encoded_s)))
|
||||
if mtime is None:
|
||||
mtime = self.dynamic_mtime
|
||||
self.send_header("Last-Modified", self.date_time_string(mtime))
|
||||
self.end_headers()
|
||||
return BytesIO(encoded_s)
|
||||
|
||||
def send_patched_file(self, path, ctype):
|
||||
# Allow a very limited set of variables. This is pretty gross.
|
||||
variables = {}
|
||||
variables["report"] = ""
|
||||
m = kReportFileRE.match(path)
|
||||
if m:
|
||||
variables["report"] = m.group(2)
|
||||
|
||||
try:
|
||||
f = open(path, "rb")
|
||||
except IOError:
|
||||
return self.send_404()
|
||||
fs = os.fstat(f.fileno())
|
||||
data = f.read().decode("utf-8")
|
||||
for a, b in kReportReplacements:
|
||||
data = a.sub(b % variables, data)
|
||||
return self.send_string(data, ctype, mtime=fs.st_mtime)
|
||||
|
||||
|
||||
def create_server(address, options, root):
|
||||
import Reporter
|
||||
|
||||
reporters = Reporter.getReporters()
|
||||
|
||||
return ScanViewServer(address, ScanViewRequestHandler, root, reporters, options)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 318 B |
@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Utility for opening a file using the default application in a cross-platform
|
||||
manner. Modified from http://code.activestate.com/recipes/511443/.
|
||||
"""
|
||||
|
||||
__version__ = "1.1x"
|
||||
__all__ = ["open"]
|
||||
|
||||
import os
|
||||
import sys
|
||||
import webbrowser
|
||||
import subprocess
|
||||
|
||||
_controllers = {}
|
||||
_open = None
|
||||
|
||||
|
||||
class BaseController(object):
|
||||
"""Base class for open program controllers."""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def open(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Controller(BaseController):
|
||||
"""Controller for a generic open program."""
|
||||
|
||||
def __init__(self, *args):
|
||||
super(Controller, self).__init__(os.path.basename(args[0]))
|
||||
self.args = list(args)
|
||||
|
||||
def _invoke(self, cmdline):
|
||||
if sys.platform[:3] == "win":
|
||||
closefds = False
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
else:
|
||||
closefds = True
|
||||
startupinfo = None
|
||||
|
||||
if (
|
||||
os.environ.get("DISPLAY")
|
||||
or sys.platform[:3] == "win"
|
||||
or sys.platform == "darwin"
|
||||
):
|
||||
inout = file(os.devnull, "r+")
|
||||
else:
|
||||
# for TTY programs, we need stdin/out
|
||||
inout = None
|
||||
|
||||
# if possible, put the child precess in separate process group,
|
||||
# so keyboard interrupts don't affect child precess as well as
|
||||
# Python
|
||||
setsid = getattr(os, "setsid", None)
|
||||
if not setsid:
|
||||
setsid = getattr(os, "setpgrp", None)
|
||||
|
||||
pipe = subprocess.Popen(
|
||||
cmdline,
|
||||
stdin=inout,
|
||||
stdout=inout,
|
||||
stderr=inout,
|
||||
close_fds=closefds,
|
||||
preexec_fn=setsid,
|
||||
startupinfo=startupinfo,
|
||||
)
|
||||
|
||||
# It is assumed that this kind of tools (gnome-open, kfmclient,
|
||||
# exo-open, xdg-open and open for OSX) immediately exit after launching
|
||||
# the specific application
|
||||
returncode = pipe.wait()
|
||||
if hasattr(self, "fixreturncode"):
|
||||
returncode = self.fixreturncode(returncode)
|
||||
return not returncode
|
||||
|
||||
def open(self, filename):
|
||||
if isinstance(filename, basestring):
|
||||
cmdline = self.args + [filename]
|
||||
else:
|
||||
# assume it is a sequence
|
||||
cmdline = self.args + filename
|
||||
try:
|
||||
return self._invoke(cmdline)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
# Platform support for Windows
|
||||
if sys.platform[:3] == "win":
|
||||
|
||||
class Start(BaseController):
|
||||
"""Controller for the win32 start program through os.startfile."""
|
||||
|
||||
def open(self, filename):
|
||||
try:
|
||||
os.startfile(filename)
|
||||
except WindowsError:
|
||||
# [Error 22] No application is associated with the specified
|
||||
# file for this operation: '<URL>'
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
_controllers["windows-default"] = Start("start")
|
||||
_open = _controllers["windows-default"].open
|
||||
|
||||
|
||||
# Platform support for MacOS
|
||||
elif sys.platform == "darwin":
|
||||
_controllers["open"] = Controller("open")
|
||||
_open = _controllers["open"].open
|
||||
|
||||
|
||||
# Platform support for Unix
|
||||
else:
|
||||
|
||||
try:
|
||||
from commands import getoutput
|
||||
except ImportError:
|
||||
from subprocess import getoutput
|
||||
|
||||
# @WARNING: use the private API of the webbrowser module
|
||||
from webbrowser import _iscommand
|
||||
|
||||
class KfmClient(Controller):
|
||||
"""Controller for the KDE kfmclient program."""
|
||||
|
||||
def __init__(self, kfmclient="kfmclient"):
|
||||
super(KfmClient, self).__init__(kfmclient, "exec")
|
||||
self.kde_version = self.detect_kde_version()
|
||||
|
||||
def detect_kde_version(self):
|
||||
kde_version = None
|
||||
try:
|
||||
info = getoutput("kde-config --version")
|
||||
|
||||
for line in info.splitlines():
|
||||
if line.startswith("KDE"):
|
||||
kde_version = line.split(":")[-1].strip()
|
||||
break
|
||||
except (OSError, RuntimeError):
|
||||
pass
|
||||
|
||||
return kde_version
|
||||
|
||||
def fixreturncode(self, returncode):
|
||||
if returncode is not None and self.kde_version > "3.5.4":
|
||||
return returncode
|
||||
else:
|
||||
return os.EX_OK
|
||||
|
||||
def detect_desktop_environment():
|
||||
"""Checks for known desktop environments
|
||||
|
||||
Return the desktop environments name, lowercase (kde, gnome, xfce)
|
||||
or "generic"
|
||||
|
||||
"""
|
||||
|
||||
desktop_environment = "generic"
|
||||
|
||||
if os.environ.get("KDE_FULL_SESSION") == "true":
|
||||
desktop_environment = "kde"
|
||||
elif os.environ.get("GNOME_DESKTOP_SESSION_ID"):
|
||||
desktop_environment = "gnome"
|
||||
else:
|
||||
try:
|
||||
info = getoutput("xprop -root _DT_SAVE_MODE")
|
||||
if ' = "xfce4"' in info:
|
||||
desktop_environment = "xfce"
|
||||
except (OSError, RuntimeError):
|
||||
pass
|
||||
|
||||
return desktop_environment
|
||||
|
||||
def register_X_controllers():
|
||||
if _iscommand("kfmclient"):
|
||||
_controllers["kde-open"] = KfmClient()
|
||||
|
||||
for command in ("gnome-open", "exo-open", "xdg-open"):
|
||||
if _iscommand(command):
|
||||
_controllers[command] = Controller(command)
|
||||
|
||||
def get():
|
||||
controllers_map = {
|
||||
"gnome": "gnome-open",
|
||||
"kde": "kde-open",
|
||||
"xfce": "exo-open",
|
||||
}
|
||||
|
||||
desktop_environment = detect_desktop_environment()
|
||||
|
||||
try:
|
||||
controller_name = controllers_map[desktop_environment]
|
||||
return _controllers[controller_name].open
|
||||
|
||||
except KeyError:
|
||||
if "xdg-open" in _controllers:
|
||||
return _controllers["xdg-open"].open
|
||||
else:
|
||||
return webbrowser.open
|
||||
|
||||
if os.environ.get("DISPLAY"):
|
||||
register_X_controllers()
|
||||
_open = get()
|
||||
|
||||
|
||||
def open(filename):
|
||||
"""Open a file or a URL in the registered default application."""
|
||||
|
||||
return _open(filename)
|
||||
Reference in New Issue
Block a user