Skip to content

Commit bb8df96

Browse files
Make wheels contain platform and python ABI tags (#3)
* Made wheels contain platform and python ABI tags * Changed setuptools requirement; ensured bdist_wheel used on Windows; improved Windows build script * Used 3.13-slim python image for DockerFile.linux * Ensured Python 3.13 before running demo * Ensured linux wheels are manylinux wheels
1 parent 75fb1b0 commit bb8df96

8 files changed

Lines changed: 85 additions & 19 deletions

File tree

.github/workflows/build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ jobs:
123123
mkdir -p wheels/linux
124124
docker cp jecq-wheel-builder:/app/wheels/linux/. wheels/linux/
125125
docker rm jecq-wheel-builder
126+
127+
- name: Set up Python
128+
uses: actions/setup-python@v5
129+
with:
130+
python-version: '3.13'
126131

127132
- name: Run sample demo in clean environment
128133
run: |

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"cSpell.words": [
77
"addlicense",
88
"atleast",
9+
"auditwheel",
10+
"bdist",
911
"BLAS",
12+
"cmdclass",
1013
"CMPLR",
1114
"DBLA",
1215
"DBUILD",

Dockerfile.linux

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
FROM ubuntu:22.04
2+
FROM python:3.13-slim
23

34
ARG BUILD_TYPE=Debug
45

build.ps1

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Write-Output "Setting up virtual environment..."
6565
Push-Location .
6666
python -m venv .venv; Test-Exit-Code
6767
./.venv/scripts/activate.ps1; Test-Exit-Code
68+
python -m pip install --upgrade pip; Test-Exit-Code
6869
pip install -r ../jecq/python/requirements.txt; Test-Exit-Code
6970
Pop-Location
7071

@@ -149,12 +150,14 @@ Test-Exit-Code
149150
# Install Python package
150151
Write-Output "Installing Python package..."
151152
Push-Location jecq/python
152-
python setup.py install; Test-Exit-Code
153153
pip install wheel; Test-Exit-Code
154-
pip wheel --no-binary jecq .; Test-Exit-Code
154+
python setup.py bdist_wheel; Test-Exit-Code
155+
Get-ChildItem dist/jecq*.whl | ForEach-Object { pip install $_.FullName }
156+
Pop-Location
157+
158+
# Verify installation
155159
python -c "import jecq;jecq.IndexJecq();jecq.IndexIVFJecq()"; Test-Exit-Code
156160
python -c "import jecq;assert jecq.IndexJecq.__module__ == 'jecq.swigjecq_avx2'"; Test-Exit-Code
157-
Pop-Location
158161

159162
# Update wheels
160163
Write-Output "Updating wheels..."

build.sh

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pushd .
4646
echo "Setting up virtual environment..."
4747
python3 -m venv .venv
4848
source .venv/bin/activate
49+
python -m pip install --upgrade pip
4950
pip install -r ../jecq/python/requirements.txt
5051
popd
5152

@@ -87,13 +88,22 @@ fi
8788
echo "Installing Python package..."
8889
pushd .
8990
cd ./jecq/python
90-
# Make sure wheel is installed
9191
pip install wheel
92-
python3 setup.py install
93-
pip wheel --no-binary jecq .
92+
python3 setup.py bdist_wheel
93+
# Repair wheel for Linux compatibility
94+
pip install auditwheel
95+
pushd ./dist
96+
for f in jecq*.whl; do
97+
auditwheel repair "$f" -w . && rm "$f"
98+
done
99+
popd
100+
# Install wheels
101+
pip install dist/jecq*.whl
102+
popd
103+
104+
# Verify installation
94105
python3 -c "import jecq;jecq.IndexJecq();jecq.IndexIVFJecq()"
95106
python3 -c "import jecq;assert jecq.IndexJecq.__module__ == 'jecq.swigjecq_avx2'"
96-
popd
97107

98108
# Update wheels
99109
echo "Updating wheels..."

jecq/python/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
faiss-cpu
22
numpy>=2
3-
setuptools
3+
setuptools>=80
44
packaging
55
swig

jecq/python/setup.py

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
import platform
2424
import shutil
2525

26-
from setuptools import setup
26+
from setuptools import setup, Extension
27+
from setuptools.command.build_ext import build_ext as _build_ext
2728

2829
# make the jecq python package dir
2930
shutil.rmtree("jecq", ignore_errors=True)
@@ -32,23 +33,22 @@
3233
shutil.copyfile("loader.py", "jecq/loader.py")
3334
shutil.copyfile("class_wrappers.py", "jecq/class_wrappers.py")
3435

35-
ext = ".pyd" if platform.system() == "Windows" else ".so"
36+
is_windows = platform.system() == "Windows"
3637

38+
ext = ".pyd" if is_windows else ".so"
3739
build_type = os.environ.get("JECQ_BUILD_TYPE", "RelWithDebInfo")
38-
prefix = f"{build_type}/" * (platform.system() == "Windows")
40+
prefix = f"{build_type}/" * is_windows
3941

4042
swigjecq_generic_lib = f"{prefix}_swigjecq{ext}"
4143
swigjecq_avx2_lib = f"{prefix}_swigjecq_avx2{ext}"
4244
swigjecq_avx512_lib = f"{prefix}_swigjecq_avx512{ext}"
4345
swigjecq_avx512_spr_lib = f"{prefix}_swigjecq_avx512_spr{ext}"
44-
callbacks_lib = f"{prefix}libfaiss_python_callbacks{ext}"
4546
swigjecq_sve_lib = f"{prefix}_swigjecq_sve{ext}"
4647

4748
found_swigjecq_generic = os.path.exists(swigjecq_generic_lib)
4849
found_swigjecq_avx2 = os.path.exists(swigjecq_avx2_lib)
4950
found_swigjecq_avx512 = os.path.exists(swigjecq_avx512_lib)
5051
found_swigjecq_avx512_spr = os.path.exists(swigjecq_avx512_spr_lib)
51-
found_callbacks = os.path.exists(callbacks_lib)
5252
found_swigjecq_sve = os.path.exists(swigjecq_sve_lib)
5353

5454
assert (
@@ -64,50 +64,93 @@
6464
f"Jecq may not be compiled yet."
6565
)
6666

67+
libs = []
68+
6769
if found_swigjecq_generic:
6870
print(f"Copying {swigjecq_generic_lib}")
6971
shutil.copyfile("swigjecq.py", "jecq/swigjecq.py")
7072
shutil.copyfile(swigjecq_generic_lib, f"jecq/_swigjecq{ext}")
73+
libs.append("_swigjecq")
7174

7275
if found_swigjecq_avx2:
7376
print(f"Copying {swigjecq_avx2_lib}")
7477
shutil.copyfile("swigjecq_avx2.py", "jecq/swigjecq_avx2.py")
7578
shutil.copyfile(swigjecq_avx2_lib, f"jecq/_swigjecq_avx2{ext}")
79+
libs.append("_swigjecq_avx2")
7680

7781
if found_swigjecq_avx512:
7882
print(f"Copying {swigjecq_avx512_lib}")
7983
shutil.copyfile("swigjecq_avx512.py", "jecq/swigjecq_avx512.py")
8084
shutil.copyfile(swigjecq_avx512_lib, f"jecq/_swigjecq_avx512{ext}")
85+
libs.append("_swigjecq_avx512")
86+
8187

8288
if found_swigjecq_avx512_spr:
8389
print(f"Copying {swigjecq_avx512_spr_lib}")
8490
shutil.copyfile("swigjecq_avx512_spr.py", "jecq/swigjecq_avx512_spr.py")
8591
shutil.copyfile(swigjecq_avx512_spr_lib, f"jecq/_swigjecq_avx512_spr{ext}")
92+
libs.append("_swigjecq_avx512_spr")
8693

87-
if found_callbacks:
88-
print(f"Copying {callbacks_lib}")
89-
shutil.copyfile(callbacks_lib, f"jecq/{callbacks_lib}")
9094

9195
if found_swigjecq_sve:
9296
print(f"Copying {swigjecq_sve_lib}")
9397
shutil.copyfile("swigjecq_sve.py", "jecq/swigjecq_sve.py")
9498
shutil.copyfile(swigjecq_sve_lib, f"jecq/_swigjecq_sve{ext}")
99+
libs.append("_swigjecq_sve")
100+
101+
ext_modules = [Extension(f"jecq.{mod}", sources=[]) for mod in libs]
102+
103+
104+
# 2) CopyBuildExt just copies the .so into the build directory without compiling
105+
class CopyBuildExt(_build_ext):
106+
def run(self):
107+
# skip the normal compiler run
108+
for extension in self.extensions:
109+
self.build_extension(extension)
110+
111+
def build_extension(self, extension):
112+
name = extension.name.split(".", 1)[1] # e.g. "_swigjecq"
113+
src = f"{prefix}{name}{ext}"
114+
if not os.path.exists(src):
115+
raise FileNotFoundError(f"Binary output '{src}' not found")
116+
dst = self.get_ext_fullpath(extension.name)
117+
self.mkpath(os.path.dirname(dst))
118+
shutil.copyfile(src, dst)
119+
print(f"Copied {src} to {dst}")
120+
95121

96122
long_description = """
97123
Jecq is a library for efficient similarity search based on the Faiss library.
98124
"""
125+
126+
try:
127+
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
128+
129+
"""Custom bdist_wheel to ensure that the root is not pure Python."""
130+
131+
class bdist_wheel(_bdist_wheel):
132+
def finalize_options(self):
133+
_bdist_wheel.finalize_options(self)
134+
self.root_is_pure = False
135+
136+
except ImportError:
137+
print("Not using custom bdist_wheel; wheel package not installed")
138+
99139
setup(
100140
name="jecq",
101141
version="0.0.1",
102142
description="A Faiss-based library for efficient similarity search "
103143
"and clustering of dense vectors",
104144
long_description=long_description,
105-
license="TODO",
145+
url="https://github.com/JaneaSystems/jecq",
146+
license="MIT",
106147
keywords="search nearest neighbors",
107148
install_requires=["numpy", "packaging", "faiss-cpu"],
108149
packages=["jecq"],
109150
package_data={
110-
"jecq": ["*.so", "*.pyd"],
151+
"jecq": ["*.pyd"] if is_windows else [],
111152
},
112153
zip_safe=False,
154+
ext_modules=[] if is_windows else ext_modules,
155+
cmdclass={"bdist_wheel": bdist_wheel, "build_ext": CopyBuildExt},
113156
)

requirements.linux

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ cmake
77
build-essential
88
autoconf
99
automake
10-
libtool
10+
libtool
11+
patchelf

0 commit comments

Comments
 (0)