summaryrefslogtreecommitdiff
path: root/tools/release-wrangler.py
blob: 818179c6538876fb915b3571c09dc3b78e566593 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/python
#
# release-wrangler.py - very basic release system, primarily for
# Metacity, might be useful for others. In very early stages of
# development.
#
# Copyright (C) 2008 Thomas Thurman
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.

# This script doesn't do all the work yet, but it will soon.

import os
import posixpath
import re
import sys
import commands

# First step is always to get up to date.
os.system("svn up")

################################################################

# Are we up to date now?

changed = []
for line in commands.getoutput('/usr/bin/svn status').split('\n'):
  if line!='' and (line[0]=='C' or line[0]=='M'):
    changed.append(line[1:].lstrip())

if changed:
  print 'These files are out of date; I can\'t continue until you fix them.'
  print ', '.join(changed)
  sys.exit(255)

################################################################

# FIXME: This is all very metacity-specific. Compare fusa, etc
#
# Okay, read through configure.in and find who and where we are.
#
# We also try to figure out where the next micro version number
# will be; some programs (e.g. Metacity) use a custom numbering
# scheme, and if they have a list of numbers on the line before the
# micro version then that will be used. Otherwise we will just
# increment.
version = {}
previous_line = ''
for line in file("configure.in").readlines():
  product_name = re.search("^AC_INIT\(\[([^\]]*)\]", line)
  if product_name:
    version['name'] = product_name.group(1)

  version_number = re.search("^m4_define\(\[.*_(.*)_version\], \[(\d+)\]", line)

  if version_number:
    version_type = version_number.group(1)
    version_value = int(version_number.group(2))

    version[version_type] = version_value

    if version_type == 'micro':
      group_of_digits = re.search("^\#\s*([0-9, ]+)\n$", previous_line)
      if group_of_digits:
        versions = [int(x) for x in group_of_digits.group(1).split(',')]

        if version_value in versions:
          try:
            version['micro_next'] = versions[versions.index(version_value)+1]
          except:
            print "You gave a list of micro version numbers, but we've used them up!"
            sys.exit(255)
        else:
          print "You gave a list of micro version numbers, but the current one wasn't in it!"
          print "Current is ",version_value
          print "Your list is ",versions
          sys.exit(255)

  previous_line = line

if not 'micro_next' in version:
  version['micro_next'] = version['micro']+1

################################################################

archive_filename = '%(name)s-%(major)s.%(minor)s.%(micro)s.tar.gz' % (version)
if os.access(archive_filename, os.F_OK):
  print "Sorry, you already have a file called %s! Please delete it or move it first." % (archive_filename)
  sys.exit(255)

################################################################

changelog = file("ChangeLog").readlines()

# Find the most recent release.

def is_date(str):
  return len(str)>3 and str[4]=='-'

release_date = None

for line in changelog:
  if is_date(line):
    release_date = line[:10]
  if "Post-release bump to %s.%s.%s." % (version['major'], version['minor'], version['micro']) in line:
    changelog = changelog[:changelog.index(line)+1]
    break

contributors = {}
thanks = ''
entries = []

def assumed_surname(name):
  # might get more complicated later, but for now...
  return name.split()[-1]

def assumed_forename(name):
  return name.split()[0]

bug_re = re.compile('bug \#?(\d+)', re.IGNORECASE)
hash_re = re.compile('\#(\d+)')

for line in changelog:
  if is_date(line):
    line = line[10:].lstrip()
    line = line[:line.find('<')].rstrip()
    contributors[assumed_surname(line)] = line
    entries.append('(%s)' % (assumed_forename(line)))
  else:
    match = bug_re.search(line)
    if not match: match = hash_re.search(line)
    if match:
      entries[-1] += ' (#%s)' % (match.group(1))

contributors_list = contributors.keys()
contributors_list.sort()
thanksline = ', '.join([contributors[x] for x in contributors_list])
thanksline = thanksline.replace(contributors[contributors_list[-1]], 'and '+contributors[contributors_list[-1]])

