blob: feb04aa1c87273b8ecd8148c5a040d8dc102d479 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2025 Thomas Mühlbacher <tmuehlbacher@posteo.net>
#
# SPDX-License-Identifier: LGPL-2.1-or-later
import argparse
import json
import logging
import os
import subprocess
import sys
from typing import Iterator
def parse_version(ver):
return tuple(map(int, ver.split(".")))
# see https://github.com/mesonbuild/meson/pull/14890
def old_meson_missing_vapi_deps_tag() -> bool:
version_str = subprocess.check_output(
["meson", "--version"],
text=True,
)
return parse_version(version_str.strip()) <= parse_version("1.9.0")
def objects_with_tag(obj) -> Iterator[dict]:
match obj:
case dict(d):
if "tag" in d:
yield d
for v in d.values():
yield from objects_with_tag(v)
case list(l):
for i in l:
yield from objects_with_tag(i)
def collect_tags(install_plan) -> list[str]:
tags = set()
for obj in objects_with_tag(install_plan):
tags.add(obj["tag"])
return sorted(["null" if t is None else t for t in tags])
def collect_files(install_plan, tag) -> list[str]:
files = list()
if tag == "null":
tag = None
for obj in objects_with_tag(install_plan):
if obj["tag"] == tag:
files.append(obj["destination"])
return sorted(files)
def print_tags(install_plan):
for tag in collect_tags(install_plan):
print(tag)
def print_files(install_plan, tag):
for file in collect_files(install_plan, tag):
print(file)
def print_all(install_plan):
for tag in collect_tags(install_plan):
for file in collect_files(install_plan, tag):
print(f"{tag}: {file}")
def check(install_plan) -> int:
exit_code = 0
tags = collect_tags(install_plan)
# check for files missing install tag
ignore_vapi_deps = old_meson_missing_vapi_deps_tag()
for null_file in collect_files(install_plan, None):
if ignore_vapi_deps and null_file.endswith("fwupd.deps"):
logging.warning(f"missing meson install tag: {null_file}")
else:
logging.error(f"missing meson install tag: {null_file}")
exit_code = 1
# check for incorrect test file tags
for tag in [t for t in tags if t != "tests"]:
files = collect_files(install_plan, tag)
files = [f for f in files if "installed-tests" in f]
for f in files:
logging.error(f"file should have 'tests' tag: {f}")
exit_code = 1
if exit_code != 0:
logging.warning(
"meson build directory may be outdated which requires full wipe and new `meson setup`"
)
return exit_code
def main() -> int:
parser = argparse.ArgumentParser(description="Meson install tag helper")
parser.add_argument(
"-C", dest="working_directory", default=".", help="Meson build directory"
)
parser.add_argument(
"command",
choices=[
"check",
"list",
"list-files",
"list-tags",
],
)
parser.add_argument("-t", "--tag", help="Optional tag for list-files command")
args = parser.parse_args()
install_plan = json.loads(
subprocess.check_output(
["meson", "introspect", "--install-plan"],
cwd=args.working_directory,
text=True,
)
)
exit_code = 0
match args.command:
case "check":
exit_code = check(install_plan)
case "list":
print_all(install_plan)
case "list-files":
print_files(install_plan, args.tag)
case "list-tags":
print_tags(install_plan)
return exit_code
if __name__ == "__main__":
logging.basicConfig(
level=logging.INFO, format="%(levelname)s: %(message)s", stream=sys.stderr
)
if "PRE_COMMIT" not in os.environ:
sys.exit(main())
if not os.path.exists("venv/build"):
logging.info("no configured build directory, skipping check-meson-install-tag")
sys.exit(0)
install_plan = json.loads(
subprocess.check_output(
["meson", "introspect", "--install-plan"],
cwd="venv/build",
text=True,
)
)
if len(collect_files(install_plan, None)) > 10:
logging.info("build dir is likely outdated, skipping check-meson-install-tag")
sys.exit(0)
check(install_plan)
sys.exit(0)