Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ patches = [
'Python-3.12.3_avoid-tkinter-build.patch',
'Python-3.13.1_runshared-ld-preload.patch',
]

# Like patches, but these will only be applied if EasyBuild is configured to filter $LD_LIBRARY_PATH
# In that scenario, ctypes needs to be patched since it heavily relies on $LD_LIBRARY_PATH to find libraries
patch_ctypes_ld_library_path = 'Python-3.11.5-custom-ctypes.patch'

checksums = [
{'Python-3.13.5.tgz': 'e6190f52699b534ee203d9f417bdbca05a92f23e35c19c691a50ed2942835385'},
{'Python-3.12.3_avoid-tkinter-build.patch': '34fa44ca67fc08d41c58db2e289317f12f32777a352a982dca2e63459fc089e3'},
{'Python-3.13.1_runshared-ld-preload.patch': 'ca9ec56c71aafa881e7ddf6fba23fbecc016be48c2d912e5ccd92962ddd38edf'},
{'Python-3.11.5-custom-ctypes.patch': 'b29c22f47587460149e05296ff09b29bf790a83e2b3b13fb2f42f5f236ad8ea7'},
]

builddependencies = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ patches = [
'Python-3.13.1_runshared-ld-preload.patch',
'Python-3.14.2_skip-inf-recursion-tests.patch',
]

# Like patches, but these will only be applied if EasyBuild is configured to filter $LD_LIBRARY_PATH
# In that scenario, ctypes needs to be patched since it heavily relies on $LD_LIBRARY_PATH to find libraries
patch_ctypes_ld_library_path = 'Python-3.14.2-custom-ctypes.patch'

checksums = [
{'Python-3.14.2.tgz': 'c609e078adab90e2c6bacb6afafacd5eaf60cd94cf670f1e159565725fcd448d'},
{'Python-3.12.3_avoid-tkinter-build.patch': '34fa44ca67fc08d41c58db2e289317f12f32777a352a982dca2e63459fc089e3'},
{'Python-3.13.1_runshared-ld-preload.patch': 'ca9ec56c71aafa881e7ddf6fba23fbecc016be48c2d912e5ccd92962ddd38edf'},
{'Python-3.14.2_skip-inf-recursion-tests.patch':
'ff9d0951f169fa5c34f883093d7688b1823693e2950b6c4cfb21682ece33646a'},
{'Python-3.14.2-custom-ctypes.patch': '4c7659026a88961330c9629b0a1c23e9a8ed38108af765bfc0f537f55b7d610f'},
]

