ksp-builder
A PEP 517 build backend for KSProject-based packages that combines Cython compilation, Kivy KV compilation, Java source injection (pyjnius-builder convention), Swift build support (pyswiftkit-builder), and Android Gradle configuration injection into a single backend.
Requires Python ≥ 3.13 (uses tomllib from the standard library and aligns
with the minimum version requirements of pyswiftkit-builder and pyjnius-builder).
Usage
[build-system]
requires = ["ksp-builder"]
build-backend = "ksp_builder"
Configuration
Build scripts — [tool.ksp-builder]
Run your own scripts around the build. before_build runs before the artifact
is assembled, after_build runs once it exists on disk. Both keys are optional
and paths are relative to the project root.
[tool.ksp-builder]
before_build = "scripts/before_build.py"
after_build = "scripts/sign_wheel.sh"
The scripts do not have to be Python. A .py path is run with the interpreter
running the build; any other path is executed directly, so it needs its
executable bit set (chmod +x) and a shebang naming its interpreter:
#!/usr/bin/env bash
set -e
codesign --sign "$SIGNING_IDENTITY" "$KSP_BUILD_ARTIFACT"
Scripts run with the project root as their working directory. after_build
receives the artifact path as its first argument. Both are given the
environment variables KSP_BUILD_PROJECT_ROOT, KSP_BUILD_TARGET
(wheel, sdist or editable), and — for after_build —
KSP_BUILD_ARTIFACT. A script that exits non-zero fails the build.
Cython compilation — [tool.ksp-builder]
Compiles your package sources into extension modules, replacing the
cythonized_app setup.py (there is no setup.py with a PEP 517 backend, so
ksp-builder injects the ext_modules itself).
[tool.ksp-builder]
cythonize = true
py_to_pyx = true
The two switches are separate on purpose:
| Setting | Effect |
|---|---|
cythonize = true |
Compiles the .pyx sources already in your packages. .py modules are untouched. |
py_to_pyx = true |
Also copies each .py module to .dist_src/ as a .pyx and compiles it. Requires cythonize — on its own it does nothing. |
__init__.py and __main__.py are never converted — the first keeps the
package importable, the second keeps python -m yourpackage working, since
runpy needs a code object that a compiled extension cannot provide. Both are
still shipped, just as .py. Top-level modules (py-modules) are left alone
too. A converted module ships only as its
compiled extension: the original .py is dropped from the wheel. Generated
.pyx and C files are written to .dist_src/ at the project root, which you
can add to .gitignore. Nothing is ever written back into your package: a
build leaves the source tree exactly as it found it.
.kv files and other assets
Plain setuptools ships only Python modules, so a cythonized Kivy app would build
fine and then fail at runtime with its .kv files missing. Whenever it is
compiling something (cythonize or compile_kv), ksp-builder therefore
ships every file in your packages as package data —
.kv, images, fonts, JSON, whatever is there — including files in nested data
directories such as assets/images/. No configuration needed.
kvapp/__init__.py -> kvapp/__init__.py
kvapp/app.py -> kvapp/app.cpython-313-darwin.so
kvapp/app.kv -> kvapp/app.kv
kvapp/data/images/logo.png -> kvapp/data/images/logo.png
kvapp/fonts/Roboto.ttf -> kvapp/fonts/Roboto.ttf
Assets land next to the compiled extension, so Path(__file__).parent still
finds them. Directories containing an __init__.py are skipped here and
collected as the packages they are. (With compile_kv = true the .kv files
are the exception: they are compiled into their modules instead of shipped —
see below.)
Sources and build output are the one exception — .py, .pyx, .pxd, .c,
.h, .o, .so, .pyd, .pyc and __pycache__ are never shipped as assets,
since that would hand back the very sources the compilation just removed. To
ship one of those deliberately (a prebuilt binary, say), name it explicitly in
[tool.setuptools.package-data], which is merged with what is found here.
Set include_assets = false to turn the whole behaviour off and go back to
declaring package data yourself.
Package layout still comes from [tool.setuptools] — packages, package-dir
and packages.find are read as-is (with the same src/flat auto-discovery
setuptools does), so nothing is duplicated in this section. The remaining keys
only tune the Cython step:
[tool.ksp-builder]
cythonize = true
py_to_pyx = true
cythonize_exclude = ["main.py", "**/legacy/*.py"] # keep these as .py
cythonize_keep_py = true # ship the .py sources too
include_assets = false # stop auto-shipping assets
[tool.ksp-builder.cythonize_directives]
language_level = "3" # the default
boundscheck = false
A pattern in cythonize_exclude without a / matches that file name at any
depth; a pattern with one is matched against the whole project-relative path.
Editable installs (pip install -e .) compile real .pyx sources but never
convert .py, so your edits keep taking effect during development. Sdists are
never cythonized — compilation happens when a wheel is built from the sdist.
Note that setuptools does not add .pyx files to an sdist on its own, so if you
ship hand-written Cython sources, include them (MANIFEST.in with
recursive-include <pkg> *.pyx *.pxd) or a wheel built from the sdist will have
nothing to compile.
Kivy KV compilation — [tool.ksp-builder]
[tool.ksp-builder]
compile_kv = true
Turns every .kv file in your packages into a Python module using the
compilekv library: the KV rules
become real widget classes, so nothing has to be parsed at startup and the
result can be compiled like any other module.
Each .kv is compiled together with the .py of the same name beside it, and
the module the two produce replaces that .py in the wheel. A .kv with no
.py beside it simply becomes a module of its own. The .kv itself is not
shipped — it has been compiled into the module, and shipping it too would only
invite a second, conflicting set of rules at runtime.
kvapp/app.py + kvapp/app.kv -> kvapp/app.py (the two, merged)
kvapp/theme.kv -> kvapp/theme.py (no .py of its own)
Only .kv files sitting directly in a package are compiled, since a
compiled KV file becomes an importable module and there is no module name for
one in a plain data directory. A .kv under kvapp/ui/ stays ordinary
package data.
All three switches together
compile_kv composes with the Cython switches. With all three on, the module
compiled out of the .kv is staged as a .pyx and compiled to an extension,
so neither the .kv nor the .py that fed it reaches the wheel:
[tool.ksp-builder]
cythonize = true
py_to_pyx = true
compile_kv = true
kvapp/__init__.py -> kvapp/__init__.py
kvapp/__main__.py -> kvapp/__main__.py
kvapp/app.py + kvapp/app.kv -> kvapp/app.cpython-313-darwin.so
kvapp/theme.kv -> kvapp/theme.cpython-313-darwin.so
kvapp/assets/logo.png -> kvapp/assets/logo.png
A module named by cythonize_exclude is still compiled out of its KV file, it
just stays a plain .py. __init__ and __main__ are never turned into
extensions here either.
Editable installs (pip install -e .) leave .kv files alone — the source
tree is what gets imported, so a generated module would never be used and your
.kv edits keep taking effect during development.
Android Gradle config — [tool.kivy-school.android]
When present, ksp-builder generates a .gradle/<package_name>.json file and
injects it into the built wheel and sdist. ksproject can then discover and
merge these JSON files from all installed packages to assemble the final Gradle
build configuration (permissions, gradle dependencies, etc.).
[tool.kivy-school]
app_name = "MyApp"
[tool.kivy-school.android]
package_name = "org.example.myapp"
gradle_dependencies = [
"com.google.firebase:firebase-analytics:21.0.0",
]
permissions = [
"INTERNET",
"CAMERA",
]
[tool.kivy-school.android.meta_data]
"com.google.android.gms.ads.APPLICATION_ID" = "ca-app-pub-3940256099942544~3347511713"
Java sources — [tool.pyjnius]
Java source files are injected into the wheel and sdist under .java/, following
the pyjnius-builder convention.
[tool.pyjnius]
java-paths = ["java/"]
Swift packages — [tool.pyswiftkit]
If pyswiftkit-builder is installed and [tool.pyswiftkit] is configured,
swift build runs automatically before the wheel is assembled and the compiled
artifacts are injected into the wheel.
[tool.pyswiftkit]
products = ["mymodule"]
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ksp_builder-0.0.1.tar.gz.
File metadata
- Download URL: ksp_builder-0.0.1.tar.gz
- Upload date:
- Size: 30.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef8207421d046a3a5d2d968c0f372e0a18aa1516b96607071346628fdb2f8e33
|
|
| MD5 |
8ff05c8e0ee5254158f1769847fce0c5
|
|
| BLAKE2b-256 |
743b04ed99b944c6504fb358286beae313ce9c1abc160aaf7a3a8e4f51cddee3
|
Provenance
The following attestation bundles were made for ksp_builder-0.0.1.tar.gz:
Publisher:
pypi-release.yml on kivy-school/ksp-builder
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ksp_builder-0.0.1.tar.gz -
Subject digest:
ef8207421d046a3a5d2d968c0f372e0a18aa1516b96607071346628fdb2f8e33 - Sigstore transparency entry: 2814936587
- Sigstore integration time:
-
Permalink:
kivy-school/ksp-builder@53ca397e43db9e8b8e3ff8f788cb7457767f4a70 -
Branch / Tag:
refs/tags/0.0.1 - Owner: https://github.com/kivy-school
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@53ca397e43db9e8b8e3ff8f788cb7457767f4a70 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ksp_builder-0.0.1-py3-none-any.whl.
File metadata
- Download URL: ksp_builder-0.0.1-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
202730118801d88348c0bb2cc6d58a42dbdb7722d3fef495af898de2d70cc715
|
|
| MD5 |
b8c6b2b8686cbd5a27b0d66c69f2475c
|
|
| BLAKE2b-256 |
5939e837c082ff7abb1c9721a768e64cd6388b35eec6dda603963dca976dd12c
|
Provenance
The following attestation bundles were made for ksp_builder-0.0.1-py3-none-any.whl:
Publisher:
pypi-release.yml on kivy-school/ksp-builder
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ksp_builder-0.0.1-py3-none-any.whl -
Subject digest:
202730118801d88348c0bb2cc6d58a42dbdb7722d3fef495af898de2d70cc715 - Sigstore transparency entry: 2814936647
- Sigstore integration time:
-
Permalink:
kivy-school/ksp-builder@53ca397e43db9e8b8e3ff8f788cb7457767f4a70 -
Branch / Tag:
refs/tags/0.0.1 - Owner: https://github.com/kivy-school
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@53ca397e43db9e8b8e3ff8f788cb7457767f4a70 -
Trigger Event:
push
-
Statement type: