Skip to content

Commit

Permalink
Build ABI3 wheels containing libmagic
Browse files Browse the repository at this point in the history
  • Loading branch information
ddelange committed Sep 6, 2023
1 parent 2a01b18 commit 95b63d6
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 15 deletions.
123 changes: 123 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: GH

on:
pull_request:
push:
branches: master
release:
types: [released, prereleased]
workflow_dispatch: # allows running workflow manually from the Actions tab

jobs:

build-sdist:
runs-on: ubuntu-latest

env:
PIP_DISABLE_PIP_VERSION_CHECK: 1

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- run: sudo apt-get install -y libmagic1

- name: Build source distribution
run: |
pip install -U setuptools wheel pip
python setup.py sdist
- uses: actions/upload-artifact@v3
with:
name: dist
path: dist/*.tar.*


build-wheels-matrix:
runs-on: ubuntu-latest
outputs:
include: ${{ steps.set-matrix.outputs.include }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- run: pip install cibuildwheel==2.15.0
- id: set-matrix
env:
CIBW_PROJECT_REQUIRES_PYTHON: '==3.11.*'
run: |
MATRIX_INCLUDE=$(
{
cibuildwheel --print-build-identifiers --platform linux --arch x86_64,aarch64 | grep cp | grep many | jq -nRc '{"only": inputs, "os": "ubuntu-latest"}' \
&& cibuildwheel --print-build-identifiers --platform macos --arch x86_64 | grep cp | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
&& cibuildwheel --print-build-identifiers --platform macos --arch arm64 | grep cp | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
&& cibuildwheel --print-build-identifiers --platform windows --arch x86,AMD64 | grep cp | jq -nRc '{"only": inputs, "os": "windows-latest"}'
} | jq -sc
)
echo "include=$MATRIX_INCLUDE" >> $GITHUB_OUTPUT
build-wheels:
needs: build-wheels-matrix
runs-on: ${{ matrix.os }}
name: Build ${{ matrix.only }}

strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.build-wheels-matrix.outputs.include) }}

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v2

- uses: pypa/[email protected]
timeout-minutes: 10
with:
only: ${{ matrix.only }}
env:
CIBW_BUILD_VERBOSITY: 1
CIBW_BEFORE_BUILD: 'bash -c "make install_libmagic"'

- uses: actions/upload-artifact@v3
with:
name: dist
path: wheelhouse/*.whl


publish:
needs: [build-sdist, build-wheels]
if: github.event_name == 'release'
runs-on: ubuntu-latest

steps:
- uses: actions/download-artifact@v3
with:
name: dist
path: dist/

- run: ls -ltra dist/

- name: Upload release assets
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ${{ github.ref }}

- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
SHELL := /bin/bash

.PHONY: install_libmagic
## Install libmagic
install_libmagic:
# Debian https://packages.ubuntu.com/libmagic1
# RHEL https://git.almalinux.org/rpms/file
# Mac https://formulae.brew.sh/formula/libmagic
# Windows https://github.com/julian-r/file-windows
( ( ( 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"; print(machine); 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 )
# on cibuildwheel, the lib needs to exist in the project before running setup.py
python -c "import subprocess; from magic.loader import load_lib; lib = load_lib()._name; print(f'linking {lib}'); subprocess.check_call(['cp', lib, 'magic'])"
ls magic

.DEFAULT_GOAL := help
.PHONY: help
## Print Makefile documentation
help:
@perl -0 -nle 'printf("\033[36m %-15s\033[0m %s\n", "$$2", "$$1") while m/^##\s*([^\r\n]+)\n^([\w.-]+):[^=]/gm' $(MAKEFILE_LIST) | sort
30 changes: 18 additions & 12 deletions magic/loader.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
from ctypes.util import find_library
import ctypes
import sys
import glob
import os.path
import subprocess
import sys

def _lib_candidates():

yield find_library('magic')

if sys.platform == 'darwin':

paths = [
'/opt/local/lib',
'/usr/local/lib',
'/opt/homebrew/lib',
'/opt/local/lib',
'/usr/local/lib',
'/opt/homebrew/lib',
] + glob.glob('/usr/local/Cellar/libmagic/*/lib')

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

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

prefixes = ['libmagic', 'magic1', 'magic-1', 'cygmagic-1', 'libmagic-1', 'msys-magic-1']
prefixes = ['magic', 'libmagic', 'magic1', 'magic-1', 'cygmagic-1', 'libmagic-1', 'msys-magic-1']

for i in prefixes:
# find_library searches in %PATH% but not the current directory,
# so look for both
yield './%s.dll' % (i,)
yield os.path.join('.', '%s.dll' % i)
yield find_library(i)

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

yield find_library('magic')


def load_lib():
Expand All @@ -42,9 +47,10 @@ def load_lib():
continue
try:
return ctypes.CDLL(lib)
except OSError:
except OSError as exc:
print(exc)
pass
else:
# It is better to raise an ImportError since we are importing magic module
raise ImportError('failed to find libmagic. Check your installation')
raise ImportError('failed to find libmagic. Check your installation')

Check failure on line 55 in magic/loader.py

View workflow job for this annotation

GitHub Actions / Build cp311-win32

failed to find libmagic. Check your installation

37 changes: 34 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,48 @@
# -*- coding: utf-8 -*-

import setuptools
import subprocess
import io
import os
import sys

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

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

def get_cmdclass():
"""Build a forward compatible ABI3 wheel when `setup.py bdist_wheel` is called."""
if sys.version_info[0] == 2:
return {}

try:
from wheel.bdist_wheel import bdist_wheel
except ImportError:
return {}

class bdist_wheel_abi3(bdist_wheel):
def get_tag(self):
_, _, plat = super().get_tag()
self.root_is_pure = False
self.py_limited_api = "cp38" if plat == "macosx_11_0_arm64" else "cp33"
return super().get_tag()

return {"bdist_wheel": bdist_wheel_abi3}

cmdclass = get_cmdclass()
package_data = {'magic': ['py.typed', '*.pyi', '*.dylib*', '*.dll', '*.so*']}

# # package the lib into the wheel to make it self-contained
# if cmdclass:
# # package_data can only source relative to the project, so we symlink
# subprocess.check_call(['ln', '-sf', lib, 'magic'])

setuptools.setup(
name='python-magic',
description='File type identification using libmagic',
Expand All @@ -22,9 +54,8 @@ def read(file_name):
long_description=read('README.md'),
long_description_content_type='text/markdown',
packages=['magic'],
package_data={
'magic': ['py.typed', '*.pyi', '**/*.pyi'],
},
package_data=package_data,
cmdclass=cmdclass,
keywords="mime magic file",
license="MIT",
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
Expand Down

0 comments on commit 95b63d6

Please sign in to comment.