po: add test to check potfiles list contents

We often forget to add new files to POTFILES, and also
have some paths in the skip file that have not existed
for 15+ years. We should ensure that these files include
the paths that should be there, and nothing more.

This commit adds a test that checks whether all the files
in the po lists exist, and vice versa, that all the files
in the source tree that should be included in this list,
are indeed included in this list.
This commit is contained in:
Jan Vaclav
2025-04-14 15:21:31 +02:00
parent e22de07553
commit 104be9da87
2 changed files with 142 additions and 0 deletions

View File

@@ -1,3 +1,8 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
i18n.gettext(nm_name, preset: 'glib')
test(
'check-potfile-list',
find_program(join_paths(source_root, 'src/tests/check-potfile-list.py'))
)

137
src/tests/check-potfile-list.py Executable file
View File

@@ -0,0 +1,137 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2025 Red Hat, Inc.
from pathlib import Path
import subprocess
import itertools
import shutil
import sys
import os
xgettext = shutil.which("xgettext")
# Paths relative to repo root which should be
# excluded from pot->tree, or tree->pot checks.
ignore_in_potfiles = ()
ignore_in_tree = ()
def err(s):
print(f"ERR: {s}", file=sys.stderr)
def read_potfile_list(root, path):
with open(root / path, "r") as f:
for line in f.readlines():
stripped = line.strip()
if stripped.startswith("#") or len(stripped) == 0:
continue
if stripped in ignore_in_potfiles:
continue
yield (root / stripped).resolve()
def get_existing_entries(root):
in_list = read_potfile_list(root, "po/POTFILES.in")
skip_list = read_potfile_list(root, "po/POTFILES.skip")
return set(itertools.chain(in_list, skip_list))
def check_exists_in_tree(root, paths):
is_ok = True
for path in paths:
if not path.exists():
err(
f"{path.relative_to(root)} exists in POTFILES.in or POTFILES.skip, but missing in sources"
)
is_ok = False
return is_ok
def get_gettext_args(root):
arg_name = "XGETTEXT_OPTIONS ="
with open(root / "po" / "Makevars", "r") as f:
for line in f.readlines():
if line.startswith(arg_name):
return line[len(arg_name) :].strip().split(" ")
raise Exception("could not get gettext args")
def list_c_sources(root):
for path, _, files in os.walk(root / "src"):
for file in files:
relpath_str = str(Path(path).relative_to(root) / file)
extension = file.replace(".in", "").split(".").pop()
if extension in ["c", "h"] and relpath_str not in ignore_in_tree:
full_path = root / path / file
yield full_path.resolve()
def gettext_dry_run(root, paths):
args = get_gettext_args(root)
process = subprocess.Popen(
[xgettext, "--files-from=-", "--output=-"] + args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
)
for line in paths:
process.stdin.write(f"{line.resolve()}\n")
process.stdin.close()
seen = set()
for line in process.stdout:
if line.startswith("#: "):
out_path = Path(line.replace("#: ", "").split(":")[0])
if out_path not in seen:
seen.add(out_path)
yield out_path
process.stdout.close()
assert process.wait() == 0
def check_exists_in_potfiles(root, pot_paths):
unseen_paths = filter(lambda path: path not in pot_paths, list_c_sources(root))
is_ok = True
for path in gettext_dry_run(root, unseen_paths):
err(
f"{path.relative_to(root)} code contains gettext macros, but missing in POTFILES.in or POTFILES.skip"
)
is_ok = False
return is_ok
def check_potfiles():
root = (Path(os.path.dirname(__file__)) / "../../").resolve()
file_entries = get_existing_entries(root)
is_ok = True
# Let's first check that all the files that we
# have in POTFILES.* actually exist.
is_ok &= check_exists_in_tree(root, file_entries)
# Now the other direction -- check that all sources
# that should be included in POTFILES, are included.
is_ok &= check_exists_in_potfiles(root, file_entries)
return is_ok
if __name__ == "__main__":
if xgettext is None:
raise Exception("xgettext is missing")
out_msg = "POTFILES consistency check: %s"
if check_potfiles():
print(out_msg % "ok")
else:
print(out_msg % "failed", file=sys.stderr)
sys.exit(1)