12
12
from glob import glob
13
13
import re
14
14
from typing import Any
15
+ import warnings
15
16
16
17
if os .name == "nt" :
17
18
EXE_SUFFIX = "bat"
@@ -23,6 +24,10 @@ class ResourceNotFound(RuntimeError):
23
24
pass
24
25
25
26
27
+ class ResolverWarning (RuntimeWarning ):
28
+ pass
29
+
30
+
26
31
class AbstractResolver (ABC ):
27
32
"""
28
33
Interface for resolvers.
@@ -204,6 +209,9 @@ class ExecutableResolver(AbstractResolver):
204
209
and have the executable bit set. :meth:`.search` yields tuples of version strings and full paths to the executable
205
210
instead of plain strings.
206
211
212
+ Except on windows results are filtered to make sure all returned scripts have the executable bit set.
213
+ When the bit is not set, a warning is printed.
214
+
207
215
>>> exe = ExecutableResolver(..., "lammps")
208
216
>>> exe.list() # doctest: +SKIP
209
217
[
@@ -255,9 +263,18 @@ def _search(self, name):
255
263
256
264
def cond (path ):
257
265
isfile = os .path .isfile (path )
266
+ # HINT: this is always True on windows
258
267
isexec = os .access (
259
268
path , os .X_OK , effective_ids = os .access in os .supports_effective_ids
260
269
)
270
+ if isfile and not isexec :
271
+ warnings .warn (
272
+ f"Found file '{ path } ', but skipping it because it is not executable!" ,
273
+ category = ResolverWarning ,
274
+ # TODO: maybe used from python3.12 onwards
275
+ # skip_file_prefixes=(os.path.dirname(__file__),),
276
+ stacklevel = 4 ,
277
+ )
261
278
return isfile and isexec
262
279
263
280
for path in filter (cond , self ._resolver .search (self ._glob )):
0 commit comments