diff --git a/check_out_of_date.py b/check_out_of_date.py new file mode 100755 index 0000000000..0f32991cab --- /dev/null +++ b/check_out_of_date.py @@ -0,0 +1,188 @@ +#!/usr/bin/python2 +# needs to be python2 for portage + +# Prints out a list of all packages in portage-stable and how they stand relative to gentoo upstream + +import argparse +import json +import os +import subprocess +import sys + +import portage.versions + + +def split_package(p): + # split into cat/package,ver-rev + split = portage.versions.catpkgsplit(p.strip()) + return (split[0] + "/" + split[1], split[2] + "-" + split[3]) + + +def build_pkg_map(pkgs): + pkgs = map(split_package, pkgs) + package_map = dict() + for pkg, ver in pkgs: + if pkg not in package_map: + package_map[pkg] = [ver] + else: + package_map[pkg].append(ver) + return package_map + + +def exec_command_strict(cmd): + """ Wraps check_output splitting the input and string'ing the output""" + return bytes.decode(subprocess.check_output(cmd.split())) + + +def exec_command(cmd): + """ Like exec_command_strict but returns the output even if the command exited unsuccessfully""" + try: + return exec_command_strict(cmd) + except subprocess.CalledProcessError as e: + return bytes.decode(e.output) + + +def get_portage_tree_packages(tree_path): + """ returns a list of all packages in a portage tree/overlay in the form of cat/pkg-ver""" + pkgs = exec_command_strict("find -L {} -maxdepth 3 -type f -name *.ebuild -not -name skel.ebuild -printf %P\\n".format(tree_path)) + + def process_line(line): + # cat/pkg/pkg-ver.ebuild -> cat/pkg-ver + chunks = line.split("/") + end = chunks[2].replace(".ebuild", "") + return chunks[0] + "/" + end + return build_pkg_map(map(process_line, pkgs.splitlines())) + + +def process_emerge_output(eout): + """ transform from emerge --unordered-dispaly to cat/pkg-ver""" + def process_line(line): + return line.strip().split("] ")[1].split(":")[0] + + def is_package(line): + # none of the header line have a / + return "/" in line + + return map(process_line, filter(is_package, eout.splitlines())) + + +def get_board_packages(board): + """ gets a list of packages used by a board. valid boards are {arm,amd}64-usr, sdk, and bootstrap""" + emerge_args = "--emptytree --pretend --verbose --unordered-display" + if board == "sdk": + cmd = "emerge {} @system sdk-depends sdk-extras".format(emerge_args) + elif board == "amd64-usr" or board == "arm64-usr": + cmd = "emerge-{} {} @system board-packages".format(board, emerge_args) + elif board == "bootstrap": + pkgs = exec_command_strict("/usr/lib64/catalyst/targets/stage1/build.py") + cmd = "emerge {} {}".format(emerge_args, pkgs) + elif board == "image": + cmd = "emerge-amd64-usr {} --usepkgonly board-packages".format(emerge_args) + else: + raise "invalid board" + return build_pkg_map(process_emerge_output(exec_command(cmd))) + + +def print_table(report, head, line_head, line_tail, tail, joiner, pkg_joiner): + print(head) + # metapackage that acts as the header + report.insert(0, {"name": "Package", + "common": ["Common"], + "ours": ["Ours"], + "upstream": ["Upstream"], + "tag": "Tag", + "sdk": ["sdk"], + "arm64-usr": ["arm64-usr"], + "amd64-usr": ["amd64-usr"], + "bootstrap": ["bootstrap"], + "modified": "Modified"}) + for entry in report: + print(line_head + joiner.join([entry.get("name",""), + pkg_joiner.join(entry.get("common",[])), + pkg_joiner.join(entry.get("ours",[])), + pkg_joiner.join(entry.get("upstream",[])), + entry.get("tag",""), + pkg_joiner.join(entry.get("sdk", [])), + pkg_joiner.join(entry.get("arm64-usr", [])), + pkg_joiner.join(entry.get("amd64-usr", [])), + pkg_joiner.join(entry.get("bootstrap", [])), + entry.get("modified","")]) + line_tail) + print(tail) + + +def print_table_human(report): + print_table(report, "", "", "", "", "\t", " ") + + +def print_html_table(report): + print_table(report, "", "", "
", "
", "", "
") + + +def get_date(pkg, repo_root, fmt): + return exec_command_strict("git -C {} --no-pager log -1 --pretty=%ad --date={} {}".format(repo_root, fmt, pkg)).strip() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--update-upstream", help="run git-pull in the gentoo mirror repo first", action="store_true") + parser.add_argument("--upstream-git", help="git uri to clone for upstream", default="https://github.com/gentoo/gentoo.git") + parser.add_argument("--upstream-path", help="path to gentoo tree", default="/mnt/host/source/src/gentoo-portage") + parser.add_argument("--portage-stable-path", help="path to portage-stable", default="/mnt/host/source/src/third_party/portage-stable") + parser.add_argument("--date-fmt", help="format for git-date to use", default="relative") + parser.add_argument("--output", help="output format, json, table, and html are accepted", default="json") + args = parser.parse_args() + + if not os.path.exists(args.upstream_path): + os.makedirs(args.upstream_path) + subprocess.check_call(["git", "clone", args.upstream_git, args.upstream_path]) + elif args.update_upstream: + # elif to not pull if we just cloned + subprocess.check_call(["git", "-C", args.upstream_path, "pull"]) + + pkg_lists = {} + sources = ["sdk", "bootstrap", "amd64-usr", "arm64-usr", "image"] + for i in sources: + pkg_lists[i] = get_board_packages(i) + + gentoo_packages = get_portage_tree_packages(args.upstream_path) + packages = get_portage_tree_packages(args.portage_stable_path) + + # time to make the report + report = [] + for pkg, vers in packages.iteritems(): + upstream = gentoo_packages.get(pkg, []) + + entry = { + "name": pkg, + "common": list(set(vers).intersection(upstream)), + "ours": list(set(vers).difference(upstream)), + "upstream": list(set(upstream).difference(vers)), + "modified": get_date(pkg, args.portage_stable_path, args.date_fmt) + } + if not entry["upstream"]: + entry["tag"] = "updated" + elif entry["common"]: + entry["tag"] = "has_update" + elif pkg in gentoo_packages: + entry["tag"] = "no_ebuild_upstream" + else: + entry["tag"] = "deleted_upstream" + + for src in sources: + if pkg in pkg_lists[src]: + entry[src] = pkg_lists[src][pkg] + report.append(entry) + + if args.output == "json": + print(json.dumps(report)) + elif args.output == "table": + print_table_human(report) + elif args.output == "html": + print_html_table(report) + else: + print("Unknown output type. Dying.") + sys.exit(2) + + +if __name__ == "__main__": + main()