26
26
from importlib import import_module
27
27
from numpy .f2py ._backends ._meson import MesonBackend
28
28
29
+ #
30
+ # Check if compilers are available at all...
31
+ #
32
+
33
+ def check_language (lang , code_snippet = None ):
34
+ if sys .platform == "win32" :
35
+ pytest .skip ("No Fortran tests on Windows (Issue #25134)" , allow_module_level = True )
36
+ tmpdir = tempfile .mkdtemp ()
37
+ try :
38
+ meson_file = os .path .join (tmpdir , "meson.build" )
39
+ with open (meson_file , "w" ) as f :
40
+ f .write ("project('check_compilers')\n " )
41
+ f .write (f"add_languages('{ lang } ')\n " )
42
+ if code_snippet :
43
+ f .write (f"{ lang } _compiler = meson.get_compiler('{ lang } ')\n " )
44
+ f .write (f"{ lang } _code = '''{ code_snippet } '''\n " )
45
+ f .write (
46
+ f"_have_{ lang } _feature ="
47
+ f"{ lang } _compiler.compiles({ lang } _code,"
48
+ f" name: '{ lang } feature check')\n "
49
+ )
50
+ try :
51
+ runmeson = subprocess .run (
52
+ ["meson" , "setup" , "btmp" ],
53
+ check = False ,
54
+ cwd = tmpdir ,
55
+ capture_output = True ,
56
+ )
57
+ except subprocess .CalledProcessError :
58
+ pytest .skip ("meson not present, skipping compiler dependent test" , allow_module_level = True )
59
+ return runmeson .returncode == 0
60
+ finally :
61
+ shutil .rmtree (tmpdir )
62
+ return False
63
+
64
+
65
+ fortran77_code = '''
66
+ C Example Fortran 77 code
67
+ PROGRAM HELLO
68
+ PRINT *, 'Hello, Fortran 77!'
69
+ END
70
+ '''
71
+
72
+ fortran90_code = '''
73
+ ! Example Fortran 90 code
74
+ program hello90
75
+ type :: greeting
76
+ character(len=20) :: text
77
+ end type greeting
78
+
79
+ type(greeting) :: greet
80
+ greet%text = 'hello, fortran 90!'
81
+ print *, greet%text
82
+ end program hello90
83
+ '''
84
+
85
+ # Dummy class for caching relevant checks
86
+ class CompilerChecker :
87
+ def __init__ (self ):
88
+ self .compilers_checked = False
89
+ self .has_c = False
90
+ self .has_f77 = False
91
+ self .has_f90 = False
92
+
93
+ def check_compilers (self ):
94
+ if (not self .compilers_checked ) and (not sys .platform == "cygwin" ):
95
+ with concurrent .futures .ThreadPoolExecutor () as executor :
96
+ futures = [
97
+ executor .submit (check_language , "c" ),
98
+ executor .submit (check_language , "fortran" , fortran77_code ),
99
+ executor .submit (check_language , "fortran" , fortran90_code )
100
+ ]
101
+
102
+ self .has_c = futures [0 ].result ()
103
+ self .has_f77 = futures [1 ].result ()
104
+ self .has_f90 = futures [2 ].result ()
105
+
106
+ self .compilers_checked = True
107
+
108
+ if not IS_WASM :
109
+ checker = CompilerChecker ()
110
+ checker .check_compilers ()
111
+
112
+ def has_c_compiler ():
113
+ return checker .has_c
114
+
115
+ def has_f77_compiler ():
116
+ return checker .has_f77
117
+
118
+ def has_f90_compiler ():
119
+ return checker .has_f90
120
+
121
+ def has_fortran_compiler ():
122
+ return (checker .has_f90 and checker .has_f77 )
123
+
124
+
29
125
#
30
126
# Maintaining a temporary module directory
31
127
#
@@ -109,6 +205,9 @@ def build_module(source_files, options=[], skip=[], only=[], module_name=None):
109
205
code = f"import sys; sys.path = { sys .path !r} ; import numpy.f2py; numpy.f2py.main()"
110
206
111
207
d = get_module_dir ()
208
+ # gh-27045 : Skip if no compilers are found
209
+ if not has_fortran_compiler ():
210
+ pytest .skip ("No Fortran compiler available" )
112
211
113
212
# Copy files
114
213
dst_sources = []
@@ -199,92 +298,6 @@ def build_code(source_code,
199
298
module_name = module_name )
200
299
201
300
202
- #
203
- # Check if compilers are available at all...
204
- #
205
-
206
- def check_language (lang , code_snippet = None ):
207
- tmpdir = tempfile .mkdtemp ()
208
- try :
209
- meson_file = os .path .join (tmpdir , "meson.build" )
210
- with open (meson_file , "w" ) as f :
211
- f .write ("project('check_compilers')\n " )
212
- f .write (f"add_languages('{ lang } ')\n " )
213
- if code_snippet :
214
- f .write (f"{ lang } _compiler = meson.get_compiler('{ lang } ')\n " )
215
- f .write (f"{ lang } _code = '''{ code_snippet } '''\n " )
216
- f .write (
217
- f"_have_{ lang } _feature ="
218
- f"{ lang } _compiler.compiles({ lang } _code,"
219
- f" name: '{ lang } feature check')\n "
220
- )
221
- runmeson = subprocess .run (
222
- ["meson" , "setup" , "btmp" ],
223
- check = False ,
224
- cwd = tmpdir ,
225
- capture_output = True ,
226
- )
227
- return runmeson .returncode == 0
228
- finally :
229
- shutil .rmtree (tmpdir )
230
- return False
231
-
232
- fortran77_code = '''
233
- C Example Fortran 77 code
234
- PROGRAM HELLO
235
- PRINT *, 'Hello, Fortran 77!'
236
- END
237
- '''
238
-
239
- fortran90_code = '''
240
- ! Example Fortran 90 code
241
- program hello90
242
- type :: greeting
243
- character(len=20) :: text
244
- end type greeting
245
-
246
- type(greeting) :: greet
247
- greet%text = 'hello, fortran 90!'
248
- print *, greet%text
249
- end program hello90
250
- '''
251
-
252
- # Dummy class for caching relevant checks
253
- class CompilerChecker :
254
- def __init__ (self ):
255
- self .compilers_checked = False
256
- self .has_c = False
257
- self .has_f77 = False
258
- self .has_f90 = False
259
-
260
- def check_compilers (self ):
261
- if (not self .compilers_checked ) and (not sys .platform == "cygwin" ):
262
- with concurrent .futures .ThreadPoolExecutor () as executor :
263
- futures = [
264
- executor .submit (check_language , "c" ),
265
- executor .submit (check_language , "fortran" , fortran77_code ),
266
- executor .submit (check_language , "fortran" , fortran90_code )
267
- ]
268
-
269
- self .has_c = futures [0 ].result ()
270
- self .has_f77 = futures [1 ].result ()
271
- self .has_f90 = futures [2 ].result ()
272
-
273
- self .compilers_checked = True
274
-
275
- if not IS_WASM :
276
- checker = CompilerChecker ()
277
- checker .check_compilers ()
278
-
279
- def has_c_compiler ():
280
- return checker .has_c
281
-
282
- def has_f77_compiler ():
283
- return checker .has_f77
284
-
285
- def has_f90_compiler ():
286
- return checker .has_f90
287
-
288
301
#
289
302
# Building with meson
290
303
#
@@ -303,6 +316,11 @@ def build_meson(source_files, module_name=None, **kwargs):
303
316
"""
304
317
Build a module via Meson and import it.
305
318
"""
319
+
320
+ # gh-27045 : Skip if no compilers are found
321
+ if not has_fortran_compiler ():
322
+ pytest .skip ("No Fortran compiler available" )
323
+
306
324
build_dir = get_module_dir ()
307
325
if module_name is None :
308
326
module_name = get_temp_module_name ()
@@ -327,13 +345,7 @@ def build_meson(source_files, module_name=None, **kwargs):
327
345
extra_dat = kwargs .get ("extra_dat" , {}),
328
346
)
329
347
330
- # Compile the module
331
- # NOTE: Catch-all since without distutils it is hard to determine which
332
- # compiler stack is on the CI
333
- try :
334
- backend .compile ()
335
- except subprocess .CalledProcessError :
336
- pytest .skip ("Failed to compile module" )
348
+ backend .compile ()
337
349
338
350
# Import the compiled module
339
351
sys .path .insert (0 , f"{ build_dir } /{ backend .meson_build_dir } " )
@@ -369,6 +381,7 @@ def setup_class(cls):
369
381
F2PyTest ._has_c_compiler = has_c_compiler ()
370
382
F2PyTest ._has_f77_compiler = has_f77_compiler ()
371
383
F2PyTest ._has_f90_compiler = has_f90_compiler ()
384
+ F2PyTest ._has_fortran_compiler = has_fortran_compiler ()
372
385
373
386
def setup_method (self ):
374
387
if self .module is not None :
@@ -386,7 +399,7 @@ def setup_method(self):
386
399
pytest .skip ("No Fortran 77 compiler available" )
387
400
if needs_f90 and not self ._has_f90_compiler :
388
401
pytest .skip ("No Fortran 90 compiler available" )
389
- if needs_pyf and not ( self ._has_f90_compiler or self . _has_f77_compiler ) :
402
+ if needs_pyf and not self ._has_fortran_compiler :
390
403
pytest .skip ("No Fortran compiler available" )
391
404
392
405
# Build the module
0 commit comments