blob: 22e43112df11fcdef2f463814ed23bb886fdde3c [file] [log] [blame]
Daniel Dunbare33d3682008-09-19 23:32:11 +00001"""Methods for reporting bugs."""
2
3import subprocess, sys, os
4
Daniel Dunbar54722492008-09-20 01:43:16 +00005__all__ = ['ReportFailure', 'BugReport', 'getReporters']
6
7#
8
9class ReportFailure(Exception):
10 """Generic exception for failures in bug reporting."""
11 def __init__(self, value):
12 self.value = value
Daniel Dunbare33d3682008-09-19 23:32:11 +000013
14# Collect information about a bug.
15
16class BugReport:
17 def __init__(self, title, description, files):
18 self.title = title
19 self.description = description
20 self.files = files
21
22# Reporter interfaces.
23
24import os
25
26import email, mimetypes, smtplib
27from email import encoders
28from email.message import Message
29from email.mime.base import MIMEBase
30from email.mime.multipart import MIMEMultipart
31from email.mime.text import MIMEText
32
33class EmailReporter:
34 def getName(self):
35 return 'Email'
36
37 def getParameterNames(self):
38 return ['To', 'From', 'SMTP Server', 'SMTP Port']
39
40 # Lifted from python email module examples.
41 def attachFile(self, outer, path):
42 # Guess the content type based on the file's extension. Encoding
43 # will be ignored, although we should check for simple things like
44 # gzip'd or compressed files.
45 ctype, encoding = mimetypes.guess_type(path)
46 if ctype is None or encoding is not None:
47 # No guess could be made, or the file is encoded (compressed), so
48 # use a generic bag-of-bits type.
49 ctype = 'application/octet-stream'
50 maintype, subtype = ctype.split('/', 1)
51 if maintype == 'text':
52 fp = open(path)
53 # Note: we should handle calculating the charset
54 msg = MIMEText(fp.read(), _subtype=subtype)
55 fp.close()
56 else:
57 fp = open(path, 'rb')
58 msg = MIMEBase(maintype, subtype)
59 msg.set_payload(fp.read())
60 fp.close()
61 # Encode the payload using Base64
62 encoders.encode_base64(msg)
63 # Set the filename parameter
64 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
65 outer.attach(msg)
66
67 def fileReport(self, report, parameters):
68 mainMsg = """\
69BUG REPORT
70---
71Title: %s
72Description: %s
73"""%(report.title, report.description)
74
Daniel Dunbar47cceff2008-09-22 01:21:30 +000075 if not parameters.get('To'):
76 raise ReportFailure('No "To" address specified.')
77 if not parameters.get('From'):
78 raise ReportFailure('No "From" address specified.')
Daniel Dunbare33d3682008-09-19 23:32:11 +000079
80 msg = MIMEMultipart()
81 msg['Subject'] = 'BUG REPORT: %s'%(report.title)
82 # FIXME: Get config parameters
83 msg['To'] = parameters.get('To')
84 msg['From'] = parameters.get('From')
85 msg.preamble = mainMsg
86
87 msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
88 for file in report.files:
89 self.attachFile(msg, file)
Daniel Dunbar47cceff2008-09-22 01:21:30 +000090
91 try:
92 s = smtplib.SMTP(host=parameters.get('SMTP Server'),
93 port=parameters.get('SMTP Port'))
94 s.sendmail(msg['From'], msg['To'], msg.as_string())
95 s.close()
96 except:
97 raise ReportFailure('Unable to send message via SMTP.')
98
99 return "Message sent!"
Daniel Dunbare33d3682008-09-19 23:32:11 +0000100
101class BugzillaReporter:
102 def getName(self):
103 return 'Bugzilla'
104
105 def getParameterNames(self):
106 return ['URL', 'Product']
107
108 def fileReport(self, report, parameters):
109 raise NotImplementedError
110
111class RadarReporter:
112 @staticmethod
113 def isAvailable():
114 # FIXME: Find this .scpt better
115 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
116 try:
117 p = subprocess.Popen(['osascript',path],
118 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
119 except:
120 return False
121 data,err = p.communicate()
122 res = p.wait()
123 # FIXME: Check version? Check for no errors?
124 return res == 0
125
126 def getName(self):
127 return 'Radar'
128
129 def getParameterNames(self):
130 return ['Component', 'Component Version']
131
132 def fileReport(self, report, parameters):
133 component = parameters.get('Component', '')
134 componentVersion = parameters.get('Component Version', '')
135 personID = ""
136 diagnosis = ""
137 config = ""
138
139 if not component.strip():
140 component = 'Bugs found by clang Analyzer'
141 if not componentVersion.strip():
142 componentVersion = 'X'
143
144 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
145 args = ['osascript', script, component, componentVersion, personID, report.title,
146 report.description, diagnosis, config] + map(os.path.abspath, report.files)
147# print >>sys.stderr, args
148 try:
149 p = subprocess.Popen(args,
150 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
151 except:
Daniel Dunbar54722492008-09-20 01:43:16 +0000152 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000153 data, err = p.communicate()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000154 res = p.wait()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000155
156 if res:
Daniel Dunbar54722492008-09-20 01:43:16 +0000157 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000158
Daniel Dunbar54722492008-09-20 01:43:16 +0000159 try:
160 values = eval(data)
161 except:
162 raise ReportFailure("Unable to process radar results.")
163
164 # We expect (int: bugID, str: message)
165 if len(values) != 2 or not isinstance(values[0], int):
166 raise ReportFailure("Unable to process radar results.")
167
168 bugID,message = values
169 bugID = int(bugID)
170
171 if not bugID:
172 raise ReportFailure(message)
173
174 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000175
176###
177
178def getReporters():
179 reporters = []
180 if RadarReporter.isAvailable():
181 reporters.append(RadarReporter())
Daniel Dunbarfeee21a2008-09-21 19:06:51 +0000182 reporters.append(EmailReporter())
Daniel Dunbare33d3682008-09-19 23:32:11 +0000183 return reporters
184