diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbd4c916..8ceb980e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -88,6 +88,14 @@ check-layers: - kas shell --update --force-checkout ci/base.yml:ci/meta-arm-autonomy.yml:ci/meta-openembedded.yml --command \ "$CI_PROJECT_DIR/ci/check-layers.py $CI_PROJECT_DIR/ci/check-layers.yml $CI_PROJECT_DIR $KAS_WORK_DIR" +pending-updates: + extends: .setup + artifacts: + paths: + - update-report.html + script: + - kas shell ci/qemuarm64.yml:ci/meta-openembedded.yml -c "$CI_PROJECT_DIR/scripts/machine-summary.py -t updates.html -o $CI_PROJECT_DIR/update-report.html $($CI_PROJECT_DIR/ci/listmachines.py meta-arm meta-arm-bsp)" + corstone500: extends: .build diff --git a/scripts/machine-summary-overview.txt.jinja b/scripts/machine-summary-overview.txt.jinja new file mode 100644 index 00000000..d585065e --- /dev/null +++ b/scripts/machine-summary-overview.txt.jinja @@ -0,0 +1,12 @@ +Machine Overview +Generated at {{ timestamp }}. +{% for machine, data in data|dictsort %} + +MACHINE: {{ machine }} +{% for recipe in recipes|sort %} +{% if recipe in data %} +{% set details = data[recipe] %} +{{ details.recipe }}: {{ details.version }} +{% endif %} +{% endfor %} +{% endfor %} diff --git a/scripts/machine-summary-updates.html.jinja b/scripts/machine-summary-updates.html.jinja new file mode 100644 index 00000000..d3ac2ff6 --- /dev/null +++ b/scripts/machine-summary-updates.html.jinja @@ -0,0 +1,47 @@ + + + + Pending Machine Upgrades Report + + + + +
+
+

Pending Machine Upgrades Report

+

Generated at {{ timestamp }}.

+
+ + + + + + {% for recipe in recipes|sort %} + + {% endfor %} + + + + {% for machine, data in data|dictsort %} + + + {% for recipe in recipes|sort %} + {% if recipe in data %} + {% set details = data[recipe] %} + {% set is_old = details.version is old(details.upstream) %} + + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
Machine{{ recipe }} ({{releases[recipe]|default("?")}})
{{ machine }} + {{ details.recipe if details.recipe != recipe}} + {{ details.version }} + {{ "(patched)" if details.patched }} + -
+
+ + diff --git a/scripts/machine-summary.py b/scripts/machine-summary.py new file mode 100755 index 00000000..7d2eb53f --- /dev/null +++ b/scripts/machine-summary.py @@ -0,0 +1,122 @@ +#! /usr/bin/env python3 + +import os +import sys +import argparse +import datetime + +import jinja2 + +def get_template(name): + template_dir = os.path.dirname(os.path.abspath(__file__)) + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(template_dir), + autoescape=jinja2.select_autoescape(), + trim_blocks=True, + lstrip_blocks=True + ) + def is_old(version, upstream): + if "+git" in version: + # strip +git and see if this is a post-release snapshot + version = version.replace("+git", "") + return version != upstream + env.tests["old"] = is_old + + return env.get_template(f"machine-summary-{name}.jinja") + +def trim_pv(pv): + """ + Strip anything after +git from the PV + """ + return "".join(pv.partition("+git")[:2]) + +def layer_path(layername, d): + """ + Return the path to the specified layer, or None if the layer isn't present. + """ + import re + bbpath = d.getVar("BBPATH").split(":") + pattern = d.getVar('BBFILE_PATTERN_' + layername) + for path in reversed(sorted(bbpath)): + if re.match(pattern, path + "/"): + return path + return None + +def harvest_data(machines, recipes): + import bb.tinfoil, bb.utils + with bb.tinfoil.Tinfoil() as tinfoil: + tinfoil.prepare(config_only=True) + corepath = layer_path("core", tinfoil.config_data) + sys.path.append(os.path.join(corepath, "lib")) + import oe.recipeutils + import oe.patch + + # Queue of recipes that we're still looking for upstream releases for + to_check = list(recipes) + + # Upstream releases + upstreams = {} + # Machines to recipes to versions + versions = {} + + for machine in machines: + print(f"Gathering data for {machine}...") + os.environ["MACHINE"] = machine + with bb.tinfoil.Tinfoil() as tinfoil: + versions[machine] = {} + + tinfoil.prepare(quiet=2) + for recipe in recipes: + try: + d = tinfoil.parse_recipe(recipe) + except bb.providers.NoProvider: + continue + + if recipe in to_check: + try: + info = oe.recipeutils.get_recipe_upstream_version(d) + upstreams[recipe] = info["version"] + to_check.remove(recipe) + except (bb.providers.NoProvider, KeyError): + pass + + details = versions[machine][recipe] = {} + details["recipe"] = d.getVar("PN") + details["version"] = trim_pv(d.getVar("PV")) + details["patched"] = bool(oe.patch.src_patches(d)) + + # Now backfill the upstream versions + for machine in versions: + for recipe in versions[machine]: + versions[machine][recipe]["upstream"] = upstreams[recipe] + + return upstreams, versions + +# TODO can this be inferred from the list of recipes in the layer +recipes = ("virtual/kernel", + "scp-firmware", + "trusted-firmware-a", + "trusted-firmware-m", + "edk2-firmware", + "u-boot", + "optee-os", + "armcompiler-native", + "gcc-aarch64-none-elf-native", + "gcc-arm-none-eabi-native") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="machine-summary") + parser.add_argument("machines", nargs="+", help="machine names", metavar="MACHINE") + parser.add_argument("-t", "--template", required=True) + parser.add_argument("-o", "--output", required=True, type=argparse.FileType('w', encoding='UTF-8')) + args = parser.parse_args() + + template = get_template(args.template) + + context = {} + # TODO: include git describe for meta-arm + context["timestamp"] = str(datetime.datetime.now().strftime("%c")) + context["recipes"] = sorted(recipes) + context["releases"], context["data"] = harvest_data(args.machines, recipes) + + args.output.write(template.render(context))