Fix C++ PCH issues.
[clang.git] / tools / scan-view / Reporter.py
blob9560fc1aabbb3d87d7c7471154b0ca3ed620113f
1 """Methods for reporting bugs."""
3 import subprocess, sys, os
5 __all__ = ['ReportFailure', 'BugReport', 'getReporters']
9 class ReportFailure(Exception):
10 """Generic exception for failures in bug reporting."""
11 def __init__(self, value):
12 self.value = value
14 # Collect information about a bug.
16 class BugReport:
17 def __init__(self, title, description, files):
18 self.title = title
19 self.description = description
20 self.files = files
22 # Reporter interfaces.
24 import os
26 import email, mimetypes, smtplib
27 from email import encoders
28 from email.message import Message
29 from email.mime.base import MIMEBase
30 from email.mime.multipart import MIMEMultipart
31 from email.mime.text import MIMEText
33 #===------------------------------------------------------------------------===#
34 # ReporterParameter
35 #===------------------------------------------------------------------------===#
37 class ReporterParameter:
38 def __init__(self, n):
39 self.name = n
40 def getName(self):
41 return self.name
42 def getValue(self,r,bugtype,getConfigOption):
43 return getConfigOption(r.getName(),self.getName())
44 def saveConfigValue(self):
45 return True
47 class TextParameter (ReporterParameter):
48 def getHTML(self,r,bugtype,getConfigOption):
49 return """\
50 <tr>
51 <td class="form_clabel">%s:</td>
52 <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
53 </tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
55 class SelectionParameter (ReporterParameter):
56 def __init__(self, n, values):
57 ReporterParameter.__init__(self,n)
58 self.values = values
60 def getHTML(self,r,bugtype,getConfigOption):
61 default = self.getValue(r,bugtype,getConfigOption)
62 return """\
63 <tr>
64 <td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
66 </select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
67 <option value="%s"%s>%s</option>"""%(o[0],
68 o[0] == default and ' selected="selected"' or '',
69 o[1]) for o in self.values]))
71 #===------------------------------------------------------------------------===#
72 # Reporters
73 #===------------------------------------------------------------------------===#
75 class EmailReporter:
76 def getName(self):
77 return 'Email'
79 def getParameters(self):
80 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
82 # Lifted from python email module examples.
83 def attachFile(self, outer, path):
84 # Guess the content type based on the file's extension. Encoding
85 # will be ignored, although we should check for simple things like
86 # gzip'd or compressed files.
87 ctype, encoding = mimetypes.guess_type(path)
88 if ctype is None or encoding is not None:
89 # No guess could be made, or the file is encoded (compressed), so
90 # use a generic bag-of-bits type.
91 ctype = 'application/octet-stream'
92 maintype, subtype = ctype.split('/', 1)
93 if maintype == 'text':
94 fp = open(path)
95 # Note: we should handle calculating the charset
96 msg = MIMEText(fp.read(), _subtype=subtype)
97 fp.close()
98 else:
99 fp = open(path, 'rb')
100 msg = MIMEBase(maintype, subtype)
101 msg.set_payload(fp.read())
102 fp.close()
103 # Encode the payload using Base64
104 encoders.encode_base64(msg)
105 # Set the filename parameter
106 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
107 outer.attach(msg)
109 def fileReport(self, report, parameters):
110 mainMsg = """\
111 BUG REPORT
113 Title: %s
114 Description: %s
115 """%(report.title, report.description)
117 if not parameters.get('To'):
118 raise ReportFailure('No "To" address specified.')
119 if not parameters.get('From'):
120 raise ReportFailure('No "From" address specified.')
122 msg = MIMEMultipart()
123 msg['Subject'] = 'BUG REPORT: %s'%(report.title)
124 # FIXME: Get config parameters
125 msg['To'] = parameters.get('To')
126 msg['From'] = parameters.get('From')
127 msg.preamble = mainMsg
129 msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
130 for file in report.files:
131 self.attachFile(msg, file)
133 try:
134 s = smtplib.SMTP(host=parameters.get('SMTP Server'),
135 port=parameters.get('SMTP Port'))
136 s.sendmail(msg['From'], msg['To'], msg.as_string())
137 s.close()
138 except:
139 raise ReportFailure('Unable to send message via SMTP.')
141 return "Message sent!"
143 class BugzillaReporter:
144 def getName(self):
145 return 'Bugzilla'
147 def getParameters(self):
148 return map(lambda x:TextParameter(x),['URL','Product'])
150 def fileReport(self, report, parameters):
151 raise NotImplementedError
154 class RadarClassificationParameter(SelectionParameter):
155 def __init__(self):
156 SelectionParameter.__init__(self,"Classification",
157 [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
158 ['3', 'Performance'], ['4', 'UI/Usability'],
159 ['6', 'Serious Bug'], ['7', 'Other']])
161 def saveConfigValue(self):
162 return False
164 def getValue(self,r,bugtype,getConfigOption):
165 if bugtype.find("leak") != -1:
166 return '3'
167 elif bugtype.find("dereference") != -1:
168 return '2'
169 elif bugtype.find("missing ivar release") != -1:
170 return '3'
171 else:
172 return '7'
174 class RadarReporter:
175 @staticmethod
176 def isAvailable():
177 # FIXME: Find this .scpt better
178 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
179 try:
180 p = subprocess.Popen(['osascript',path],
181 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
182 except:
183 return False
184 data,err = p.communicate()
185 res = p.wait()
186 # FIXME: Check version? Check for no errors?
187 return res == 0
189 def getName(self):
190 return 'Radar'
192 def getParameters(self):
193 return [ TextParameter('Component'), TextParameter('Component Version'),
194 RadarClassificationParameter() ]
196 def fileReport(self, report, parameters):
197 component = parameters.get('Component', '')
198 componentVersion = parameters.get('Component Version', '')
199 classification = parameters.get('Classification', '')
200 personID = ""
201 diagnosis = ""
202 config = ""
204 if not component.strip():
205 component = 'Bugs found by clang Analyzer'
206 if not componentVersion.strip():
207 componentVersion = 'X'
209 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
210 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
211 report.description, diagnosis, config] + map(os.path.abspath, report.files)
212 # print >>sys.stderr, args
213 try:
214 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
215 except:
216 raise ReportFailure("Unable to file radar (AppleScript failure).")
217 data, err = p.communicate()
218 res = p.wait()
220 if res:
221 raise ReportFailure("Unable to file radar (AppleScript failure).")
223 try:
224 values = eval(data)
225 except:
226 raise ReportFailure("Unable to process radar results.")
228 # We expect (int: bugID, str: message)
229 if len(values) != 2 or not isinstance(values[0], int):
230 raise ReportFailure("Unable to process radar results.")
232 bugID,message = values
233 bugID = int(bugID)
235 if not bugID:
236 raise ReportFailure(message)
238 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
242 def getReporters():
243 reporters = []
244 if RadarReporter.isAvailable():
245 reporters.append(RadarReporter())
246 reporters.append(EmailReporter())
247 return reporters