Skip to content

Adding packages

To add a new package to emscripten-forge, just create a Pull Request in this repository. The recipe format is described in the rattler-build recipe format

C/C++ Packages

CMake

Adding cmake based packages is easy. Usually it is enough to replace the cmake command with the emcmake command and make with emmake (see the emscripten documentation for more details).

To build a shared library with CMake, one needs flags. These flags can be put into a .cmake file:

# overwriteProp.cmake
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) # does not need to be global :)
set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-s SIDE_MODULE=1")
set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS "-s SIDE_MODULE=1")
set(CMAKE_STRIP FALSE)  # used by default in pybind11 on .so modules # only for needed when using pybind11

This can be passed to the as command line argument to cmake

# CLI
... -DCMAKE_PROJECT_INCLUDE=overwriteProp.cmake

Example recipes:

Configure / Make

Usually it is enough to replace the ./configure command with the emconfigure ./configure (see the emscripten documentation for more details).

Python Packages

pip / setuptools

For a simple package, only these requirements are usually necessary:

requirements:
  build:
    - python
    - cross-python_${{ target_platform }}
    - ${{ compiler("c") }}
    - pip
  host:
    - python
  run:
    - python

Example recipes:

meson

For a meson package, the following requirements are usually needed.

requirements:
  build:
  - ${{ compiler('cxx') }}
  - cross-python_${{target_platform}}
  - meson-python
  - pip >=24
  host:
  - python
Furthermore, an emscripten.meson.cross file is necessary to set the correct compiler and flags for cross compilation.

# binaries section is at the end to be able to append the python binary.

[properties]
needs_exe_wrapper = true
skip_sanity_check = true
longdouble_format = 'IEEE_QUAD_LE' # for numpy

[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm'
endian = 'little'

[binaries]
exe_wrapper = 'node'
pkgconfig = 'pkg-config'

In the build script, we append the python binary to the .cross file and pass this .cross file to the pip command.

#!/bin/bash

cp $RECIPE_DIR/emscripten.meson.cross $SRC_DIR
echo "python = '${PYTHON}'" >> $SRC_DIR/emscripten.meson.cross

${PYTHON} -m pip install . -vvv --no-deps --no-build-isolation \
    -Csetup-args="--cross-file=$SRC_DIR/emscripten.meson.cross"

Example recipes:

rust

Building rust packages with PyO3 / maturin typically works out of the box.

For a maturin / cffi / pyo3 package, the following requirements are usually necessary:

requirements:
  build:
  - cross-python_${{ target_platform }}
  - cffi == 1.15.1 (# at the time of writing pinning cffi == 1.15.1 is necessary, may change in the future)
  - setuptools-rust
  - rust
  - maturin

  host:
  - python
  # at the time of writing pinning cffi == 1.15.1 is necessary, may change in the future)
  - cffi == 1.15.1
  run:
  # at the time of writing pinning cffi == 1.15.1 is necessary, may change in the future)
  - cffi == 1.15.1

Example recipes:

R Packages

For a package without dependencies of other R packages, only these requirements are usually necessary:

requirements:
  build:
  - cross-r-base_${{ target_platform }}
  - ${{ compiler("c") }}

Some R packages use the Rcpp package for a seamless integration of R and C++. Rcpp and all other R-dependencies must be in all 3 requirements sections (build, host, and run). For example,

requirements:
  build:
  - cross-r-base_${{ target_platform }}
  - ${{ compiler("c") }}
  - r-rcpp
  host:
  - r-rcpp
  run:
  - r-rcpp

There needs to be 2 urls because very frequently the first one is not reachable.

For the tests, we only check that the shared library for the package exists. Eventually we should be able to load the package and do actual tests, but that's not available yet.

Example recipes: