Преглед на файлове

scripts: add machine summary report tool

machine-summary.py is a tool to generate a report of what recipes and
versions are used for what machines, and what the latest upstream release
is.

Change-Id: Iecf21a14057df0fd1cb05be9b54c621dfbaddd94
Signed-off-by: Ross Burton <ross.burton@arm.com>
Signed-off-by: Jon Mason <jon.mason@arm.com>
Ross Burton преди 3 години
родител
ревизия
b1d667c396
променени са 4 файла, в които са добавени 189 реда и са изтрити 0 реда
  1. 8 0
      .gitlab-ci.yml
  2. 12 0
      scripts/machine-summary-overview.txt.jinja
  3. 47 0
      scripts/machine-summary-updates.html.jinja
  4. 122 0
      scripts/machine-summary.py

+ 8 - 0
.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
 

+ 12 - 0
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 %}

+ 47 - 0
scripts/machine-summary-updates.html.jinja

@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Pending Machine Upgrades Report</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
+  </head>
+  <body>
+    <section class="section">
+      <div class="content">
+        <h1 class="title">Pending Machine Upgrades Report</h1>
+        <p>Generated at {{ timestamp }}.</p>
+      </div>
+
+      <table class="table is-striped">
+        <thead>
+          <tr>
+            <th>Machine</th>
+            {% for recipe in recipes|sort %}
+            <th>{{ recipe }} ({{releases[recipe]|default("?")}})</th>
+            {% endfor %}
+          </tr>
+        </thead>
+        <tbody>
+          {% for machine, data in data|dictsort %}
+          <tr>
+            <th>{{ machine }}</th>
+            {% for recipe in recipes|sort %}
+              {% if recipe in data %}
+                {% set details = data[recipe] %}
+                {% set is_old = details.version is old(details.upstream) %}
+                <td class="{% if is_old %}has-text-weight-bold{% endif %}">
+                {{ details.recipe if details.recipe != recipe}}
+                {{ details.version }}
+                {{ "(patched)" if details.patched }}
+                </td>
+              {% else %}
+                <td>-</td>
+              {% endif %}
+            {% endfor %}
+          </tr>
+          {% endfor %}
+        </tbody>
+      </table>
+    </section>
+  </body>
+</html>

+ 122 - 0
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))