diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py index 99504911a3dbe0..378f12167c6842 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -173,6 +173,25 @@ def find_library(name): fname = f"{directory}/lib{name}.so" return fname if os.path.isfile(fname) else None +elif sys.platform == "emscripten": + def _is_wasm(filename): + # Return True if the given file is an WASM module + wasm_header = b"\x00asm" + with open(filename, 'br') as thefile: + return thefile.read(4) == wasm_header + + def find_library(name): + candidates = [f"lib{name}.so", f"lib{name}.wasm"] + paths = os.environ.get("LD_LIBRARY_PATH", "") + for libdir in paths.split(":"): + for name in candidates: + libfile = os.path.join(libdir, name) + + if os.path.isfile(libfile) and _is_wasm(libfile): + return libfile + + return None + elif os.name == "posix": # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump import re, tempfile diff --git a/Lib/test/test_ctypes/test_find.py b/Lib/test/test_ctypes/test_find.py index 3bd41a0e435d91..8bc84c3d2ef9f8 100644 --- a/Lib/test/test_ctypes/test_find.py +++ b/Lib/test/test_ctypes/test_find.py @@ -153,5 +153,73 @@ def test_find(self): self.assertIsNone(find_library(name)) +@unittest.skipUnless(test.support.is_emscripten, + 'Test only valid for Emscripten') +class FindLibraryEmscripten(unittest.TestCase): + @classmethod + def setUpClass(cls): + import tempfile + + # A very simple wasm module + # In WAT format: (module) + cls.wasm_module = b'\x00asm\x01\x00\x00\x00\x00\x08\x04name\x02\x01\x00' + + cls.non_wasm_content = b'This is not a WASM file' + + cls.temp_dir = tempfile.mkdtemp() + cls.libdummy_so_path = os.path.join(cls.temp_dir, 'libdummy.so') + with open(cls.libdummy_so_path, 'wb') as f: + f.write(cls.wasm_module) + + cls.libother_wasm_path = os.path.join(cls.temp_dir, 'libother.wasm') + with open(cls.libother_wasm_path, 'wb') as f: + f.write(cls.wasm_module) + + cls.libnowasm_so_path = os.path.join(cls.temp_dir, 'libnowasm.so') + with open(cls.libnowasm_so_path, 'wb') as f: + f.write(cls.non_wasm_content) + + @classmethod + def tearDownClass(cls): + import shutil + shutil.rmtree(cls.temp_dir) + + def test_find_wasm_file_with_so_extension(self): + with os_helper.EnvironmentVarGuard() as env: + env.set('LD_LIBRARY_PATH', self.temp_dir) + result = find_library('dummy') + self.assertEqual(result, self.libdummy_so_path) + def test_find_wasm_file_with_wasm_extension(self): + with os_helper.EnvironmentVarGuard() as env: + env.set('LD_LIBRARY_PATH', self.temp_dir) + result = find_library('other') + self.assertEqual(result, self.libother_wasm_path) + + def test_ignore_non_wasm_file(self): + with os_helper.EnvironmentVarGuard() as env: + env.set('LD_LIBRARY_PATH', self.temp_dir) + result = find_library('nowasm') + self.assertIsNone(result) + + def test_find_nothing_without_ld_library_path(self): + with os_helper.EnvironmentVarGuard() as env: + if 'LD_LIBRARY_PATH' in env: + del env['LD_LIBRARY_PATH'] + result = find_library('dummy') + self.assertIsNone(result) + result = find_library('other') + self.assertIsNone(result) + + def test_find_nothing_with_wrong_ld_library_path(self): + import tempfile + with tempfile.TemporaryDirectory() as empty_dir: + with os_helper.EnvironmentVarGuard() as env: + env.set('LD_LIBRARY_PATH', empty_dir) + result = find_library('dummy') + self.assertIsNone(result) + result = find_library('other') + self.assertIsNone(result) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-09-05-05-53-43.gh-issue-99948.KMSlG6.rst b/Misc/NEWS.d/next/Library/2025-09-05-05-53-43.gh-issue-99948.KMSlG6.rst new file mode 100644 index 00000000000000..46e6dfad1cd5d8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-05-05-53-43.gh-issue-99948.KMSlG6.rst @@ -0,0 +1 @@ +:func:`ctypes.util.find_library` now works in Emscripten build.