Install signtool for exe and msi jobs
[inkscape.git] / buildtools / media-check-icons.py
blob39dc0f1dab4a2443ffa0d9832b17a1ae0433db86
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Icon checker: test that icon themes contain all needed icons
4 # Author: Martin Owens <doctormo@geek-2.com>
5 # Licensed under GPL version 2 or any later version, read the file "COPYING" for more information.
7 import fnmatch
8 import os
9 import sys
11 from collections import defaultdict
13 THEME_PATH = os.path.join('.', 'share', 'icons')
14 IGNORE_THEMES = [
15 'application',
16 'Tango',
18 FALLBACK_THEME = 'hicolor'
19 IGNORE_ICONS = [
20 # These are hard coded as symbolic in the gtk source code
21 'list-add-symbolic.svg',
22 'list-add.svg',
23 'list-remove-symbolic.svg',
24 'list-remove.svg',
25 'applications-graphics.svg',
26 'applications-graphics-symbolic.svg',
27 'edit-find.svg',
28 'edit-find-symbolic.svg',
29 'dialog-warning.svg',
30 'dialog-warning-symbolic.svg',
31 'edit-clear.svg',
32 'edit-clear-symbolic.svg',
33 'view-refresh-symbolic.svg',
34 'view-refresh.svg',
35 # Those are illustrations rather than icons
36 'feBlend-icon-symbolic.svg',
37 'feColorMatrix-icon-symbolic.svg',
38 'feComponentTransfer-icon-symbolic.svg',
39 'feComposite-icon-symbolic.svg',
40 'feConvolveMatrix-icon-symbolic.svg',
41 'feDiffuseLighting-icon-symbolic.svg',
42 'feDisplacementMap-icon-symbolic.svg',
43 'feFlood-icon-symbolic.svg',
44 'feGaussianBlur-icon-symbolic.svg',
45 'feImage-icon-symbolic.svg',
46 'feMerge-icon-symbolic.svg',
47 'feMorphology-icon-symbolic.svg',
48 'feOffset-icon-symbolic.svg',
49 'feSpecularLighting-icon-symbolic.svg',
50 'feTile-icon-symbolic.svg',
51 'feTurbulence-icon-symbolic.svg',
52 'feBlend-icon.svg',
53 'feColorMatrix-icon.svg',
54 'feComponentTransfer-icon.svg',
55 'feComposite-icon.svg',
56 'feConvolveMatrix-icon.svg',
57 'feDiffuseLighting-icon.svg',
58 'feDisplacementMap-icon.svg',
59 'feFlood-icon.svg',
60 'feGaussianBlur-icon.svg',
61 'feImage-icon.svg',
62 'feMerge-icon.svg',
63 'feMorphology-icon.svg',
64 'feOffset-icon.svg',
65 'feSpecularLighting-icon.svg',
66 'feTile-icon.svg',
67 'feTurbulence-icon.svg',
68 # Those are UI elements in form of icons; themes may define them, but they shouldn't have to
69 'resizing-handle-horizontal-symbolic.svg',
70 'resizing-handle-vertical-symbolic.svg',
73 NO_PROBLEM,\
74 BAD_SYMBOLIC_NAME,\
75 BAD_SCALABLE_NAME,\
76 MISSING_FROM,\
77 ONLY_FOUND_IN = range(5)
79 def icon_themes():
80 for name in os.listdir(THEME_PATH):
81 filename = os.path.join(THEME_PATH, name)
82 if name in IGNORE_THEMES or not os.path.isdir(filename):
83 continue
84 yield name, filename
86 def theme_to_string(name, kind):
87 return f"{name}-{kind}"
89 def find_errors_in(themes):
90 errors = []
91 warnings = []
93 data = defaultdict(set)
94 bad_symbolic = []
95 bad_scalable = []
96 all_symbolics = set()
98 for name, path in themes:
99 for root, dirs, files in os.walk(path):
100 orig = root
101 root = root[len(path)+1:]
102 if '/' not in root:
103 continue
104 (kind, root) = root.split('/', 1)
105 if kind not in ("symbolic", "scalable"):
106 continue # Not testing cursors, maybe later.
108 theme_name = (name, kind)
109 if kind == "symbolic":
110 all_symbolics.add(name)
112 for fname in files:
113 if fname in IGNORE_ICONS:
114 continue
115 if not fname.endswith('.svg'):
116 continue
118 if kind == "symbolic":
119 if not fname.endswith('-symbolic.svg'):
120 bad_symbolic.append(os.path.join(orig, fname))
121 continue
122 else:
123 # Make filenames consistant for comparison
124 fname = fname.replace('-symbolic.svg', '.svg')
125 elif kind == "scalable" and fname.endswith('-symbolic.svg'):
126 bad_scalable.append(os.path.join(orig, fname))
127 continue
129 filename = os.path.join(root, fname)
130 data[filename].add(theme_name)
132 if bad_symbolic:
133 errors.append((BAD_SYMBOLIC_NAME, bad_symbolic))
134 if bad_scalable:
135 errors.append((BAD_SCALABLE_NAME, bad_scalable))
137 only_found_in = defaultdict(list)
138 missing_from = defaultdict(list)
139 warn_missing_from = defaultdict(list)
141 for filename in sorted(data):
142 datum = data[filename]
144 symbolics = set(name for (name, kind) in datum if kind == 'symbolic')
145 scalables = set(name for (name, kind) in datum if kind == 'scalable')
147 # For every scalable, there must be a symbolic
148 diff = scalables - symbolics
149 if len(diff) > 0:
150 for name in diff:
151 missing_from[f"{name}-symbolic"].append(filename)
152 continue
154 # Icon present in all themes => no error
155 if symbolics == all_symbolics:
156 continue
158 # Icon present in fallback theme but missing from some other theme => warning
159 if FALLBACK_THEME in symbolics:
160 for name in all_symbolics - symbolics:
161 warn_missing_from[name].append(filename)
162 continue
164 # Icon present in some theme but not fallback => error
165 if len(datum) == 1:
166 only_found_in[theme_to_string(*list(datum)[0])].append(filename)
167 continue
168 missing_from[FALLBACK_THEME].append(filename)
170 if only_found_in:
171 errors.append((ONLY_FOUND_IN, only_found_in))
172 if missing_from:
173 errors.append((MISSING_FROM, missing_from))
174 if warn_missing_from:
175 warnings.append((MISSING_FROM, warn_missing_from))
177 return errors, warnings
179 if __name__ == '__main__':
180 errors, warnings = find_errors_in(icon_themes())
181 if errors:
182 count = 0
183 for error, themes in errors:
184 if isinstance(themes, list):
185 count += len(themes)
186 elif isinstance(themes, dict):
187 count += sum([len(v) for v in themes.values()])
188 sys.stderr.write(f" == {count} errors found in icon themes! == \n\n")
189 for error, themes in errors:
190 if error is BAD_SCALABLE_NAME:
191 sys.stderr.write(f"Scalable themes should not have symbolic icons in them (They end with -symbolic.svg so won't be used):\n")
192 for name in themes:
193 sys.stderr.write(f" - {name}\n")
194 sys.stderr.write("\n")
195 elif error is BAD_SYMBOLIC_NAME:
196 sys.stderr.write(f"Symbolic themes should only have symbolic icons in them (They don't end with -symbolic.svg so can't be used):\n")
197 for name in themes:
198 sys.stderr.write(f" - {name}\n")
199 sys.stderr.write("\n")
200 elif error is MISSING_FROM:
201 for theme in themes:
202 sys.stderr.write(f"Icons missing from {theme}:\n")
203 for name in themes[theme]:
204 sys.stderr.write(f" - {name}\n")
205 sys.stderr.write("\n")
206 elif error is ONLY_FOUND_IN:
207 for theme in themes:
208 sys.stderr.write(f"Icons only found in {theme}:\n")
209 for name in themes[theme]:
210 sys.stderr.write(f" + {name}\n")
211 sys.stderr.write("\n")
212 else:
213 pass
214 if warnings:
215 count = 0
216 for warning, themes in warnings:
217 count += sum([len(v) for v in themes.values()])
218 sys.stderr.write(f" == {count} warnings found in icon themes == \n\n")
219 for warning, themes in warnings:
220 if warning is MISSING_FROM:
221 for theme in themes:
222 sys.stderr.write(f"Icons missing from {theme}:\n")
223 for name in themes[theme]:
224 sys.stderr.write(f" - {name}\n")
225 sys.stderr.write("\n")
226 else:
227 pass
228 if errors:
229 sys.exit(5)
231 # vi:sw=4:expandtab: