Skip to content

Commit eef62de

Browse files
authored
Merge pull request #1 from Dandelion-Science/chore/add-ci
Setup CI
2 parents 05768af + 606db09 commit eef62de

4 files changed

Lines changed: 162 additions & 0 deletions

File tree

.github/workflows/publish.yaml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: publish
2+
concurrency:
3+
group: ${{ github.workflow }}
4+
on: # yamllint disable-line rule:truthy
5+
release:
6+
types: [published]
7+
8+
jobs:
9+
build-wheels:
10+
if: ${{ github.repository == 'Dandelion-Science/python-evdev' }}
11+
name: wheels on ${{ matrix.os }}
12+
# evdev is Linux-only (needs <linux/input.h>); no macOS/Windows wheels are
13+
# possible. Native runners build their own arch (archs = "native"), no QEMU.
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest, ubuntu-24.04-arm]
19+
timeout-minutes: 30
20+
steps:
21+
- uses: actions/checkout@v7
22+
with:
23+
fetch-depth: 0
24+
- uses: pypa/cibuildwheel@v4.1.0
25+
- uses: actions/upload-artifact@v4
26+
with:
27+
name: cibw-wheels-${{ matrix.os }}
28+
path: ./wheelhouse/*.whl
29+
30+
build-sdist:
31+
if: ${{ github.repository == 'Dandelion-Science/python-evdev' }}
32+
runs-on: ubuntu-slim
33+
timeout-minutes: 15
34+
steps:
35+
- uses: actions/checkout@v7
36+
with:
37+
fetch-depth: 0
38+
- uses: astral-sh/setup-uv@v7
39+
- run: uv build --sdist
40+
- uses: actions/upload-artifact@v4
41+
with:
42+
name: cibw-sdist
43+
path: dist/*.tar.gz
44+
45+
publish:
46+
needs: [build-wheels, build-sdist]
47+
runs-on: ubuntu-slim
48+
timeout-minutes: 15
49+
permissions:
50+
id-token: write
51+
contents: write
52+
steps:
53+
- uses: aws-actions/configure-aws-credentials@v6
54+
with:
55+
aws-region: ${{ vars.AWS_REGION }}
56+
role-to-assume: ${{ vars.ARN_IAM_ROLE_GHA_CORE }}
57+
- uses: actions/download-artifact@v4
58+
with:
59+
path: dist
60+
pattern: cibw-*
61+
merge-multiple: true
62+
- uses: softprops/action-gh-release@v3
63+
with:
64+
files: dist/*
65+
tag_name: ${{ github.event.release.tag_name }}
66+
- name: Get CodeArtifact token
67+
run: |
68+
AWS_CODEARTIFACT_TOKEN=$(aws codeartifact get-authorization-token \
69+
--domain ${{ vars.CODEARTIFACT_DOMAIN }} \
70+
--domain-owner ${{ vars.CODEARTIFACT_DOMAIN_OWNER }} \
71+
--query authorizationToken \
72+
--output text)
73+
echo "AWS_CODEARTIFACT_TOKEN=$AWS_CODEARTIFACT_TOKEN" >> $GITHUB_ENV
74+
- uses: Dandelion-Science/github-actions/setup-uv@main
75+
- run: uv publish
76+
env:
77+
UV_PUBLISH_USERNAME: aws
78+
UV_PUBLISH_PASSWORD: ${{ env.AWS_CODEARTIFACT_TOKEN }}
79+
UV_PUBLISH_URL: ${{ vars.CODEARTIFACT_PYPI_STORE_REPOSITORY_URL }}

.github/workflows/pytest.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: pytest
2+
concurrency:
3+
group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }}
4+
cancel-in-progress: true
5+
on: # yamllint disable-line rule:truthy
6+
pull_request:
7+
push:
8+
branches: [main]
9+
workflow_dispatch:
10+
11+
jobs:
12+
pytest:
13+
if: ${{ !cancelled() && github.repository == 'Dandelion-Science/python-evdev' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
14+
name: py${{ matrix.python-version }}
15+
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
python-version: ["3.13", "3.13t", "3.14", "3.14t"]
20+
timeout-minutes: 30
21+
steps:
22+
- uses: actions/checkout@v7
23+
with:
24+
fetch-depth: 0
25+
- uses: astral-sh/setup-uv@v7
26+
with:
27+
activate-environment: true
28+
python-version: ${{ matrix.python-version }}
29+
- run: uv sync -q --no-default-groups --group test
30+
# test_uinput.py needs root (opens /dev/uinput) and does not skip without it,
31+
# so the rest of the suite runs as the normal user and uinput runs under sudo.
32+
- run: pytest tests --ignore=tests/test_uinput.py
33+
- run: sudo modprobe uinput
34+
- run: sudo .venv/bin/pytest tests/test_uinput.py

pyproject.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,34 @@ classifiers = [
2828
[project.urls]
2929
"Homepage" = "https://github.com/gvalkov/python-evdev"
3030

31+
[dependency-groups]
32+
test = ["pytest"]
33+
3134
[tool.ruff]
3235
line-length = 120
3336

3437
[tool.ruff.lint]
3538
ignore = ["E265", "E241", "F403", "F401", "E401", "E731"]
3639

40+
[tool.cibuildwheel]
41+
# evdev is Linux-only (needs <linux/input.h>): no macOS/Windows wheels exist.
42+
# Build CPython 3.11-3.14 incl. the free-threaded 3.13t/3.14t builds; glibc only
43+
# (musllinux skipped), CPython only (no PyPy).
44+
build = ["cp311-*", "cp312-*", "cp313-*", "cp314-*", "cp313t-*", "cp314t-*"]
45+
enable = ["cpython-freethreading"]
46+
skip = ["*-musllinux*", "pp*"]
47+
archs = "native"
48+
build-frontend = "build[uv]"
49+
# Smoke-test that the wheel imports and every compiled extension loads.
50+
test-command = "python -c \"import evdev, evdev.ecodes, evdev._input, evdev._uinput, evdev._ecodes\""
51+
52+
[tool.cibuildwheel.linux]
53+
# manylinux images are glibc/AlmaLinux based and normally ship kernel-headers
54+
# (pulled in by glibc-devel), but evdev's build hard-fails without <linux/input.h>;
55+
# ensure they are present. No-op when already installed.
56+
before-all = "yum install -y kernel-headers || true"
57+
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
58+
3759
[tool.bumpversion]
3860
current_version = "1.9.3"
3961
commit = true

tests/test_free_threaded.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
2+
import subprocess
3+
import sys
4+
5+
import pytest
6+
7+
8+
def _is_freethreaded_build() -> bool:
9+
import sysconfig
10+
11+
return bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
12+
13+
14+
@pytest.mark.skipif(not _is_freethreaded_build(), reason="requires free-threaded Python build")
15+
def test_evdev_does_not_reenable_gil():
16+
# Run with PYTHON_GIL unset (not 0): PYTHON_GIL=0 force-disables the GIL
17+
# regardless of the module, which would make this pass even for a C extension
18+
# that has not declared Py_MOD_GIL_NOT_USED. We want the default behavior so
19+
# importing a non-free-threaded extension actually re-enables the GIL and fails.
20+
env = {k: v for k, v in os.environ.items() if k != "PYTHON_GIL"}
21+
result = subprocess.run(
22+
[sys.executable, "-c", "import sys; import evdev; assert not sys._is_gil_enabled()"],
23+
capture_output=True,
24+
text=True,
25+
env=env,
26+
)
27+
assert result.returncode == 0, f"evdev re-enabled the GIL:\n{result.stderr}"

0 commit comments

Comments
 (0)