Skip to content

Commit 92fba48

Browse files
committed
move build system to meson
1 parent 2791a73 commit 92fba48

7 files changed

Lines changed: 208 additions & 141 deletions

File tree

.flake8

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ per-file-ignores =
4141
mkl_fft/interfaces/scipy_fft.py: F401
4242
mkl_fft/interfaces/numpy_fft.py: F401
4343

44-
exclude = _vendored/conv_template.py
44+
exclude =
45+
_vendored/conv_template.py
46+
_vendored/process_src_template.py
4547

4648
filename = *.py, *.pyx, *.pxi, *.pxd
4749
max_line_length = 80

_vendored/README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
## Vendored files
22

3-
File `conv_template.py` is copied from NumPy's numpy/distutils folder, since
4-
`numpy.distutils` is absent from the installation layout starting with
5-
Python 3.12
3+
Files `conv_template.py` and `process_src_template.py` are copied from NumPy's numpy/numpy/_build_utils folder

_vendored/conv_template.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@
8282
__all__ = ['process_str', 'process_file']
8383

8484
import os
85-
import sys
8685
import re
86+
import sys
8787

8888
# names for replacement that are already global.
8989
global_names = {}
@@ -106,12 +106,12 @@ def parse_structure(astr, level):
106106
at zero. Returns an empty list if no loops found.
107107
108108
"""
109-
if level == 0 :
109+
if level == 0:
110110
loopbeg = "/**begin repeat"
111111
loopend = "/**end repeat**/"
112-
else :
113-
loopbeg = "/**begin repeat%d" % level
114-
loopend = "/**end repeat%d**/" % level
112+
else:
113+
loopbeg = f"/**begin repeat{level}"
114+
loopend = f"/**end repeat{level}**/"
115115

116116
ind = 0
117117
line = 0
@@ -124,9 +124,9 @@ def parse_structure(astr, level):
124124
start2 = astr.find("\n", start2)
125125
fini1 = astr.find(loopend, start2)
126126
fini2 = astr.find("\n", fini1)
127-
line += astr.count("\n", ind, start2+1)
128-
spanlist.append((start, start2+1, fini1, fini2+1, line))
129-
line += astr.count("\n", start2+1, fini2)
127+
line += astr.count("\n", ind, start2 + 1)
128+
spanlist.append((start, start2 + 1, fini1, fini2 + 1, line))
129+
line += astr.count("\n", start2 + 1, fini2)
130130
ind = fini2
131131
spanlist.sort()
132132
return spanlist
@@ -135,10 +135,13 @@ def parse_structure(astr, level):
135135
def paren_repl(obj):
136136
torep = obj.group(1)
137137
numrep = obj.group(2)
138-
return ','.join([torep]*int(numrep))
138+
return ','.join([torep] * int(numrep))
139+
139140

140141
parenrep = re.compile(r"\(([^)]*)\)\*(\d+)")
141142
plainrep = re.compile(r"([^*]+)\*(\d+)")
143+
144+
142145
def parse_values(astr):
143146
# replaces all occurrences of '(a,b,c)*4' in astr
144147
# with 'a,b,c,a,b,c,a,b,c,a,b,c'. Empty braces generate
@@ -155,7 +158,7 @@ def parse_values(astr):
155158
named_re = re.compile(r"#\s*(\w*)\s*=([^#]*)#")
156159
exclude_vars_re = re.compile(r"(\w*)=(\w*)")
157160
exclude_re = re.compile(":exclude:")
158-
def parse_loop_header(loophead) :
161+
def parse_loop_header(loophead):
159162
"""Find all named replacements in the header
160163
161164
Returns a list of dictionaries, one for each loop iteration,
@@ -179,14 +182,13 @@ def parse_loop_header(loophead) :
179182
name = rep[0]
180183
vals = parse_values(rep[1])
181184
size = len(vals)
182-
if nsub is None :
185+
if nsub is None:
183186
nsub = size
184-
elif nsub != size :
187+
elif nsub != size:
185188
msg = "Mismatch in number of values, %d != %d\n%s = %s"
186189
raise ValueError(msg % (nsub, size, name, vals))
187190
names.append((name, vals))
188191

189-
190192
# Find any exclude variables
191193
excludes = []
192194

@@ -200,30 +202,33 @@ def parse_loop_header(loophead) :
200202

201203
# generate list of dictionaries, one for each template iteration
202204
dlist = []
203-
if nsub is None :
205+
if nsub is None:
204206
raise ValueError("No substitution variables found")
205207
for i in range(nsub):
206208
tmp = {name: vals[i] for name, vals in names}
207209
dlist.append(tmp)
208210
return dlist
209211

212+
210213
replace_re = re.compile(r"@(\w+)@")
211-
def parse_string(astr, env, level, line) :
212-
lineno = "#line %d\n" % line
214+
215+
216+
def parse_string(astr, env, level, line):
217+
lineno = f"#line {line}\n"
213218