version_string = '%(major)s.%(minor)s.%(micro)s' % (version)

def wordwrap(str, prefix=''):
  "Really simple wordwrap"

  # Ugly hack:
  # We know that all open brackets are preceded by spaces.
  # We don't want to split on these spaces. Therefore:
  str = str.replace(' (','(')

  result = ['']
  for word in str.split():

    if result[-1]=='':
      candidate = prefix + word
    else:
      candidate = '%s %s' % (result[-1], word)

    if len(candidate)>80:
      result.append(prefix+word)
    else:
      result[-1] = candidate

  return '\n'.join(result).replace('(',' (')

thanks = '%s\n%s\n\n' % (version_string, '='*len(version_string))
thanks += wordwrap('Thanks to %s for improvements in this version.' % (thanksline))
thanks += '\n\n'
for line in entries:
  thanks += '  - xxx %s\n' % (line)

# and now pick up the translations.

translations = {}
language_re = re.compile('\*\s*(.+)\.po')

for line in file("po/ChangeLog").readlines():
  match = language_re.search(line)
  if match:
    translations[match.group(1)] = 1
  if is_date(line) and line[:10]<release_date:
    break

translator_list = translations.keys()
translator_list.sort()

last_translator_re = re.compile('Last-Translator:([^<"]*)', re.IGNORECASE)

def translator_name(language):
  name = 'unknown'
  for line in file('po/%s.po' % (language)).readlines():
    match = last_translator_re.search(line)
    if match:
      name = match.group(1).rstrip().lstrip()
      break
  return "%s (%s)" % (name, language)

thanks += '\nTranslations\n'
thanks += wordwrap(', '.join([translator_name(x) for x in translator_list]), '  ')
thanks += '\n\n'

changes = '## '+ ' '.join(changelog).replace('\n', '\n## ')

filename = posixpath.expanduser("~/.release-wrangler-%(name)s-%(major)s-%(minor)s-%(micro)s.txt" % version)
tmp = open(filename, 'w')
tmp.write('## You are releasing %(name)s, version %(major)s.%(minor)s.%(micro)s.\n' % version)
tmp.write('## The text at the foot of the page is the part of the ChangeLog which\n')
tmp.write('## has changed since the last release. Please summarise it.\n')
tmp.write('## Anything preceded by a # is ignored.\n')
tmp.write(thanks)
tmp.write(changes)
tmp.close()

os.spawnl(os.P_WAIT, '/bin/nano', 'nano', '+6', filename)

################################################################

# Write it out to NEWS

news_tmp = open('NEWS.tmp', 'a')
for line in open(filename, 'r').readlines():
  if line=='' or line[0]!='#':
    news_tmp.write(line)

for line in open('NEWS').readlines():
  news_tmp.write(line)

news_tmp.close()

os.rename('NEWS.tmp', 'NEWS')

################################################################

# Now build the thing.

autogen_prefix= '/prefix' # FIXME: this is specific to tthurman's laptop!

if os.spawnl(os.P_WAIT, './autogen.sh', './autogen.sh', '--prefix', autogen_prefix) != 0:
    print 'autogen failed'
    sys.exit(255)
    
if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make') != 0:
    print 'make failed'
    sys.exit(255)

if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make', 'install') != 0:
    print 'install failed'
    sys.exit(255)

if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make', 'distcheck') != 0:
    print 'distcheck failed'
    sys.exit(255)

if not os.access(archive_filename, os.F_OK):
  print "Sorry, we don't appear to have a file called %s!" % (archive_filename)
  sys.exit(255)

# No, we won't have a configuration option to set your name on svn.g.o; that's
# what ~/.ssh/config is for.

print "Uploading..."
upload_result = commands.getstatusoutput('scp %s master.gnome.org:' % (archive_filename))

if upload_result[0]!=0:
  print "There appears to have been an uploading problem: %d\n%s\n" % (upload_result[0], upload_result[1])