Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions packages/libboost-python/meta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package:
name: libboost-python
version: 1.84.0
tag:
- library
- static_library

source:
url: https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.gz
sha256: 4d27e9efed0f6f152dc28db6430b9d3dfb40c0345da7342eaa5a987dde57bd95

requirements:
host:
- libzlib

build:
type: static_library
script: |
# Patch boost.numpy for NumPy 2.x (PyArray_Descr->elsize removed)
sed -i 's/reinterpret_cast<PyArray_Descr\*>(ptr())->elsize/PyDataType_ELSIZE(reinterpret_cast<PyArray_Descr*>(ptr()))/' \
libs/python/src/numpy/dtype.cpp

./bootstrap.sh --prefix=${WASM_LIBRARY_DIR} \
--with-libraries=system,python,serialization,iostreams \
--with-python=python${PYMAJOR}.${PYMINOR}

# https://github.com/emscripten-core/emscripten/issues/17052
# Without this, boost outputs WASM modules not static library archives.
printf "using clang : emscripten : emcc : <archiver>emar <ranlib>emranlib <linker>emlink ;\n" \
| tee -a ./project-config.jam

# Bypass standard python detection, specify cross-compilation paths
sed -i 's/using python/#using python/' ./project-config.jam
NUMPY_INC=$(python3 -c "import numpy; print(numpy.get_include())")
printf "using python : ${PYMAJOR}.${PYMINOR} : python${PYMAJOR}.${PYMINOR} : ${PYTHONINCLUDE} ${NUMPY_INC} ;\n" \
| tee -a ./project-config.jam

./b2 variant=release toolset=clang-emscripten link=static threading=single \
address-model=32 --disable-icu \
cxxflags="$SIDE_MODULE_CXXFLAGS -fwasm-exceptions -std=c++20 -DBOOST_SP_DISABLE_THREADS=1" \
cflags="$SIDE_MODULE_CFLAGS -fwasm-exceptions -DBOOST_SP_DISABLE_THREADS=1" \
linkflags="-fpic $SIDE_MODULE_LDFLAGS" \
python=${PYMAJOR}.${PYMINOR} \
-sZLIB_INCLUDE=${WASM_LIBRARY_DIR}/include \
-sZLIB_LIBPATH=${WASM_LIBRARY_DIR}/lib \
--layout=system -j"${PYODIDE_JOBS:-3}" --prefix=${WASM_LIBRARY_DIR} \
install || true

# Verify key libraries were built
echo "=== Boost libraries ==="
ls -la ${WASM_LIBRARY_DIR}/lib/libboost_python*.a
ls -la ${WASM_LIBRARY_DIR}/lib/libboost_numpy*.a
ls -la ${WASM_LIBRARY_DIR}/lib/libboost_serialization*.a
ls -la ${WASM_LIBRARY_DIR}/lib/libboost_iostreams*.a

about:
home: https://www.boost.org/
summary: Boost C++ libraries with Python and NumPy support for Emscripten
license: Boost
158 changes: 158 additions & 0 deletions packages/librdkit/meta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package:
name: librdkit
version: 2025.9.6
tag:
- library
- shared_library

source:
url: https://github.com/rdkit/rdkit/archive/refs/tags/Release_2025_09_6.tar.gz
sha256: 57b92e8f47d9dbd559bd808d5cf6c48a628bc36118bc35b832a35e2ca8a0c7a1
extract_dir: rdkit-Release_2025_09_6

requirements:
host:
- libboost-python
- libzlib

build:
type: shared_library
script: |
set -e

# ========================
# 1. Setup β€” derive Python paths from pyodide config
# ========================
PYINC=$(pyodide config get python_include_dir)
PYVER=$(pyodide config get python_version | cut -d. -f1-2)
PYMAJ=$(echo $PYVER | cut -d. -f1)
PYMIN=$(echo $PYVER | cut -d. -f2)
NUMPY_INC=$(python3 -c "import numpy; print(numpy.get_include())")