214219
# local function for string replacement, uses env
215220
def replace(match):
216221
name = match.group(1)
217-
try :
222+
try:
218223
val = env[name]
219224
except KeyError:
220-
msg = 'line %d: no definition of key "%s"'%(line, name)
225+
msg = f'line {line}: no definition of key "{name}"'
221226
raise ValueError(msg) from None
222227
return val
223228

224229
code = [lineno]
225230
struct = parse_structure(astr, level)
226-
if struct :
231+
if struct:
227232
# recurse over inner loops
228233
oldend = 0
229234
newlevel = level + 1
@@ -234,18 +239,18 @@ def replace(match):
234239
oldend = sub[3]
235240
newline = line + sub[4]
236241
code.append(replace_re.sub(replace, pref))
237-
try :
242+
try:
238243
envlist = parse_loop_header(head)
239244
except ValueError as e:
240-
msg = "line %d: %s" % (newline, e)
245+
msg = f"line {newline}: {e}"
241246
raise ValueError(msg)
242-
for newenv in envlist :
247+
for newenv in envlist:
243248
newenv.update(env)
244249
newcode = parse_string(text, newenv, newlevel, newline)
245250
code.extend(newcode)
246251
suff = astr[oldend:]
247252
code.append(replace_re.sub(replace, suff))
248-
else :
253+
else:
249254
# replace keys
250255
code.append(replace_re.sub(replace, astr))
251256
code.append('\n')
@@ -284,8 +289,8 @@ def process_file(source):
284289
try:
285290
code = process_str(''.join(lines))
286291
except ValueError as e:
287-
raise ValueError('In "%s" loop at %s' % (sourcefile, e)) from None
288-
return '#line 1 "%s"\n%s' % (sourcefile, code)
292+
raise ValueError(f'In "{sourcefile}" loop at {e}') from None
293+
return f'#line 1 "{sourcefile}"\n{code}'
289294

290295

291296
def unique_key(adict):
@@ -321,9 +326,10 @@ def main():
321326
try:
322327
writestr = process_str(allstr)
323328
except ValueError as e:
324-
raise ValueError("In %s loop at %s" % (file, e)) from None
329+
raise ValueError(f"In {file} loop at {e}") from None
325330

326331
outfile.write(writestr)
327332

333+
328334
if __name__ == "__main__":
329335
main()

_vendored/process_src_template.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import importlib.util
4+
import os
5+
6+
7+
def get_processor():
8+
# Convoluted because we can't import from numpy
9+
# (numpy is not yet built)
10+
conv_template_path = os.path.join(
11+
os.path.dirname(__file__),
12+
'conv_template.py'
13+
)
14+
spec = importlib.util.spec_from_file_location(
15+
'conv_template', conv_template_path
16+
)
17+
mod = importlib.util.module_from_spec(spec)
18+
spec.loader.exec_module(mod)
19+
return mod.process_file
20+
21+
22+
def process_and_write_file(fromfile, outfile):
23+
"""Process tempita templated file and write out the result.
24+
25+
The template file is expected to end in `.src`
26+
(e.g., `.c.src` or `.h.src`).
27+
Processing `npy_somefile.c.src` generates `npy_somefile.c`.
28+
29+
"""
30+
process_file = get_processor()
31+
content = process_file(fromfile)
32+
with open(outfile, 'w') as f:
33+
f.write(content)
34+
35+
36+
def main():
37+
parser = argparse.ArgumentParser()
38+
parser.add_argument(
39+
"infile",
40+
type=str,
41+
help="Path to the input file"
42+
)
43+
parser.add_argument(
44+
"-o",
45+
"--outfile",
46+
type=str,
47+
help="Path to the output file"
48+
)
49+
parser.add_argument(
50+
"-i",
51+
"--ignore",
52+
type=str,
53+
help="An ignored input - may be useful to add a "
54+
"dependency between custom targets",
55+
)
56+
args = parser.parse_args()
57+
58+
if not args.infile.endswith('.src'):
59+
raise ValueError(f"Unexpected extension: {args.infile}")
60+
61+
outfile_abs = os.path.join(os.getcwd(), args.outfile)
62+
process_and_write_file(args.infile, outfile_abs)
63+
64+
65+
if __name__ == "__main__":
66+
main()

meson.build

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
project(
2+
'mkl_fft',
3+
['c', 'cython'],
4+
version: run_command(
5+
'python', '-c',
6+
'import os; exec(open("mkl_fft/_version.py").read()); print(__version__)',
7+
check: true
8+
).stdout().strip(),
9+
default_options: [
10+
'buildtype=release',
11+
]
12+
)
13+
14+
py = import('python').find_installation(pure: false)
15+
16+
# numpy includes
17+
np_dir = run_command(py,
18+
['-c', 'import numpy; print(numpy.get_include())'],
19+
check: true
20+
).stdout().strip()
21+
22+
inc_np = include_directories(np_dir, 'mkl_fft/src', 'mkl_fft')
23+
24+
# compiler/linker
25+
cc = meson.get_compiler('c')
26+
c_args = ['-DNDEBUG']
27+
28+
mkl_dep = dependency('MKL', method: 'cmake',
29+
modules: ['MKL::MKL'],
30+
cmake_args: [
31+
'-DMKL_ARCH=intel64',
32+
'-DMKL_LINK=dynamic',
33+
'-DMKL_THREADING=intel_thread',
34+
'-DMKL_INTERFACE=lp64'
35+
],
36+
required: true
37+
)
38+
39+
# code gen
40+
src_file_cli = files('_vendored/process_src_template.py')
41+
42+
gen_mklfft_c = custom_target(
43+
'mklfft_c_generated',
44+
output: 'mklfft.c',
45+
input: 'mkl_fft/src/mklfft.c.src',
46+
command: [py, src_file_cli, '@INPUT@', '-o', '@OUTPUT@'],
47+
)
48+
49+
# cython
50+
py.extension_module(
51+
'_pydfti',
52+
sources: [
53+
'mkl_fft/_pydfti.pyx',
54+
gen_mklfft_c
55+
],
56+
include_directories: inc_np,
57+
dependencies: [mkl_dep],
58+
c_args: c_args + [
59+
'-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION',
60+
'-DPY_ARRAY_UNIQUE_SYMBOL=mkl_fft_ext'
61+
],
62+
install: true,
63+
subdir: 'mkl_fft'
64+
)
65+
66+
# python
67+
py.install_sources(
68+
[
69+
'mkl_fft/__init__.py',
70+
'mkl_fft/_fft_utils.py',
71+
'mkl_fft/_init_helper.py',
72+
'mkl_fft/_mkl_fft.py',
73+
'mkl_fft/_patch_numpy.py',
74+
'mkl_fft/_version.py',
75+
],
76+
subdir: 'mkl_fft'
77+
)
78+
79+
py.install_sources(
80+
[
81+
'mkl_fft/interfaces/__init__.py',
82+
'mkl_fft/interfaces/_float_utils.py',
83+
'mkl_fft/interfaces/_numpy_fft.py',
84+
'mkl_fft/interfaces/_numpy_helper.py',
85+
'mkl_fft/interfaces/_scipy_fft.py',
86+
'mkl_fft/interfaces/numpy_fft.py',
87+
'mkl_fft/interfaces/scipy_fft.py',
88+
],
89+
subdir: 'mkl_fft/interfaces'
90+
)
91+
92+
install_subdir(
93+
'mkl_fft/tests',
94+
install_dir: py.get_install_dir() / 'mkl_fft'
95+
)

pyproject.toml

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2525

2626
[build-system]
27-
build-backend = "setuptools.build_meta"
28-
requires = ["setuptools>=77", "Cython", "numpy", "mkl-devel"]
27+
build-backend = "mesonpy"
28+
requires = [
29+
"meson-python>=0.13.0",
30+
"Cython",
31+
"numpy",
32+
"mkl-devel"
33+
]
2934

3035
[project]
31-
authors = [
32-
{name = "Intel Corporation", email = "scripting@intel.com"}
33-
]
36+
authors = [{name = "Intel Corporation"}]
3437
classifiers = [
3538
"Development Status :: 5 - Production/Stable",
3639
"Intended Audience :: Science/Research",
@@ -68,7 +71,8 @@ Download = "http://github.com/IntelPython/mkl_fft"
6871
Homepage = "http://github.com/IntelPython/mkl_fft"
6972

7073
[tool.black]
71-
exclude = "_vendored/conv_template.py"
74+
extend-exclude = "(^|/)_vendored/"
75+
force-exclude = "(^|/)_vendored/"
7276
line-length = 80
7377

7478
[tool.cython-lint]
@@ -81,15 +85,5 @@ force_grid_wrap = 0
8185
include_trailing_comma = true
8286
line_length = 80
8387
multi_line_output = 3
84-
skip = ["_vendored/conv_template.py"]
88+
skip = ["_vendored/conv_template.py", "_vendored/process_src_template.py"]
8589
use_parentheses = true
86-
87-
[tool.setuptools]
88-
include-package-data = true
89-
packages = ["mkl_fft", "mkl_fft.interfaces"]
90-
91-
[tool.setuptools.dynamic]
92-
version = {attr = "mkl_fft._version.__version__"}
93-
94-
[tool.setuptools.package-data]
95-
"mkl_fft" = ["tests/**/*.py"]

0 commit comments

Comments
 (0)