Skip to content

Commit 969ee17

Browse files
authored
v1.0.0: Setup CI/CD using cibuildwheel + GitHub Actions, Makefile, build (#2)
* v1.0.0: Setup CI/CD using cibuildwheel + GitHub Actions, Makefile, build - Makefile that allows in-place testing using make test - setup.py that always turns on optimizations on Linux prod builds - GitHub Actions/cibuildhweel config isolating the tests from source - Badges - v1.0.0 - Update benchmarks: now 40x faster than wcwidth
1 parent 688ca42 commit 969ee17

File tree

6 files changed

+213
-10
lines changed

6 files changed

+213
-10
lines changed

.github/workflows/build_deploy.yml

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
---
2+
name: Build and upload
3+
4+
on: # yamllint disable-line rule:truthy
5+
push:
6+
paths-ignore:
7+
- 'README.md'
8+
release:
9+
types: [created]
10+
11+
jobs:
12+
build_wheels:
13+
name: Build wheels on ${{ matrix.os }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
matrix:
17+
os: [ubuntu-latest, windows-latest, macos-latest]
18+
19+
if: github.event_name == 'release' && github.event.action == 'created'
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Build wheels
24+
uses: pypa/[email protected]
25+
env:
26+
CIBW_TEST_COMMAND_WINDOWS: >
27+
cd /d {package}
28+
&& ( rmdir ..\uwcwidth_tmp /s /q 2>NUL || cd . )
29+
&& mkdir ..\uwcwidth_tmp
30+
&& cd ..\uwcwidth_tmp
31+
&& xcopy {package} /s
32+
&& rmdir uwcwidth /s /q
33+
&& pytest
34+
35+
- uses: actions/upload-artifact@v4
36+
with:
37+
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
38+
path: ./wheelhouse/*.whl
39+
40+
build_sdist:
41+
name: Build source distribution
42+
runs-on: ubuntu-latest
43+
if: github.event_name == 'release' && github.event.action == 'created'
44+
steps:
45+
- uses: actions/checkout@v4
46+
47+
- name: Install dependencies
48+
run: python3 -m pip install --upgrade build
49+
50+
- name: Build sdist
51+
run: python3 -m build --sdist
52+
53+
- uses: actions/upload-artifact@v4
54+
with:
55+
name: cibw-sdist
56+
path: dist/*.tar.gz
57+
58+
upload_pypi:
59+
needs: [build_wheels, build_sdist]
60+
runs-on: ubuntu-latest
61+
environment: production
62+
permissions:
63+
id-token: write
64+
if: |
65+
github.event_name == 'release' && github.event.action == 'created'
66+
&& !endsWith(github.ref, '-test')
67+
steps:
68+
- uses: actions/download-artifact@v4
69+
with:
70+
# unpacks all CIBW artifacts into dist/
71+
pattern: cibw-*
72+
path: dist
73+
merge-multiple: true
74+
75+
- uses: pypa/gh-action-pypi-publish@release/v1
76+
77+
upload_pypi_test:
78+
needs: [build_wheels, build_sdist]
79+
runs-on: ubuntu-latest
80+
environment: production
81+
permissions:
82+
id-token: write
83+
if: |
84+
github.event_name == 'release' && github.event.action == 'created'
85+
&& endsWith(github.ref, '-test')
86+
steps:
87+
- uses: actions/download-artifact@v4
88+
with:
89+
# unpacks all CIBW artifacts into dist/
90+
pattern: cibw-*
91+
path: dist
92+
merge-multiple: true
93+
94+
- uses: pypa/gh-action-pypi-publish@release/v1
95+
with:
96+
repository-url: https://test.pypi.org/legacy/
97+
98+
upload_gh_release:
99+
needs: [build_wheels, build_sdist]
100+
runs-on: ubuntu-latest
101+
environment: production
102+
permissions:
103+
contents: write
104+
id-token: write
105+
106+
steps:
107+
- uses: actions/download-artifact@v4
108+
with:
109+
# unpacks all CIBW artifacts into dist/
110+
pattern: cibw-*
111+
path: dist
112+
merge-multiple: true
113+
114+
- uses: sigstore/[email protected]
115+
with:
116+
inputs: >-
117+
./dist/*.tar.gz
118+
./dist/*.whl
119+
120+
- uses: softprops/action-gh-release@v2
121+
with:
122+
files: dist/**

.github/workflows/test.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
paths-ignore:
6+
- 'README.md'
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-python@v5
15+
with:
16+
python-version: '3.13'
17+
- run: make test

Makefile

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.PHONY: all build build-debug clean clean-build clean-venv test
2+
3+
all: build venv
4+
@:
5+
6+
build: venv
7+
DEBUG=$(DEBUG) venv/bin/python3 setup.py build_ext --inplace
8+
9+
build-debug: override DEBUG=1
10+
build-debug: build ;
11+
12+
clean: clean-venv clean-build
13+
@:
14+
15+
clean-venv:
16+
rm -rf venv
17+
18+
clean-build:
19+
rm -rf build
20+
21+
test: build
22+
venv/bin/pytest
23+
24+
venv:
25+
python3 -mvenv venv
26+
venv/bin/pip install setuptools Cython pytest

README.md

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
## Overview
1+
## uwcwidth
2+
*terminal width of Unicode 16.0+Emoji strings in nanoseconds*
3+
4+
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/uwcwidth)
5+
[![PyPI - Version](https://img.shields.io/pypi/v/uwcwidth)](https://pypi.org/project/uwcwidth/)
6+
[![PyPI - License](https://img.shields.io/pypi/l/uwcwidth)](https://github.com/Z4JC/uwcwidth/blob/main/LICENSE)
7+
![PyPI - Downloads](https://img.shields.io/pypi/dm/uwcwidth)<br>
8+
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Z4JC/uwcwidth/build_deploy.yml)](https://github.com/Z4JC/uwcwidth/actions/workflows/build_deploy.yml)
9+
[![GitHub branch check runs](https://img.shields.io/github/check-runs/Z4JC/uwcwidth/main)](https://github.com/Z4JC/uwcwidth/actions/workflows/test.yml)
10+
![PyPI - Status](https://img.shields.io/pypi/status/uwcwidth)
11+
![PyPI - Wheel](https://img.shields.io/pypi/wheel/uwcwidth)<br>
12+
213
Use `uwcwidth` when you want to very quickly find out how many characters a Unicode string takes up in your terminal.
314

415
For example, `uwcwidth.wcswidth('Hello🥹')` returns `7` because your terminal will use 5 places for "Hello" and then 2 places for the "🥹" emoji.
@@ -40,20 +51,20 @@ See the `tests` folder for more.
4051
`uwcwidth` reserves around 4 KB of memory for its lookup tables. Parts of the storage scheme are derived from an older `wcwidth` implementation in [musl libc](https://musl.libc.org/). Generally sparse or dense bitmaps are used to look things up.
4152
The `uwcwidth.pyx` file is under 100 lines of code, with comments and whitespace.
4253

43-
## Performance: 30x faster than `wcwidth`
44-
`uwcwidth` is about 30 times faster than the popular, well-documented and highly tested [wcwidth](https://github.com/jquast/wcwidth) library, while maintaining similar accuracy. It's also 5 times faster than `cwcwidth`, which does not work on new Emojis and breaks on some other edge cases.
54+
## Performance: 40x faster than `wcwidth`
55+
`uwcwidth` is about 40 times faster than the popular, well-documented and highly tested [wcwidth](https://github.com/jquast/wcwidth) library, while maintaining similar accuracy. It's also 5 times faster than `cwcwidth`, which does not work on new Emojis and breaks on some other edge cases.
4556

4657
```python3
4758
In [1]: import wcwidth, cwcwidth, uwcwidth
4859
In [2]: %%timeit
4960
...: wcwidth.wcswidth("コンニチハ, セカイ!")
50-
1.28 μs ± 6.22 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
61+
1.73 μs ± 7.93 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
5162

5263
In [3]: %%timeit
5364
...: cwcwidth.wcswidth("コンニチハ, セカイ!")
54-
205 ns ± 0.408 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
65+
211 ns ± 3.63 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
5566

5667
In [4]: %%timeit
5768
...: uwcwidth.wcswidth("コンニチハ, セカイ!")
58-
38.5 ns ± 0.29 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
59-
```
69+
41 ns ± 0.0363 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
70+
```

pyproject.toml

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uwcwidth"
3-
version = "0.9.2"
3+
version = "1.0.0"
44
authors = [{name = "!ZAJC!"}]
55
readme = "README.md"
66
description = "terminal width of Unicode 16.0+Emoji strings in nanoseconds"
@@ -33,5 +33,17 @@ exclude = []
3333
namespaces = false
3434

3535
[tool.pytest.ini_options]
36+
pythonpath = ["."]
3637
testpaths = ["tests"]
3738
addopts = ["--import-mode=importlib"]
39+
40+
[tool.cibuildwheel]
41+
build-frontend = "build"
42+
test-command = """
43+
cd $( mktemp -d ) \
44+
&& cp -pr {project}/* ./ \
45+
&& rm -rf uwcwidth \
46+
&& pytest
47+
"""
48+
test-requires = "pytest"
49+
skip = ["cp36-*", "cp37-*"]

setup.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
# SPDX-License-Identifier: MIT
2+
import os
3+
import platform
4+
25
from setuptools import setup, Extension
36

7+
8+
DEBUG=(os.getenv('DEBUG') or '').strip().lower() in ['1', 'y', 'true']
9+
MSVC=(platform.platform().startswith('Windows') and
10+
platform.python_compiler().startswith('MS'))
11+
COMPILE_ARGS=[] if MSVC else (["-g", "-O0", "-UNDEBUG"] if DEBUG else ["-O3"])
12+
13+
14+
def uwcwidth_ext(module, pyx_file):
15+
return Extension(module,
16+
sources=[pyx_file],
17+
extra_compile_args=COMPILE_ARGS)
18+
19+
420
setup(
521
name='uwcwidth',
6-
ext_modules=[Extension("uwcwidth.uwcwidth",
7-
sources=["uwcwidth/uwcwidth.pyx"])],
22+
ext_modules=[uwcwidth_ext("uwcwidth.uwcwidth", "uwcwidth/uwcwidth.pyx")],
823
package_data={'uwcwidth': ['__init__.pxd', 'uwcwidth.pxd', 'tables.pxd']}
924
)

0 commit comments

Comments
 (0)