#!/usr/bin/python # vi: ts=4 expandtab import os import subprocess import sys import tempfile import re import textwrap import shutil import zipfile import glob import tempita from datetime import datetime from datetime import date from distutils import version as ver # This is more just for running from the bin folder so that # cloud-init binary can find the cloudinit module possible_topdir = os.path.normpath(os.path.join(os.path.abspath( sys.argv[0]), os.pardir, os.pardir)) if os.path.exists(os.path.join(possible_topdir, "cloudinit", "__init__.py")): sys.path.insert(0, possible_topdir) from cloudinit import util from cloudinit import version import contextlib # Mapping of expected packages to there full name... PKG_MP = { 'boto': 'python-boto', 'tempita': 'python-tempita', 'prettytable': 'python-prettytable', 'oauth': 'python-oauth', 'configobj': 'python-configobj', 'yaml': 'PyYAML', 'argparse': 'python-argparse' } @contextlib.contextmanager def chdir(where_to): cur_cwd = os.path.abspath(os.getcwd()) where_to = os.path.abspath(where_to) os.chdir(where_to) yield where_to os.chdir(cur_cwd) PWD = os.getcwd() def info(msg): print("INFO: %s" % (msg)) def warn(msg): print("WARNING: %s" % (msg)) def cut_up(entry, maxline=80): if len(entry) < maxline: return entry else: c = entry[0:maxline] return "%s..." % (c) def extract_entry(collecting): a_entry = {} for t in ['tags', 'revno', 'author', 'timestamp', 'committer']: look_for = "%s:" % (t) for v in collecting: if v.startswith(look_for): a_entry[t] = v[len(look_for):].strip() break i = -1 for a, v in enumerate(collecting): if v.startswith("message:"): i = a break if i != -1: msg_lines = collecting[i + 1:] n_lines = [] for m in msg_lines: m = m.strip() if not m: continue m = m.replace("\n", " ") n_lines.append("" + m.lstrip()) message = " ".join(n_lines).lstrip() a_entry['message'] = message return a_entry def build_changelog(history=-1): cmd = ['bzr', 'log', '--timezone=utc'] (stdout, _stderr) = util.subp(cmd) # Clean the format up entries = stdout.splitlines() all_entries = [] collecting = [] for e in entries: if e.startswith("---"): if collecting: a_entry = extract_entry(collecting) if a_entry: all_entries.append(a_entry) collecting = [] else: collecting.append(e) a_entry = extract_entry(collecting) if a_entry: all_entries.append(a_entry) if history > 0: take_entries = list(all_entries[0:history]) else: take_entries = list(all_entries) # Merge those with same date date_entries = {} for e in take_entries: author = e.get('author') if not author: author = e.get('committer') if not author: continue timestamp = e.get('timestamp') if not timestamp: continue msg = e.get('message') if not msg: continue revno = e.get('revno') if not revno: continue # http://bugs.python.org/issue6641 timestamp = timestamp.replace("+0000", '').strip() ds = datetime.strptime(timestamp, '%a %Y-%m-%d %H:%M:%S') c_ds = ds.date() if c_ds not in date_entries: ap_entry = {} ap_entry['messages'] = [] ap_entry['authors'] = [] ap_entry['revnos'] = [] date_entries[c_ds] = ap_entry ap_entry = date_entries[c_ds] ap_entry['messages'].append(msg) ap_entry['authors'].append(author) ap_entry['revnos'].append(revno) dates = sorted(date_entries.keys()) chglog = [] for ds in reversed(dates): e = date_entries[ds] authors = ", ".join(set(e['authors'])) revnos = ", ".join(list(sorted(e['revnos']))) top_line = "%s %s - %s" % (ds.strftime("%a %b %d %Y"), authors, revnos) chglog.append("* %s" % (top_line)) for msg in e['messages']: chglog.append("- %s" % (cut_up(msg))) return "\n".join(chglog) def generate_spec_contents(tmpl_fn): # Version junk cmd = [os.path.join(os.pardir, 'tools', 'read-version')] (stdout, _stderr) = util.subp(cmd) i_version = stdout.strip() # Ensure ok match! if ver.StrictVersion(i_version) != version.version(): raise RuntimeError("Version found does not match the code version") # Tmpl params subs = {} subs['version'] = i_version (stdout, _stderr) = util.subp(['bzr', 'revno']) subs['revno'] = "%s" % (stdout.strip()) subs['release'] = "%s" % (subs['revno']) subs['archive_name'] = '%{name}-%{version}-' + subs['revno'] + '.tar.gz' subs['bd_requires'] = ['python-devel', 'python-setuptools'] requires = [] cmd = [os.path.join(os.pardir, 'tools', 'read-dependencies')] (stdout, _stderr) = util.subp(cmd) pkgs = stdout.splitlines() # Map to known packages for e in pkgs: e = e.lower().strip() tgt_pkg = None for n in PKG_MP.keys(): if e.find(n) != -1: tgt_pkg = PKG_MP.get(n) if not tgt_pkg: raise RuntimeError(("Do not know how to translate %s to " " a known package") % (e)) else: requires.append(tgt_pkg) base_name = 'cloud-init-%s-%s' % (i_version, subs['revno']) subs['requires'] = requires subs['changelog'] = build_changelog() # See: http://www.zarb.org/~jasonc/macros.php # Pickup any special files docs = [ 'TODO', 'LICENSE', 'ChangeLog', 'Requires', '%{_defaultdocdir}/cloud-init/*', ] subs['docs'] = docs configs = [ 'cloud/cloud.cfg', 'cloud/cloud.cfg.d/*.cfg', 'cloud/cloud.cfg.d/README', 'cloud/templates/*', ] subs['configs'] = configs other_files = [ '%{_bindir}/*', '/usr/lib/cloud-init/*', ] subs['files'] = other_files with open(tmpl_fn, 'r') as fh: tmpl = tempita.Template(fh.read()) contents = tmpl.substitute(**subs) return (base_name, '%s.tar.gz' % (base_name), contents) def main(): root_dir = os.path.expanduser("~/rpmbuild") info("Cleaning %s" % (root_dir)) if os.path.isdir(root_dir): util.delete_dir_contents(root_dir) arc_dir = os.path.join(root_dir, 'SOURCES') util.ensure_dirs([root_dir, arc_dir]) tmpl_fn = os.path.join(os.getcwd(), 'brpm.tmpl') info("Generated spec file from template %s" % (tmpl_fn)) (base_name, arc_name, contents) = generate_spec_contents(tmpl_fn) spec_fn = os.path.join(root_dir, 'cloud-init.spec') util.write_file(spec_fn, contents) info("Wrote spec file to %s" % (spec_fn)) with util.tempdir() as td: src_dir = os.path.join(td, base_name) os.makedirs(src_dir) for fn in os.listdir(os.pardir): if fn.startswith("."): continue full_fn = os.path.abspath(os.path.join(os.pardir, fn)) if os.path.isfile(full_fn): shutil.copy(full_fn, os.path.join(src_dir, fn)) else: shutil.copytree(full_fn, os.path.join(src_dir, fn), ignore=shutil.ignore_patterns('*.pyc', '.bzr', 'tmp*', '*bzr*')) arc_fn = os.path.join(arc_dir, arc_name) cmd = ['tar', '-zcvf', arc_fn, '-C', td] cmd.extend(os.listdir(td)) util.subp(cmd) info("Archived code at %s" % (arc_fn)) cmd = ['rpmbuild', '-ba', spec_fn] info("Running rpmbuild %s" % (cmd)) util.subp(cmd) info("Rpmbuild completed!") globs = [] globs.extend(glob.glob("%s/*.rpm" % (os.path.join(root_dir, 'RPMS', 'noarch')))) globs.extend(glob.glob("%s/*.rpm" % (os.path.join(root_dir, 'RPMS')))) globs.extend(glob.glob("%s/*.rpm" % (os.path.join(root_dir, 'SRPMS')))) for fn in globs: n = os.path.basename(fn) tgt_fn = os.path.join(os.getcwd(), n) util.copy(fn, tgt_fn) info("Copied %s to %s" % (n, tgt_fn)) return 0 if __name__ == '__main__': sys.exit(main())