| |
@@ -0,0 +1,255 @@
|
| |
+ # -*- coding: utf-8 -*-
|
| |
+
|
| |
+
|
| |
+ import argparse
|
| |
+ import copy
|
| |
+ import json
|
| |
+ import os
|
| |
+
|
| |
+ import productmd
|
| |
+ from kobo.rpmlib import parse_nvra
|
| |
+
|
| |
+
|
| |
+ class ComposeRpmsDiff(object):
|
| |
+ def get_srpms(self, rpmm):
|
| |
+ """Get dictionary of all srpm paths"""
|
| |
+ result = {}
|
| |
+ for variant in rpmm.rpms:
|
| |
+ for arch in rpmm.rpms[variant]:
|
| |
+ for srpm_nevra in rpmm.rpms[variant][arch].keys():
|
| |
+ for data in rpmm.rpms[variant][arch][srpm_nevra].values():
|
| |
+ if data["category"] != "source":
|
| |
+ continue
|
| |
+ result[data["path"]] = (variant, srpm_nevra, data)
|
| |
+ return result
|
| |
+
|
| |
+ def get_rpms(self, rpmm):
|
| |
+ """Get dictionary of all rpm paths"""
|
| |
+ result = {}
|
| |
+ for variant in rpmm.rpms:
|
| |
+ for arch in rpmm.rpms[variant]:
|
| |
+ if arch == "src":
|
| |
+ continue
|
| |
+ for srpm_nevra in rpmm.rpms[variant][arch]:
|
| |
+ for rpm_nevra, data in rpmm.rpms[variant][arch][srpm_nevra].items():
|
| |
+ result[data["path"]] = (
|
| |
+ variant,
|
| |
+ arch,
|
| |
+ srpm_nevra,
|
| |
+ rpm_nevra,
|
| |
+ data,
|
| |
+ )
|
| |
+ return result
|
| |
+
|
| |
+ def get_diff(self, old_compose_path, new_compose_path):
|
| |
+ """Compare composes and produce a dict with difference information"""
|
| |
+ result = {}
|
| |
+
|
| |
+ old_rm = productmd.compose.Compose(old_compose_path).rpms
|
| |
+ result["old_compose"] = old_rm.compose.id
|
| |
+
|
| |
+ new_rm = productmd.compose.Compose(new_compose_path).rpms
|
| |
+ result["new_compose"] = new_rm.compose.id
|
| |
+
|
| |
+ srpms_old = self.get_srpms(old_rm)
|
| |
+ srpms_new = self.get_srpms(new_rm)
|
| |
+
|
| |
+ rpms_old = self.get_rpms(old_rm)
|
| |
+ rpms_new = self.get_rpms(new_rm)
|
| |
+
|
| |
+ for i in (
|
| |
+ list(srpms_old.keys())
|
| |
+ + list(srpms_new.keys())
|
| |
+ + list(rpms_old.keys())
|
| |
+ + list(rpms_new.keys())
|
| |
+ ):
|
| |
+ if i in srpms_old and i in srpms_new:
|
| |
+ del srpms_old[i]
|
| |
+ del srpms_new[i]
|
| |
+ if i in rpms_old and i in rpms_new:
|
| |
+ del rpms_old[i]
|
| |
+ del rpms_new[i]
|
| |
+
|
| |
+ result["summary"] = {
|
| |
+ "added_srpms": len(srpms_new),
|
| |
+ "dropped_srpms": len(srpms_old),
|
| |
+ "added_rpms": len(rpms_new),
|
| |
+ "dropped_rpms": len(rpms_old),
|
| |
+ }
|
| |
+
|
| |
+ # Build dicts of added/dropped rpms - rpm_manifest like format variant/arch/srpm_nvra/[rpm_nevra/]:data
|
| |
+ added_rpms = {}
|
| |
+ dropped_rpms = {}
|
| |
+
|
| |
+ for srpms, dictionary in ((srpms_new, added_rpms), (srpms_old, dropped_rpms)):
|
| |
+ for path, (variant, srpm_nevra, data) in srpms.items():
|
| |
+ dictionary.setdefault(variant, {}).setdefault("src", {})[
|
| |
+ srpm_nevra
|
| |
+ ] = data
|
| |
+
|
| |
+ for rpms, dictionary in ((rpms_new, added_rpms), (rpms_old, dropped_rpms)):
|
| |
+ for path, (variant, arch, srpm_nevra, rpm_nevra, data) in rpms.items():
|
| |
+ dictionary.setdefault(variant, {}).setdefault(arch, {}).setdefault(
|
| |
+ srpm_nevra, {}
|
| |
+ )[rpm_nevra] = data
|
| |
+
|
| |
+ result["manifest"] = {}
|
| |
+ result["manifest"]["added"] = added_rpms
|
| |
+ result["manifest"]["dropped"] = dropped_rpms
|
| |
+
|
| |
+ # Build dicts of added/dropped rpms - by path path:data
|
| |
+ added_rpms_by_path = {}
|
| |
+ dropped_rpms_by_path = {}
|
| |
+
|
| |
+ for srpms, dictionary in (
|
| |
+ (srpms_new, added_rpms_by_path),
|
| |
+ (srpms_old, dropped_rpms_by_path),
|
| |
+ ):
|
| |
+ for path, (variant, srpm_nevra, data) in srpms.items():
|
| |
+ data = copy.copy(data)
|
| |
+ data["variant"] = variant
|
| |
+ data["arch"] = "src"
|
| |
+ data["srpm_nevra"] = srpm_nevra
|
| |
+ del data["path"]
|
| |
+ dictionary[path] = data
|
| |
+
|
| |
+ for rpms, dictionary in (
|
| |
+ (rpms_new, added_rpms_by_path),
|
| |
+ (rpms_old, dropped_rpms_by_path),
|
| |
+ ):
|
| |
+ for path, (variant, arch, srpm_nevra, rpm_nevra, data) in rpms.items():
|
| |
+ data = copy.copy(data)
|
| |
+ data["variant"] = variant
|
| |
+ data["arch"] = arch
|
| |
+ data["srpm_nevra"] = srpm_nevra
|
| |
+ data["rpm_nevra"] = rpm_nevra
|
| |
+ del data["path"]
|
| |
+ dictionary[path] = data
|
| |
+
|
| |
+ result["brief"] = {}
|
| |
+ result["brief"]["added"] = added_rpms_by_path
|
| |
+ result["brief"]["dropped"] = dropped_rpms_by_path
|
| |
+
|
| |
+ # Chages by variants and name.arch
|
| |
+ changes = {} # {"variant": {"added": {"pkgname.arch"}}}
|
| |
+ for action, dictionary in (
|
| |
+ ("added", added_rpms_by_path),
|
| |
+ ("dropped", dropped_rpms_by_path),
|
| |
+ ):
|
| |
+ for path, info in dictionary.items():
|
| |
+ variant = info.get("variant", "UnknownVariant")
|
| |
+ arch = info.get("arch", "unknown_arch")
|
| |
+ name = parse_nvra(os.path.basename(path))["name"]
|
| |
+ changes.setdefault(variant, {}).setdefault(action, set()).add(
|
| |
+ "%s.%s" % (name, arch)
|
| |
+ )
|
| |
+ for variant in changes:
|
| |
+ added = changes[variant].get("added", set())
|
| |
+ dropped = changes[variant].get("dropped", set())
|
| |
+ changes[variant]["added"] = sorted(added - dropped)
|
| |
+ changes[variant]["dropped"] = sorted(dropped - added)
|
| |
+
|
| |
+ result["diff"] = changes
|
| |
+
|
| |
+ return result
|
| |
+
|
| |
+ def _get_summary(self, data):
|
| |
+ """Gen list of summary lines"""
|
| |
+ result = []
|
| |
+ result.append("===== SUMMARY =====")
|
| |
+ result.append("Added source rpms: %s" % data["summary"]["added_srpms"])
|
| |
+ result.append("Dropped source rpms: %s" % data["summary"]["dropped_srpms"])
|
| |
+ result.append("Added rpms: %s" % data["summary"]["added_rpms"])
|
| |
+ result.append("Dropped rpms: %s" % data["summary"]["dropped_rpms"])
|
| |
+ result.append("")
|
| |
+
|
| |
+ return result
|
| |
+
|
| |
+ def get_brief_log(self, data):
|
| |
+ result = []
|
| |
+ result.append("OLD: %s" % data["old_compose"])
|
| |
+ result.append("NEW: %s" % data["new_compose"])
|
| |
+ result.append("")
|
| |
+
|
| |
+ result.extend(self._get_summary(data))
|
| |
+
|
| |
+ brief_data = data.get("brief", {})
|
| |
+
|
| |
+ result.append("===== ADDED RPMS =====")
|
| |
+ for i in sorted(brief_data["added"]):
|
| |
+ result.append("+ %s" % i)
|
| |
+ result.append("")
|
| |
+
|
| |
+ result.append("===== DROPPED RPMS =====")
|
| |
+ for i in sorted(brief_data["dropped"]):
|
| |
+ result.append("- %s" % i)
|
| |
+ result.append("")
|
| |
+
|
| |
+ return "\n".join(result)
|
| |
+
|
| |
+ def get_diff_log(self, data):
|
| |
+ result = []
|
| |
+ result.append("OLD: %s" % data["old_compose"])
|
| |
+ result.append("NEW: %s" % data["new_compose"])
|
| |
+ result.append("")
|
| |
+
|
| |
+ result.extend(self._get_summary(data))
|
| |
+
|
| |
+ for variant, data in data.get("diff", {}).items():
|
| |
+ added = sorted(data.get("added", set()))
|
| |
+ dropped = sorted(data.get("dropped", set()))
|
| |
+ result.append("===== %s =====" % variant)
|
| |
+ for prefix, items in (("+ ", added), ("- ", dropped)):
|
| |
+ for item in items:
|
| |
+ result.append("%s%s" % (prefix, item))
|
| |
+ result.append("")
|
| |
+ return "\n".join(result)
|
| |
+
|
| |
+ def write(self, data, path=None, name=None):
|
| |
+ """Write output files with results"""
|
| |
+ if name:
|
| |
+ name = "diff-rpms-%s" % name
|
| |
+ else:
|
| |
+ name = "diff-rpms"
|
| |
+ name = name.replace(" ", "_")
|
| |
+ name = name.replace("/", "_")
|
| |
+ name = name.replace("\\", "_")
|
| |
+
|
| |
+ path = path or ""
|
| |
+
|
| |
+ # json
|
| |
+ json_log = os.path.join(path, "%s.json" % name)
|
| |
+ with open(json_log, "w") as f:
|
| |
+ json.dump(data.get("manifest", {}), f, sort_keys=True, indent=4)
|
| |
+
|
| |
+ json_log = os.path.join(path, "%s-brief.json" % name)
|
| |
+ with open(json_log, "w") as f:
|
| |
+ json.dump(data.get("brief", {}), f, sort_keys=True, indent=4)
|
| |
+
|
| |
+ json_log = os.path.join(path, "%s-diff.json" % name)
|
| |
+ with open(json_log, "w") as f:
|
| |
+ json.dump(data.get("diff", {}), f, sort_keys=True, indent=4)
|
| |
+
|
| |
+ # brief
|
| |
+ brief_log = os.path.join(path, "%s-brief.log" % name)
|
| |
+ with open(brief_log, "w") as f:
|
| |
+ f.write(self.get_brief_log(data))
|
| |
+
|
| |
+ # diff
|
| |
+ verbose_log = os.path.join(path, "%s-diff.log" % name)
|
| |
+ with open(verbose_log, "w") as f:
|
| |
+ f.write(self.get_diff_log(data))
|
| |
+
|
| |
+
|
| |
+ def main(argv=None):
|
| |
+ parser = argparse.ArgumentParser()
|
| |
+ parser.add_argument("--old", metavar="PATH", help="old compose", required=True)
|
| |
+ parser.add_argument("--new", metavar="PATH", help="new compose", required=True)
|
| |
+ parser.add_argument("-o", "--outputdir", help="output dir")
|
| |
+ parser.add_argument("--name", help="log name appended to file name")
|
| |
+
|
| |
+ args = parser.parse_args(argv)
|
| |
+
|
| |
+ cdiff = ComposeRpmsDiff()
|
| |
+ data = cdiff.get_diff(args.old, args.new)
|
| |
+ cdiff.write(data, args.outputdir, args.name)
|
| |
A bit more low level than the changelog.