Skip to content

FAQ

Where this page comes from

This page is generated from FAQ.md in the kivyforge repository when the site is built. Links to design documents and other repository files open on GitHub.

Introduction

kivyforge is a declarative, PEP 621-aligned build toolchain for Kivy (and other Python) apps — the successor to kivy-ios, python-for-android, and buildozer. You describe your app once in pyproject.toml and kivyforge packages it for the platform you target. The goal is to support every platform Kivy runs on, and all five — Android, iOS, Linux, macOS, and Windows — are implemented. On iOS the toolchain resolves dependencies into a lockfile, downloads the official Python.xcframework plus prebuilt iOS wheels, and generates an Xcode .xcodeproj; on Android it does the same with the per-ABI python.org Android runtime and Android wheels and generates a Gradle/AGP project that produces a signed .apk/.aab; the desktop backends bundle a relocatable CPython plus wheels into a .app (macOS), an AppImage/AppDir (Linux), or a run-from-folder onedir (Windows).

For the full workflow see the README; for design and reference details see the design docs. When something looks wrong, kivyforge doctor runs environment and project health checks and is a good first stop.

FAQ

kivyforge: command not found

The kivyforge script (and its kf alias) is installed into your virtual environment. Activate it (. .venv/bin/activate) and make sure kivyforge is installed (pip install -e ".[dev]" from the repo).

Error: SDK "iphonesimulator" cannot be located

The active Xcode path is not set correctly. Point xcode-select at your Xcode:

sudo xcode-select --switch /Applications/Xcode.app

If the command line tools are missing, install them with xcode-select --install.

kivyforge build says the lock is out of sync

Your pyproject.toml changed since pylock.ios.toml was generated. Re-resolve:

kivyforge lock

That re-lock takes the newest version each specifier in [project].dependencies allows. Require an exact version, such as kivy==2.3.1, to hold a package still. See Pin a package version.

In CI, kivyforge lock --check exits non-zero when the lock is stale (it writes nothing). Use --no-verify-lock on build only if you intentionally want to skip the drift check.

Downloading Python.xcframework fails with HTTP 404

[tool.kivy.ios.python].version must match a build that python.org actually publishes. iOS support is new, so during the preview period you may need a prerelease such as 3.15.0b4 rather than a final 3.15.0. Set the version to a published release and re-run kivyforge lock.

"invalid character in Bundle Identifier"

A bundle identifier is a UTI: only letters, digits, hyphen (-), and period (.) are allowed — no underscores. Fix [tool.kivy.ios].bundle_id, e.g. use org.example.hello-world instead of org.example.hello_world.

I edited my Python source but the app didn't change

Editing Python source does not require kivyforge build: the generated project links your source directory (app/ is a symlink to app_dir), so just relaunch — kivyforge run --simulator, or ⌘R in Xcode. Re-run kivyforge build only when you change app config or need to regenerate the project, and kivyforge clean to reset the generated <app>-ios/ folder for a fresh build.

Where are downloaded artifacts stored?

Python.xcframework and other xcframeworks are cached under ~/Library/Caches/kivy-ios/artifacts/ and shared across projects. Flush the cache with kivyforge clean --cache, or force a fresh download for one build with kivyforge build --no-cache.

Can I bundle a plain Python app without Kivy?

Yes. List no Kivy in dependencies and the toolchain bundles a pure-Python app (this is what examples/mobile/hello-world does). It runs Python directly with no UI — ideal as a smoke test of the toolchain or for validating pure-Python code on-device. To ship an actual app you still need a UI layer: Kivy (via SDL), or a native bridge such as rubicon-objc/pyobjus that your Python code drives.

Android builds warn "This version only understands SDK XML versions up to 3"

Android builds can print this before Gradle starts:

Warning: SDK processing. This version only understands SDK XML versions up to 3 but an SDK XML file of version 4 was encountered. This can happen if you use versions of Android Studio and the command-line tools that were released at different times.

The warning is harmless: the build reads the SDK correctly and succeeds. You don't need to change anything.

