Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: build wheels for linux, windows, mac using cibuildwheel #23

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Continuous Integration

on:
workflow_dispatch:
pull_request:
push:
branches:
- main
release:
types:
- published

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# macos-13 is an intel runner, macos-14 is apple silicon
os: [ ubuntu-latest, windows-latest, macos-13, macos-14 ]

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

- name: Build wheels
uses: pypa/[email protected]
# configured in pyproject.toml [tool.cibuildwheel]

- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl

build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Build SDist
run: pipx run build --sdist

- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: dist/*.tar.gz

upload_pypi:
name: Publish to PyPi
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
if: github.event_name == 'release' && github.event.action == 'published'
steps:
- uses: actions/download-artifact@v4
with:
# unpacks all CIBW artifacts into dist/
pattern: cibw-*
path: dist
merge-multiple: true

# trusted publishing workflow:
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/
- uses: pypa/gh-action-pypi-publish@release/v1.8
Comment on lines +52 to +70
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are many options for publishing the wheels, i included this as an example.

this would:

  • wait for a release that is published
  • run the build again and generate artifacts (zip files containing wheels and sdist)
  • unpack all artifacts
  • push wheels and sdist to pypi

but it is certainly possible to do something else or do it manually.

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,9 @@ ENV/

# Pipenv
Pipfile.lock

# macOS
.DS_Store

# PyCharm
.idea/
1 change: 0 additions & 1 deletion MANIFEST.in

This file was deleted.

1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
build = "*"
tox = "*"
pytest = "*"
blurhash-python = {editable = true,extras = ["testing"],path = "."}
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ Use `tox` to run test suite against all supported python versions
```
$ tox
```

Local Build
-----------

Build source distribution and wheels into `dist/` directory.
```
$ python -m build
```
Comment on lines +63 to +69
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i removed the container build scripts since they seemed aimed at producing wheels for manual upload to PyPi and the python -m build command is suitable for testing the build locally. Other platform wheels could be downloaded from github CI artifacts during a PR review for additional testing if desired.

29 changes: 0 additions & 29 deletions build.sh

This file was deleted.

20 changes: 0 additions & 20 deletions container-build.sh

This file was deleted.

58 changes: 56 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,57 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta:__legacy__"
requires = ["setuptools>=64", "setuptools-scm>=8", "wheel", "cffi"]
build-backend = "setuptools.build_meta"

[project]
name = "blurhash-python"
description = "BlurHash encoder implementation for Python"
requires-python = ">=3.8"
readme = "README.md"
license = { file = "LICENSE" }
authors = [
{ name = "Atte Lautanala", email = "[email protected]" }
]
dynamic = ["version"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"cffi",
"Pillow",
"six",
]

[project.optional-dependencies]
testing = [
"pytest",
]

[project.urls]
Homepage = "https://blurha.sh"
Repository = "https://github.com/woltapp/blurhash-python"

[tool.setuptools.packages.find]
where = ["src"]
include = ["blurhash*"]

[tool.setuptools_scm]
version_file = "src/blurhash/_version.py"

[tool.cibuildwheel]
build-frontend = "build"
# skip platforms
# pp* - don't build any PyPy variations
# win32* - don't build 32bit windows variations
# *_i686 - don't build 32bit linux variations
# *musllinux* - don't build musl linux variations
skip = "pp* *-win32 *_i686 *musllinux*"
test-requires = "pytest"
test-command = "pytest {project}/tests"
2 changes: 0 additions & 2 deletions setup.cfg

This file was deleted.

46 changes: 0 additions & 46 deletions setup.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,51 +1,5 @@
#!/usr/bin/env python
from setuptools import setup


with open('README.md', 'r') as readme_file:
long_description = readme_file.read()


tests_require = [
'pytest',
]

setup(
name='blurhash-python',
description='BlurHash encoder implementation for Python',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://blurha.sh',
use_scm_version=dict(
write_to='src/blurhash/_version.py',
),
author='Atte Lautanala',
author_email='[email protected]',
packages=['blurhash'],
package_dir={'': 'src'},
install_requires=[
'cffi',
'Pillow',
'six',
],
setup_requires=[
'cffi',
'setuptools-scm',
],
cffi_modules=['src/build_blurhash.py:ffibuilder'],
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has no equivalent that I am aware of in the new pyproject.toml spec - alternatives all seemed to need custom build scripts.

tests_require=tests_require,
extras_require={
'testing': tests_require,
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
],
)
24 changes: 16 additions & 8 deletions tests/test_encode.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
from __future__ import absolute_import

import os

import pytest

from PIL import Image
from blurhash import encode

_test_dir = os.path.dirname(__file__)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cibuildwheel will fail to find relative paths when running tests against each wheel so resolving the paths in a different way was required.



def _get_test_file(relative_path: str) -> str:
return os.path.join(_test_dir, relative_path)


def test_encode_file():
with open('tests/pic2.png', 'rb') as image_file:
with open(_get_test_file('pic2.png'), 'rb') as image_file:
result = encode(image_file, 4, 3)

assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH'


def test_encode_pil_image():
with Image.open('tests/pic2.png') as image:
with Image.open(_get_test_file('pic2.png')) as image:
result = encode(image, 4, 3)

assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH'


def test_encode_with_filename():
result = encode('tests/pic2.png', 4, 3)
result = encode(_get_test_file('pic2.png'), 4, 3)
assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH'


def test_encode_black_and_white_picture():
result = encode('tests/pic2_bw.png', 4, 3)
result = encode(_get_test_file('pic2_bw.png'), 4, 3)
assert result == 'LjIY5?00?bIUofWBWBM{WBofWBj['


Expand All @@ -42,15 +50,15 @@ def test_file_does_not_exist():

def test_invalid_x_components():
with pytest.raises(ValueError):
encode('tests/pic2.png', 10, 3)
encode(_get_test_file('pic2.png'), 10, 3)

with pytest.raises(ValueError):
encode('tests/pic2.png', 0, 3)
encode(_get_test_file('pic2.png'), 0, 3)


def test_invalid_y_components():
with pytest.raises(ValueError):
encode('tests/pic2.png', 4, 10)
encode(_get_test_file('pic2.png'), 4, 10)

with pytest.raises(ValueError):
encode('tests/pic2.png', 4, 0)
encode(_get_test_file('pic2.png'), 4, 0)