# Download Eigen3 headers (header-only)
if [ ! -d "eigen-3.4.0" ]; then
wget -q https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz
tar xf eigen-3.4.0.tar.gz
fi

# Dummy libpython β€” side modules don't link against it, but CMake's FindPython3 needs it
emar rcs /tmp/libpython${PYVER}.a

# Patch CMakeLists.txt for Emscripten support
sed -i 's/OR RDK_BUILD_MINIMAL_LIB)/OR EMSCRIPTEN OR RDK_BUILD_MINIMAL_LIB)/' CMakeLists.txt

# ========================
# 2. CMake configure
# ========================
mkdir -p build && cd build

emcmake cmake \
-DCMAKE_BUILD_TYPE=Release \
-DRDK_BUILD_PYTHON_WRAPPERS=ON \
-DRDK_BUILD_MINIMAL_LIB=OFF \
-DRDK_BUILD_CPP_TESTS=OFF \
-DRDK_BUILD_INCHI_SUPPORT=ON \
-DRDK_USE_BOOST_SERIALIZATION=ON \
-DRDK_USE_BOOST_IOSTREAMS=ON \
-DRDK_OPTIMIZE_POPCNT=OFF \
-DRDK_BUILD_THREADSAFE_SSS=OFF \
-DRDK_BUILD_DESCRIPTORS3D=ON \
-DRDK_TEST_MULTITHREADED=OFF \
-DRDK_BUILD_CHEMDRAW_SUPPORT=ON \
-DRDK_BUILD_MAEPARSER_SUPPORT=ON \
-DRDK_BUILD_COORDGEN_SUPPORT=ON \
-DRDK_BUILD_SLN_SUPPORT=ON \
-DRDK_BUILD_CAIRO_SUPPORT=OFF \
-DRDK_BUILD_QT_SUPPORT=OFF \
-DRDK_BUILD_PGSQL=OFF \
-DRDK_BUILD_SWIG_WRAPPERS=OFF \
-DRDK_BUILD_FREETYPE_SUPPORT=OFF \
-DRDK_BUILD_AVALON_SUPPORT=ON \
-DRDK_BUILD_YAEHMOP_SUPPORT=OFF \
-DRDK_BUILD_XYZ2MOL_SUPPORT=ON \
-DRDK_INSTALL_INTREE=ON \
-DRDK_INSTALL_STATIC_LIBS=ON \
-DRDK_BUILD_STATIC_LIBS_ONLY=ON \
-DBoost_USE_STATIC_LIBS=ON \
-DCMAKE_PREFIX_PATH="${WASM_LIBRARY_DIR}" \
-DCMAKE_FIND_ROOT_PATH="${WASM_LIBRARY_DIR}" \
-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH \
-DBoost_INCLUDE_DIR=${WASM_LIBRARY_DIR}/include \
-DCMAKE_INCLUDE_PATH="${WASM_LIBRARY_DIR}/include" \
-DCMAKE_LIBRARY_PATH="${WASM_LIBRARY_DIR}/lib" \
-DZLIB_INCLUDE_DIR=${WASM_LIBRARY_DIR}/include \
-DZLIB_LIBRARY=${WASM_LIBRARY_DIR}/lib/libz.a \
-DEIGEN3_INCLUDE_DIR=$PWD/../eigen-3.4.0 \
-DPython3_INCLUDE_DIR=${PYINC} \
-DPython3_LIBRARY=/tmp/libpython${PYVER}.a \
-DPython3_NumPy_INCLUDE_DIR=${NUMPY_INC} \
-DPython3_EXECUTABLE=$(which python${PYVER}) \
-DCMAKE_CXX_FLAGS="-fwasm-exceptions -O2 -DNDEBUG -fPIC -std=c++20" \
-DCMAKE_C_FLAGS="-fwasm-exceptions -O2 -DNDEBUG -fPIC" \
..

# ========================
# 3. Build
# ========================
emmake make -k -j ${PYODIDE_JOBS:-3} || true
cd ..

