Merged mostly completed `be email-bugs'.
[be.git] / release.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19 import os
20 import os.path
21 import shutil
22 import string
23 import sys
24
25 from libbe.subproc import Pipe, invoke
26 from update_copyright import update_authors, update_files
27
28 def validate_tag(tag):
29     """
30     >>> validate_tag('1.0.0')
31     >>> validate_tag('A.B.C-r7')
32     >>> validate_tag('A.B.C r7')
33     Traceback (most recent call last):
34       ...
35     Exception: Invalid character ' ' in tag 'A.B.C r7'
36     >>> validate_tag('"')
37     Traceback (most recent call last):
38       ...
39     Exception: Invalid character '"' in tag '"'
40     >>> validate_tag("'")
41     Traceback (most recent call last):
42       ...
43     Exception: Invalid character ''' in tag '''
44     """
45     for char in tag:
46         if char in string.digits:
47             continue
48         elif char in string.letters:
49             continue
50         elif char in ['.','-']:
51             continue
52         raise Exception("Invalid character '%s' in tag '%s'" % (char, tag))
53
54 def bzr_pending_changes():
55     """Use `bzr diff`s exit status to detect change:
56     1 - changed
57     2 - unrepresentable changes
58     3 - error
59     0 - no change
60     """
61     p = Pipe([['bzr', 'diff']])
62     if p.status == 0:
63         return False
64     elif p.status in [1,2]:
65         return True
66     raise Exception("Error in bzr diff %d\n%s" % (p.status, p.stderrs[-1]))
67
68 def set_release_version(tag):
69     print "set libbe.version._VERSION = '%s'" % tag
70     p = Pipe([['sed', '-i', "s/^# *_VERSION *=.*/_VERSION = '%s'/" % tag,
71                os.path.join('libbe', 'version.py')]])
72     assert p.status == 0, p.statuses
73
74 def bzr_commit(commit_message):
75     print 'commit current status:', commit_message
76     p = Pipe([['bzr', 'commit', '-m', commit_message]])
77     assert p.status == 0, p.statuses
78
79 def bzr_tag(tag):
80     print 'tag current revision', tag
81     p = Pipe([['bzr', 'tag', tag]])
82     assert p.status == 0, p.statuses
83
84 def bzr_export(target_dir):
85     print 'export current revision to', target_dir
86     p = Pipe([['bzr', 'export', target_dir]])
87     assert p.status == 0, p.statuses
88
89 def make_version():
90     print 'generate libbe/_version.py'
91     p = Pipe([['make', os.path.join('libbe', '_version.py')]])
92     assert p.status == 0, p.statuses
93
94 def make_changelog(filename, tag):
95     print 'generate ChangeLog file', filename, 'up to tag', tag
96     p = invoke(['bzr', 'log', '--gnu-changelog', '-n1', '-r',
97                 '..tag:%s' % tag], stdout=file(filename, 'w'))
98     status = p.wait()
99     assert status == 0, status
100
101 def set_vcs_name(filename, vcs_name='None'):
102     """Exported directory is not a bzr repository, so set vcs_name to
103     something that will work.
104       vcs_name: new_vcs_name
105     """
106     print 'set vcs_name in', filename, 'to', vcs_name
107     p = Pipe([['sed', '-i', "s/^vcs_name:.*/vcs_name: %s/" % vcs_name,
108                filename]])
109     assert p.status == 0, p.statuses
110
111 def create_tarball(tag):
112     release_name='be-%s' % tag
113     export_dir = release_name
114     bzr_export(export_dir)
115     make_version()
116     print 'copy libbe/_version.py to %s/libbe/_version.py' % export_dir
117     shutil.copy(os.path.join('libbe', '_version.py'),
118                 os.path.join(export_dir, 'libbe', '_version.py'))
119     make_changelog(os.path.join(export_dir, 'ChangeLog'), tag)
120     set_vcs_name(os.path.join(export_dir, '.be', 'settings'))
121     tarball_file = '%s.tar.gz' % release_name
122     print 'create tarball', tarball_file
123     p = Pipe([['tar', '-czf', tarball_file, export_dir]])
124     assert p.status == 0, p.statuses
125     print 'remove', export_dir
126     shutil.rmtree(export_dir)
127
128 def test():
129     import doctest
130     doctest.testmod() 
131
132 if __name__ == '__main__':
133     import optparse
134     usage = """%prog [options] TAG
135
136 Create a bzr tag and a release tarball from the current revision.
137 For example
138   %prog 1.0.0
139 """
140     p = optparse.OptionParser(usage)
141     p.add_option('--test', dest='test', default=False,
142                  action='store_true', help='Run internal tests and exit')
143     options,args = p.parse_args()
144
145     if options.test == True:
146         test()
147         sys.exit(0)
148
149     assert len(args) == 1, '%d (!= 1) arguments: %s' % (len(args), args)
150     tag = args[0]
151     validate_tag(tag)
152
153     if bzr_pending_changes() == True:
154         print "Handle pending changes before releasing."
155         sys.exit(1)
156     set_release_version(tag)
157     update_authors()
158     update_files()
159     bzr_commit("Bumped to version %s" % tag)
160     bzr_tag(tag)
161     create_tarball(tag)