Botan  1.10.9
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 
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)
def check_subprocess_results(subproc, name)
Definition: dist.py:22
def run_monotone(db, args)
Definition: dist.py:40
def gpg_sign(keyid, files)
Definition: dist.py:86
def get_certs(db, rev_id)
Definition: dist.py:47
def datestamp(db, rev_id)
Definition: dist.py:77
std::runtime_error Exception
Definition: exceptn.h:19
def remove_file_if_exists(fspath)
Definition: dist.py:116
def parse_args(args)
Definition: dist.py:97
def main
Definition: dist.py:123