Every package in the Android SDK has a package.xml written by the tool that installed it. Recent versions of Android Studio's SDK Manager and the Android SDK command-line tools write format version 4. The Android Gradle plugin that kivyforge pins (AGP_VERSION in kivyforge/platforms/android/toolchain.py) reads it but expects version 3, so it prints the warning. kivyforge also installs the pinned NDK and CMake with the SDK's own sdkmanager, so even an SDK that started without version-4 files can have them after a build.

Only Android Gradle plugin 9.2 or later reads version 4 without the warning. Moving to it is a larger change for kivyforge projects, so kivyforge stays on its current pin for now. See issue #91.

macOS Developer ID signing fails with errSecInternalComponent

If kivyforge package -p macos (with [tool.kivy.macos.signing] configured) fails on every Mach-O with errSecInternalComponent — even after retrying, even on files that ad-hoc sign fine — your login keychain's private key ACL is likely corrupted rather than the error being transient. This has been observed after using Keychain Access's "reset my default keychain" flow; the corruption can persist across reboots and re-issuing the certificate.

Check for the smoking gun:

security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k <password> login.keychain-db

If this aborts with SecKeychainItemCopyAccess: The specified item is no longer valid, the fix is to stop fighting the corrupted keychain and isolate signing into a fresh one:

security create-keychain -p <new-password> signing.keychain-db
security list-keychains -d user -s signing.keychain-db login.keychain-db
security unlock-keychain -p <new-password> signing.keychain-db
security set-keychain-settings signing.keychain-db
security default-keychain -s signing.keychain-db
# In Xcode: Settings → Accounts → Manage Certificates → + → Developer ID Application
# (it's created in signing.keychain-db since that's now default)
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k <new-password> signing.keychain-db
security default-keychain -s login.keychain-db   # restore normal default

If codesign then reports ambiguous (a same-named certificate matches in both keychains), delete the broken copy from login.keychain-db (Keychain Access → Certificates tab, right-click → Delete) so only the working one in signing.keychain-db remains. Recreate your xcrun notarytool store-credentials profile too if it was wiped by the same reset — pass --keychain ~/Library/Keychains/signing.keychain-db to keep it alongside the signing identity, immune to future login-keychain resets. See the "Developer ID sign + notarize + staple" section of the macOS spec for the full config reference.

This isolated-keychain approach isn't a workaround unique to kivyforge — it's Apple's own recommendation. Apple DTS's The Care and Feeding of Developer ID suggests keeping a Developer ID identity in its own keychain (separate password/locking policy from the login keychain) precisely because these identities are hard to replace, and also recommends exporting a .p12 backup so a corrupted or reset keychain never puts you in this position again. For the general errSecInternalComponent failure mode (locked keychains, ACL prompts, SSH/CI contexts) — as opposed to the specific corrupted-item case above — see Apple DTS's Resolving errSecInternalComponent errors during code signing.

kivyforge doctor checks for this proactively: the Signing identity check WARNs if your configured identity is found in login.keychain-db, before you ever hit a corrupted-ACL failure.

Why does the Python multiprocessing/subprocess module not work?

The iOS application model does not support spawning subprocesses in a cross-platform-compatible way. The platform focuses on minimizing processor usage (and therefore power consumption) and promotes an alternative concurrency model. Use threads or async concurrency instead.

My Linux AppImage fails with a libfuse2 / "dlopen(): error loading libfuse.so.2" error

kivyforge builds AppImages with a pinned static-FUSE (type2) runtime embedded (appimagetool --runtime-file), so a correctly built kivyforge AppImage does not need the host's libfuse2. If you still hit a FUSE error (e.g. an old runtime, a container, or a locked-down CI box), use the universal no-FUSE fallback — the AppImage extracts itself to a temp dir and runs from there:

./MyApp-1.0.0-x86_64.AppImage --appimage-extract-and-run
# or, equivalently:
APPIMAGE_EXTRACT_AND_RUN=1 ./MyApp-1.0.0-x86_64.AppImage

kivyforge already sets APPIMAGE_EXTRACT_AND_RUN=1 when it invokes appimagetool at build time, so building an AppImage never needs FUSE on the build host (WSL2 / containers / CI just work).

