Skip to content

Commit 672f9b6

Browse files
committed
chore: Add argument and env to specific compress level
1 parent b01e842 commit 672f9b6

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

src/auditwheel/main_repair.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import argparse
44
import logging
5+
import zlib
56
from pathlib import Path
67

78
from auditwheel.patcher import Patchelf
89

10+
from . import tools
911
from .policy import WheelPolicies
1012
from .tools import EnvironmentDefault
1113

@@ -40,6 +42,18 @@ def configure_parser(sub_parsers) -> None: # type: ignore[no-untyped-def]
4042
formatter_class=argparse.RawDescriptionHelpFormatter,
4143
)
4244
parser.add_argument("WHEEL_FILE", type=Path, help="Path to wheel file.", nargs="+")
45+
parser.add_argument(
46+
"-z",
47+
"--zip-level",
48+
action=EnvironmentDefault,
49+
metavar="zip",
50+
env="AUDITWHEEL_ZIP_LEVEL",
51+
dest="zip",
52+
type=int,
53+
help="Compress level to be used to create zip file.",
54+
choices=list(range(zlib.Z_NO_COMPRESSION, zlib.Z_BEST_COMPRESSION + 1)),
55+
default=zlib.Z_DEFAULT_COMPRESSION,
56+
)
4357
parser.add_argument(
4458
"--plat",
4559
action=EnvironmentDefault,
@@ -119,6 +133,7 @@ def execute(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
119133
wheel_dir: Path = args.WHEEL_DIR.absolute()
120134
wheel_files: list[Path] = args.WHEEL_FILE
121135
wheel_policy = WheelPolicies()
136+
tools._COMPRESS_LEVEL = args.zip
122137

123138
for wheel_file in wheel_files:
124139
if not wheel_file.is_file():

src/auditwheel/tools.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
from __future__ import annotations
22

33
import argparse
4+
import logging
45
import os
56
import subprocess
67
import zipfile
8+
import zlib
79
from collections.abc import Generator, Iterable
810
from datetime import datetime, timezone
911
from pathlib import Path
1012
from typing import Any, TypeVar
1113

1214
_T = TypeVar("_T")
1315

16+
logger = logging.getLogger(__name__)
17+
18+
# Default: zlib.Z_DEFAULT_COMPRESSION (-1 aka. level 6) balances speed and size.
19+
# Maintained for typical builds where iteration speed outweighs distribution savings.
20+
# Override via AUDITWHEEL_ZIP_LEVEL/--zip-level for:
21+
# - some test builds that needs no compression at all (0)
22+
# - bandwidth-constrained or large amount of downloads (9)
23+
_COMPRESS_LEVEL = zlib.Z_DEFAULT_COMPRESSION
24+
1425

1526
def unique_by_index(sequence: Iterable[_T]) -> list[_T]:
1627
"""unique elements in `sequence` in the order in which they occur
@@ -90,6 +101,7 @@ def zip2dir(zip_fname: Path, out_dir: Path) -> None:
90101
out_dir : str
91102
Directory path containing files to go in the zip archive
92103
"""
104+
start = datetime.now()
93105
with zipfile.ZipFile(zip_fname, "r") as z:
94106
for name in z.namelist():
95107
member = z.getinfo(name)
@@ -102,6 +114,9 @@ def zip2dir(zip_fname: Path, out_dir: Path) -> None:
102114
attr &= 511 # only keep permission bits
103115
attr |= 6 << 6 # at least read/write for current user
104116
os.chmod(extracted_path, attr)
117+
logger.debug(
118+
"zip2dir from %s to %s takes %s", zip_fname, out_dir, datetime.now() - start
119+
)
105120

106121

107122
def dir2zip(in_dir: Path, zip_fname: Path, date_time: datetime | None = None) -> None:
@@ -120,6 +135,7 @@ def dir2zip(in_dir: Path, zip_fname: Path, date_time: datetime | None = None) ->
120135
date_time : Optional[datetime]
121136
Time stamp to set on each file in the archive
122137
"""
138+
start = datetime.now()
123139
in_dir = in_dir.resolve(strict=True)
124140
if date_time is None:
125141
st = in_dir.stat()
@@ -140,7 +156,10 @@ def dir2zip(in_dir: Path, zip_fname: Path, date_time: datetime | None = None) ->
140156
zinfo.date_time = date_time_args
141157
zinfo.compress_type = compression
142158
with open(fname, "rb") as fp:
143-
z.writestr(zinfo, fp.read())
159+
z.writestr(zinfo, fp.read(), compresslevel=_COMPRESS_LEVEL)
160+
logger.debug(
161+
"dir2zip from %s to %s takes %s", in_dir, zip_fname, datetime.now() - start
162+
)
144163

145164

146165
def tarbz2todir(tarbz2_fname: Path, out_dir: Path) -> None:
@@ -157,11 +176,14 @@ def __init__(
157176
required: bool = True,
158177
default: str | None = None,
159178
choices: Iterable[str] | None = None,
179+
type: type | None = None,
160180
**kwargs: Any,
161181
) -> None:
162182
self.env_default = os.environ.get(env)
163183
self.env = env
164184
if self.env_default:
185+
if type:
186+
self.env_default = type(self.env_default)
165187
default = self.env_default
166188
if default:
167189
required = False

src/auditwheel/wheeltools.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
from itertools import product
1616
from os.path import splitext
1717
from pathlib import Path
18+
from tempfile import TemporaryDirectory
1819
from types import TracebackType
20+
from typing import Any, ClassVar
1921

2022
from packaging.utils import parse_wheel_filename
2123

@@ -98,8 +100,13 @@ class InWheel(InTemporaryDirectory):
98100
On entering, you'll find yourself in the root tree of the wheel. If you've
99101
asked for an output wheel, then on exit we'll rewrite the wheel record and
100102
pack stuff up for you.
103+
104+
If `out_wheel` is None, we assume the wheel won't be modified and we can
105+
cache the unpacked wheel for future use.
101106
"""
102107

108+
_whl_cache: ClassVar[dict[Path, TemporaryDirectory[Any]]] = {}
109+
103110
def __init__(self, in_wheel: Path, out_wheel: Path | None = None) -> None:
104111
"""Initialize in-wheel context manager
105112
@@ -113,9 +120,35 @@ def __init__(self, in_wheel: Path, out_wheel: Path | None = None) -> None:
113120
"""
114121
self.in_wheel = in_wheel.absolute()
115122
self.out_wheel = None if out_wheel is None else out_wheel.absolute()
116-
super().__init__()
123+
self.read_only = out_wheel is None
124+
self.use_cache = self.in_wheel in self._whl_cache
125+
if self.use_cache and not Path(self._whl_cache[self.in_wheel].name).exists():
126+
self.use_cache = False
127+
logger.debug(
128+
"Wheel ctx %s for %s is no longer valid",
129+
self._whl_cache.pop(self.in_wheel),
130+
self.in_wheel,
131+
)
132+
133+
if self.use_cache:
134+
logger.debug(
135+
"Reuse %s for %s", self._whl_cache[self.in_wheel], self.in_wheel
136+
)
137+
self._tmpdir = self._whl_cache[self.in_wheel]
138+
if not self.read_only:
139+
self._whl_cache.pop(self.in_wheel)
140+
else:
141+
super().__init__()
142+
if self.read_only:
143+
self._whl_cache[self.in_wheel] = self._tmpdir
117144

118145
def __enter__(self) -> Path:
146+
if self.use_cache or self.read_only:
147+
if not self.use_cache:
148+
zip2dir(self.in_wheel, self.name)
149+
self._pwd = Path.cwd()
150+
os.chdir(self.name)
151+
return Path(self.name)
119152
zip2dir(self.in_wheel, self.name)
120153
return super().__enter__()
121154

@@ -132,6 +165,16 @@ def __exit__(
132165
if timestamp:
133166
date_time = datetime.fromtimestamp(int(timestamp), tz=timezone.utc)
134167
dir2zip(self.name, self.out_wheel, date_time)
168+
if self.use_cache or self.read_only:
169+
logger.debug(
170+
"Exiting reused %s for %s",
171+
self._whl_cache[self.in_wheel],
172+
self.in_wheel,
173+
)
174+
os.chdir(self._pwd)
175+
if not self.read_only:
176+
super().__exit__(exc, value, tb)
177+
return None
135178
return super().__exit__(exc, value, tb)
136179

137180

0 commit comments

Comments
 (0)