From 0934b8fc91e3f08369d98fb9dcc24a1a5521a132 Mon Sep 17 00:00:00 2001 From: Dimitri John Ledkov Date: Wed, 4 Jun 2025 22:30:07 +0100 Subject: [PATCH 01/10] test_analyze_wheel_abi: refactor test with explicit env Be explicit in the test-case matrix when to set env variable. This enables to check if environment variable is actually correctly reset. This commit passes unit tests. --- tests/integration/test_bundled_wheels.py | 25 ++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index 046b8fc0..f7bc6296 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -1,13 +1,11 @@ from __future__ import annotations import importlib -import os import platform import sys import zipfile from argparse import Namespace from datetime import datetime, timezone -from os.path import isabs from pathlib import Path from unittest.mock import Mock @@ -23,69 +21,76 @@ @pytest.mark.parametrize( - ("file", "external_libs", "exclude"), + ("file", "external_libs", "exclude", "env"), [ ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libffi.so.5", "libpython2.7.so.1.0"}, frozenset(), + None, ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", set(), frozenset(["libffi.so.5", "libpython2.7.so.1.0"]), + None, ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libffi.so.5", "libpython2.7.so.1.0"}, frozenset(["libffi.so.noexist", "libnoexist.so.*"]), + None, ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libpython2.7.so.1.0"}, frozenset(["libffi.so.[4,5]"]), + None, ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libffi.so.5", "libpython2.7.so.1.0"}, frozenset(["libffi.so.[6,7]"]), + None, ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libpython2.7.so.1.0"}, frozenset([f"{HERE}/*"]), + "LD_LIBRARY_PATH", ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libpython2.7.so.1.0"}, frozenset(["libffi.so.*"]), + None, ), - ("cffi-1.5.0-cp27-none-linux_x86_64.whl", set(), frozenset(["*"])), + ("cffi-1.5.0-cp27-none-linux_x86_64.whl", set(), frozenset(["*"]), None), ( "python_snappy-0.5.2-pp260-pypy_41-linux_x86_64.whl", {"libsnappy.so.1"}, frozenset(), + None, ), ], ) -def test_analyze_wheel_abi(file, external_libs, exclude): +def test_analyze_wheel_abi(file, external_libs, exclude, env): # If exclude libs contain path, LD_LIBRARY_PATH need to be modified to find the libs # `lddtree.load_ld_paths` needs to be reloaded for it's `lru_cache`-ed. - modify_ld_library_path = any(isabs(e) for e in exclude) with pytest.MonkeyPatch.context() as cp: - if modify_ld_library_path: - cp.setenv("LD_LIBRARY_PATH", f"{HERE}") + if env: + cp.setenv(env, f"{HERE}") importlib.reload(lddtree) winfo = analyze_wheel_abi( Libc.GLIBC, Architecture.x86_64, HERE / file, exclude, False, True ) assert set(winfo.external_refs["manylinux_2_5_x86_64"].libs) == external_libs, ( - f"{HERE}, {exclude}, {os.environ}" + f"{HERE}, {exclude}, {env}" ) - if modify_ld_library_path: + if env: importlib.reload(lddtree) From 9f06c60d451a1b92bda33eaca25f882634b9d5e7 Mon Sep 17 00:00:00 2001 From: Dimitri John Ledkov Date: Wed, 4 Jun 2025 22:39:22 +0100 Subject: [PATCH 02/10] test_analyze_wheel_abi: add failing test case Add a failing test case confiring that environment is not being correctly reset. --- tests/integration/test_bundled_wheels.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index f7bc6296..0c1d2227 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -59,6 +59,12 @@ frozenset([f"{HERE}/*"]), "LD_LIBRARY_PATH", ), + ( + "cffi-1.5.0-cp27-none-linux_x86_64.whl", + {"libffi.so.5", "libpython2.7.so.1.0"}, + frozenset([f"{HERE}/*"]), + None, + ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libpython2.7.so.1.0"}, From e2063e3893b01268c5821b47a663334e0ecc8c4e Mon Sep 17 00:00:00 2001 From: Dimitri John Ledkov Date: Wed, 4 Jun 2025 22:46:52 +0100 Subject: [PATCH 03/10] test_analyze_wheel_abi: reload and reimport wheel_abi It seems that in addition to reloading lddtree, reload of auditwheel.wheel_abi is needed to full flush and reset analyze_wheel_abi() lru_caches. --- tests/integration/test_bundled_wheels.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index 0c1d2227..7fa01700 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -11,6 +11,7 @@ import pytest +import auditwheel.wheel_abi from auditwheel import lddtree, main_repair from auditwheel.architecture import Architecture from auditwheel.libc import Libc @@ -87,7 +88,8 @@ def test_analyze_wheel_abi(file, external_libs, exclude, env): with pytest.MonkeyPatch.context() as cp: if env: cp.setenv(env, f"{HERE}") - importlib.reload(lddtree) + importlib.reload(lddtree) + importlib.reload(auditwheel.wheel_abi) winfo = analyze_wheel_abi( Libc.GLIBC, Architecture.x86_64, HERE / file, exclude, False, True @@ -96,8 +98,8 @@ def test_analyze_wheel_abi(file, external_libs, exclude, env): f"{HERE}, {exclude}, {env}" ) - if env: - importlib.reload(lddtree) + importlib.reload(lddtree) + importlib.reload(auditwheel.wheel_abi) def test_analyze_wheel_abi_pyfpe(): From 496e92ef4e00cbb34e1b18394f0d993eb233b764 Mon Sep 17 00:00:00 2001 From: Dimitri John Ledkov Date: Wed, 16 Apr 2025 12:36:33 +0100 Subject: [PATCH 04/10] lddtree: support alternative LD_LIBRARY_PATH called AUDITWHEEL_LD_LIBRARY_PATH Sometimes wheels are different only just by shared libraries that are vendored in. Sometimes the wheel being repaired and the libraries that need to be vendored in are accessible on the host that was not used for building the wheel. In such cases, it could be the case that shared libraries used by python that is executing the auditwheel script, are incompatible (too old or too new) than those that need to be vendored. In such cases, LD_LIBRARY_PATH is not convenient to use, as the python interpreter for the auditwheel script is crashing. Add an AUDITWHEEL_LD_LIBRARY_PATH whichis used by lddtree, but otherwise does not affect the python interpreter executing auditwheel script. This helps vendoring in older libraries from an alternative path, into a wheel built against an older python, whilst otherwise running auditwheel repair on a modern system with a much newer python. This is particularly useful when stripping a wheel of vendored libraries, and repair it again to update the shared library dependencies coming from an unpacked chroot. Separately it also helps creating alternative wheels with shared libraries rebuilt with higher march settings. Given that python upstream doesn't support loading optimised libraries as has been implemented and supported in Intel ClearLinux Python which allows one to have alternative march setting shared library deps. --- src/auditwheel/lddtree.py | 13 +++++++++++-- tests/integration/test_bundled_wheels.py | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index 44785791..661446a4 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -321,8 +321,17 @@ def load_ld_paths( """ ldpaths: dict[str, list[str]] = {"conf": [], "env": [], "interp": []} - # Load up $LD_LIBRARY_PATH. - env_ldpath = os.environ.get("LD_LIBRARY_PATH") + # Load up $AUDITWHEEL_LD_LIBRARY_PATH and $LD_LIBRARY_PATH + env_ldpath = ":".join( + filter( + None, + ( + os.environ.get("AUDITWHEEL_LD_LIBRARY_PATH"), + os.environ.get("LD_LIBRARY_PATH"), + ), + ) + ) + if env_ldpath is not None: if root != "/": log.warning("ignoring LD_LIBRARY_PATH due to ROOT usage") diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index 7fa01700..97f12487 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -60,6 +60,12 @@ frozenset([f"{HERE}/*"]), "LD_LIBRARY_PATH", ), + ( + "cffi-1.5.0-cp27-none-linux_x86_64.whl", + {"libpython2.7.so.1.0"}, + frozenset([f"{HERE}/*"]), + "AUDITWHEEL_LD_LIBRARY_PATH", + ), ( "cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libffi.so.5", "libpython2.7.so.1.0"}, From f17efc9eb4a6a3c901ea85f0c97f2e52767fa74c Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 2 May 2026 08:08:07 +0200 Subject: [PATCH 05/10] re-work ignoring LD_LIBRARY_PATH --- src/auditwheel/lddtree.py | 24 ++++++++++-------------- tests/integration/test_bundled_wheels.py | 2 -- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index 22c3ac59..45407a62 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -334,24 +334,20 @@ def load_ld_paths( """ ldpaths: dict[str, list[str]] = {"conf": [], "env": [], "interp": []} + ld_library_path = os.environ.get("LD_LIBRARY_PATH") + if root != "/" and ld_library_path is not None: + log.warning("ignoring LD_LIBRARY_PATH due to ROOT usage") + ld_library_path = None + # Load up $AUDITWHEEL_LD_LIBRARY_PATH and $LD_LIBRARY_PATH env_ldpath = ":".join( - filter( - None, - ( - os.environ.get("AUDITWHEEL_LD_LIBRARY_PATH"), - os.environ.get("LD_LIBRARY_PATH"), - ), - ) + filter(None, (os.environ.get("AUDITWHEEL_LD_LIBRARY_PATH"), ld_library_path)), ) - if env_ldpath is not None: - if root != "/": - log.warning("ignoring LD_LIBRARY_PATH due to ROOT usage") - else: - # TODO: If this contains $ORIGIN, we probably have to parse this - # on a per-ELF basis so it can get turned into the right thing. - ldpaths["env"] = parse_ld_paths(env_ldpath, path="") + if env_ldpath: + # TODO: If this contains $ORIGIN, we probably have to parse this + # on a per-ELF basis so it can get turned into the right thing. + ldpaths["env"] = parse_ld_paths(env_ldpath, path="") if libc == Libc.MUSL: # from https://git.musl-libc.org/cgit/musl/tree/ldso diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index c01a7804..5e7d6d36 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -1,9 +1,7 @@ from __future__ import annotations import importlib -import platform import json -import os import sys import zipfile from argparse import Namespace From 9741f19cd06b1ea33c0261e214b7cbb57d350e6e Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 2 May 2026 08:54:59 +0200 Subject: [PATCH 06/10] address review comments --- src/auditwheel/lddtree.py | 2 +- tests/integration/test_bundled_wheels.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index 45407a62..a7127f9b 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -318,7 +318,7 @@ def load_ld_paths( ) -> dict[str, list[str]]: """Load linker paths from common locations - This parses the ld.so.conf and LD_LIBRARY_PATH env var. + This parses the ld.so.conf and AUDITWHEEL_LD_LIBRARY_PATH / LD_LIBRARY_PATH env vars. Parameters ---------- diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index 5e7d6d36..aaee5c53 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -89,10 +89,13 @@ ], ) def test_analyze_wheel_abi(file, external_libs, exclude, env): - # If exclude libs contain path, LD_LIBRARY_PATH need to be modified to find the libs - # `lddtree.load_ld_paths` needs to be reloaded for it's `lru_cache`-ed. + # If exclude libs contain path, the parametrized environment variable "env" needs to be + # modified to find the libs `lddtree.load_ld_paths` needs to be reloaded for + # it's `lru_cache`-ed. with pytest.MonkeyPatch.context() as cp: + cp.delenv("AUDITWHEEL_LD_LIBRARY_PATH", raising=False) + cp.delenv("LD_LIBRARY_PATH", raising=False) if env: cp.setenv(env, f"{HERE}") importlib.reload(lddtree) From cbc1790337bf5ad68ddeb6cc1f550d56141d9a5e Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 2 May 2026 09:18:56 +0200 Subject: [PATCH 07/10] Update test_bundled_wheels.py --- tests/integration/test_bundled_wheels.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration/test_bundled_wheels.py b/tests/integration/test_bundled_wheels.py index aaee5c53..0fb48214 100644 --- a/tests/integration/test_bundled_wheels.py +++ b/tests/integration/test_bundled_wheels.py @@ -1,7 +1,7 @@ from __future__ import annotations -import importlib import json +import os import sys import zipfile from argparse import Namespace @@ -90,16 +90,16 @@ ) def test_analyze_wheel_abi(file, external_libs, exclude, env): # If exclude libs contain path, the parametrized environment variable "env" needs to be - # modified to find the libs `lddtree.load_ld_paths` needs to be reloaded for - # it's `lru_cache`-ed. + # modified to find the libs + + lddtree.load_ld_paths.cache_clear() + auditwheel.wheel_abi.get_wheel_elfdata.cache_clear() with pytest.MonkeyPatch.context() as cp: cp.delenv("AUDITWHEEL_LD_LIBRARY_PATH", raising=False) cp.delenv("LD_LIBRARY_PATH", raising=False) if env: cp.setenv(env, f"{HERE}") - importlib.reload(lddtree) - importlib.reload(auditwheel.wheel_abi) winfo = analyze_wheel_abi( Libc.GLIBC, @@ -110,11 +110,11 @@ def test_analyze_wheel_abi(file, external_libs, exclude, env): allow_graft=True, ) assert set(winfo.external_refs["manylinux_2_5_x86_64"].libs) == external_libs, ( - f"{HERE}, {exclude}, {env}" + f"{HERE}, {exclude}, {os.environ}" ) - importlib.reload(lddtree) - importlib.reload(auditwheel.wheel_abi) + lddtree.load_ld_paths.cache_clear() + auditwheel.wheel_abi.get_wheel_elfdata.cache_clear() def test_analyze_wheel_abi_pyfpe(): From 6859734e03ff0b66f6ec43c6eb55e1190c8fc398 Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 2 May 2026 09:31:20 +0200 Subject: [PATCH 08/10] Update lddtree.py --- src/auditwheel/lddtree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index a7127f9b..1643c46e 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -347,7 +347,7 @@ def load_ld_paths( if env_ldpath: # TODO: If this contains $ORIGIN, we probably have to parse this # on a per-ELF basis so it can get turned into the right thing. - ldpaths["env"] = parse_ld_paths(env_ldpath, path="") + ldpaths["env"] = parse_ld_paths(env_ldpath, path="", root=root) if libc == Libc.MUSL: # from https://git.musl-libc.org/cgit/musl/tree/ldso From bd8a91faac4702645b62da9c0386899eccdd7fee Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 2 May 2026 09:33:55 +0200 Subject: [PATCH 09/10] Revert "Update lddtree.py" This reverts commit 6859734e03ff0b66f6ec43c6eb55e1190c8fc398. --- src/auditwheel/lddtree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index 1643c46e..a7127f9b 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -347,7 +347,7 @@ def load_ld_paths( if env_ldpath: # TODO: If this contains $ORIGIN, we probably have to parse this # on a per-ELF basis so it can get turned into the right thing. - ldpaths["env"] = parse_ld_paths(env_ldpath, path="", root=root) + ldpaths["env"] = parse_ld_paths(env_ldpath, path="") if libc == Libc.MUSL: # from https://git.musl-libc.org/cgit/musl/tree/ldso From 3eb900cd72fb89caa88d1e832b1c82e1dbee42ea Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 2 May 2026 09:35:39 +0200 Subject: [PATCH 10/10] Update lddtree.py --- src/auditwheel/lddtree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index a7127f9b..eadf7d9f 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -347,6 +347,8 @@ def load_ld_paths( if env_ldpath: # TODO: If this contains $ORIGIN, we probably have to parse this # on a per-ELF basis so it can get turned into the right thing. + # don't pass root: in case root != "/", only AUDITWHEEL_LD_LIBRARY_PATH is checked + # it shall already contain fully resolved paths ldpaths["env"] = parse_ld_paths(env_ldpath, path="") if libc == Libc.MUSL: