1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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)
|