Skip to content

Commit 559e43a

Browse files
committed
build: Wheels for more platforms
Ref: #102, #131
1 parent d91f324 commit 559e43a

8 files changed

Lines changed: 235 additions & 233 deletions

File tree

.github/workflows/build.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ jobs:
4949
python -m pip install flask
5050
# Starts the server in background
5151
python ./css-inline/tests/server.py &
52-
5352
- uses: actions-rs/toolchain@v1
5453
with:
5554
profile: minimal

.github/workflows/python-release.yml

Lines changed: 70 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -6,132 +6,107 @@ on:
66
- python-v*
77

88
jobs:
9-
create_macos_and_windows_wheels:
10-
name: Wheels for Python ${{ matrix.python-version }} / ${{ matrix.os }}
11-
strategy:
12-
matrix:
13-
os: [macos-latest, windows-latest]
14-
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
15-
architecture: [x86, x64]
16-
exclude:
17-
- os: macos-latest
18-
architecture: x86
19-
- os: windows-latest
20-
# TODO: Re-enable windows 32bits
21-
architecture: x86
22-
runs-on: ${{ matrix.os }}
23-
steps:
24-
- uses: actions/checkout@v2
25-
- uses: actions/setup-python@v2
26-
with:
27-
python-version: ${{ matrix.python-version }}
28-
architecture: ${{ matrix.architecture }}
29-
- uses: actions-rs/toolchain@v1
30-
with:
31-
profile: minimal
32-
toolchain: stable
33-
override: true
34-
- name: Install Tox
35-
run: pip install tox
36-
- name: Build wheel
37-
working-directory: ./bindings/python
38-
run: tox -e build-wheel
39-
- uses: actions/upload-artifact@v2
40-
with:
41-
name: Distribution Artifacts
42-
path: bindings/python/dist/
43-
44-
create_wheels_manylinux:
45-
name: Wheels for Python ${{ matrix.PYTHON_IMPLEMENTATION_ABI }} / Linux
9+
wheels:
10+
name: "Create wheels for ${{ matrix.BUILD.NAME }}"
11+
runs-on: ${{ matrix.BUILD.OS }}
4612
strategy:
4713
fail-fast: false
4814
matrix:
49-
# List of the language-implementation API pairs to publish wheels for
50-
# The list of supported is obtainable by running `docker run quay.io/pypa/manylinux2014_x86_64 ls /opt/python`
51-
PYTHON_IMPLEMENTATION_ABI: [cp36-cp36m, cp37-cp37m, cp38-cp38, cp39-cp39, cp310-cp310]
52-
runs-on: ubuntu-latest
53-
container: quay.io/pypa/manylinux2014_x86_64 # Builds wheels on CentOS 7 (supported until 2024)
15+
BUILD:
16+
- NAME: "Linux x86_64 manylinux"
17+
OS: "ubuntu-20.04"
18+
ARCH: "x86_64"
19+
CIBW_BUILD: "*-manylinux*"
20+
RUST_TOOLCHAIN: "x86_64-unknown-linux-gnu"
21+
- NAME: "Linux aarch64 manylinux"
22+
OS: "ubuntu-20.04"
23+
ARCH: "aarch64"
24+
CIBW_BUILD: "*-manylinux*"
25+
RUST_TOOLCHAIN: "aarch64-unknown-linux-gnu"
26+
- NAME: "Linux x86_64 musllinux"
27+
OS: "ubuntu-20.04"
28+
ARCH: "x86_64"
29+
CIBW_BUILD: "*-musllinux*"
30+
RUST_TOOLCHAIN: "x86_64-unknown-linux-musl"
31+
- NAME: "Linux aarch64 musllinux"
32+
OS: "ubuntu-20.04"
33+
ARCH: "aarch64"
34+
CIBW_BUILD: "*-musllinux*"
35+
RUST_TOOLCHAIN: "aarch64-unknown-linux-musl"
36+
- NAME: "macOS x86_64"
37+
OS: "macos-11"
38+
ARCH: "x86_64"
39+
RUST_TOOLCHAIN: "x86_64-apple-darwin"
40+
- NAME: "macOS arm64"
41+
OS: "macos-11"
42+
ARCH: "arm64"
43+
RUST_TOOLCHAIN: "aarch64-apple-darwin"
44+
- NAME: "Windows x86_64"
45+
OS: "windows-2019"
46+
ARCH: "AMD64"
47+
RUST_TOOLCHAIN: "x86_64-pc-windows-msvc"
48+
- NAME: "Windows i686"
49+
OS: "windows-2019"
50+
ARCH: "x86"
51+
RUST_TOOLCHAIN: "i686-pc-windows-msvc"
5452
env:
55-
# Variable needed for PyO3 to properly identify the python interpreter
56-
PYTHON_SYS_EXECUTABLE: /opt/python/${{ matrix.PYTHON_IMPLEMENTATION_ABI }}/bin/python
53+
# Install Rust toolchain inside the container
54+
CIBW_BEFORE_ALL: "curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -t ${{ matrix.BUILD.RUST_TOOLCHAIN }} -y --profile minimal"
55+
CIBW_ENVIRONMENT: 'PATH="$PATH:$HOME/.cargo/bin"'
56+
CIBW_BUILD: ${{ matrix.BUILD.CIBW_BUILD }}
5757
steps:
5858
- uses: actions/checkout@v2
59-
- name: Install/Update OpenSSL
60-
run: |
61-
retryCount=0
62-
# yum install seems to be flakey (due to network timeouts)
63-
# retry up to 5 times with a 10s sleep in case of failure
64-
until yum install openssl-devel --assumeyes --noplugins; do
65-
# For some reason the install has failed
66-
if [ ${retryCount} -eq 5 ]; then
67-
false
68-
else
69-
retryCount=$((${retryCount}+1))
70-
fi
71-
sleep 10
72-
done
73-
- uses: actions-rs/toolchain@v1
59+
- uses: actions/setup-python@v2
60+
- name: Setup QEMU
61+
# QEMU is needed for building aarch64 wheels on Linux
62+
if: ${{ matrix.BUILD.ARCH == 'aarch64' && runner.os == 'Linux' }}
63+
uses: docker/setup-qemu-action@v1
7464
with:
75-
profile: minimal
76-
toolchain: stable
77-
override: true
78-
- name: Install Tox
79-
run: ${{ env.PYTHON_SYS_EXECUTABLE }} -m pip install tox
80-
- name: Build wheel
81-
working-directory: ./bindings/python
82-
run: |
83-
${{ env.PYTHON_SYS_EXECUTABLE }} -m tox -e build-wheel
84-
# Ensure that the wheel is tagged as manylinux2014 platform
85-
auditwheel repair \
86-
--wheel-dir=./dist \
87-
--plat manylinux2014_x86_64 \
88-
./dist/css_inline-*-${{ matrix.PYTHON_IMPLEMENTATION_ABI }}-linux_x86_64.whl
89-
# Remove `linux_x86_64` tagged wheels as they are not supported by https://pypi.org
90-
# Example https://github.com/Stranger6667/jsonschema-rs/runs/766075274
91-
rm ./dist/css_inline-*-${{ matrix.PYTHON_IMPLEMENTATION_ABI }}-linux_x86_64.whl
65+
platforms: all
66+
- name: Install cibuildwheel
67+
run: python -m pip install cibuildwheel
68+
- name: Build wheels
69+
run: python -m cibuildwheel bindings/python --output-dir wheelhouse
70+
env:
71+
CIBW_ARCHS: ${{ matrix.BUILD.ARCH }}
9272
- uses: actions/upload-artifact@v2
9373
with:
9474
name: Distribution Artifacts
95-
path: bindings/python/dist/
75+
path: wheelhouse/*.whl
9676

97-
create_source_dist:
77+
sdist:
9878
name: Create sdist package
99-
runs-on: ubuntu-latest
79+
runs-on: ubuntu-20.04
10080
steps:
10181
- uses: actions/checkout@v2
10282
- uses: actions/setup-python@v2
10383
with:
104-
python-version: 3.7
105-
- uses: actions-rs/toolchain@v1
106-
with:
107-
profile: minimal
108-
toolchain: stable
109-
override: true
84+
python-version: 3.9
11085
- name: Install Tox
11186
run: pip install tox
87+
- run: mkdir wheelhouse
11288
- name: Build sdist
89+
run: tox -e build-sdist && mv dist/css_inline-*.tar.gz ../../wheelhouse
11390
working-directory: ./bindings/python
114-
run: tox -e build-sdist
11591
- uses: actions/upload-artifact@v2
11692
with:
11793
name: Distribution Artifacts
118-
path: bindings/python/dist/
94+
path: wheelhouse/*.tar.gz
11995

12096
upload_to_pypi:
12197
needs:
122-
- create_macos_and_windows_wheels
123-
- create_wheels_manylinux
124-
- create_source_dist
98+
- wheels
99+
- sdist
125100
name: Upload Artifacts to PyPi
126-
runs-on: ubuntu-latest
101+
runs-on: ubuntu-20.04
127102
steps:
128103
- uses: actions/download-artifact@v2
129104
with:
130-
name: Distribution Artifacts
131-
path: bindings/python/dist/
105+
name: Download Artifacts
106+
path: wheelhouse/
132107
- name: Publish distribution package to PyPI
133-
uses: pypa/gh-action-pypi-publish@v1.2.2
108+
uses: pypa/gh-action-pypi-publish@v1
134109
with:
135110
user: ${{ secrets.PYPI_USERNAME }}
136111
password: ${{ secrets.PYPI_PASSWORD }}
137-
packages_dir: bindings/python/dist/
112+
packages_dir: wheelhouse/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ To use it in your project add the following line to your `dependencies` section
3838
css-inline = "0.7"
3939
```
4040

41+
Minimum Supported Rust Version is 1.53.
42+
4143
## Usage
4244

4345
```rust

bindings/python/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- Include missing stylesheet path to `InlineError` message. [#124](https://github.com/Stranger6667/css-inline/issues/124)
8+
- Build wheels for more platforms, including CPython on Apple Silicon, and PyPy on x86_64. [#102](https://github.com/Stranger6667/css-inline/issues/102), [#131](https://github.com/Stranger6667/css-inline/issues/131)
89

910
## [0.7.8] - 2022-01-08
1011

bindings/python/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ default-features = false
2121
[dependencies]
2222
url = "2"
2323
rayon = "1"
24-
pyo3 = { version = "^0.15", features = ["extension-module"] }
24+
pyo3 = { version = "^0.15", features = ["extension-module", "abi3"] }
2525
pyo3-built = "0.4"
2626

2727
[profile.release]

bindings/python/README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
css_inline
2+
==========
3+
4+
[![Build](https://github.com/Stranger6667/css-inline/workflows/ci/badge.svg)](https://github.com/Stranger6667/css_inline/actions)
5+
[![PyPI](https://img.shields.io/pypi/v/css_inline.svg)](https://pypi.org/project/css_inline/)
6+
[![Python versions](https://img.shields.io/pypi/pyversions/css_inline.svg)](https://pypi.org/project/css_inline/)
7+
[![License](https://img.shields.io/pypi/l/css_inline.svg)](https://opensource.org/licenses/MIT)
8+
9+
Blazing-fast CSS inlining for Python implemented with Mozilla's Servo project components.
10+
11+
Features:
12+
13+
- Removing `style` tags after inlining;
14+
- Resolving external stylesheets (including local files);
15+
- Control if `style` tags should be processed;
16+
- Additional CSS to inline;
17+
- Inlining multiple documents in parallel (via Rust-level threads)
18+
19+
The project supports CSS Syntax Level 3.
20+
21+
Installation
22+
------------
23+
24+
To install `css_inline` via `pip` run the following command:
25+
26+
```
27+
pip install css_inline
28+
```
29+
30+
Pre-compiled wheels for most popular platforms are provided. If your platform is not in the support table below, you will need
31+
a Rust compiler to build this package from source. The minimum supported Rust version is 1.53.
32+
33+
Usage
34+
-----
35+
36+
To inline CSS in a HTML document:
37+
38+
```python
39+
import css_inline
40+
41+
HTML = """<html>
42+
<head>
43+
<title>Test</title>
44+
<style>h1 { color:blue; }</style>
45+
</head>
46+
<body>
47+
<h1>Big Text</h1>
48+
</body>
49+
</html>"""
50+
51+
inlined = css_inline.inline(HTML)
52+
# HTML becomes this:
53+
#
54+
# <html>
55+
# <head>
56+
# <title>Test</title>
57+
# <style>h1 { color:blue; }</style>
58+
# </head>
59+
# <body>
60+
# <h1 style="color:blue;">Big Text</h1>
61+
# </body>
62+
# </html>
63+
```
64+
65+
If you want to inline many HTML documents, you can utilize `inline_many` that processes the input in parallel.
66+
67+
```python
68+
import css_inline
69+
70+
css_inline.inline_many(["<...>", "<...>"])
71+
```
72+
73+
`inline_many` will use Rust-level threads; thus, you can expect it's running faster than `css_inline.inline` via Python's `multiprocessing` or `threading` modules.
74+
75+
For customization options use the `CSSInliner` class:
76+
77+
```python
78+
import css_inline
79+
80+
inliner = css_inline.CSSInliner(remove_style_tags=True)
81+
inliner.inline("...")
82+
```
83+
84+
Performance
85+
-----------
86+
87+
Due to the usage of efficient tooling from Mozilla's Servo project (`html5ever`, `rust-cssparser` and others) this
88+
library has excellent performance characteristics. In comparison with other Python projects, it is ~9-21x faster than the nearest alternative.
89+
90+
For inlining CSS in the html document from the `Usage` section above there is the following breakdown in the benchmarks:
91+
92+
- `css_inline 0.7.8` - 21.76 us
93+
- `premailer 3.10.0` - 461.54 us (**x21.21**)
94+
- `toronado 0.1.0` - 1.87 ms (**x85.93**)
95+
- `inlinestyler 0.2.4` - 2.85 ms (**x130.97**)
96+
- `pynliner 0.8.0` - 3.34 ms (**x153.49**)
97+
98+
And for a more realistic email:
99+
100+
- `css_inline 0.7.8` - 433.39 us
101+
- `premailer 3.10.0` - 3.9 ms (**x9.01**)
102+
- `toronado 0.1.0` - 43.89 ms (**x101.27**)
103+
- `inlinestyler 0.2.4` - 75.77 ms (**x174.83**)
104+
- `pynliner 0.8.0` - 123.6 ms (**x285.19**)
105+
106+
You can take a look at the benchmarks' code at `benches/bench.py` file.
107+
The results above were measured with stable `rustc 1.57.0`, `Python 3.9.9`, `Linux x86_64` on i8700K, and 32GB RAM.
108+
109+
Python support
110+
--------------
111+
112+
`css_inline` supports CPython 3.6, 3.7, 3.8, 3.9, 3.10, and PyPy 3.7 and 3.8.
113+
114+
The following wheels are available:
115+
116+
| | manylinux<br/>musllinux<br/>x86_64 | manylinux<br/>musllinux<br/>aarch64 | macOS Intel | macOS ARM64 | Windows 64bit | Windows 32bit |
117+
|----------------|:----------------:|:-----------------:|:-----------:|:-----------:|:-------------:|:-------------:|
118+
| CPython 3.6 ||| ✔️ | N/A | ✔️ | ✔️ |
119+
| CPython 3.7 ||| ✔️ | N/A | ✔️ | ✔️ |
120+
| CPython 3.8 ||| ✔️ | ✔️ | ✔️ | ✔️ |
121+
| CPython 3.9 ||| ✔️ | ✔️ | ✔️ | ✔️ |
122+
| CPython 3.10 ||| ✔️ | ✔️ | ✔️ | ✔️ |
123+
| PyPy 3.7 v7.3 | ✔¹ | ✔¹ | ✔️ | N/A | ✔️ | N/A |
124+
| PyPy 3.8 v7.3 | ✔¹ | ✔¹ | ✔️ | N/A | ✔️ | N/A |
125+
126+
<sup>¹ PyPy is only supported for manylinux wheels.</sup><br>
127+
128+
Extra materials
129+
---------------
130+
131+
If you want to know how this library was created & how it works internally, you could take a look at these articles:
132+
133+
- [Rust crate](https://dygalo.dev/blog/rust-for-a-pythonista-2/)
134+
- [Python bindings](https://dygalo.dev/blog/rust-for-a-pythonista-3/)
135+
136+
License
137+
-------
138+
139+
The code in this project is licensed under [MIT license](https://opensource.org/licenses/MIT).
140+
By contributing to `css_inline`, you agree that your contributions
141+
will be licensed under its MIT license.

0 commit comments

Comments
 (0)