blob: ba7b733be285a39b4a7488095089dcab8c5de2f9 [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
Ted Kremenek19c88202008-09-30 16:08:13 +000033#===------------------------------------------------------------------------===#
34# ReporterParameter
35#===------------------------------------------------------------------------===#
36
37class ReporterParameter:
38 def __init__(self, n):
39 self.name = n
40
41 def getName(self):
42 return self.name
43
44 def saveConfigValue(self):
45 return True
46
47class TextParameter (ReporterParameter):
48 def getValue(self,r,bugtype,getConfigOption):
49 return getConfigOption(r.getName(),self.getName())
50
51 def getHTML(self,r,bugtype,getConfigOption):
52 return """\
53<tr>
54 <td class="form_clabel">%s:</td>
55 <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
56</tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
57
58#===------------------------------------------------------------------------===#
59# Reporters
60#===------------------------------------------------------------------------===#
61
Daniel Dunbare33d3682008-09-19 23:32:11 +000062class EmailReporter:
63 def getName(self):
64 return 'Email'
65
Ted Kremenek19c88202008-09-30 16:08:13 +000066 def getParameters(self):
67 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
Daniel Dunbare33d3682008-09-19 23:32:11 +000068
69 # Lifted from python email module examples.
70 def attachFile(self, outer, path):
71 # Guess the content type based on the file's extension. Encoding
72 # will be ignored, although we should check for simple things like
73 # gzip'd or compressed files.
74 ctype, encoding = mimetypes.guess_type(path)
75 if ctype is None or encoding is not None:
76 # No guess could be made, or the file is encoded (compressed), so
77 # use a generic bag-of-bits type.
78 ctype = 'application/octet-stream'
79 maintype, subtype = ctype.split('/', 1)
80 if maintype == 'text':
81 fp = open(path)
82 # Note: we should handle calculating the charset
83 msg = MIMEText(fp.read(), _subtype=subtype)
84 fp.close()
85 else:
86 fp = open(path, 'rb')
87 msg = MIMEBase(maintype, subtype)
88 msg.set_payload(fp.read())
89 fp.close()
90 # Encode the payload using Base64
91 encoders.encode_base64(msg)
92 # Set the filename parameter
93 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
94 outer.attach(msg)
95
96 def fileReport(self, report, parameters):
97 mainMsg = """\
98BUG REPORT
99---
100Title: %s
101Description: %s
102"""%(report.title, report.description)
103
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000104 if not parameters.get('To'):
105 raise ReportFailure('No "To" address specified.')
106 if not parameters.get('From'):
107 raise ReportFailure('No "From" address specified.')
Daniel Dunbare33d3682008-09-19 23:32:11 +0000108
109 msg = MIMEMultipart()
110 msg['Subject'] = 'BUG REPORT: %s'%(report.title)
111 # FIXME: Get config parameters
112 msg['To'] = parameters.get('To')
113 msg['From'] = parameters.get('From')
114 msg.preamble = mainMsg
115
116 msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
117 for file in report.files:
118 self.attachFile(msg, file)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000119
120 try:
121 s = smtplib.SMTP(host=parameters.get('SMTP Server'),
122 port=parameters.get('SMTP Port'))
123 s.sendmail(msg['From'], msg['To'], msg.as_string())
124 s.close()
125 except:
126 raise ReportFailure('Unable to send message via SMTP.')
127
128 return "Message sent!"
Daniel Dunbare33d3682008-09-19 23:32:11 +0000129
130class BugzillaReporter:
131 def getName(self):
132 return 'Bugzilla'
133
Ted Kremenek19c88202008-09-30 16:08:13 +0000134 def getParameters(self):
135 return map(lambda x:TextParameter(x),['URL','Product'])
Daniel Dunbare33d3682008-09-19 23:32:11 +0000136
137 def fileReport(self, report, parameters):
138 raise NotImplementedError
139
140class RadarReporter:
141 @staticmethod
142 def isAvailable():
143 # FIXME: Find this .scpt better
144 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
Ted Kremenek13c03d82008-09-30 05:45:59 +0000145 try:
146 p = subprocess.Popen(['osascript',path],
147 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
148 except:
Daniel Dunbare33d3682008-09-19 23:32:11 +0000149 return False
150 data,err = p.communicate()
151 res = p.wait()
152 # FIXME: Check version? Check for no errors?
153 return res == 0
154
155 def getName(self):
156 return 'Radar'
157
Ted Kremenek19c88202008-09-30 16:08:13 +0000158 def getParameters(self):
159 return map(lambda x:TextParameter(x),['Component', 'Component Version'])
Daniel Dunbare33d3682008-09-19 23:32:11 +0000160
161 def fileReport(self, report, parameters):
162 component = parameters.get('Component', '')
163 componentVersion = parameters.get('Component Version', '')
164 personID = ""
165 diagnosis = ""
166 config = ""
167
168 if not component.strip():
169 component = 'Bugs found by clang Analyzer'
170 if not componentVersion.strip():
171 componentVersion = 'X'
172
173 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
174 args = ['osascript', script, component, componentVersion, personID, report.title,
175 report.description, diagnosis, config] + map(os.path.abspath, report.files)
176# print >>sys.stderr, args
177 try:
178 p = subprocess.Popen(args,
179 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
180 except:
Daniel Dunbar54722492008-09-20 01:43:16 +0000181 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000182 data, err = p.communicate()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000183 res = p.wait()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000184
185 if res:
Daniel Dunbar54722492008-09-20 01:43:16 +0000186 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000187
Daniel Dunbar54722492008-09-20 01:43:16 +0000188 try:
189 values = eval(data)
190 except:
191 raise ReportFailure("Unable to process radar results.")
192
193 # We expect (int: bugID, str: message)
194 if len(values) != 2 or not isinstance(values[0], int):
195 raise ReportFailure("Unable to process radar results.")
196
197 bugID,message = values
198 bugID = int(bugID)
199
200 if not bugID:
201 raise ReportFailure(message)
202
203 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000204
205###
206
207def getReporters():
208 reporters = []
209 if RadarReporter.isAvailable():
210 reporters.append(RadarReporter())
Daniel Dunbarfeee21a2008-09-21 19:06:51 +0000211 reporters.append(EmailReporter())
Daniel Dunbare33d3682008-09-19 23:32:11 +0000212 return reporters
213