#!/usr/bin/python import contextlib import glob import os import shutil import subprocess import sys import tempfile import re import tempita from datetime import datetime # 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 tmpdir(): t = tempfile.mkdtemp() try: yield t finally: shutil.rmtree(t) def join(*paths): p = os.path.join(*paths) return os.path.abspath(p) def tiny_p(cmd, capture=True): # Darn python 2.6 doesn't have check_output (argggg) info("Running %s" % (cmd)) stdout = subprocess.PIPE stderr = subprocess.PIPE if not capture: stdout = None stderr = None sp = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, stdin=None) (out, err) = sp.communicate() if sp.returncode not in [0]: raise RuntimeError("Failed running %s [rc=%s] (%s, %s)" % (cmd, sp.returncode, out, err)) return (out, err) def get_log_header(version): cmd = ['bzr', 'tags'] (stdout, _stderr) = tiny_p(cmd) a_rev = None for t in stdout.splitlines(): ver, rev = t.split(None) if ver == version: a_rev = rev break if not a_rev: return format_change_line(datetime.now(), '??', version) cmd = ['bzr', 'log', '-r%s' % (a_rev), '--timezone=utc'] (stdout, _stderr) = tiny_p(cmd) kvs = { 'comment': version, } for line in stdout.splitlines(): if line.startswith('committer:'): kvs['who'] = line[len('committer:'):].strip() if line.startswith('timestamp:'): ts = line[len('timestamp:'):] ts = ts.strip() # http://bugs.python.org/issue6641 ts = ts.replace("+0000", '').strip() ds = datetime.strptime(ts, '%a %Y-%m-%d %H:%M:%S') kvs['ds'] = ds return format_change_line(**kvs) def format_change_line(ds, who, comment=None): d = format_rpm_date(ds) d += " - %s" % (who) if comment: d += " - %s" % (comment) return "* %s" % (d) def format_rpm_date(ds): return ds.strftime("%a %b %d %Y") def info(msg): print("INFO: %s" % (msg)) def warn(msg): print("WARNING: %s" % (msg)) def generate_spec_contents(tmpl_fn, revno, version): # Tmpl params subs = {} subs['version'] = version subs['revno'] = revno subs['release'] = revno subs['archive_name'] = '%{name}-%{version}-' + subs['revno'] + '.tar.gz' subs['bd_requires'] = ['python-devel', 'python-setuptools'] requires = [] cmd = [sys.executable, join(os.pardir, 'tools', 'read-dependencies')] (stdout, _stderr) = tiny_p(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' % (version, subs['revno']) subs['requires'] = requires # Format a nice changelog (as best as we can) changelog = '' with open(join(os.pardir, 'ChangeLog')) as fh: changelog = fh.read() ch_lines = [] for line in changelog.splitlines(): if not line.strip(): continue if re.match(r"^\s*[\d][.][\d][.][\d]:\s*", line): line = line.strip(":") header = get_log_header(line) ch_lines.append(header) else: ch_lines.append(line) subs['changelog'] = "\n".join(ch_lines) # 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 archive_code(): (stdout, _stderr) = tiny_p([sys.executable, join(os.getcwd(), 'make-tarball')]) (revno, version, bname, arc_fn) = stdout.split(None) return (revno, version, arc_fn) def main(): # Clean out the root dir and make sure the dirs we want are in place root_dir = os.path.expanduser("~/rpmbuild") info("Cleaning %r" % (root_dir)) if os.path.isdir(root_dir): shutil.rmtree(root_dir) arc_dir = os.path.join(root_dir, 'SOURCES') for d in [root_dir, arc_dir]: os.makedirs(d) # Archive the code (revno, version, archive_fn) = archive_code() real_archive_fn = os.path.join(arc_dir, os.path.basename(archive_fn)) shutil.move(archive_fn, real_archive_fn) info("Archived code to %r" % (real_archive_fn)) # Form the spec file to be used tmpl_fn = os.path.join(os.getcwd(), 'redhat', 'cloud-init.spec') info("Generated spec file from template %r" % (tmpl_fn)) (base_name, arc_name, contents) = generate_spec_contents(tmpl_fn, revno, version) spec_fn = os.path.join(root_dir, 'cloud-init.spec') with open(spec_fn, 'w') as fh: fh.write(contents) info("Wrote spec file to %r" % (spec_fn)) # Now build it! cmd = ['rpmbuild', '-ba', spec_fn] tiny_p(cmd, capture=False) info("Rpmbuild completed!") # Copy the items built to our local dir 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) shutil.move(fn, tgt_fn) info("Copied %s to %s" % (n, tgt_fn)) return 0 if __name__ == '__main__': sys.exit(main())