Skip to content

Commit bfa4e6f

Browse files
committed
Use repairwheel to correct references between whisper libraries.
I'm still unsure if this is the best approach but I wanted `pip install .` to work, normally there is an option in cibuildwheel that let us run wheelrepair, but this leaves the pip install broken. It is quite difficult to find information how to do it properly, so I'm still unsure that it is right solution as we are still ending up with libs in two places in the wheel, but at least they reference them selfs correctly so which ever is picked first the rest is loaded. I've tested this only on my mac so I'm not sure that it is working correctly in windows and linux but it should as repairwheel is multiplatform. After repair the libs are in pywhispercpp-1.2.0/*.dylib and in pywhispercpp-1.2.0/pywhispercpp/.dylibs/*.dylib, but they reference each other have a look at the output of otool -L ``` otool -L pywhispercpp-1.2.0/libwhisper.1.7.1.dylib pywhispercpp-1.2.0/libwhisper.1.7.1.dylib: @rpath/libwhisper.1.dylib (compatibility version 1.0.0, current version 1.7.1) @loader_path/pywhispercpp/.dylibs/libggml.dylib (compatibility version 0.0.0, current version 0.0.0) @loader_path/pywhispercpp/.dylibs/libwhisper.coreml.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0) ``` ```` otool -L pywhispercpp-1.2.0/pywhispercpp/.dylibs/libwhisper.1.7.1.dylib pywhispercpp-1.2.0/pywhispercpp/.dylibs/libwhisper.1.7.1.dylib: /DLC/pywhispercpp/.dylibs/libwhisper.1.7.1.dylib (compatibility version 1.0.0, current version 1.7.1) @loader_path/libggml.dylib (compatibility version 0.0.0, current version 0.0.0) @loader_path/libwhisper.coreml.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0) ```
1 parent fe405a6 commit bfa4e6f

File tree

2 files changed

+39
-18
lines changed

2 files changed

+39
-18
lines changed

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ requires = [
44
"wheel",
55
"ninja",
66
"cmake>=3.12",
7+
"repairwheel",
78
]
89
build-backend = "setuptools.build_meta"
910

@@ -34,6 +35,8 @@ test-skip = ["*universal2:arm64"]
3435
# Setuptools bug causes collision between pypy and cpython artifacts
3536
before-build = "rm -rf {project}/build"
3637

38+
#repair-wheel-command = "wheelrepair \"{wheel}\" -O build/ ; mv build/*.whl {wheel}" # TODO: figure out if this works alread after adding it to setup.py
39+
3740
[tool.ruff]
3841
extend-select = [
3942
"B", # flake8-bugbear

setup.py

+36-18
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import re
33
import subprocess
44
import sys
5-
from glob import glob
65
from pathlib import Path
6+
import subprocess
77

88
from setuptools import Extension, setup, find_packages
99
from setuptools.command.build_ext import build_ext
10-
10+
from setuptools.command.bdist_wheel import bdist_wheel
1111
# Convert distutils Windows platform specifiers to CMake -A arguments
1212
PLAT_TO_CMAKE = {
1313
"win32": "Win32",
@@ -25,6 +25,7 @@ def __init__(self, name: str, sourcedir: str = "") -> None:
2525
super().__init__(name, sources=[])
2626
self.sourcedir = os.fspath(Path(sourcedir).resolve())
2727

28+
dll_folder = 'unset'
2829

2930
class CMakeBuild(build_ext):
3031
def build_extension(self, ext: CMakeExtension) -> None:
@@ -130,26 +131,41 @@ def build_extension(self, ext: CMakeExtension) -> None:
130131

131132
def copy_extensions_to_source(self):
132133
super().copy_extensions_to_source()
133-
# Copy the shared library to the lib folder
134-
ext_fullpath = Path.cwd() / self.get_ext_fullpath(self.extensions[0].name) # type: ignore[no-untyped-call]
135-
extdir = ext_fullpath.parent.resolve()
136-
so_files = os.path.join(extdir, '*.so*')
137-
dylib_files = os.path.join(extdir, '*.dylib')
134+
# store the dll folder in a global variable to use in repairwheel
135+
global dll_folder
138136
cfg = "Debug" if self.debug else "Release"
139-
dll_files = os.path.join(self.build_temp, '_pywhispercpp', 'bin', cfg, "*.dll")
140-
shared_libs = glob(so_files) + glob(dll_files) + glob(dylib_files)
141-
shared_libs = [f for f in shared_libs if not Path(f).name.startswith("_")] # exclude the extension itself
142-
dest_folder = Path.cwd() / 'pywhispercpp' / 'lib'
143-
if not dest_folder.resolve().exists():
144-
dest_folder.mkdir(parents=True)
145-
for file_path in shared_libs:
146-
filename = os.path.basename(file_path)
147-
self.copy_file(file_path, (dest_folder / filename).resolve())
137+
dll_folder = os.path.join(self.build_temp, '_pywhispercpp', 'bin', cfg)
138+
148139

149140
# read the contents of your README file
150141
this_directory = Path(__file__).parent
151142
long_description = (this_directory / "README.md").read_text()
152143

144+
145+
class RepairWheel(bdist_wheel):
146+
def run(self):
147+
super().run()
148+
# on windows the dlls are in D:\a\pywhispercpp\pywhispercpp\build\temp.win-amd64-cpython-311\Release\_pywhispercpp\bin\Release\whisper.dll
149+
global dll_folder
150+
print("dll_folder in repairwheel",dll_folder)
151+
print("Files in dll_folder:", *Path(dll_folder).glob('*'))
152+
#build\temp.win-amd64-cpython-311\Release\_pywhispercpp\bin\Release\whisper.dll
153+
154+
wheel_path = next(Path(self.dist_dir).glob(f"{self.distribution.get_name()}*.whl"))
155+
# Create a temporary directory for the repaired wheel
156+
import tempfile
157+
with tempfile.TemporaryDirectory(prefix='repaired_wheel_') as tmp_dir:
158+
tmp_dir = Path(tmp_dir)
159+
subprocess.call(['repairwheel', wheel_path, '-o', tmp_dir, '-l', dll_folder])
160+
print("Repaired wheel: ", *tmp_dir.glob('*.whl'))
161+
# We need to glob as repairwheel may change the name of the wheel
162+
# on linux from pywhispercpp-1.2.0-cp312-cp312-linux_aarch64.whl
163+
# to pywhispercpp-1.2.0-cp312-cp312-manylinux_2_34_aarch64.whl
164+
repaired_wheel = next(tmp_dir.glob("*.whl"))
165+
self.copy_file(repaired_wheel, wheel_path)
166+
print(f"Copied repaired wheel to: {wheel_path}")
167+
168+
153169
# The information here can also be placed in setup.cfg - better separation of
154170
# logic and declaration, and simpler if you include description/version in a file.
155171
setup(
@@ -159,14 +175,15 @@ def copy_extensions_to_source(self):
159175
description="Python bindings for whisper.cpp",
160176
long_description=long_description,
161177
ext_modules=[CMakeExtension("_pywhispercpp")],
162-
cmdclass={"build_ext": CMakeBuild},
178+
cmdclass={"build_ext": CMakeBuild,
179+
'bdist_wheel': RepairWheel,},
163180
zip_safe=False,
164181
# extras_require={"test": ["pytest>=6.0"]},
165182
python_requires=">=3.8",
166183
packages=find_packages('.'),
167184
package_dir={'': '.'},
168185
include_package_data=True,
169-
package_data={'pywhispercpp': ['lib/*']},
186+
package_data={'pywhispercpp': []},
170187
long_description_content_type="text/markdown",
171188
license='MIT',
172189
entry_points={
@@ -182,4 +199,5 @@ def copy_extensions_to_source(self):
182199
},
183200
install_requires=['numpy', "requests", "tqdm", "platformdirs"],
184201
extras_require={"examples": ["sounddevice", "webrtcvad"]},
202+
185203
)

0 commit comments

Comments
 (0)