This guide explains how to use the build dependencies and host dependencies features in the Platform Wheels Builder.
Build dependencies are other packages that must be built before your package can be built. This is useful when:
- Your package's post-compilation tests need to import other packages
- Your package needs to test against other packages during the build process
- Your package has integration tests that require pre-built wheels
package:
name: my-package
build_dependencies:
- cffi
- numpy
- some-other-packagepackages:
- name: my-package
build_dependencies:
- cffi
- numpy-
Dependency Resolution: When you run
read_packages.py, it performs a topological sort on all packages based on theirbuild_dependencies. This ensures packages are built in the correct order and assigns each package a dependency level (0 = no dependencies, 1 = depends on level 0 packages, etc.). -
Build Order: The workflow uses GitHub Actions'
needskeyword to create a dependency chain:- Level 0 packages (no dependencies) build first in the
build_level_0job - Level 1 packages build next in the
build_level_1job, which depends onbuild_level_0vianeeds - Level 2 and 3 jobs follow the same pattern if packages exist at those levels
- Packages within the same level build in parallel using matrix strategy
- Level 0 packages (no dependencies) build first in the
-
Dependency Handling: When a package has build dependencies:
- The job waits for previous level jobs to complete (enforced by
needs) - Once the previous level completes, artifacts are guaranteed to be available
- The workflow downloads dependency artifacts using
actions/download-artifact@v4 - Dependency wheels are installed before building the current package
- No polling required - GitHub Actions handles job orchestration
- The job waits for previous level jobs to complete (enforced by
-
Error Handling:
- Circular dependencies are detected during
read_packages.pyand reported as errors - Missing dependencies (not in the package list) generate warnings
- If a dependency build fails, dependent builds are automatically skipped
- Clear visualization of dependency chain in GitHub Actions UI
- Circular dependencies are detected during
- Build dependencies must be packages defined in the same workflow (recipes or packages.yaml)
- For external Python dependencies from PyPI, use
pip_dependenciesinstead - Build dependencies are resolved per platform (Android/iOS builds are independent)
- The dependency level system supports up to 4 levels (0-3); expand if needed
Host dependencies are system libraries (like libffi, openssl, zlib) that need to be compiled for the target architecture before building your package. This is necessary for cross-compilation to Android/iOS.
When building for Android/iOS, you're cross-compiling from x86_64 (build host) to ARM64 (target device). System libraries installed via apt-get or brew are compiled for the build host, not the target architecture.
You need to provide a cibw_before_all script that cross-compiles your dependencies for the target architecture.
Example from recipes/cffi/recipe.yaml:
package:
name: cffi
host_dependencies:
- libffi-dev
# Custom script that cross-compiles libffi
cibw_before_all: bash $GITHUB_WORKSPACE/recipes/cffi/build_libffi.sh
cibw_environment:
PKG_CONFIG: "" # Disable pkg-config to avoid finding host librariesThe build_libffi.sh script:
- Detects the target architecture from cibuildwheel environment variables
- Downloads libffi source code
- Cross-compiles it using Android NDK or iOS SDK toolchain
- Installs to a temporary location (e.g.,
/tmp/libffi-install-arm64-v8a) - Exports environment variables for the package build
See recipes/cffi/build_libffi.sh for a complete example.
After your custom script runs, the system automatically sources scripts/setup_cross_compile_env.sh which:
-
Searches for libraries in common locations:
/tmp/<library>-install-*/tmp/<library>-*$HOME/.local/<library>
-
Sets environment variables for discovered libraries:
LIBFFI_INCLUDE_DIR→/tmp/libffi-install-arm64/includeLIBFFI_LIB_DIR→/tmp/libffi-install-arm64/lib- Similar for other libraries (OPENSSL, ZLIB, etc.)
-
Configures compiler flags:
- Adds
-I<include-dir>toCFLAGSandCPPFLAGS - Adds
-L<lib-dir>toLDFLAGS - Adds
<lib-dir>/pkgconfigtoPKG_CONFIG_PATH
- Adds
Your package's setup.py should check for these environment variables:
import os
# Check for explicit environment variables first
include_dirs = []
library_dirs = []
if os.environ.get('FFI_INCLUDE_DIR'):
include_dirs.append(os.environ['FFI_INCLUDE_DIR'])
if os.environ.get('FFI_LIB_DIR'):
library_dirs.append(os.environ['FFI_LIB_DIR'])
# If not set, try to find cross-compiled library
if not include_dirs:
import glob
libffi_dirs = glob.glob('/tmp/libffi-install-*/include')
if libffi_dirs:
include_dirs.append(libffi_dirs[0])
lib_dir = libffi_dirs[0].replace('/include', '/lib')
if os.path.exists(lib_dir):
library_dirs.append(lib_dir)This is typically done via a patch file. See recipes/cffi/patches/mobile.patch for an example.
The environment setup script maps common package names to library names:
| Package Name | Library Name | Environment Variables |
|---|---|---|
| libffi-dev | libffi | LIBFFI_INCLUDE_DIR, LIBFFI_LIB_DIR |
| libssl-dev | openssl | OPENSSL_INCLUDE_DIR, OPENSSL_LIB_DIR |
| zlib1g-dev | zlib | ZLIB_INCLUDE_DIR, ZLIB_LIB_DIR |
| libjpeg-dev | libjpeg | LIBJPEG_INCLUDE_DIR, LIBJPEG_LIB_DIR |
| libtiff-dev | libtiff | LIBTIFF_INCLUDE_DIR, LIBTIFF_LIB_DIR |
# recipes/cryptography/recipe.yaml
package:
name: cryptography
# Wait for cffi to be built first
build_dependencies:
- cffi
host_dependencies:
- libssl-dev
- libffi-dev
- cargo
- rustcIn this case:
- cffi is built first (with its libffi dependency)
- cryptography waits for cffi wheel to be available
- cffi wheel is installed before building cryptography
- cryptography can now import and test against cffi during build
# recipes/pillow/recipe.yaml
package:
name: pillow
host_dependencies:
- zlib1g-dev
# Custom script to build zlib, libjpeg, and libtiff for target architecture
cibw_before_all: |
bash $GITHUB_WORKSPACE/recipes/pillow/build_zlib.sh
bash $GITHUB_WORKSPACE/recipes/pillow/build_libjpeg.sh
bash $GITHUB_WORKSPACE/recipes/pillow/build_libtiff.sh
cibw_environment:
PKG_CONFIG: "" # Disable pkg-config
# Patch to find cross-compiled libraries
patches:
- patches/cross_compile_libs_detect.patchThe build scripts:
- Cross-compile zlib, libjpeg, libtiff for target architecture
- Install to
/tmp/zlib-install-arm64, etc. - Environment setup script automatically finds them
- Patch uses environment variables to locate libraries
# recipes/complex-package/recipe.yaml
package:
name: complex-package
version: ">=1.0.0"
# Wait for these packages to be built first
build_dependencies:
- cffi
- pillow
# Need these system libraries
host_dependencies:
- libffi-dev
- libjpeg-dev
# Custom build script
cibw_before_all: |
bash $GITHUB_WORKSPACE/recipes/complex-package/build_deps.sh
# Environment variables
cibw_environment:
PKG_CONFIG: ""
CUSTOM_FLAG: "value"
# Patches
patches:
- patches/cross_compile.patchProblem: "Timeout waiting for dependency X"
Solutions:
- Check if dependency X is in the package list
- Verify dependency X isn't failing to build
- Check if there's a circular dependency
- Look at the workflow logs for dependency X's build status
Problem: "Circular dependency detected among packages: A, B, C"
Solutions:
- Review your build_dependencies to find the cycle
- Remove unnecessary dependencies
- Consider if the dependency should be a pip_dependency instead
Problem: "Library not found during build"
Solutions:
- Verify your
cibw_before_allscript successfully built the library - Check if the library is installed to
/tmp/<library>-install-* - Add debug output to see what environment variables are set:
cibw_before_all: | bash $GITHUB_WORKSPACE/recipes/mypackage/build_lib.sh echo "Environment variables:" env | grep -E "_(INCLUDE|LIB)_DIR" | sort
- Verify your patch/setup.py checks for the environment variables
Problem: "Found host library instead of cross-compiled library"
Solutions:
- Set
PKG_CONFIG: ""incibw_environmentto disable pkg-config - Disable system include directories in your patch
- Make sure your setup.py checks environment variables first
-
Test locally first: Use the
read_packages.pyscript to verify dependency order:python read_packages.py 2>&1 | grep "Found.*packages"
-
Add debug output: Add
echostatements to your build scripts to see what's happening -
Check artifact names: Build artifacts are named
cibw-wheels-<platform>-<package-name> -
Review existing recipes: Look at
recipes/cffi/andrecipes/pillow/for working examples -
Test dependency resolution: Run
python test_dependencies.pyto verify your setup
- See
README.mdfor general platform wheels documentation - See
recipes/README.mdfor recipe format documentation - See
packages.yaml.examplefor configuration examples - Check
.github/workflows/wheels.ymlto understand the build process