builddependencies = [
Expand Down
154 changes: 154 additions & 0 deletions easybuild/easyconfigs/p/Python/Python-3.14.2-custom-ctypes.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
Ctypes heavily relies on LD_LIBRARY_PATH in it's find_library, ctypes.CDLL, ctypes.cdll.LoadLibrary functions.
This patch is meant for systems where LD_LIBRARY_PATH is filtered. It will rely on LIBRARY_PATH instead.
It makes the following essential changes:
- Whereever find_library searched LD_LIBRARY_PATH, LIBRARY_PATH will be searched instead
- find_library is adapted so that it returns the full library path, not just the library name, which replaces
https://github.com/easybuilders/easybuild-easyblocks/pull/3352
- The internal function _findLib_gcc, one of the functions called by find_library to locate libraries, is adapted
so that it also works when called by full library name (e.g. libfoo.so.1) instead of the short name (foo) only
This is necessary since CDLL is typically called by full name, and needs to be able to call find_library 9see below)
- The initialization of CDLL is adapted so that it calls find_library. Then, it overwrites the name
with the full library path. This defers all the library localization issues to the (patched) find_library
Authors: Danilo Gonzalez (DoItNow group), Caspar van Leeuwen (SURF) and Alan O'cais (CECAM)
Updated for Python 3.14.2 by Bob Dröge (University of Groningen) and Caspar van Leeuwen (SURF)
diff -Nru Python-3.14.2.orig/Lib/ctypes/__init__.py Python-3.14.2/Lib/ctypes/__init__.py
--- Python-3.14.2.orig/Lib/ctypes/__init__.py 2025-12-05 17:49:16.000000000 +0100
+++ Python-3.14.2/Lib/ctypes/__init__.py 2026-01-21 09:44:04.003467039 +0100
@@ -469,6 +469,13 @@
"""
if name and name.endswith(")") and ".a(" in name:
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
+ # define CDLL instance name as fullpath
+ if _os.name == "posix":
+ if name:
+ from ctypes import util
+ fullpath = util.find_library(name)
+ if fullpath is not None:
+ name = fullpath
self._name = name
return _dlopen(name, mode)

diff -Nru Python-3.14.2.orig/Lib/ctypes/util.py Python-3.14.2/Lib/ctypes/util.py
--- Python-3.14.2.orig/Lib/ctypes/util.py 2025-12-05 17:49:16.000000000 +0100
+++ Python-3.14.2/Lib/ctypes/util.py 2026-01-21 09:47:05.102745349 +0100
@@ -205,12 +205,19 @@
except FileNotFoundError:
return False

- def _findLib_gcc(name):
+ def _findLib_gcc(name, name_is_fullname=False):
# Run GCC's linker with the -t (aka --trace) option and examine the
# library name it prints out. The GCC command will fail because we
# haven't supplied a proper program with main(), but that does not
# matter.
- expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
+ # If the name is a full library name (e.g. libfoo.so), this function calls gcc with -l:<name> instead of
+ # -l<name> and uses a slightly more strict regular expression to avoid matching the error
+ # '/path/to/ld: cannot find -l:libfoo.so: No such file or directory'
+ # since we only want a regex match if the library exists
+ if name_is_fullname:
+ expr = os.fsencode(r'^[^\(\)\s]*%s[^\(\)\s]*' % re.escape(name))
+ else:
+ expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))

c_compiler = shutil.which('gcc')
if not c_compiler:
@@ -221,7 +228,10 @@

temp = tempfile.NamedTemporaryFile()
try:
- args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
+ if name_is_fullname:
+ args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l:' + name]
+ else:
+ args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]

env = dict(os.environ)
env['LC_ALL'] = 'C'
@@ -242,7 +252,12 @@
# Raised if the file was already removed, which is the normal
# behaviour of GCC if linking fails
pass
- res = re.findall(expr, trace)
+ # If name_is_fullname, the regex in expr starts wity ^.
+ # Use re.MULTILINE, to make surethat ^ matches the start of EACH line in trace
+ if name_is_fullname:
+ res = re.findall(expr, trace, re.MULTILINE)
+ else:
+ res = re.findall(expr, trace)
if not res:
return None

@@ -252,8 +267,11 @@
# shared objects. See bpo-41976 for more details
if not _is_elf(file):
continue
- return os.fsdecode(file)
-
+ file = os.fsdecode(file)
+ # Avoid returning CUDA stubs libraries, as those are not intended for runtime use
+ if re.search(r'cuda.*stubs', file, re.IGNORECASE):
+ continue
+ return file

if sys.platform == "sunos5":
# use /usr/ccs/bin/dump on solaris
@@ -389,7 +407,8 @@
abi_type = mach_map.get(machine, 'libc6')

# XXX assuming GLIBC's ldconfig (with option -p)
- regex = r'\s+(lib%s\.[^\s]+)\s+\(%s'
+ # Regular expresion that captures complete line of ldconfig -p output that matches with library name.
+ regex = r'\s+(lib%s\.[^\s]+)\s+\(%s\)\s+=>\s+(\S+)'
regex = os.fsencode(regex % (re.escape(name), abi_type))
try:
with subprocess.Popen(['/sbin/ldconfig', '-p'],
@@ -399,7 +418,8 @@
env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
res = re.search(regex, p.stdout.read())
if res:
- return os.fsdecode(res.group(1))
+ # return the regex second group, that is the full path to the library
+ return os.fsdecode(res.group(2))
except OSError:
pass

@@ -407,10 +427,17 @@
# See issue #9998 for why this is needed
expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
cmd = ['ld', '-t']
- libpath = os.environ.get('LD_LIBRARY_PATH')
+ # use LIBRARY_PATH instead of LD_LIBRARY_PATH to use EB provided shared libraries
+ libpath = os.environ.get('LIBRARY_PATH')
if libpath:
for d in libpath.split(':'):
- cmd.extend(['-L', d])
+ # Avoid picking up CUDA stubs libraries, as those are not intended for runtime use
+ parts = d.split('/')
+ # We want to add 'd' in all cases EXCEPT if the path contains both CUDA (as partial
+ # directory name) and stubs (as full directory name). I.e. /my/bar/stubs and /my/cudafoo
+ # will be added to cmd, but /my/cudafoo/bar/stubs won't be
+ if not (any('cuda' in p.lower() for p in parts) and 'stubs' in parts):
+ cmd.extend(['-L', d])
cmd.extend(['-o', os.devnull, '-l%s' % name])
result = None
try:
@@ -431,9 +458,15 @@
return result

def find_library(name):
- # See issue #9998
- return _findSoname_ldconfig(name) or \
- _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
+ # Redefine find_library function, it will return the provided name if
+ # path exist, else it will use the set of functions to find the full library path.
+ # it will return the one that has a match.
+ if os.path.isabs(name) and os.path.exists(name):
+ return name
+ else:
+ return _findSoname_ldconfig(name) or \
+ _findLib_gcc(name) or _findLib_ld(name) or \
+ _findLib_gcc(name, name_is_fullname=True)


# Listing loaded libraries on other systems will try to use