teichmann@542: #!/usr/bin/env python teichmann@542: # -*- coding: UTF-8 -*- teichmann@542: # teichmann@542: # Copyright (C) 2011 by Intevation GmbH teichmann@542: # Authors: teichmann@542: # Sascha L. Teichmann teichmann@542: # teichmann@542: # This program is free software under the GPL (>=v2) teichmann@542: # Read the file COPYING coming with the software for details. teichmann@542: teichmann@542: import os teichmann@542: import re teichmann@542: import sys teichmann@542: import subprocess teichmann@542: import logging teichmann@542: import traceback teichmann@542: teichmann@542: from optparse import OptionParser teichmann@542: from shutil import copyfile teichmann@542: teichmann@542: log = logging.getLogger(__name__) teichmann@542: log.setLevel(logging.WARNING) teichmann@542: log.addHandler(logging.StreamHandler(sys.stderr)) teichmann@542: teichmann@542: SAEGEWERKER = "saegewerker" teichmann@542: teichmann@543: FIELD = re.compile("([a-zA-Z]+):\s*(.+)") teichmann@543: EPOCH = re.compile("(?:\d+:)(.+)") teichmann@543: UNSHARP = re.compile("([^-]+)") teichmann@542: teichmann@542: teichmann@542: class DebCmp(object): teichmann@542: """Helper class to make deb files comparable teichmann@542: by there versions. teichmann@542: """ teichmann@542: teichmann@542: def __init__(self, version, path): teichmann@542: self.version = version teichmann@542: self.path = path teichmann@542: teichmann@542: def __cmp__(self, other): teichmann@542: if self.version == other.version: teichmann@542: return 0 teichmann@542: if (subprocess.call([ teichmann@542: "dpkg", "--compare-versions", teichmann@542: self.version, "gt", other.version]) == 0): teichmann@542: return +1 teichmann@542: if (subprocess.call([ teichmann@542: "dpkg", "--compare-versions", teichmann@542: self.version, "lt", other.version]) == 0): teichmann@542: return -1 teichmann@542: return 0 teichmann@542: teichmann@542: def __str__(self): teichmann@542: return "version: %s / path: %s" % ( teichmann@542: self.version, teichmann@542: self.path) teichmann@542: teichmann@543: teichmann@542: def deb_info(deb, fields=["Package", "Version"]): teichmann@542: """Extract some meta info from a deb file.""" teichmann@542: po = subprocess.Popen( teichmann@542: ["dpkg-deb", "-f", deb] + fields, teichmann@542: stdout=subprocess.PIPE) teichmann@542: out = po.communicate()[0] teichmann@542: return dict([m.groups() teichmann@542: for m in map(FIELD.match, out.splitlines()) if m]) teichmann@542: teichmann@542: teichmann@542: def copy_pkgs(src, dst, options): teichmann@542: teichmann@542: archs = {} teichmann@542: teichmann@542: for arch in os.listdir(src): teichmann@542: if arch == 'source': continue teichmann@542: arch_dir = os.path.join(src, arch) teichmann@542: if not os.path.isdir(arch_dir): continue teichmann@543: log.info("found arch: '%s'" % arch) teichmann@542: teichmann@542: tracks = {} teichmann@542: teichmann@542: for track in os.listdir(arch_dir): teichmann@542: track_dir = os.path.join(arch_dir, track) teichmann@542: if not os.path.isdir(track_dir): continue teichmann@542: teichmann@542: packages = {} teichmann@542: teichmann@543: log.info("track dir: '%s'" % track_dir) teichmann@542: for f in os.listdir(track_dir): teichmann@542: if not f.endswith(".deb"): continue teichmann@542: deb_path = os.path.join(track_dir, f) teichmann@542: if not os.path.isfile(deb_path): continue teichmann@542: teichmann@542: info = deb_info(deb_path) teichmann@542: deb_cmp = DebCmp(info['Version'], deb_path) teichmann@542: teichmann@542: packages.setdefault(info['Package'], []).append(deb_cmp) teichmann@542: teichmann@543: if packages: teichmann@543: tracks[track] = [max(debs) for debs in packages.itervalues()] teichmann@542: teichmann@542: archs[arch] = tracks teichmann@542: teichmann@542: copy = options.no_hardlinks and copyfile or os.link teichmann@543: action = "%s %%s -> %%s" % (options.no_hardlinks and "copy" or "link") teichmann@543: teichmann@543: track_versions = {} teichmann@542: teichmann@542: for arch, tracks in archs.iteritems(): teichmann@542: log.debug("writing arch '%s'" % arch) teichmann@542: for track, debs in tracks.iteritems(): teichmann@542: log.debug(" writing track '%s'" % track) teichmann@542: dst_dir = os.path.join(dst, arch, track) teichmann@542: if not os.path.exists(dst_dir): teichmann@543: try: os.makedirs(dst_dir) teichmann@543: except: log.warn(traceback.format_exc()); continue teichmann@543: teichmann@543: track_ver = track_versions.setdefault(track, set()) teichmann@542: teichmann@542: for deb in debs: teichmann@542: src_path = deb.path teichmann@542: dst_path = os.path.join(dst_dir, os.path.basename(src_path)) teichmann@543: log.info(action % (src_path, dst_path)) teichmann@542: if os.path.isfile(dst_path): teichmann@542: try: os.remove(dst_path) teichmann@542: except: log.warn(traceback.format_exc()); continue teichmann@542: try: copy(src_path, dst_path) teichmann@543: except: log.error(traceback.format_exc()); continue teichmann@543: teichmann@543: ver = deb.version teichmann@543: m = EPOCH.match(ver) teichmann@543: if m: ver = m.group(1) teichmann@543: teichmann@543: track_ver.add(ver) teichmann@543: teichmann@543: src_source_dir = os.path.join(src, "source") teichmann@543: if not os.path.isdir(src_source_dir): teichmann@543: log.info("no source dir found") teichmann@543: return teichmann@543: teichmann@543: dst_source_dir = os.path.join(dst, "source") teichmann@543: teichmann@543: for track in os.listdir(src_source_dir): teichmann@543: try: versions = track_versions[track] teichmann@543: except KeyError: continue teichmann@543: track_path = os.path.join(src_source_dir, track) teichmann@543: if not os.path.isdir(track_path): continue teichmann@543: log.info("found source track: %s" % track) teichmann@543: unsharp = [UNSHARP.match(x).group(1) for x in versions] teichmann@543: for f in os.listdir(track_path): teichmann@543: f_path = os.path.join(track_path, f) teichmann@543: if not os.path.isfile(f_path): continue teichmann@543: cand = f.split("_", 1) teichmann@543: if len(cand) < 2: continue teichmann@543: cand = cand[1] teichmann@543: for version in f.endswith(".tar.gz") and unsharp or versions: teichmann@543: if cand.startswith(version): break teichmann@543: else: teichmann@543: continue teichmann@543: teichmann@543: dst_track_dir = os.path.join(dst_source_dir, track) teichmann@543: teichmann@543: if not os.path.exists(dst_track_dir): teichmann@543: try: os.makedirs(dst_track_dir) teichmann@543: except: log.error(traceback.format_exc()); continue teichmann@543: teichmann@543: dst_f = os.path.join(dst_track_dir, f) teichmann@543: teichmann@543: log.info(action % (f_path, dst_f)) teichmann@543: if os.path.isfile(dst_f): teichmann@543: try: os.remove(dst_f) teichmann@543: except: log.warn(traceback.format_exc()); continue teichmann@543: try: copy(f_path, dst_f) teichmann@543: except: log.error(traceback.format_exc()); continue teichmann@542: teichmann@542: teichmann@542: def main(): teichmann@542: usage = "usage: %prog [options] src-dir dst-dir" teichmann@542: parser = OptionParser(usage=usage) teichmann@542: parser.add_option( teichmann@542: "-v", "--verbose", action="store_true", teichmann@542: dest="verbose", teichmann@542: help="verbose output") teichmann@542: parser.add_option( teichmann@542: "-d", "--dry-run", action="store_true", teichmann@542: dest="dry_run", default=False, teichmann@542: help="don't copy the deb files") teichmann@542: parser.add_option( teichmann@542: "-n", "--no-saegewerker", action="store_true", teichmann@542: dest="no_saegewerker", default=False, teichmann@542: help="Don't force run as '%s'" % SAEGEWERKER) teichmann@542: parser.add_option( teichmann@542: "-l", "--no-hardlinks", action="store_false", teichmann@542: dest="no_hardlinks", default=False, teichmann@542: help="copy files instead of hard linking") teichmann@542: teichmann@542: options, args = parser.parse_args() teichmann@542: teichmann@542: if len(args) < 2: teichmann@542: log.error("need at least two arguments") teichmann@542: sys.exit(1) teichmann@542: teichmann@542: src, dst = args[0], args[1] teichmann@542: teichmann@542: for d in (src, dst): teichmann@542: if not os.path.isdir(d): teichmann@542: log.error("'%s' is not a directory." % d) teichmann@542: sys.exit(1) teichmann@542: teichmann@542: if options.verbose: log.setLevel(logging.INFO) teichmann@542: teichmann@542: if not options.no_saegewerker and os.environ['USER'] != SAEGEWERKER: teichmann@542: log.error("Need to run as '%s'" % SAEGEWERKER) teichmann@542: sys.exit(1) teichmann@542: teichmann@542: copy_pkgs(src, dst, options) teichmann@542: teichmann@543: teichmann@542: if __name__ == '__main__': teichmann@542: main()