Skip to content

Commit dfada52

Browse files
committed
Build ABI3 wheels containing libmagic
1 parent 2a01b18 commit dfada52

File tree

4 files changed

+197
-16
lines changed

4 files changed

+197
-16
lines changed

.github/workflows/main.yml

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: GH
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: master
7+
release:
8+
types: [released, prereleased]
9+
workflow_dispatch: # allows running workflow manually from the Actions tab
10+
11+
jobs:
12+
13+
build-sdist:
14+
runs-on: ubuntu-latest
15+
16+
env:
17+
PIP_DISABLE_PIP_VERSION_CHECK: 1
18+
19+
steps:
20+
- uses: actions/checkout@v3
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@v4
26+
with:
27+
python-version: '3.x'
28+
29+
- run: sudo apt-get install -y libmagic1
30+
31+
- name: Build source distribution
32+
run: |
33+
pip install -U setuptools wheel pip
34+
python setup.py sdist
35+
36+
- uses: actions/upload-artifact@v3
37+
with:
38+
name: dist
39+
path: dist/*.tar.*
40+
41+
42+
build-wheels-matrix:
43+
runs-on: ubuntu-latest
44+
outputs:
45+
include: ${{ steps.set-matrix.outputs.include }}
46+
steps:
47+
- uses: actions/checkout@v3
48+
- uses: actions/setup-python@v4
49+
with:
50+
python-version: '3.x'
51+
- run: pip install cibuildwheel==2.15.0
52+
- id: set-matrix
53+
env:
54+
CIBW_PROJECT_REQUIRES_PYTHON: '==3.11.*'
55+
run: |
56+
MATRIX_INCLUDE=$(
57+
{
58+
cibuildwheel --print-build-identifiers --platform linux --arch x86_64,aarch64 | grep cp | grep many | jq -nRc '{"only": inputs, "os": "ubuntu-latest"}' \
59+
&& cibuildwheel --print-build-identifiers --platform macos --arch x86_64 | grep cp | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
60+
&& cibuildwheel --print-build-identifiers --platform macos --arch arm64 | grep cp | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
61+
&& cibuildwheel --print-build-identifiers --platform windows --arch x86,AMD64 | grep cp | jq -nRc '{"only": inputs, "os": "windows-latest"}'
62+
} | jq -sc
63+
)
64+
echo "include=$MATRIX_INCLUDE" >> $GITHUB_OUTPUT
65+
66+
67+
build-wheels:
68+
needs: build-wheels-matrix
69+
runs-on: ${{ matrix.os }}
70+
name: Build ${{ matrix.only }}
71+
72+
strategy:
73+
fail-fast: false
74+
matrix:
75+
include: ${{ fromJson(needs.build-wheels-matrix.outputs.include) }}
76+
77+
steps:
78+
- uses: actions/checkout@v3
79+
with:
80+
fetch-depth: 0
81+
82+
- name: Set up QEMU
83+
if: runner.os == 'Linux'
84+
uses: docker/setup-qemu-action@v2
85+
86+
- uses: pypa/[email protected]
87+
with:
88+
only: ${{ matrix.only }}
89+
env:
90+
CIBW_BUILD_VERBOSITY: 1
91+
CIBW_BEFORE_BUILD: 'bash -c "make install_libmagic"'
92+
93+
- uses: actions/upload-artifact@v3
94+
with:
95+
name: dist
96+
path: wheelhouse/*.whl
97+
98+
99+
publish:
100+
needs: [build-sdist, build-wheels]
101+
if: github.event_name == 'release'
102+
runs-on: ubuntu-latest
103+
104+
steps:
105+
- uses: actions/download-artifact@v3
106+
with:
107+
name: dist
108+
path: dist/
109+
110+
- run: ls -ltra dist/
111+
112+
- name: Upload release assets
113+
uses: softprops/action-gh-release@v1
114+
with:
115+
files: dist/*
116+
tag_name: ${{ github.ref }}
117+
118+
- name: Upload to PyPI
119+
uses: pypa/gh-action-pypi-publish@release/v1
120+
with:
121+
user: __token__
122+
password: ${{ secrets.PYPI_TOKEN }}

Makefile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
SHELL := /bin/bash
2+
3+
.PHONY: install_libmagic
4+
## Install libmagic
5+
install_libmagic:
6+
# Debian https://packages.ubuntu.com/libmagic1
7+
# RHEL https://git.almalinux.org/rpms/file
8+
# Mac https://formulae.brew.sh/formula/libmagic
9+
# Windows https://github.com/julian-r/file-windows
10+
( ( ( brew install libmagic || ( apt-get update && apt-get install -y libmagic1 ) ) || apk add --update libmagic ) || yum install file-libs ) || ( python -c 'import platform, io, zipfile, urllib.request; assert platform.system() == "Windows"; machine = "x64" if platform.machine() == "AMD64" else "x86"; zipfile.ZipFile(io.BytesIO(urllib.request.urlopen(f"https://github.com/julian-r/file-windows/releases/download/v5.44/file_5.44-build104-vs2022-{machine}.zip").read())).extractall(".")' && ls )
11+
# on cibuildwheel, the lib needs to exist in the project before running setup.py
12+
python -c "import subprocess; from magic.loader import load_lib; lib = load_lib()._name; print(f'linking {lib}'); subprocess.check_call(['cp', lib, 'magic'])"
13+
ls magic
14+
15+
.DEFAULT_GOAL := help
16+
.PHONY: help
17+
## Print Makefile documentation
18+
help:
19+
@perl -0 -nle 'printf("\033[36m %-15s\033[0m %s\n", "$$2", "$$1") while m/^##\s*([^\r\n]+)\n^([\w.-]+):[^=]/gm' $(MAKEFILE_LIST) | sort

magic/loader.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,59 @@
11
from ctypes.util import find_library
22
import ctypes
3-
import sys
43
import glob
54
import os.path
5+
import subprocess
6+
import sys
67

78
def _lib_candidates():
89

9-
yield find_library('magic')
10-
1110
if sys.platform == 'darwin':
1211

1312
paths = [
14-
'/opt/local/lib',
15-
'/usr/local/lib',
16-
'/opt/homebrew/lib',
13+
'/opt/local/lib',
14+
'/usr/local/lib',
15+
'/opt/homebrew/lib',
1716
] + glob.glob('/usr/local/Cellar/libmagic/*/lib')
1817

