remove released flag
[empathy.git] / release.py
1 #!/usr/bin/env python
2
3 import os
4 import urllib
5 import csv
6 import datetime
7 from string import Template
8 from optparse import OptionParser
9 import dateutil.parser
10
11 last_tag_pattern = 'EMPATHY_3_12*'
12 upload_server = 'master.gnome.org'
13 template = '''\
14 $name $version is now available for download from:
15 $download
16
17 $md5sums
18
19 What is it?
20 ===========
21 $about
22
23 You can visit the project web site:
24 $website
25
26 What's New?
27 ===========
28 $news
29
30 $footer'''
31
32 class Bug:
33     number = ''
34     author = ''
35
36 class Project:
37     def __init__(self):
38         f = open('config.h', 'r')
39         s = f.read()
40         f.close()
41
42         key = {}
43         key['package'] = '#define PACKAGE_NAME "'
44         key['version'] = '#define PACKAGE_VERSION "'
45         key['bugreport'] = '#define PACKAGE_BUGREPORT "'
46
47         for line in s.splitlines(1):
48             if line.startswith(key['package']):
49                 p1 = len(key['package'])
50                 p2 = line.rfind('"')
51                 self.package_name = line[p1:p2]
52             elif line.startswith(key['version']):
53                 p1 = len(key['version'])
54                 p2 = line.rfind('"')
55                 self.package_version = line[p1:p2]
56             elif line.startswith(key['bugreport']):
57                 p2 = line.rfind('"')
58                 p1 = line.rfind('=') + 1
59                 self.package_module = line[p1:p2]
60
61         first = self.package_version.find('.')
62         second = self.package_version.find('.', first + 1)
63         if first == -1 or second == -1 or first == second:
64             version_dir = self.package_version
65         else:
66             version_dir = self.package_version[:second]
67         self.package_dl_url = 'http://download.gnome.org/sources/%s/%s/' % (self.package_name.lower(),
68                 version_dir)
69         tags_str = self.exec_cmd('git tag -l %s' % (last_tag_pattern))
70         tags = tags_str.splitlines()
71         self.last_tag = tags[len(tags)-1]
72
73     def exec_cmd(self,cmd):
74         return os.popen(cmd).read()
75
76     def get_news(self):
77         f = open ('NEWS', 'r')
78         s = f.read()
79         f.close()
80         start = s.find ('NEW in '+ self.package_version)
81         if start != -1:
82             start = s.find ('\n', start) + 1
83             start = s.find ('\n', start) + 1
84             end = s.find ('NEW in', start) - 1
85             return s[start:end].strip()
86
87     def get_md5sums(self):
88         md5sums = ''
89
90         cmd = 'md5sum %s-%s.tar.xz' % (self.package_name.lower(), self.package_version)
91         md5sums += self.exec_cmd(cmd)
92
93         return md5sums
94
95     def get_bugzilla_info(self):
96         query = 'http://bugzilla.gnome.org/browse.cgi?product=%s' % (self.package_module)
97         f = urllib.urlopen(query)
98         s = f.read()
99         f.close()
100
101         s1 = '<p><i>'
102         i = s.find(s1)
103         start = i + len(s1)
104         s2 = '</i></p>'
105         end = s.find(s2, i + 1)
106         description = s[start:end]
107
108         s1 = "homepage"
109         i = s.find(s1)
110         s1 = "href"
111         i = s.rfind(s1, 0, i)
112         start = i + 6
113         s2 = '">'
114         end = s.find(s2, start)
115         project_url = s[start:end]
116
117         return (description, project_url)
118
119     def get_release_notes(self):
120         name = self.package_name
121         version = self.package_version
122         download = self.package_dl_url
123         md5sums = self.get_md5sums()
124         (about, website) = self.get_bugzilla_info()
125         news = self.get_news()
126         footer = '%s\n%s team' % (datetime.date.today().strftime('%d %B %Y'),\
127                                   self.package_name)
128
129         t = Template(template)
130         return t.substitute(locals())
131
132     def get_translations(self, cmd, format):
133         translations = ''
134         files_str = self.exec_cmd(cmd)
135         files = files_str.splitlines()
136         for line in files:
137             f = line[line.rfind(' '):]
138             lang = f[f.rfind('/')+1:f.rfind('.')]
139             commit_str = self.exec_cmd("git log %s..  %s" % (self.last_tag, f))
140             if commit_str == '':
141                 continue
142
143             authors = ''
144             for line in commit_str.splitlines():
145                 if line.startswith('Author:'):
146                     p1 = line.find(' ')
147                     p2 = line.find('<')
148                     author = line[p1:p2].strip()
149
150                     if authors.find(author) != -1:
151                         continue
152                     if authors != '':
153                         authors += ", "
154                     authors += author
155
156             translations += format % (lang, authors)
157         return translations
158
159     def get_bug_author(self, bug_number):
160         cmd = 'git log %s.. | grep -B 20 -E \
161               "(bug %s|#%s)|bugzilla.gnome.org/show_bug.cgi\?id=%s"' \
162               ' | tac | grep ^Author: | head -1' \
163               % (self.last_tag, bug_number, bug_number, bug_number)
164         line = self.exec_cmd (cmd)
165         p1 = line.find(" ")
166         p2 = line.find("<")
167
168         return line[p1:p2].strip()
169
170     def get_bugs(self):
171         commit_str = self.exec_cmd('git show %s' % (self.last_tag))
172         for line in commit_str.splitlines():
173             if line.startswith('Date:'):
174                 time_str = line[5:]
175                 t = dateutil.parser.parse(time_str)
176                 last_tag_date = t.strftime('%Y-%m-%d')
177                 break
178
179         query = 'http://bugzilla.gnome.org/buglist.cgi?' \
180                 'ctype=csv&product=empathy&' \
181                 'bug_status=RESOLVED,CLOSED,VERIFIED&resolution=FIXED&' \
182                 'chfieldfrom=%s&chfieldto=Now' % (last_tag_date)
183         f = urllib.urlopen(query)
184         s = f.read()
185         f.close()
186
187         col_bug_id = -1
188         col_description = -1
189
190         reader = csv.reader(s.splitlines(1))
191         header = reader.next()
192         i = 0
193
194         for col in header:
195             if col == 'bug_id':
196                 col_bug_id = i
197             if col == 'short_short_desc':
198                 col_description = i
199             i = i + 1
200
201         bugs = ''
202         for row in reader:
203             bug_number = row[col_bug_id]
204             description = row[col_description]
205             author = self.get_bug_author(bug_number)
206             bugs += ' - Fixed #%s, %s' % (bug_number, description)
207             if author != '':
208                 bugs += ' (%s)' % (author)
209             bugs += '\n'
210         return bugs
211
212     def generate_news(self):
213         translations = self.get_translations("ls -l po/*.po", \
214                 " - Updated %s Translation (%s)\n")
215         help_translations = self.get_translations("ls -l help/*/*.po", \
216                 " - Updated %s Documentation translation (%s)\n")
217         bugs = self.get_bugs()
218
219         news = 'NEW in '+ self.package_version
220         line = '=' * len(news)
221         today = datetime.date.today()
222         news += ' (%s)\n%s\n' % (today.strftime('%d/%m/%Y'),line)
223         if bugs != '':
224             news += 'Bugs fixed:\n' + bugs + '\n'
225         if translations != '':
226             news += 'Translations:\n' + translations + '\n'
227         if help_translations != '':
228             news += 'Documentation translations:\n' + \
229                     help_translations + '\n'
230
231         return news
232
233     def write_news(self):
234         news = self.generate_news()
235
236         f = open ('/tmp/NEWS', 'w')
237         f.write(news)
238         f.close()
239
240         self.exec_cmd('cat NEWS >> /tmp/NEWS')
241         self.exec_cmd('mv /tmp/NEWS .')
242
243     def make_tag(self):
244         new_tag = self.package_name.upper() + '_' +\
245                   self.package_version.replace('.', '_')
246         self.exec_cmd('git tag -m "Tagged for release %s." %s' % ( self.package_version, new_tag))
247
248     def _get_username(self):
249         username = os.environ.get('GNOME_ACCOUNT_NAME')
250         if username is not None:
251             return username
252
253         return os.getlogin()
254
255     def upload_tarball(self):
256         username = self._get_username()
257         tarball = '%s-%s.tar.xz' % (self.package_name.lower(), self.package_version)
258
259         cmd = 'scp %s %s@%s:' % (tarball, username, upload_server)
260         self.exec_cmd(cmd)
261
262         cmd = 'ssh %s@%s install-module -u %s' % (username, upload_server, tarball)
263         self.exec_cmd(cmd)
264
265     def send_email(self):
266         notes = self.get_release_notes()
267         print notes
268         cmd = 'xdg-email ' \
269               ' --cc telepathy@lists.freedesktop.org' \
270               ' --subject "ANNOUNCE: Empathy %s"' \
271               ' --body "%s"' \
272               ' gnome-announce-list@gnome.org' % (self.package_version,
273                                                   notes.replace('"', '\\"'))
274         self.exec_cmd(cmd)
275
276     def release(self):
277         self.make_tag()
278         self.upload_tarball()
279         self.send_email()
280
281 if __name__ == '__main__':
282     p = Project()
283     parser = OptionParser()
284     parser.add_option("-n", "--print-news", action="store_true",\
285             dest="print_news", help="Generate and print news")
286     parser.add_option("-p", "--print-notes", action="store_true",\
287             dest="print_notes", help="Generate and print the release notes")
288     parser.add_option("-w", "--write-news", action="store_true",\
289             dest="write_news", help="Generate and write news into the NEWS file")
290     parser.add_option("-r", "--release", action="store_true",\
291             dest="release", help="Release the tarball")
292     parser.add_option("-e", "--email", action="store_true",\
293             dest="email", help="Send the release announce email")
294
295     (options, args) = parser.parse_args ()
296     if (options.print_news):
297         print p.generate_news ()
298     if (options.print_notes):
299         print p.get_release_notes ()
300     if (options.write_news):
301         p.write_news ()
302     if (options.release):
303         p.release ()
304     if (options.email):
305         p.send_email ()