forked from scikit-learn/scikit-learn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck-meson-openmp-dependencies.py
172 lines (137 loc) · 5.34 KB
/
check-meson-openmp-dependencies.py
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
Check that OpenMP dependencies are correctly defined in meson.build files.
This is based on trying to make sure the the following two things match:
- the Cython files using OpenMP (based on a git grep regex)
- the Cython extension modules that are built with OpenMP compiler flags (based
on meson introspect json output)
"""
import json
import re
import subprocess
from pathlib import Path
def has_source_openmp_flags(target_source):
return any("openmp" in arg for arg in target_source["parameters"])
def has_openmp_flags(target):
"""Return whether target sources use OpenMP flags.
Make sure that both compiler and linker source use OpenMP.
Look at `get_meson_info` docstring to see what `target` looks like.
"""
target_sources = target["target_sources"]
target_use_openmp_flags = any(
has_source_openmp_flags(target_source) for target_source in target_sources
)
if not target_use_openmp_flags:
return False
# When the target use OpenMP we expect a compiler + linker source and we
# want to make sure that both the compiler and the linker use OpenMP
assert len(target_sources) == 2
compiler_source, linker_source = target_sources
assert "compiler" in compiler_source
assert "linker" in linker_source
compiler_use_openmp_flags = any(
"openmp" in arg for arg in compiler_source["parameters"]
)
linker_use_openmp_flags = any(
"openmp" in arg for arg in linker_source["parameters"]
)
assert compiler_use_openmp_flags == linker_use_openmp_flags
return compiler_use_openmp_flags
def get_canonical_name_meson(target, build_path):
"""Return a name based on generated shared library.
The goal is to return a name that can be easily matched with the output
from `git_grep_info`.
Look at `get_meson_info` docstring to see what `target` looks like.
"""
# Expect a list with one element with the name of the shared library
assert len(target["filename"]) == 1
shared_library_path = Path(target["filename"][0])
shared_library_relative_path = shared_library_path.relative_to(
build_path.absolute()
)
# Needed on Windows to match git grep output
rel_path = shared_library_relative_path.as_posix()
# OS-specific naming of the shared library .cpython- on POSIX and
# something like .cp312- on Windows
pattern = r"\.(cpython|cp\d+)-.+"
return re.sub(pattern, "", str(rel_path))
def get_canonical_name_git_grep(filename):
"""Return name based on filename.
The goal is to return a name that can easily be matched with the output
from `get_meson_info`.
"""
return re.sub(r"\.pyx(\.tp)?", "", filename)
def get_meson_info():
"""Return names of extension that use OpenMP based on meson introspect output.
The meson introspect json info is a list of targets where a target is a dict
that looks like this (parts not used in this script are not shown for simplicity):
{
'name': '_k_means_elkan.cpython-312-x86_64-linux-gnu',
'filename': [
'<meson_build_dir>/sklearn/cluster/_k_means_elkan.cpython-312-x86_64-linux-gnu.so'
],
'target_sources': [
{
'compiler': ['ccache', 'cc'],
'parameters': [
'-Wall',
'-std=c11',
'-fopenmp',
...
],
...
},
{
'linker': ['cc'],
'parameters': [
'-shared',
'-fPIC',
'-fopenmp',
...
]
}
]
}
"""
build_path = Path("build/introspect")
subprocess.check_call(["meson", "setup", build_path, "--reconfigure"])
json_out = subprocess.check_output(
["meson", "introspect", build_path, "--targets"], text=True
)
target_list = json.loads(json_out)
meson_targets = [target for target in target_list if has_openmp_flags(target)]
return [get_canonical_name_meson(each, build_path) for each in meson_targets]
def get_git_grep_info():
"""Return names of extensions that use OpenMP based on git grep regex."""
git_grep_filenames = subprocess.check_output(
["git", "grep", "-lP", "cython.*parallel|_openmp_helpers"], text=True
).splitlines()
git_grep_filenames = [f for f in git_grep_filenames if ".pyx" in f]
return [get_canonical_name_git_grep(each) for each in git_grep_filenames]
def main():
from_meson = set(get_meson_info())
from_git_grep = set(get_git_grep_info())
only_in_git_grep = from_git_grep - from_meson
only_in_meson = from_meson - from_git_grep
msg = ""
if only_in_git_grep:
only_in_git_grep_msg = "\n".join(
[f" {each}" for each in sorted(only_in_git_grep)]
)
msg += (
"Some Cython files use OpenMP,"
" but their meson.build is missing the openmp_dep dependency:\n"
f"{only_in_git_grep_msg}\n\n"
)
if only_in_meson:
only_in_meson_msg = "\n".join([f" {each}" for each in sorted(only_in_meson)])
msg += (
"Some Cython files do not use OpenMP,"
" you should remove openmp_dep from their meson.build:\n"
f"{only_in_meson_msg}\n\n"
)
if from_meson != from_git_grep:
raise ValueError(
f"Some issues have been found in Meson OpenMP dependencies:\n\n{msg}"
)
if __name__ == "__main__":
main()