blob: 1b863eaad3ef2593a74d294f0cc1dee11231239c [file] [log] [blame]
Armin Ronacherabfbc182011-07-24 21:48:29 +02001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""
4 make-release
5 ~~~~~~~~~~~~
6
7 Helper script that performs a release. Does pretty much everything
8 automatically for us.
9
10 :copyright: (c) 2011 by Armin Ronacher.
11 :license: BSD, see LICENSE for more details.
12"""
13import sys
14import os
15import re
16from datetime import datetime, date
17from subprocess import Popen, PIPE
18
19
20def parse_changelog():
21 with open('CHANGES') as f:
22 lineiter = iter(f)
23 for line in lineiter:
24 match = re.search('^Version\s+(.*)', line.strip())
25 if match is None:
26 continue
27 length = len(match.group(1))
28 version = match.group(1).strip()
29 if lineiter.next().count('-') != len(match.group(0)):
30 continue
31 while 1:
32 change_info = lineiter.next().strip()
33 if change_info:
34 break
35
36 match = re.search(r'(?:codename (.*),\s*)?'
37 r'released on (\w+\s+\d+\w+\s+\d+)(?i)',
38 change_info)
39 if match is None:
40 continue
41
42 codename, datestr = match.groups()
43 return version, parse_date(datestr), codename
44
45
46def bump_version(version):
47 try:
48 parts = map(int, version.split('.'))
49 except ValueError:
50 fail('Current version is not numeric')
51 parts[-1] += 1
52 return '.'.join(map(str, parts))
53
54
55def parse_date(string):
56 string = string.replace('th ', ' ').replace('nd ', ' ') \
57 .replace('rd ', ' ').replace('st ', ' ')
58 return datetime.strptime(string, '%B %d %Y')
59
60
61def set_filename_version(filename, version_number, pattern):
62 changed = []
63 def inject_version(match):
64 before, old, after = match.groups()
65 changed.append(True)
66 return before + version_number + after
67 with open(filename) as f:
68 contents = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern,
69 inject_version, f.read())
70
71 if not changed:
72 fail('Could not find %s in %s', pattern, filename)
73
74 with open(filename, 'w') as f:
75 f.write(contents)
76
77
78def set_init_version(version):
79 info('Setting __init__.py version to %s', version)
80 set_filename_version('jinja2/__init__.py', version, '__version__')
81
82
83def set_setup_version(version):
84 info('Setting setup.py version to %s', version)
85 set_filename_version('setup.py', version, 'version')
86
87
88def build_and_upload():
89 Popen([sys.executable, 'setup.py', 'release', 'sdist', 'upload']).wait()
90
91
92def fail(message, *args):
93 print >> sys.stderr, 'Error:', message % args
94 sys.exit(1)
95
96
97def info(message, *args):
98 print >> sys.stderr, message % args
99
100
101def get_git_tags():
102 return set(Popen(['git', 'tag'], stdout=PIPE).communicate()[0].splitlines())
103
104
105def git_is_clean():
106 return Popen(['git', 'diff', '--quiet']).wait() == 0
107
108
109def make_git_commit(message, *args):
110 message = message % args
111 Popen(['git', 'commit', '-am', message]).wait()
112
113
114def make_git_tag(tag):
115 info('Tagging "%s"', tag)
116 Popen(['git', 'tag', tag]).wait()
117
118
119def main():
120 os.chdir(os.path.join(os.path.dirname(__file__), '..'))
121
122 rv = parse_changelog()
123 if rv is None:
124 fail('Could not parse changelog')
125
126 version, release_date, codename = rv
127 dev_version = bump_version(version) + '-dev'
128
129 info('Releasing %s (codename %s, release date %s)',
130 version, codename, release_date.strftime('%d/%m/%Y'))
131 tags = get_git_tags()
132
133 if version in tags:
134 fail('Version "%s" is already tagged', version)
135 if release_date.date() != date.today():
136 fail('Release date is not today (%s != %s)')
137
138 if not git_is_clean():
139 fail('You have uncommitted changes in git')
140
141 set_init_version(version)
142 set_setup_version(version)
143 make_git_commit('Bump version number to %s', version)
144 make_git_tag(version)
145 build_and_upload()
146 set_init_version(dev_version)
147 set_setup_version(dev_version)
148
149
150if __name__ == '__main__':
151 main()