Skip to content

Commit 8c18c7c

Browse files
committed
Fix cirular import in policy submodule
The `external_versioned_symbols` and `versioned_symbols_policy` functions used to live in two separate files, importing code from the `__init__.py` file in the same module. As this file itself imports both of these symbols, this creates a cirtular import. We can easily break this by moving the two functions into the same `__init__.py` file.
1 parent e9e2240 commit 8c18c7c

File tree

6 files changed

+131
-155
lines changed

6 files changed

+131
-155
lines changed

Diff for: src/auditwheel/policy/__init__.py

+111-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
import json
44
import logging
55
import platform as _platform_module
6+
import re
67
import sys
78
from collections import defaultdict
89
from os.path import abspath, dirname, join
910
from pathlib import Path
11+
from typing import Any, Generator
12+
13+
from auditwheel.elfutils import filter_undefined_symbols, is_subdir
1014

1115
from ..libc import Libc, get_libc
1216
from ..musllinux import find_musl_libc, get_musl_version
13-
from .external_references import lddtree_external_references
14-
from .versioned_symbols import versioned_symbols_policy
1517

1618
_HERE = Path(__file__).parent
19+
LIBPYTHON_RE = re.compile(r"^libpython\d+\.\d+m?.so(.\d)*$")
1720

1821
logger = logging.getLogger(__name__)
1922

@@ -97,6 +100,112 @@ def get_priority_by_name(self, name: str) -> int | None:
97100
policy = self.get_policy_by_name(name)
98101
return None if policy is None else policy["priority"]
99102

103+
def versioned_symbols_policy(self, versioned_symbols: dict[str, set[str]]) -> int:
104+
def policy_is_satisfied(
105+
policy_name: str, policy_sym_vers: dict[str, set[str]]
106+
) -> bool:
107+
policy_satisfied = True
108+
for name in set(required_vers) & set(policy_sym_vers):
109+
if not required_vers[name].issubset(policy_sym_vers[name]):
110+
for symbol in required_vers[name] - policy_sym_vers[name]:
111+
logger.debug(
112+
"Package requires %s, incompatible with "
113+
"policy %s which requires %s",
114+
symbol,
115+
policy_name,
116+
policy_sym_vers[name],
117+
)
118+
policy_satisfied = False
119+
return policy_satisfied
120+
121+
required_vers: dict[str, set[str]] = {}
122+
for symbols in versioned_symbols.values():
123+
for symbol in symbols:
124+
sym_name, _, _ = symbol.partition("_")
125+
required_vers.setdefault(sym_name, set()).add(symbol)
126+
matching_policies: list[int] = []
127+
for p in self.policies:
128+
policy_sym_vers = {
129+
sym_name: {sym_name + "_" + version for version in versions}
130+
for sym_name, versions in p["symbol_versions"].items()
131+
}
132+
if policy_is_satisfied(p["name"], policy_sym_vers):
133+
matching_policies.append(p["priority"])
134+
135+
if len(matching_policies) == 0:
136+
# the base policy (generic linux) should always match
137+
raise RuntimeError("Internal error")
138+
139+
return max(matching_policies)
140+
141+
def lddtree_external_references(self, lddtree: dict, wheel_path: str) -> dict:
142+
# XXX: Document the lddtree structure, or put it in something
143+
# more stable than a big nested dict
144+
def filter_libs(
145+
libs: set[str], whitelist: set[str]
146+
) -> Generator[str, None, None]:
147+
for lib in libs:
148+
if "ld-linux" in lib or lib in ["ld64.so.2", "ld64.so.1"]:
149+
# always exclude ELF dynamic linker/loader
150+
# 'ld64.so.2' on s390x
151+
# 'ld64.so.1' on ppc64le
152+
# 'ld-linux*' on other platforms
153+
continue
154+
if LIBPYTHON_RE.match(lib):
155+
# always exclude libpythonXY
156+
continue
157+
if lib in whitelist:
158+
# exclude any libs in the whitelist
159+
continue
160+
yield lib
161+
162+
def get_req_external(libs: set[str], whitelist: set[str]) -> set[str]:
163+
# get all the required external libraries
164+
libs = libs.copy()
165+
reqs = set()
166+
while libs:
167+
lib = libs.pop()
168+
reqs.add(lib)
169+
for dep in filter_libs(lddtree["libs"][lib]["needed"], whitelist):
170+
if dep not in reqs:
171+
libs.add(dep)
172+
return reqs
173+
174+
ret: dict[str, dict[str, Any]] = {}
175+
for p in self.policies:
176+
needed_external_libs: set[str] = set()
177+
blacklist = {}
178+
179+
if not (p["name"] == "linux" and p["priority"] == 0):
180+
# special-case the generic linux platform here, because it
181+
# doesn't have a whitelist. or, you could say its
182+
# whitelist is the complete set of all libraries. so nothing
183+
# is considered "external" that needs to be copied in.
184+
whitelist = set(p["lib_whitelist"])
185+
blacklist_libs = set(p["blacklist"].keys()) & set(lddtree["needed"])
186+
blacklist = {k: p["blacklist"][k] for k in blacklist_libs}
187+
blacklist = filter_undefined_symbols(lddtree["realpath"], blacklist)
188+
needed_external_libs = get_req_external(
189+
set(filter_libs(lddtree["needed"], whitelist)), whitelist
190+
)
191+
192+
pol_ext_deps = {}
193+
for lib in needed_external_libs:
194+
if is_subdir(lddtree["libs"][lib]["realpath"], wheel_path):
195+
# we didn't filter libs that resolved via RPATH out
196+
# earlier because we wanted to make sure to pick up
197+
# our elf's indirect dependencies. But now we want to
198+
# filter these ones out, since they're not "external".
199+
logger.debug("RPATH FTW: %s", lib)
200+
continue
201+
pol_ext_deps[lib] = lddtree["libs"][lib]["realpath"]
202+
ret[p["name"]] = {
203+
"libs": pol_ext_deps,
204+
"priority": p["priority"],
205+
"blacklist": blacklist,
206+
}
207+
return ret
208+
100209

