feat(repair): add --strip-level option for granular strip control#682
feat(repair): add --strip-level option for granular strip control#682taegyunkim wants to merge 5 commits into
Conversation
Add a --strip-level flag to auditwheel repair with four levels: - none (default, no change in behavior) - debug (strip -g: removes debug symbols only, preserves full backtraces) - unneeded (strip --strip-unneeded: removes symbols not needed for relocation) - all (strip -s: removes all symbols, equivalent to old --strip) The existing --strip flag is deprecated in favour of --strip-level=all. Both flags are mutually exclusive. The strip_symbols() function is kept for backward API compatibility and delegates to the new process_symbols(). Closes pypa#491 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move conflict guard and strip normalization to top of repair_wheel(), before the pure-wheel early return, preventing partial wheel writes - Resolve StripLevel.NONE vs Python None confusion: execute() now resolves --strip to strip_level=ALL before the loop and no longer passes the deprecated strip= parameter to repair_wheel() - Fix zip_compression_level default from 0 (Z_NO_COMPRESSION) to zlib.Z_DEFAULT_COMPRESSION - Make _get_strip_args(NONE) raise ValueError instead of returning [] (which would invoke strip with no flags, stripping all symbols) - Fix test mocks for Python 3.14 compatibility (MagicMock comparison), lazy import patch targets, dist-info assertion, and SBOM patching
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Pull request overview
Adds a --strip-level option to auditwheel repair exposing four granularities (none, debug, unneeded, all) mapped to the corresponding strip CLI flags, while keeping the legacy --strip flag as a deprecated alias for --strip-level=all. Conflicting use of the two options is rejected up front in execute(), and repair_wheel() is extended with a normalized strip_level parameter that drives a new process_symbols() helper.
Changes:
- New
StripLevelenum +process_symbols()/_get_strip_args()inrepair.py;strip_symbols()kept as a thin compat shim. --strip-levelargparse option added and--stripdeprecated;execute()validates and resolves both into a singlestrip_levelbefore processing.- New unit tests covering the enum, arg parsing,
execute()integration,repair_wheel()strip dispatch, and a conflict guard.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/auditwheel/repair.py | Adds StripLevel, process_symbols, _get_strip_args; updates repair_wheel() signature and strip dispatch; keeps strip_symbols() as compat shim. |
| src/auditwheel/main_repair.py | Adds --strip-level CLI option, deprecates --strip, validates conflicts, resolves strip_level and forwards it to repair_wheel(). |
| tests/unit/test_strip_levels.py | Unit tests for StripLevel, _get_strip_args, process_symbols, and the strip_symbols shim. |
| tests/unit/test_repair_strip_levels.py | Tests repair_wheel() strip-level dispatch, backwards compat for strip=True, and the conflict guard. |
| tests/unit/test_main_repair_strip_levels.py | Tests CLI parsing, deprecation warning, conflict error path, and forwarding of strip_level from execute() to repair_wheel(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| wheel_files: list[Path] = args.WHEEL_FILE | ||
|
|
||
| # Validate and resolve strip arguments once, before processing any wheel. | ||
| if args.STRIP and args.STRIP_LEVEL != "none": |
| if args.STRIP: | ||
| warnings.warn( | ||
| "The --strip option is deprecated. Use --strip-level=all instead.", | ||
| DeprecationWarning, | ||
| stacklevel=2, | ||
| ) |
| def process_symbols( | ||
| libraries: Iterable[Path], | ||
| strip_level: StripLevel, | ||
| ) -> None: | ||
| """Process symbols in libraries according to the given strip level.""" | ||
| libraries_list = list(libraries) | ||
|
|
||
| if not libraries_list or strip_level == StripLevel.NONE: | ||
| return | ||
|
|
||
| strip_args = _get_strip_args(strip_level) | ||
| for lib in libraries_list: | ||
| logger.info("Stripping symbols from %s (level: %s)", lib, strip_level.value) | ||
| check_call(["strip", *strip_args, str(lib)]) |
mayeut
left a comment
There was a problem hiding this comment.
Thanks for your pull request,
Can you please address my inline comments as well as copilot ones ?
| strip: bool | None = None, | ||
| strip_level: StripLevel | None = None, | ||
| zip_compression_level: int = zlib.Z_DEFAULT_COMPRESSION, |
There was a problem hiding this comment.
auditwheel is a CLI tool as mentioned in the README. Although the modules do not follow the conventional naming for this, there's no public python API, everything is private. Please do not keep the compatibility shims in the python code.
| strip: bool | None = None, | |
| strip_level: StripLevel | None = None, | |
| zip_compression_level: int = zlib.Z_DEFAULT_COMPRESSION, | |
| strip_level: StripLevel, | |
| zip_compression_level: int, |
| # Validate and normalize strip arguments before doing any work. | ||
| if strip is not None and strip_level is not None: | ||
| msg = "Cannot specify both 'strip' and 'strip_level' parameters" | ||
| raise ValueError(msg) | ||
|
|
||
| if strip is True: | ||
| strip_level = StripLevel.ALL | ||
| elif strip_level is None: | ||
| strip_level = StripLevel.NONE | ||
|
|
There was a problem hiding this comment.
for the reason above, this is not needed without compatibility in mind.
| # Validate and normalize strip arguments before doing any work. | |
| if strip is not None and strip_level is not None: | |
| msg = "Cannot specify both 'strip' and 'strip_level' parameters" | |
| raise ValueError(msg) | |
| if strip is True: | |
| strip_level = StripLevel.ALL | |
| elif strip_level is None: | |
| strip_level = StripLevel.NONE |
| extensions = list(external_refs_by_fn.keys()) | ||
| all_libraries = list(itertools.chain(libs_to_process, extensions)) |
There was a problem hiding this comment.
no need to enforce lists, iterables should be enough
| extensions = list(external_refs_by_fn.keys()) | |
| all_libraries = list(itertools.chain(libs_to_process, extensions)) | |
| extensions =external_refs_by_fn.keys() | |
| all_libraries = itertools.chain(libs_to_process, extensions) |
| libs_to_process = [path for (_, path) in soname_map.values()] | ||
| extensions = list(external_refs_by_fn.keys()) | ||
| all_libraries = list(itertools.chain(libs_to_process, extensions)) | ||
| process_symbols(all_libraries, strip_level) |
There was a problem hiding this comment.
| process_symbols(all_libraries, strip_level) | |
| strip_symbols(all_libraries, strip_level) |
| return output_wheel | ||
|
|
||
|
|
||
| def process_symbols( |
There was a problem hiding this comment.
| def process_symbols( | |
| def strip_symbols( |
|
|
||
|
|
||
| def strip_symbols(libraries: Iterable[Path]) -> None: | ||
| for lib in libraries: | ||
| logger.info("Stripping symbols from %s", lib) | ||
| check_call(["strip", "-s", lib]) | ||
| """Legacy function for backward compatibility. Use process_symbols instead.""" | ||
| process_symbols(libraries, StripLevel.ALL) |
There was a problem hiding this comment.
| def strip_symbols(libraries: Iterable[Path]) -> None: | |
| for lib in libraries: | |
| logger.info("Stripping symbols from %s", lib) | |
| check_call(["strip", "-s", lib]) | |
| """Legacy function for backward compatibility. Use process_symbols instead.""" | |
| process_symbols(libraries, StripLevel.ALL) |
Summary
Closes #491.
--strip-leveloption toauditwheel repairwith four levels:none(default),debug(strip -g),unneeded(strip --strip-unneeded), andall(strip -s)--stripflag (maps to--strip-level=allfor backward compatibility)--stripand--strip-levelusage early, before any wheel processing beginsMotivation
The current
--stripflag only supportsstrip -s(strip all symbols), which is too aggressive for many use cases. Users building wheels with debug symbols want the ability to strip only debug info (-g) or unneeded symbols (--strip-unneeded) while keeping the symbol table intact for debugging and profiling.Design
StripLevelenum inrepair.pymaps each level to the correspondingstripCLI argumentsprocess_symbols()applies stripping to both vendored libraries and extension modulesrepair_wheel(), before the pure-wheel early return_get_strip_args(StripLevel.NONE)raisesValueErroras a safety net (callers skip processing forNONE)Test plan
TestStripLevelArgument)execute()integration (TestStripLevelExecute) — verifies strip level is passed through, deprecation warning is emitted, and conflicting args errorrepair_wheel()behavior (TestRepairWheelStripLevels) — verifiesprocess_symbolsis called/skipped correctly per level_get_strip_argsandprocess_symbols(TestGetStripArgs,TestProcessSymbols,TestStripSymbolsShim)TestRepairWheelConflict)