1918
for i in paths:
2019
yield os.path.join(i, 'libmagic.dylib')
2120

2221
elif sys.platform in ('win32', 'cygwin'):
2322

24-
prefixes = ['libmagic', 'magic1', 'magic-1', 'cygmagic-1', 'libmagic-1', 'msys-magic-1']
23+
prefixes = ['magic', 'libmagic', 'magic1', 'magic-1', 'cygmagic-1', 'libmagic-1', 'msys-magic-1']
2524

2625
for i in prefixes:
2726
# find_library searches in %PATH% but not the current directory,
2827
# so look for both
29-
yield './%s.dll' % (i,)
28+
yield os.path.join('.', '%s.dll' % i)
3029
yield find_library(i)
3130

3231
elif sys.platform == 'linux':
33-
# This is necessary because alpine is bad
34-
yield 'libmagic.so.1'
32+
# on some linux systems, find_library('magic') returns None
33+
yield subprocess.check_output(
34+
"ldconfig -p | grep 'libmagic.so.1' | grep -o '/.*'",
35+
shell=True,
36+
universal_newlines=True
37+
).strip()
38+
39+
yield find_library('magic')
3540

3641

3742
def load_lib():
3843

39-
for lib in _lib_candidates():
44+
libs = list(_lib_candidates())
45+
print(libs)
46+
47+
for lib in libs:
4048
# find_library returns None when lib not found
4149
if lib is None:
4250
continue
4351
try:
4452
return ctypes.CDLL(lib)
45-
except OSError:
53+
except OSError as exc:
54+
print(exc)
4655
pass
4756
else:
4857
# It is better to raise an ImportError since we are importing magic module
49-
raise ImportError('failed to find libmagic. Check your installation')
58+
raise ImportError('failed to find libmagic. Check your installation')
5059

setup.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,48 @@
22
# -*- coding: utf-8 -*-
33

44
import setuptools
5+
import subprocess
56
import io
67
import os
8+
import sys
79

10+
# python packages should not install succesfully if libraries are missing
11+
from magic.loader import load_lib
12+
lib = load_lib()._name
813

914
def read(file_name):
1015
"""Read a text file and return the content as a string."""
1116
with io.open(os.path.join(os.path.dirname(__file__), file_name),
1217
encoding='utf-8') as f:
1318
return f.read()
1419

20+
def get_cmdclass():
21+
"""Build a forward compatible ABI3 wheel when `setup.py bdist_wheel` is called."""
22+
if sys.version_info[0] == 2:
23+
return {}
24+
25+
try:
26+
from wheel.bdist_wheel import bdist_wheel
27+
except ImportError:
28+
return {}
29+
30+
class bdist_wheel_abi3(bdist_wheel):
31+
def get_tag(self):
32+
_, _, plat = super().get_tag()
33+
self.root_is_pure = False
34+
self.py_limited_api = "cp38" if plat == "macosx_11_0_arm64" else "cp33"
35+
return super().get_tag()
36+
37+
return {"bdist_wheel": bdist_wheel_abi3}
38+
39+
cmdclass = get_cmdclass()
40+
package_data = {'magic': ['py.typed', '*.pyi', '*.dylib*', '*.dll', '*.so*']}
41+
42+
# # package the lib into the wheel to make it self-contained
43+
# if cmdclass:
44+
# # package_data can only source relative to the project, so we symlink
45+
# subprocess.check_call(['ln', '-sf', lib, 'magic'])
46+
1547
setuptools.setup(
1648
name='python-magic',
1749
description='File type identification using libmagic',
@@ -22,9 +54,8 @@ def read(file_name):
2254
long_description=read('README.md'),
2355
long_description_content_type='text/markdown',
2456
packages=['magic'],
25-
package_data={
26-
'magic': ['py.typed', '*.pyi', '**/*.pyi'],
27-
},
57+
package_data=package_data,
58+
cmdclass=cmdclass,
2859
keywords="mime magic file",
2960
license="MIT",
3061
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',

0 commit comments

Comments
 (0)