# ========================
# 4. Relink core into a single WASM side module
# ========================
find build/lib build/External -name '*.a' 2>/dev/null | sort > /tmp/all_libs.txt
for lib in ${WASM_LIBRARY_DIR}/lib/libboost_python*.a \
${WASM_LIBRARY_DIR}/lib/libboost_numpy*.a \
${WASM_LIBRARY_DIR}/lib/libboost_system*.a \
${WASM_LIBRARY_DIR}/lib/libboost_serialization*.a \
${WASM_LIBRARY_DIR}/lib/libboost_iostreams*.a; do
[ -f "$lib" ] && echo "$lib" >> /tmp/all_libs.txt
done
echo "${WASM_LIBRARY_DIR}/lib/libz.a" >> /tmp/all_libs.txt
echo "Core libraries: $(wc -l < /tmp/all_libs.txt)"

WHOLE_ARGS=""
while read -r lib; do
WHOLE_ARGS="$WHOLE_ARGS -Wl,--whole-archive $lib -Wl,--no-whole-archive"
done < /tmp/all_libs.txt

em++ -fwasm-exceptions -sSIDE_MODULE=1 -O2 -shared \
-Wl,--no-gc-sections -Wl,--export-all \
$WHOLE_ARGS \
-o build/librdkit_core.so
ls -lh build/librdkit_core.so

# Copy core .so to $DISTDIR (Pyodide loads shared_library packages
# asynchronously, avoiding Chrome's 8MB sync WebAssembly.Compile limit)
cp build/librdkit_core.so ${DISTDIR}

# Also install to $WASM_LIBRARY_DIR/lib/ so the rdkit recipe can link
# wrapper .so modules against it (proper dynamic linking, no RTLD_GLOBAL)
cp build/librdkit_core.so ${WASM_LIBRARY_DIR}/lib/

# ========================
# 5. Stage build artifacts for the rdkit Python package recipe
# ========================
STAGING=${WASM_LIBRARY_DIR}/share/rdkit

# Stage wrapper .o files and cmake build.make (for output path derivation)
mkdir -p ${STAGING}/wrappers
for wrap_dir in $(find build -path '*/Wrap/CMakeFiles/*.dir' -type d 2>/dev/null); do
modname=$(basename "$wrap_dir" .dir)
obj_files=$(find "$wrap_dir" -name '*.o' 2>/dev/null)
if [ -z "$obj_files" ]; then continue; fi
mkdir -p "${STAGING}/wrappers/${modname}"
cp $obj_files "${STAGING}/wrappers/${modname}/"
cp "$wrap_dir/build.make" "${STAGING}/wrappers/${modname}/"
done

# Stage Python source files and Data directory
cp -r rdkit ${STAGING}/python
cp -r Data ${STAGING}/Data

echo "Staged $(ls ${STAGING}/wrappers | wc -l) wrapper modules"

about:
home: https://github.com/rdkit/rdkit
summary: RDKit core C++ libraries as a WASM side module
license: BSD-3-Clause
50 changes: 50 additions & 0 deletions packages/rdkit/extras/patch_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Patch rdkit/__init__.py for emscripten/Pyodide support.

The librdkit_core.so shared library is loaded by Pyodide via the librdkit
shared_library package (asynchronously during loadPackage). Each wrapper
.so.wasm module links against librdkit_core.so directly (like scipy links
against libopenblas), so no RTLD_GLOBAL hack is needed.

