Skip to content

Commit

Permalink
Use assumed ABI3 version for shared objects without ".abi3."
Browse files Browse the repository at this point in the history
Allow auditing arbitrary shared objects, not necessarily containing
".abi3." in their name, if --assume-minimum-abi3 option is specified:
suppose that all shared objects use this ABI3 version then.

This makes the tool more convenient to use for non-wheel-packaged
extensions.

Also specify the default, 3.2, ABI version in a single place only and
use "None" instead of the default value elsewhere.
  • Loading branch information
vadz committed Nov 11, 2024
1 parent efab2b9 commit 89b893f
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 12 deletions.
2 changes: 1 addition & 1 deletion abi3audit/_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderR
yield f"[green]:thumbs_up: {self.so}"


def audit(so: SharedObject, assume_minimum_abi3: PyVersion = PyVersion(3, 2)) -> AuditResult:
def audit(so: SharedObject, assume_minimum_abi3: PyVersion = None) -> AuditResult:
# We might fail to retrieve a minimum abi3 baseline if our context
# (the shared object or its containing wheel) isn't actually tagged
# as abi3 compatible.
Expand Down
7 changes: 4 additions & 3 deletions abi3audit/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,19 @@ def main() -> None:
parser.add_argument(
"--assume-minimum-abi3",
action=_PyVersionAction,
default=PyVersion(3, 2),
help="assumed abi3 version (3.x, with x>=2) if it cannot be detected",
)
args = parser.parse_args()

if args.debug:
logging.root.setLevel("DEBUG")

assume_minimum_abi3 = args.assume_minimum_abi3

specs = []
for spec in args.specs:
try:
specs.extend(make_specs(spec))
specs.extend(make_specs(spec, assume_minimum_abi3))
except InvalidSpec as e:
console.log(f"[red]:thumbs_down: processing error: {e}")
sys.exit(1)
Expand All @@ -253,7 +254,7 @@ def main() -> None:
status.update(f"{spec}: auditing {so}")

try:
result = audit(so, assume_minimum_abi3=args.assume_minimum_abi3)
result = audit(so, assume_minimum_abi3)
all_passed = all_passed and bool(result)
except AuditError as exc:
# TODO(ww): Refine exceptions and error states here.
Expand Down
14 changes: 9 additions & 5 deletions abi3audit/_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Union
from zipfile import ZipFile

from abi3info.models import PyVersion
from packaging import utils
from packaging.tags import Tag

Expand Down Expand Up @@ -89,7 +90,7 @@ def _extractor(self) -> Extractor:
Spec = Union[WheelSpec, SharedObjectSpec, PyPISpec]


def make_specs(val: str) -> list[Spec]:
def make_specs(val: str, assume_minimum_abi3: PyVersion = None) -> list[Spec]:
"""
Constructs a (minimally) valid list of `Spec` instances from the given input.
"""
Expand All @@ -101,10 +102,13 @@ def make_specs(val: str) -> list[Spec]:
return [WheelSpec(val)]
elif any(val.endswith(suf) for suf in _SHARED_OBJECT_SUFFIXES):
# NOTE: We allow untagged shared objects when they're indirectly
# audited (e.g. via an abi3 wheel), but not directly (since
# without a tag here we don't know if it's abi3 at all).
if ".abi3." not in val:
raise InvalidSpec(f"'{val}' must contain '.abi3.' to be recognized as a shared object")
# audited (e.g. via an abi3 wheel), but when auditing them directly we
# only allow them if we have a minimum abi3 version to check against.
if assume_minimum_abi3 is None and ".abi3." not in val:
raise InvalidSpec(
f"'{val}' must contain '.abi3.' to be recognized as a shared "
+ "object or assumed minimum ABI3 version must be specified"
)
return [SharedObjectSpec(val)]
elif re.match(_DISTRIBUTION_NAME_RE, val, re.IGNORECASE):
return [PyPISpec(val)]
Expand Down
10 changes: 7 additions & 3 deletions abi3audit/_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self, extractor: extract.SharedObjectExtractor):
self._extractor = extractor
self.path = self._extractor.path

def abi3_version(self, assume_lowest: PyVersion = PyVersion(3, 2)) -> PyVersion | None:
def abi3_version(self, assume_lowest: PyVersion) -> PyVersion | None:
# If we're dealing with a shared object that was extracted from a wheel,
# we try and suss out the abi3 version from the wheel's own tags.
if self._extractor.parent is not None:
Expand All @@ -50,14 +50,18 @@ def abi3_version(self, assume_lowest: PyVersion = PyVersion(3, 2)) -> PyVersion
# filename. This doesn't tell us anything about the specific abi3 version, so
# we assume the lowest.
if ".abi3" in self._extractor.path.suffixes:
if assume_lowest is None:
assume_lowest = PyVersion(3, 2)

logger.debug(
"no wheel to infer abi3 version from; assuming (%s)",
assume_lowest,
)
return assume_lowest

# With no wheel tags and no filename tag, we have nothing to go on.
return None
# With no wheel tags and no filename tag, fall back on the assumed ABI
# version (which is possibly None).
return assume_lowest

def __str__(self) -> str:
parents = []
Expand Down

0 comments on commit 89b893f

Please sign in to comment.