Building Packages Using Recipe#
While you can build Python packages from source,
we provide a conda-style recipe system to build multiple packages at once.
Using a recipe is also required to include the package in the Pyodide distribution. See Adding Packages into Pyodide Distribution for more information.
Creating the meta.yaml file#
To build a Python package in tree, you need to create a meta.yaml file that
defines a recipe which includes build commands and patches (source code
edits), amongst other things.
If your package is on PyPI, the easiest place to start is with the
pyodide skeleton pypi command. Run
pyodide skeleton pypi <package-name>
This will generate a meta.yaml file under packages/<package-name>/ (see
The meta.yaml specification). The pyodide cli tool will populate the latest version,
the download link and the sha256 hash by querying PyPI.
It doesn’t currently handle package dependencies, so you will need to specify
those yourself in the requirements section of the meta.yaml file.
requirements:
host:
# Dependencies that are needed to build the package
- numpy
run:
# Dependencies that are needed to run the package
- numpy
- scikit-learn
Note
To determine build and runtime dependencies, including for non Python
libraries, it is often useful to check if the package was already built on
conda-forge look at the corresponding meta.yaml
file. This can be done either by checking if the URL
https://github.com/conda-forge/<package-name>-feedstock/blob/master/recipe/meta.yaml
exists, or by searching the conda-forge GitHub
org for the package name.
The Pyodide meta.yaml file format was inspired by the one in conda, however it is
not strictly compatible.
Building Recipes#
Once the meta.yaml file is ready, build the package with the following
command and see if there are any errors.
pyodide build-recipes <package-name> --install
This command will build the package and all its dependencies. The --install
The --install flag will install the built package into the /dist directory.
Testing#
If the build succeeds you can try to load the package in the Pyodide environment.
Visit Build Pyodide or download the latest release from the GitHub repository.
Copy everything built in the
/distdirectory to the downloaded Pyodide directory. This should include thepyodide-lock.jsonfile and the wheel files that were built.Serve the Pyodide directory with
python -m http.server --directory ./dist.Open
localhost:8000/console.htmland try to import the package.You can test the package in the repl.
Modifying Build Process#
If you need to modify the build process (e.g. to fix build issues) you can do
this by modifying the meta.yaml file. The build section of the meta.yaml
is where you can run scripts, set environment variables, and add extra compile
and link flags.
build:
# You can add any shell scripts you want to run before invoking the build.
# For instance, setting environment variables or downloading files, or modifying the source can be done here.
script: |
wget https://example.com/file.tar.gz
export MY_ENV_VARIABLE=FOO
# You can pass extra compile and link flags to the compiler by adding them here.
cflags: |
-O3
cxxflags: |
-O3
ldflags: |
-lm
# This is the command that is passed to the build backend. For example, if you
# need to pass extra flags to setuptools, you can pass them here.
backend-flags: |
setup-args=-Dallow-noblas=true
# post is a script that is run after the build. This is useful for
# post-processing the wheel file.
post: |
rm unnecessary-file-in-the-wheel.txt
Patching the package source#
If the package has a bug that needs to be fixed, you can apply .patch files
to the package source. This is commonly done when the package requires special handling
for Emscripten or Pyodide environment.
Place the patch files in the patches directory of the package, and specify
them in the source.patches section of the meta.yaml file. The patches will be
applied in the order they are listed.
source:
patches:
- patches/0001-Add-Wno-return-type-flag.patch
- patches/0002-Align-xerbla_array-signature-with-scipy-expectation.patch
- patches/0003-Skip-linktest.patch
Generating patches#
If the package has a git repository, the easiest way to make a patch is usually:
Clone the git repository of the package. You might want to use the options
git clone --depth 1 --branch <version>. Find the appropriate tag given the version of the package you are trying to modify.Make a new branch with
git checkout -b pyodide-version(e.g.,pyodide-1.21.4).Make whatever changes you want. Commit them. Please split your changes up into focused commits. Write detailed commit messages! People will read them in the future, particularly when migrating patches or trying to decide if they are no longer needed. The first line of each commit message will also be used in the patch file name.
Use
git format-patch <version> -o packages/<package-name>/patches/to generate a patch file for your changes and store it directly into the patches folder.
Upstream your patches!#
Please create PRs or issues to discuss with the package maintainers to try to find ways to include your patches into the package. Many package maintainers are very receptive to including Pyodide-related patches and they reduce future maintenance work for us.
Upgrading a package#
To upgrade a package’s version to the latest one available on PyPI, do
pyodide skeleton pypi <package-name> --update
Because this does not handle package dependencies, you have to manually check
whether the requirements section of the meta.yaml file needs to be updated
for updated dependencies.
Upgrading a package’s version may lead to new build issues that need to be resolved (see above) and any patches need to be checked and potentially migrated (see below).
Migrating Patches#
When you want to upgrade the version of a package, you will need to migrate the patches. To do this:
Clone the git repository of the package. You might want to use the options
git clone --depth 1 --branch <version-tag>.Make a new branch with
git checkout -b pyodide-old-version(e.g.,pyodide-1.21.4).Apply the current patches with
git am packages/<package-name>/patches/*.Make a new branch
git checkout -b pyodide-new-version(e.g.,pyodide-1.22.0)Rebase the patches with
git rebase old-version --onto new-version(e.g.,git rebase pyodide-1.21.4 --onto pyodide-1.22.0). Resolve any rebase conflicts. If a patch has been upstreamed, you can drop it withgit rebase --skip.Remove old patches with
rm packages/<package-name>/patches/*.Use
git format-patch <version-tag> -o packages/<package-name>/patches/to generate new patch files.
Appendix#
C library dependencies#
Some Python packages depend on certain C libraries, e.g. lxml depends on
libxml. Pyodide recipes can also be used to build C libraries.
To package a C library, create a directory for it and add meta.yaml file
similar to the one for Python packages. However, unlike the Python package, you
need to specify a build script that builds the C library.
See The meta.yaml specification for more details.
The minimal example of meta.yaml for a C library that uses configure and
make is:
package:
name: <name>
version: <version>
source:
url: <url>
sha256: <sha256>
requirements:
run:
- <requirement>
build:
type: static_library
script: |
emconfigure ./configure
emmake make
You can use the meta.yaml of other C libraries such as
libxml
as a starting point.
After packaging a C library, it can be added as a dependency of a Python package
like a normal dependency. See lxml and libxml for an example (and also
scipy and OpenBLAS).
Remark: Certain C libraries come as emscripten ports, and do not have to be
built manually. They can be used by adding e.g. -s USE_ZLIB in the cflags of
the Python package. See e.g. matplotlib for an example. The full list of
libraries with Emscripten ports is
here.
Rust/PyO3 Packages#
We currently build cryptography which is a Rust extension built with PyO3 and
setuptools-rust. It should be reasonably easy to build other Rust extensions.
If you want to build a package with Rust extension, you will need Rust >= 1.41,
and you need to set the rustup toolchain to nightly, and the target to
wasm32-unknown-emscripten in the build script
as shown here,
but other than that there may be no other issues if you are lucky.
As mentioned here,
by default certain wasm-related RUSTFLAGS are set during build.script
and can be removed with export RUSTFLAGS="".
If your project builds using maturin, you need to use maturin 0.14.14 or later.
It is pretty easy to patch an existing project (see projects/fastparquet/meta.yaml for an example)
Partial Rebuilds#
By default, each time you run pyodide build-recipes, it will delete the entire
source directory and replace it with a fresh copy from the download url. This is
to ensure build repeatability. For debugging purposes, this is likely to be
undesirable. If you want to try out a modified source tree, you can pass the
flag --continue and build-recipes will try to build from the existing source
tree. This can cause various issues, but if it works it is much more convenient.
Using the --continue flag, you can modify the sources in tree to fix the
build, then when it works, copy the modified sources into your checked out copy
of the package source repository and use git format-patch to generate the
patch.