This patch:
1. Sets RDBASE for RDConfig.py path resolution
2. Registers a custom MetaPathFinder to load .so.wasm wrapper modules
"""

init_path = "rdkit/__init__.py"
init = open(init_path).read()

loader = '''import sys as _sys

if _sys.platform == 'emscripten':
import os as _os
import importlib.abc
import importlib.machinery

# Set RDBASE so RDConfig.py finds Data/, Docs/, etc. relative to this package
_os.environ['RDBASE'] = _os.path.dirname(__file__)

class _RDKitExtensionFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
parts = fullname.split('.')
if parts[0] != 'rdkit':
return None
modname = parts[-1]
if path:
for d in path:
candidate = _os.path.join(d, modname + '.so.wasm')
if _os.path.exists(candidate):
loader = importlib.machinery.ExtensionFileLoader(
fullname, candidate
)
return importlib.util.spec_from_file_location(
fullname, candidate, loader=loader,
)
return None

import importlib.util
_sys.meta_path.insert(0, _RDKitExtensionFinder())

'''

open(init_path, "w").write(loader + init)
print("Patched rdkit/__init__.py")
15 changes: 15 additions & 0 deletions packages/rdkit/extras/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"

[project]
name = "rdkit"
version = "0.0.0"
description = "RDKit cheminformatics library for Pyodide"

[tool.setuptools.packages.find]
include = ["rdkit*"]

[tool.setuptools.package-data]
"*" = ["*.so.wasm"]
rdkit = ["Data/**/*"]
91 changes: 91 additions & 0 deletions packages/rdkit/meta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package:
name: rdkit
version: 2025.9.6
top-level:
- rdkit

source:
url: https://github.com/rdkit/rdkit/archive/refs/tags/Release_2025_09_6.tar.gz
sha256: 57b92e8f47d9dbd559bd808d5cf6c48a628bc36118bc35b832a35e2ca8a0c7a1
extract_dir: rdkit-Release_2025_09_6

extras:
- [extras/pyproject.toml, pyproject.toml]
- [extras/patch_init.py, patch_init.py]

requirements:
host:
- librdkit
- numpy
run:
- librdkit
- numpy

build:
script: |
set -e

STAGING=${WASM_LIBRARY_DIR}/share/rdkit

# Set version in pyproject.toml
sed -i "s/^version = .*/version = \"${PKG_VERSION}\"/" pyproject.toml

# ========================
# 1. Relink wrappers from staged .o files
# ========================
WRAPPER_COUNT=0
for wrapper_dir in ${STAGING}/wrappers/*/; do
modname=$(basename "$wrapper_dir")
obj_files=$(find "$wrapper_dir" -name '*.o' 2>/dev/null)
if [ -z "$obj_files" ]; then
echo "SKIP: $modname (no .o files)"
continue
fi
# Derive the correct output path from cmake's build.make
dest_path=$(grep -oP "rdkit/[^ ]*${modname}\.so" "$wrapper_dir/build.make" 2>/dev/null | head -1)
if [ -z "$dest_path" ]; then
echo "SKIP: $modname (no output path found)"
continue
fi
mkdir -p "$(dirname "$dest_path")"
em++ -fwasm-exceptions -sSIDE_MODULE=1 -O2 -shared \
$obj_files \
-L${WASM_LIBRARY_DIR}/lib -lrdkit_core \
-o "$dest_path"
echo "LINKED: $modname -> $dest_path"
WRAPPER_COUNT=$((WRAPPER_COUNT + 1))
done
echo "Total wrapper .so files: $WRAPPER_COUNT"

# ========================
# 2. Assemble Python package
# ========================
# Overlay cmake-generated Python files (e.g. inchi.py) from staging
cp -rn ${STAGING}/python/* rdkit/ 2>/dev/null || true

# Copy Data directory into package
cp -r ${STAGING}/Data rdkit/Data

# Rename .so -> .so.wasm (prevents micropip from auto-loading before
# core is ready β€” the core is loaded by Pyodide via the librdkit
# shared_library package with {global: true})
find rdkit -name "*.so" -exec sh -c 'mv "$1" "$1.wasm"' _ {} \;

# Patch __init__.py to set RDBASE and register custom import finder
python3 patch_init.py

echo "=== Package contents ==="
find rdkit -name "*.so.wasm" | sort
echo "---"
du -sh rdkit

test:
imports:
- rdkit
- rdkit.Chem

about:
home: https://github.com/rdkit/rdkit
PyPI: https://pypi.org/project/rdkit
summary: RDKit cheminformatics library for Pyodide
license: BSD-3-Clause
Loading
Loading