101210
def get_arch_name() -> str:
102211
machine = _platform_module.machine()
@@ -204,7 +313,5 @@ def _load_policy_schema():
204313

205314

206315
__all__ = [
207-
"lddtree_external_references",
208-
"versioned_symbols_policy",
209316
"WheelPolicies",
210317
]

Diff for: src/auditwheel/policy/external_references.py

-78
This file was deleted.

Diff for: src/auditwheel/policy/versioned_symbols.py

-46
This file was deleted.

Diff for: src/auditwheel/wheel_abi.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@
1919
)
2020
from .genericpkgctx import InGenericPkgCtx
2121
from .lddtree import lddtree
22-
from .policy import (
23-
WheelPolicies,
24-
lddtree_external_references,
25-
versioned_symbols_policy,
26-
)
22+
from .policy import WheelPolicies
2723

2824
log = logging.getLogger(__name__)
2925
WheelAbIInfo = namedtuple(
@@ -101,8 +97,8 @@ def get_wheel_elfdata(wheel_policy: WheelPolicies, wheel_fn: str):
10197
uses_ucs2_symbols |= any(
10298
True for _ in elf_find_ucs2_symbols(elf)
10399
)
104-
full_external_refs[fn] = lddtree_external_references(
105-
wheel_policy.policies, elftree, ctx.path
100+
full_external_refs[fn] = wheel_policy.lddtree_external_references(
101+
elftree, ctx.path
106102
)
107103
else:
108104
# If the ELF is not a Python extension, it might be
@@ -144,8 +140,8 @@ def get_wheel_elfdata(wheel_policy: WheelPolicies, wheel_fn: str):
144140
# Even if a non-pyextension ELF file is not needed, we
145141
# should include it as an external reference, because
146142
# it might require additional external libraries.
147-
full_external_refs[fn] = lddtree_external_references(
148-
wheel_policy.policies, nonpy_elftree[fn], ctx.path
143+
full_external_refs[fn] = wheel_policy.lddtree_external_references(
144+
nonpy_elftree[fn], ctx.path
149145
)
150146

151147
log.debug("full_elftree:\n%s", json.dumps(full_elftree, indent=4))
@@ -201,7 +197,9 @@ def get_versioned_symbols(libs):
201197
return result
202198

203199

204-
def get_symbol_policies(wheel_policy, versioned_symbols, external_versioned_symbols, external_refs):
200+
def get_symbol_policies(
201+
wheel_policy, versioned_symbols, external_versioned_symbols, external_refs
202+
):
205203
"""Get symbol policies
206204
Since white-list is different per policy, this function inspects
207205
versioned_symbol per policy when including external refs
@@ -223,7 +221,9 @@ def get_symbol_policies(wheel_policy, versioned_symbols, external_versioned_symb
223221
ext_symbols = external_versioned_symbols[soname]
224222
for k in iter(ext_symbols):
225223
policy_symbols[k].update(ext_symbols[k])
226-
result.append((versioned_symbols_policy(wheel_policy, policy_symbols), policy_symbols))
224+
result.append(
225+
(wheel_policy.versioned_symbols_policy(policy_symbols), policy_symbols)
226+
)
227227
return result
228228

229229

@@ -252,7 +252,7 @@ def analyze_wheel_abi(wheel_policy: WheelPolicies, wheel_fn: str) -> WheelAbIInf
252252
symbol_policies = get_symbol_policies(
253253
wheel_policy, versioned_symbols, external_versioned_symbols, external_refs
254254
)
255-
symbol_policy = versioned_symbols_policy(wheel_policy, versioned_symbols)
255+
symbol_policy = wheel_policy.versioned_symbols_policy(versioned_symbols)
256256

257257
# let's keep the highest priority policy and
258258
# corresponding versioned_symbols

Diff for: tests/integration/test_policy_files.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
from jsonschema import validate
44

5-
from auditwheel.policy import (
6-
WheelPolicies,
7-
_load_policy_schema,
8-
versioned_symbols_policy,
9-
)
5+
from auditwheel.policy import WheelPolicies, _load_policy_schema
106

117

128
def test_policy():
@@ -18,15 +14,13 @@ def test_policy():
1814
def test_policy_checks_glibc():
1915
wheel_policy = WheelPolicies()
2016

21-
policy = versioned_symbols_policy(wheel_policy, {"some_library.so": {"GLIBC_2.17"}})
17+
policy = wheel_policy.versioned_symbols_policy({"some_library.so": {"GLIBC_2.17"}})
2218
assert policy > wheel_policy.priority_lowest
23-
policy = versioned_symbols_policy(wheel_policy, {"some_library.so": {"GLIBC_999"}})
19+
policy = wheel_policy.versioned_symbols_policy({"some_library.so": {"GLIBC_999"}})
2420
assert policy == wheel_policy.priority_lowest
25-
policy = versioned_symbols_policy(
26-
wheel_policy, {"some_library.so": {"OPENSSL_1_1_0"}}
21+
policy = wheel_policy.versioned_symbols_policy(
22+
{"some_library.so": {"OPENSSL_1_1_0"}}
2723
)
2824
assert policy == wheel_policy.priority_highest
29-
policy = versioned_symbols_policy(
30-
wheel_policy, {"some_library.so": {"IAMALIBRARY"}}
31-
)
25+
policy = wheel_policy.versioned_symbols_policy({"some_library.so": {"IAMALIBRARY"}})
3226
assert policy == wheel_policy.priority_highest

Diff for: tests/unit/test_policy.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
_validate_pep600_compliance,
1010
get_arch_name,
1111
get_replace_platforms,
12-
lddtree_external_references,
1312
)
1413

1514

@@ -227,8 +226,8 @@ def test_filter_libs(self):
227226
"libs": {lib: {"needed": [], "realpath": "/path/to/lib"} for lib in libs},
228227
}
229228
wheel_policy = WheelPolicies()
230-
full_external_refs = lddtree_external_references(
231-
wheel_policy.policies, lddtree, "/path/to/wheel"
229+
full_external_refs = wheel_policy.lddtree_external_references(
230+
lddtree, "/path/to/wheel"
232231
)
233232

234233
# Assert that each policy only has the unfiltered libs.

0 commit comments

Comments
 (0)