Skip to content

Commit fc1bc54

Browse files
authored
Merge pull request numpy#25364 from HaoZeke/handleIncludesMeson
ENH,BUG: Handle includes for meson backend
2 parents 0fe68a2 + 4b98384 commit fc1bc54

File tree

6 files changed

+84
-29
lines changed

6 files changed

+84
-29
lines changed

numpy/f2py/_backends/_meson.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __init__(
2424
deps: list[str],
2525
libraries: list[str],
2626
library_dirs: list[Path],
27+
include_dirs: list[Path],
2728
object_files: list[Path],
2829
linker_args: list[str],
2930
c_args: list[str],
@@ -38,12 +39,17 @@ def __init__(
3839
self.deps = deps
3940
self.libraries = libraries
4041
self.library_dirs = library_dirs
42+
if include_dirs is not None:
43+
self.include_dirs = include_dirs
44+
else:
45+
self.include_dirs = []
4146
self.substitutions = {}
4247
self.objects = object_files
4348
self.pipeline = [
4449
self.initialize_template,
4550
self.sources_substitution,
4651
self.deps_substitution,
52+
self.include_substitution,
4753
self.libraries_substitution,
4854
]
4955
self.build_type = build_type
@@ -67,13 +73,13 @@ def initialize_template(self) -> None:
6773
def sources_substitution(self) -> None:
6874
indent = " " * 21
6975
self.substitutions["source_list"] = f",\n{indent}".join(
70-
[f"'{source}'" for source in self.sources]
76+
[f"{indent}'{source}'" for source in self.sources]
7177
)
7278

7379
def deps_substitution(self) -> None:
7480
indent = " " * 21
7581
self.substitutions["dep_list"] = f",\n{indent}".join(
76-
[f"dependency('{dep}')" for dep in self.deps]
82+
[f"{indent}dependency('{dep}')" for dep in self.deps]
7783
)
7884

7985
def libraries_substitution(self) -> None:
@@ -93,10 +99,16 @@ def libraries_substitution(self) -> None:
9399

94100
indent = " " * 21
95101
self.substitutions["lib_list"] = f"\n{indent}".join(
96-
[f"{lib}," for lib in self.libraries]
102+
[f"{indent}{lib}," for lib in self.libraries]
97103
)
98104
self.substitutions["lib_dir_list"] = f"\n{indent}".join(
99-
[f"lib_dir_{i}," for i in range(len(self.library_dirs))]
105+
[f"{indent}lib_dir_{i}," for i in range(len(self.library_dirs))]
106+
)
107+
108+
def include_substitution(self) -> None:
109+
indent = " " * 21
110+
self.substitutions["inc_list"] = f",\n{indent}".join(
111+
[f"{indent}'{inc}'" for inc in self.include_dirs]
100112
)
101113

102114
def generate_meson_build(self):
@@ -138,6 +150,7 @@ def write_meson_build(self, build_dir: Path) -> None:
138150
self.dependencies,
139151
self.libraries,
140152
self.library_dirs,
153+
self.include_dirs,
141154
self.extra_objects,
142155
self.flib_flags,
143156
self.fc_flags,
@@ -171,7 +184,8 @@ def _prepare_sources(mname, sources, bdir):
171184
Path(bdir).mkdir(parents=True, exist_ok=True)
172185
# Copy sources
173186
for source in sources:
174-
shutil.copy(source, bdir)
187+
if Path(source).exists() and Path(source).is_file():
188+
shutil.copy(source, bdir)
175189
generated_sources = [
176190
Path(f"{mname}module.c"),
177191
Path(f"{mname}-f2pywrappers2.f90"),

numpy/f2py/_backends/meson.build.template

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ py.extension_module('${modulename}',
4040
${source_list},
4141
fortranobject_c
4242
],
43-
include_directories: [inc_np],
43+
include_directories: [
44+
inc_np,
45+
${inc_list}
46+
],
4447
dependencies : [
4548
py_dep,
4649
quadmath_dep,

numpy/f2py/f2py2e.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from pathlib import Path
1919
from itertools import dropwhile
2020
import argparse
21+
import copy
2122

2223
from . import crackfortran
2324
from . import rules
@@ -190,15 +191,15 @@
190191

191192
def scaninputline(inputline):
192193
files, skipfuncs, onlyfuncs, debug = [], [], [], []
193-
f, f2, f3, f5, f6, f7, f8, f9, f10 = 1, 0, 0, 0, 0, 0, 0, 0, 0
194+
f, f2, f3, f5, f6, f8, f9, f10 = 1, 0, 0, 0, 0, 0, 0, 0
194195
verbose = 1
195196
emptygen = True
196197
dolc = -1
197198
dolatexdoc = 0
198199
dorestdoc = 0
199200
wrapfuncs = 1
200201
buildpath = '.'
201-
include_paths = []
202+
include_paths, inputline = get_includes(inputline)
202203
signsfile, modulename = None, None
203204
options = {'buildpath': buildpath,
204205
'coutput': None,
@@ -258,14 +259,6 @@ def scaninputline(inputline):
258259
elif l[:8] == '-include':
259260
cfuncs.outneeds['userincludes'].append(l[9:-1])
260261
cfuncs.userincludes[l[9:-1]] = '#include ' + l[8:]
261-
elif l[:15] in '--include_paths':
262-
outmess(
263-
'f2py option --include_paths is deprecated, use --include-paths instead.\n')
264-
f7 = 1
265-
elif l[:15] in '--include-paths':
266-
# Similar to using -I with -c, however this is
267-
# also used during generation of wrappers
268-
f7 = 1
269262
elif l == '--skip-empty-wrappers':
270263
emptygen = False
271264
elif l[0] == '-':
@@ -280,9 +273,6 @@ def scaninputline(inputline):
280273
elif f6:
281274
f6 = 0
282275
buildpath = l
283-
elif f7:
284-
f7 = 0
285-
include_paths.extend(l.split(os.pathsep))
286276
elif f8:
287277
f8 = 0
288278
options["coutput"] = l
@@ -450,7 +440,7 @@ def run_main(comline_list):
450440
fobjhsrc = os.path.join(f2pydir, 'src', 'fortranobject.h')
451441
fobjcsrc = os.path.join(f2pydir, 'src', 'fortranobject.c')
452442
# gh-22819 -- begin
453-
parser = make_f2py_parser()
443+
parser = make_f2py_compile_parser()
454444
args, comline_list = parser.parse_known_args(comline_list)
455445
pyf_files, _ = filter_files("", "[.]pyf([.]src|)", comline_list)
456446
# Checks that no existing modulename is defined in a pyf file
@@ -532,7 +522,35 @@ def get_prefix(module):
532522
p = os.path.dirname(os.path.dirname(module.__file__))
533523
return p
534524

535-
def make_f2py_parser():
525+
526+
class CombineIncludePaths(argparse.Action):
527+
def __call__(self, parser, namespace, values, option_string=None):
528+
include_paths_set = set(getattr(namespace, 'include_paths', []) or [])
529+
if option_string == "--include_paths":
530+
outmess("Use --include-paths or -I instead of --include_paths which will be removed")
531+
if option_string == "--include-paths" or option_string == "--include_paths":
532+
include_paths_set.update(values.split(':'))
533+
else:
534+
include_paths_set.add(values)
535+
setattr(namespace, 'include_paths', list(include_paths_set))
536+
537+
def include_parser():
538+
parser = argparse.ArgumentParser(add_help=False)
539+
parser.add_argument("-I", dest="include_paths", action=CombineIncludePaths)
540+
parser.add_argument("--include-paths", dest="include_paths", action=CombineIncludePaths)
541+
parser.add_argument("--include_paths", dest="include_paths", action=CombineIncludePaths)
542+
return parser
543+
544+
def get_includes(iline):
545+
iline = (' '.join(iline)).split()
546+
parser = include_parser()
547+
args, remain = parser.parse_known_args(iline)
548+
ipaths = args.include_paths
549+
if args.include_paths is None:
550+
ipaths = []
551+
return ipaths, remain
552+
553+
def make_f2py_compile_parser():
536554
parser = argparse.ArgumentParser(add_help=False)
537555
parser.add_argument("--dep", action="append", dest="dependencies")
538556
parser.add_argument("--backend", choices=['meson', 'distutils'], default='distutils')
@@ -542,7 +560,7 @@ def make_f2py_parser():
542560
def preparse_sysargv():
543561
# To keep backwards bug compatibility, newer flags are handled by argparse,
544562
# and `sys.argv` is passed to the rest of `f2py` as is.
545-
parser = make_f2py_parser()
563+
parser = make_f2py_compile_parser()
546564

547565
args, remaining_argv = parser.parse_known_args()
548566
sys.argv = [sys.argv[0]] + remaining_argv
@@ -659,19 +677,19 @@ def run_compile():
659677
if '--quiet' in f2py_flags:
660678
setup_flags.append('--quiet')
661679

680+
# Ugly filter to remove everything but sources
662681
sources = sys.argv[1:]
663-
for optname in ['--include_paths', '--include-paths', '--f2cmap']:
664-
if optname in sys.argv:
665-
i = sys.argv.index(optname)
666-
f2py_flags.extend(sys.argv[i:i + 2])
667-
del sys.argv[i + 1], sys.argv[i]
668-
sources = sys.argv[1:]
682+
f2cmapopt = '--f2cmap'
683+
if f2cmapopt in sys.argv:
684+
i = sys.argv.index(f2cmapopt)
685+
f2py_flags.extend(sys.argv[i:i + 2])
686+
del sys.argv[i + 1], sys.argv[i]
687+
sources = sys.argv[1:]
669688

670689
pyf_files, _sources = filter_files("", "[.]pyf([.]src|)", sources)
671690
sources = pyf_files + _sources
672691
modulename = validate_modulename(pyf_files, modulename)
673692
extra_objects, sources = filter_files('', '[.](o|a|so|dylib)', sources)
674-
include_dirs, sources = filter_files('-I', '', sources, remove_prefix=1)
675693
library_dirs, sources = filter_files('-L', '', sources, remove_prefix=1)
676694
libraries, sources = filter_files('-l', '', sources, remove_prefix=1)
677695
undef_macros, sources = filter_files('-U', '', sources, remove_prefix=1)
@@ -694,6 +712,8 @@ def run_compile():
694712
else:
695713
run_main(f" {' '.join(f2py_flags)} {' '.join(pyf_files)}".split())
696714

715+
# Order matters here, includes are needed for run_main above
716+
include_dirs, sources = get_includes(sources)
697717
# Now use the builder
698718
builder = build_backend(
699719
modulename,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
real(8) b, n, m
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function add(n,m) result(b)
2+
implicit none
3+
include 'AB.inc'
4+
b = n + m
5+
end function add

numpy/f2py/tests/test_regression.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,15 @@ class TestModuleAndSubroutine(util.F2PyTest):
7575
def test_gh25337(self):
7676
self.module.data.set_shift(3)
7777
assert "data" in dir(self.module)
78+
79+
80+
class TestIncludeFiles(util.F2PyTest):
81+
sources = [util.getpath("tests", "src", "regression", "incfile.f90")]
82+
options = [f"-I{util.getpath('tests', 'src', 'regression')}",
83+
f"--include-paths {util.getpath('tests', 'src', 'regression')}"]
84+
85+
@pytest.mark.slow
86+
def test_gh25344(self):
87+
exp = 7.0
88+
res = self.module.add(3.0, 4.0)
89+
assert exp == res

0 commit comments

Comments
 (0)