Can I build and run the Linux backend under WSL2?

Yes — the Linux backend is developed and tested on WSL2 Ubuntu. WSLg provides an X11/Wayland session and OpenGL (Mesa via D3D12), so kivyforge run -p linux opens a real window. If GL misbehaves, force software rendering with LIBGL_ALWAYS_SOFTWARE=1. A harmless libmtdev.so.1: cannot open shared object file warning from Kivy just means the host has no multitouch device; it does not stop the app.

How do I build or test on a headless Linux box (CI, no display)?

Locking and building need no display — only run (and launching the packaged AppImage) opens a window. For headless smoke tests, wrap the run under a virtual framebuffer with software GL:

xvfb-run -a env LIBGL_ALWAYS_SOFTWARE=1 ./MyApp-1.0.0-x86_64.AppImage

kivyforge doctor -p linux reports a WARN with this hint when neither DISPLAY nor WAYLAND_DISPLAY is set.

What is the Linux "glibc floor", and why does it matter?

A kivyforge AppImage bundles Python and your wheels, but it never bundles the C library, libGL/libEGL, or a display server — those come from the host. The bundled python-build-standalone runtime is a gnu/glibc build with a glibc ≥ 2.17 floor (manylinux2014-class), so the AppImage runs on any host that new or newer. The artifact's effective floor is max(runtime floor, highest manylinux level among your locked wheels); kivyforge doctor -p linux prints it. You can raise the floor deliberately with [tool.kivy.linux].glibc_floor = "2.28" to admit newer-manylinux-only wheels — at the cost of requiring a newer host. Setting it below 2.17 is a config error. musl hosts (Alpine) are out of scope: the gnu runtime won't run there, and Kivy publishes no musllinux wheels.

My Windows app shows a "Windows protected your PC" (SmartScreen) warning

That is Microsoft SmartScreen, and it is expected for a new, unsigned (or newly-signed, low-reputation) executable — it is not a kivyforge bug. kivyforge ships the default artifact unsigned, so recipients see the blue prompt and must click More info → Run anyway. Two ways to reduce it: (1) configure Authenticode signing ([tool.kivy.windows.signing] with a certificate-store thumbprint) so kivyforge package signs + timestamps the launcher — a standard OV certificate still needs to build reputation before the prompt stops; an EV certificate clears it immediately. (2) Distribute through a channel users already trust. SmartScreen reputation is per-signature and accrues over downloads; there is no way to bypass it from inside the bundle.

kivyforge build -p windows fails with a long-path (MAX_PATH) error

A full python-build-standalone prefix nested under build\windows\<App>\python\ can exceed the legacy 260-character MAX_PATH limit. Enable long paths on the build host: set HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled to 1 (an elevated PowerShell one-liner: New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name LongPathsEnabled -Value 1 -PropertyType DWORD -Force), then reboot. kivyforge doctor -p windows WARNs when it is off. Building closer to the drive root (a short project path) also helps.

Do I need to install the Visual C++ runtime to run a kivyforge Windows app?

No. The bundled python-build-standalone runtime ships the core VC++ runtime DLLs (vcruntime140.dll, vcruntime140_1.dll) inside the onedir, and kivyforge stages them beside python.exe so the app never depends on a system-wide "VC++ Redistributable" install. (msvcp140.dll, the C++ standard library, is not in PBS; kivyforge stages it best-effort from the build host when a wheel needs it — most pure-Kivy apps don't.) The whole point of the onedir is that every DLL the app needs is a real, shipped file.

The Windows app opens a black console window / no window at all

The launcher is a windowed-subsystem .exe: double-clicking it in Explorer opens your Kivy window with no console flash. If you see a console, you are probably launching python.exe directly instead of the generated launcher — run the <App>.exe beside it. Conversely, kivyforge run -p windows deliberately attaches the app to your terminal so you can see stdout/stderr and tracebacks during development; that is the diagnostic path, not the double-click path. If the window never appears, run <App>.exe from a terminal (or use kivyforge run) to see the traceback — a missing GL/ANGLE backend or an import error in your app surfaces there.