Botan 1.10.17
dist.py
Go to the documentation of this file.
1#!/usr/bin/python
2
3"""
4Release script for botan (http://botan.randombit.net/)
5
6(C) 2011, 2012 Jack Lloyd
7
8Distributed under the terms of the Botan license
9"""
10
11import errno
12import logging
13import optparse
14import os
15import shlex
16import StringIO
17import shutil
18import subprocess
19import sys
20import tarfile
21
22def check_subprocess_results(subproc, name):
23 (stdout, stderr) = subproc.communicate()
24
25 stdout = stdout.strip()
26 stderr = stderr.strip()
27
28 if subproc.returncode != 0:
29 if stdout != '':
30 logging.error(stdout)
31 if stderr != '':
32 logging.error(stderr)
33 raise Exception('Running %s failed' % (name))
34 else:
35 if stderr != '':
36 logging.debug(stderr)
37
38 return stdout
39
40def run_monotone(db, args):
41 mtn = subprocess.Popen(['mtn', '--db', db] + args,
42 stdout=subprocess.PIPE,
43 stderr=subprocess.PIPE)
44
45 return check_subprocess_results(mtn, 'mtn')
46
47def get_certs(db, rev_id):
48 tokens = shlex.split(run_monotone(db, ['automate', 'certs', rev_id]))
49
50 def usable_cert(cert):
51 if 'signature' not in cert or cert['signature'] != 'ok':
52 return False
53 if 'trust' not in cert or cert['trust'] != 'trusted':
54 return False
55 if 'name' not in cert or 'value' not in cert:
56 return False
57 return True
58
59 def cert_builder(tokens):
60 pairs = zip(tokens[::2], tokens[1::2])
61 current_cert = {}
62 for pair in pairs:
63 if pair[0] == 'key':
64 if usable_cert(current_cert):
65 name = current_cert['name']
66 value = current_cert['value']
67 current_cert = {}
68
69 logging.debug('Cert %s "%s" for rev %s' % (name, value, rev_id))
70 yield (name, value)
71
72 current_cert[pair[0]] = pair[1]
73
74 certs = dict(cert_builder(tokens))
75 return certs
76
77def datestamp(db, rev_id):
78 certs = get_certs(db, rev_id)
79
80 if 'date' in certs:
81 return int(certs['date'].replace('-','')[0:8])
82
83 logging.info('Could not retreive date for %s' % (rev_id))
84 return 0
85
86def gpg_sign(keyid, files):
87 for filename in files:
88 logging.info('Signing %s using PGP id %s' % (filename, keyid))
89
90 gpg = subprocess.Popen(['gpg', '--armor', '--detach-sign',
91 '--local-user', keyid, filename],
92 stdout=subprocess.PIPE,
93 stderr=subprocess.PIPE)
94
95 check_subprocess_results(gpg, 'gpg')
96
97def parse_args(args):
98 parser = optparse.OptionParser()
99 parser.add_option('--verbose', action='store_true',
100 default=False, help='Extra debug output')
101
102 parser.add_option('--output-dir', metavar='DIR',
103 default='.',
104 help='Where to place output (default %default)')
105
106 parser.add_option('--mtn-db', metavar='DB',
107 default=os.getenv('BOTAN_MTN_DB', ''),
108 help='Set monotone db (default \'%default\')')
109
110 parser.add_option('--pgp-key-id', metavar='KEYID',
111 default='EFBADFBC',
112 help='PGP signing key (default %default)')
113
114 return parser.parse_args(args)
115
117 try:
118 os.unlink(fspath)
119 except OSError as e:
120 if e.errno != errno.ENOENT:
121 raise
122
123def main(args = None):
124 if args is None:
125 args = sys.argv[1:]
126
127 (options, args) = parse_args(args)
128
129 def log_level():
130 if options.verbose:
131 return logging.DEBUG
132 return logging.INFO
133
134 logging.basicConfig(stream = sys.stdout,
135 format = '%(levelname) 7s: %(message)s',
136 level = log_level())
137
138 if options.mtn_db == '':
139 logging.error('No monotone db set (use --mtn-db)')
140 return 1
141
142 if not os.access(options.mtn_db, os.R_OK):
143 logging.error('Monotone db %s not found' % (options.mtn_db))
144 return 1
145
146 if len(args) != 1:
147 logging.error('Usage: %s version' % (sys.argv[0]))
148 return 1
149
150 version = args[0]
151
152 rev_id = run_monotone(options.mtn_db,
153 ['automate', 'select', 't:' + version])
154
155 if rev_id == '':
156 logging.error('No revision for %s found' % (version))
157 return 2
158
159 output_basename = os.path.join(options.output_dir, 'Botan-' + version)
160
161 output_tgz = output_basename + '.tgz'
162 output_tbz = output_basename + '.tbz'
163
164 logging.info('Found revision id %s' % (rev_id))
165
166 if os.access(output_basename, os.X_OK):
167 shutil.rmtree(output_basename)
168
169 run_monotone(options.mtn_db,
170 ['checkout', '-r', rev_id, output_basename])
171
172 shutil.rmtree(os.path.join(output_basename, '_MTN'))
173 remove_file_if_exists(os.path.join(output_basename, '.mtn-ignore'))
174
175 version_file = os.path.join(output_basename, 'botan_version.py')
176
177 if os.access(version_file, os.R_OK):
178 # rewrite botan_version.py
179
180 contents = open(version_file).readlines()
181
182 def content_rewriter():
183 for line in contents:
184 if line == 'release_vc_rev = None\n':
185 yield 'release_vc_rev = \'mtn:%s\'\n' % (rev_id)
186 elif line == 'release_datestamp = 0\n':
187 yield 'release_datestamp = %d\n' % (datestamp(options.mtn_db, rev_id))
188 else:
189 yield line
190
191 open(version_file, 'w').write(''.join(list(content_rewriter())))
192 else:
193 logging.error('Cannot find %s' % (version_file))
194 return 2
195
196 try:
197 os.makedirs(options.output_dir)
198 except OSError as e:
199 if e.errno != errno.EEXIST:
200 logging.error('Creating dir %s failed %s' % (options.output_dir, e))
201 return 2
202
203 remove_file_if_exists(output_tgz)
204 remove_file_if_exists(output_tgz + '.asc')
205 archive = tarfile.open(output_tgz, 'w:gz')
206 archive.add(output_basename)
207 archive.close()
208
209 remove_file_if_exists(output_tbz)
210 remove_file_if_exists(output_tbz + '.asc')
211 archive = tarfile.open(output_tbz, 'w:bz2')
212 archive.add(output_basename)
213 archive.close()
214
215 if options.pgp_key_id != '':
216 gpg_sign(options.pgp_key_id, [output_tbz, output_tgz])
217
218 shutil.rmtree(output_basename)
219
220 return 0
221
222if __name__ == '__main__':
223 try:
224 sys.exit(main())
225 except Exception as e:
226 logging.error(e)
227 import traceback
228 logging.info(traceback.format_exc())
229 sys.exit(1)
remove_file_if_exists(fspath)
Definition dist.py:116
run_monotone(db, args)
Definition dist.py:40
check_subprocess_results(subproc, name)
Definition dist.py:22
main(args=None)
Definition dist.py:123
get_certs(db, rev_id)
Definition dist.py:47
gpg_sign(keyid, files)
Definition dist.py:86
parse_args(args)
Definition dist.py:97
datestamp(db, rev_id)
Definition dist.py:77