basic release script; needs work, but probably good enough for the current

2008-02-03  Thomas Thurman  <tthurman@gnome.org>

        * tools/release-wrangler.py: basic release script; needs work,
        but probably good enough for the current unstable release


svn path=/trunk/; revision=3541
This commit is contained in:
Thomas Thurman 2008-02-03 21:47:09 +00:00 committed by Thomas James Alexander Thurman
parent a660fd3805
commit 836a1f7b08
2 changed files with 230 additions and 161 deletions

View File

@ -1,3 +1,8 @@
2008-02-03 Thomas Thurman <tthurman@gnome.org>
* tools/release-wrangler.py: basic release script; needs work,
but probably good enough for the current unstable release
2008-02-02 Thomas Thurman <tthurman@gnome.org> 2008-02-02 Thomas Thurman <tthurman@gnome.org>
* src/Makefile.am: core.h is in include, not core. (Last one, I * src/Makefile.am: core.h is in include, not core. (Last one, I

View File

@ -21,45 +21,64 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA. # 02111-1307, USA.
# This script doesn't do all the work yet, but it will soon.
import os import os
import posixpath import posixpath
import re import re
import sys import sys
import commands import commands
import time
# First step is always to get up to date. def report_error(message):
os.system("svn up") print message
sys.exit(255)
################################################################ def get_up_to_date():
"First step is always to get up to date."
os.system("svn up")
# Are we up to date now? # yes, I know this is MY username. I will come back and fix it
# later, but for now there is a lot else to do. FIXME
your_username = 'Thomas Thurman <tthurman@gnome.org>'
changed = [] def changelog_and_checkin(filename, message):
for line in commands.getoutput('/usr/bin/svn status').split('\n'): changelog = open('ChangeLog.tmp', 'w')
changelog.write('%s %s\n\n * %s: %s\n\n' % (
time.strftime('%Y-%m-%d',time.gmtime()),
your_username,
filename,
message))
for line in open('ChangeLog').readlines():
changelog.write(line)
changelog.close()
os.rename('ChangeLog.tmp', 'ChangeLog')
os.system('svn commit -m \\"%s\\"' % (message.replace('"','\\"')))
def check_we_are_up_to_date():
changed = []
for line in commands.getoutput('/usr/bin/svn status').split('\n'):
if line!='' and (line[0]=='C' or line[0]=='M'): if line!='' and (line[0]=='C' or line[0]=='M'):
changed.append(line[1:].lstrip()) changed.append(line[1:].lstrip())
if changed: if changed:
print 'These files are out of date; I can\'t continue until you fix them.' report_error('These files are out of date; I can\'t continue until you fix them: ' + \
print ', '.join(changed) ', '.join(changed))
sys.exit(255)
################################################################ def version_numbers():
# FIXME: This is all very metacity-specific. Compare fusa, etc
"""Okay, read through configure.in and find who and where we are.
# FIXME: This is all very metacity-specific. Compare fusa, etc We also try to figure out where the next micro version number
# will be; some programs (e.g. Metacity) use a custom numbering
# Okay, read through configure.in and find who and where we are. 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
# We also try to figure out where the next micro version number increment."""
# 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 version = {}
# micro version then that will be used. Otherwise we will just previous_line = ''
# increment. for line in file("configure.in").readlines():
version = {}
previous_line = ''
for line in file("configure.in").readlines():
product_name = re.search("^AC_INIT\(\[([^\]]*)\]", line) product_name = re.search("^AC_INIT\(\[([^\]]*)\]", line)
if product_name: if product_name:
version['name'] = product_name.group(1) version['name'] = product_name.group(1)
@ -81,59 +100,56 @@ for line in file("configure.in").readlines():
try: try:
version['micro_next'] = versions[versions.index(version_value)+1] version['micro_next'] = versions[versions.index(version_value)+1]
except: except:
print "You gave a list of micro version numbers, but we've used them up!" report_error("You gave a list of micro version numbers, but we've used them up!")
sys.exit(255)
else: else:
print "You gave a list of micro version numbers, but the current one wasn't in it!" report_error("You gave a list of micro version numbers, but the current one wasn't in it! Current is %s and your list is %s" % (
print "Current is ",version_value `version_value`, `versions`))
print "Your list is ",versions
sys.exit(255)
previous_line = line previous_line = line
if not 'micro_next' in version: if not 'micro_next' in version:
version['micro_next'] = version['micro']+1 version['micro_next'] = version['micro']+1
################################################################ version['string'] = '%(major)s.%(minor)s.%(micro)s' % (version)
version['filename'] = '%(name)s-%(string)s.tar.gz' % (version)
return version
archive_filename = '%(name)s-%(major)s.%(minor)s.%(micro)s.tar.gz' % (version) def check_file_does_not_exist(version):
if os.access(archive_filename, os.F_OK): if os.access(version['filename'], os.F_OK):
print "Sorry, you already have a file called %s! Please delete it or move it first." % (archive_filename) report_error("Sorry, you already have a file called %s! Please delete it or move it first." % (version['filename']))
sys.exit(255)
################################################################
changelog = file("ChangeLog").readlines()
# Find the most recent release.
def is_date(str): def is_date(str):
return len(str)>3 and str[4]=='-' return len(str)>3 and str[4]=='-'
release_date = None def scan_changelog(version):
changelog = file("ChangeLog").readlines()
for line in changelog: # Find the most recent release.
release_date = None
for line in changelog:
if is_date(line): if is_date(line):
release_date = line[:10] release_date = line[:10]
if "Post-release bump to %s.%s.%s." % (version['major'], version['minor'], version['micro']) in line: if "Post-release bump to %s.%s.%s." % (version['major'], version['minor'], version['micro']) in line:
changelog = changelog[:changelog.index(line)+1] changelog = changelog[:changelog.index(line)+1]
break break
contributors = {} contributors = {}
thanks = '' thanks = ''
entries = [] entries = []
def assumed_surname(name): def assumed_surname(name):
# might get more complicated later, but for now... # might get more complicated later, but for now...
return name.split()[-1] return name.split()[-1]
def assumed_forename(name): def assumed_forename(name):
return name.split()[0] return name.split()[0]
bug_re = re.compile('bug \#?(\d+)', re.IGNORECASE) bug_re = re.compile('bug \#?(\d+)', re.IGNORECASE)
hash_re = re.compile('\#(\d+)') hash_re = re.compile('\#(\d+)')
for line in changelog: for line in changelog:
if is_date(line): if is_date(line):
line = line[10:].lstrip() line = line[10:].lstrip()
line = line[:line.find('<')].rstrip() line = line[:line.find('<')].rstrip()
@ -145,12 +161,8 @@ for line in changelog:
if match: if match:
entries[-1] += ' (#%s)' % (match.group(1)) entries[-1] += ' (#%s)' % (match.group(1))
contributors_list = contributors.keys() # FIXME: getting complex enough we should be returning a dictionary
contributors_list.sort() return (contributors, changelog, entries, release_date)
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=''): def wordwrap(str, prefix=''):
"Really simple wordwrap" "Really simple wordwrap"
@ -175,30 +187,50 @@ def wordwrap(str, prefix=''):
return '\n'.join(result).replace('(',' (') return '\n'.join(result).replace('(',' (')
thanks = '%s\n%s\n\n' % (version_string, '='*len(version_string)) def favourite_editor():
thanks += wordwrap('Thanks to %s for improvements in this version.' % (thanksline)) e = os.environ
thanks += '\n\n' if e.has_key('VISUAL'): return e['VISUAL']
for line in entries: if e.has_key('EDITOR'): return e['EDITOR']
if os.access('/usr/bin/nano', os.F_OK):
return '/usr/bin/nano'
report_error("I can't find an editor for you!")
def edit_news_entry(version):
# FIXME: still needs a lot of tidying up. Translator stuff especially needs to be
# factored out into a separate function.
(contributors, changelog, entries, release_date) = scan_changelog(version)
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]])
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) thanks += ' - xxx %s\n' % (line)
# and now pick up the translations. # and now pick up the translations.
translations = {} translations = {}
language_re = re.compile('\*\s*(.+)\.po') language_re = re.compile('\*\s*(.+)\.po')
for line in file("po/ChangeLog").readlines(): for line in file("po/ChangeLog").readlines():
match = language_re.search(line) match = language_re.search(line)
if match: if match:
translations[match.group(1)] = 1 translations[match.group(1)] = 1
if is_date(line) and line[:10]<release_date: if is_date(line) and line[:10]<release_date:
break break
translator_list = translations.keys() translator_list = translations.keys()
translator_list.sort() translator_list.sort()
last_translator_re = re.compile('Last-Translator:([^<"]*)', re.IGNORECASE) last_translator_re = re.compile('Last-Translator:([^<"]*)', re.IGNORECASE)
def translator_name(language): def translator_name(language):
name = 'unknown' name = 'unknown'
for line in file('po/%s.po' % (language)).readlines(): for line in file('po/%s.po' % (language)).readlines():
match = last_translator_re.search(line) match = last_translator_re.search(line)
@ -207,71 +239,103 @@ def translator_name(language):
break break
return "%s (%s)" % (name, language) return "%s (%s)" % (name, language)
thanks += '\nTranslations\n' thanks += '\nTranslations\n'
thanks += wordwrap(', '.join([translator_name(x) for x in translator_list]), ' ') thanks += wordwrap(', '.join([translator_name(x) for x in translator_list]), ' ')
thanks += '\n\n' thanks += '\n\n'
changes = '## '+ ' '.join(changelog).replace('\n', '\n## ') changes = '## '+ ' '.join(changelog).replace('\n', '\n## ')
filename = posixpath.expanduser("~/.release-wrangler-%(name)s-%(major)s-%(minor)s-%(micro)s.txt" % version) filename = posixpath.expanduser("~/.release-wrangler-%(name)s-%(string)s.txt" % version)
tmp = open(filename, 'w') tmp = open(filename, 'w')
tmp.write('## You are releasing %(name)s, version %(major)s.%(minor)s.%(micro)s.\n' % version) 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('## 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('## has changed since the last release. Please summarise it.\n')
tmp.write('## Anything preceded by a # is ignored.\n') tmp.write('## Anything preceded by a # is ignored.\n')
tmp.write(thanks) tmp.write(thanks)
tmp.write(changes) tmp.write(changes)
tmp.close() tmp.close()
os.spawnl(os.P_WAIT, '/bin/nano', 'nano', '+6', filename) os.system(favourite_editor()+' +6 %s ' % (filename))
################################################################ # Write it out to NEWS
# Write it out to NEWS news_tmp = open('NEWS.tmp', 'a')
for line in open(filename, 'r').readlines():
news_tmp = open('NEWS.tmp', 'a')
for line in open(filename, 'r').readlines():
if line=='' or line[0]!='#': if line=='' or line[0]!='#':
news_tmp.write(line) news_tmp.write(line)
for line in open('NEWS').readlines(): for line in open('NEWS').readlines():
news_tmp.write(line) news_tmp.write(line)
news_tmp.close() news_tmp.close()
os.rename('NEWS.tmp', 'NEWS') os.rename('NEWS.tmp', 'NEWS')
changelog_and_checkin('NEWS', '%(major)s.%(minor)s.%(micro)s release.' % (version))
################################################################ def build_it_all(version):
"Now build the thing."
autogen_prefix= '/prefix' # FIXME: this is specific to tthurman's laptop!
# Now build the thing. # FIXME: These should use os.system
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:
if os.spawnl(os.P_WAIT, './autogen.sh', './autogen.sh', '--prefix', autogen_prefix) != 0:
print 'autogen failed' print 'autogen failed'
sys.exit(255) sys.exit(255)
if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make') != 0: if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make') != 0:
print 'make failed' print 'make failed'
sys.exit(255) sys.exit(255)
if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make', 'install') != 0: if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make', 'install') != 0:
print 'install failed' print 'install failed'
sys.exit(255) sys.exit(255)
if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make', 'distcheck') != 0: if os.spawnl(os.P_WAIT, '/usr/bin/make', '/usr/bin/make', 'distcheck') != 0:
print 'distcheck failed' print 'distcheck failed'
sys.exit(255) sys.exit(255)
if not os.access(archive_filename, os.F_OK): if not os.access(version['filename'], os.F_OK):
print "Sorry, we don't appear to have a file called %s!" % (archive_filename) print "Sorry, we don't appear to have a file called %s!" % (archive_filename)
sys.exit(255) sys.exit(255)
# No, we won't have a configuration option to set your name on svn.g.o; that's def upload(version):
# what ~/.ssh/config is for. # No, we won't have a configuration option to set your name on master.g.o; that's
# what ~/.ssh/config is for.
print "Uploading..." print "Uploading..."
upload_result = commands.getstatusoutput('scp %s master.gnome.org:' % (archive_filename)) upload_result = commands.getstatusoutput('scp %s master.gnome.org:' % (version['filename']))
if upload_result[0]!=0:
report_error("There appears to have been an uploading problem: %d\n%s\n" % (upload_result[0], upload_result[1]))
def increment_version(version):
configure_in = file('configure.in.tmp', 'w')
for line in file('configure.in'):
if re.search("^m4_define\(\[.*_micro_version\], \[(\d+)\]", line):
line = line.replace('[%(micro)s]' % version, '[%(micro_next)s]' % version)
configure_in.write(line)
configure_in.close()
os.rename('configure.in.tmp', 'configure.in')
changelog_and_checkin('configure.in', 'Post-release bump to %(major)s.%(minor)s.%(micro_next)s.' % version)
def tag_the_release(version):
version['ucname'] = name.upper()
os.system("svn cp -m release . svn+ssh://svn.gnome.org/svn/%(name)s/tags/%(ucname)s_%(major)s_%(minor)_%(micro)" % (version))
def main():
get_up_to_date()
check_we_are_up_to_date()
version = version_numbers()
check_file_does_not_exist(version)
edit_news_entry(version)
build_it_all(version)
tag_the_release(version)
increment_version(version)
upload(version)
print "-- Done --"
if __name__=='__main__':
main()
if upload_result[0]!=0:
print "There appears to have been an uploading problem: %d\n%s\n" % (upload_result[0], upload_result[1])