Botan 1.10.17

dist.py
Go to the documentation of this file.
1 #!/usr/bin/python
2
3 """
4 Release script for botan (http://botan.randombit.net/)
5
6 (C) 2011, 2012 Jack Lloyd
7
8 Distributed under the terms of the Botan license
9 """
10
11 import errno
12 import logging
13 import optparse
14 import os
15 import shlex
16 import StringIO
17 import shutil
18 import subprocess
19 import sys
20 import tarfile
21
22 def 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
40 def 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
47 def 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
77 def 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
86 def 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
97 def 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
116 def remove_file_if_exists(fspath):
117 try:
118 os.unlink(fspath)
119 except OSError as e:
120 if e.errno != errno.ENOENT:
121 raise
122
123 def 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
222 if __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)
dist.check_subprocess_results
def check_subprocess_results(subproc, name)
Definition: dist.py:22
dist.run_monotone
def run_monotone(db, args)
Definition: dist.py:40
dist.gpg_sign
def gpg_sign(keyid, files)
Definition: dist.py:86
dist.get_certs
def get_certs(db, rev_id)
Definition: dist.py:47
dist.datestamp
def datestamp(db, rev_id)
Definition: dist.py:77
Botan::Exception
std::runtime_error Exception
Definition: exceptn.h:19
dist.remove_file_if_exists
def remove_file_if_exists(fspath)
Definition: dist.py:116
dist.parse_args
def parse_args(args)
Definition: dist.py:97
dist.main
def main(args=None)
Definition: dist.py:123