Skip to content
Open
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
18 changes: 18 additions & 0 deletions recipes/recipes_emscripten/duckdb/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail

# Setup emscripten toolchain for scikit-build-core
emscripten_root=$(em-config EMSCRIPTEN_ROOT)
toolchain_path="${emscripten_root}/cmake/Modules/Platform/Emscripten.cmake"

export CMAKE_ARGS="${CMAKE_ARGS} -DCMAKE_TOOLCHAIN_FILE=${toolchain_path} -DCMAKE_PROJECT_INCLUDE=${RECIPE_DIR}/overwriteProp.cmake"

# DuckDB-specific CMake flags for wasm passed via scikit-build-core config settings
${PYTHON} -m pip install . ${PIP_ARGS} \
-Ccmake.define.OVERRIDE_GIT_DESCRIBE="v${PKG_VERSION}" \
-Ccmake.define.BUILD_EXTENSIONS="parquet;json;autocomplete" \
-Ccmake.define.BUILD_SHELL=OFF \
-Ccmake.define.BUILD_UNITTESTS=OFF \
-Ccmake.define.ENABLE_EXTENSION_AUTOLOADING=OFF \
-Ccmake.define.ENABLE_EXTENSION_AUTOINSTALL=OFF \
-Ccmake.define.CMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF
9 changes: 9 additions & 0 deletions recipes/recipes_emscripten/duckdb/overwriteProp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-sSIDE_MODULE=1 -sWASM_BIGINT")
set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS "-sSIDE_MODULE=1 -sWASM_BIGINT")
set(CMAKE_STRIP FALSE)

# DuckDB links libduckdb_static.a twice to resolve circular dependencies
# between the core and extensions. Native linkers handle this but wasm-ld
# does not. Allow multiple definitions to work around this.
add_link_options("LINKER:--allow-multiple-definition")
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index abcdef1..1234567 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,6 +36,15 @@ endif()
# Dependencies
# ────────────────────────────────────────────
# PyBind11
+
+# On Emscripten, FindPython only provides Python::Module, not Python::Python
+# (there is no shared libpython to link against). pybind11_add_module internally
+# calls python_add_library which expects Python::Python, so we provide a dummy
+# IMPORTED target to satisfy that requirement.
+if(EMSCRIPTEN AND NOT TARGET Python::Python)
+ add_library(Python::Python INTERFACE IMPORTED)
+endif()
+
find_package(pybind11 REQUIRED CONFIG)

# DuckDB
@@ -100,7 +109,7 @@ if(APPLE)
target_link_options(
_duckdb PRIVATE "LINKER:-exported_symbol,_duckdb_adbc_init"
"LINKER:-exported_symbol,_PyInit__duckdb")
-elseif(UNIX AND NOT APPLE)
+elseif(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)
target_link_options(
_duckdb PRIVATE "LINKER:--export-dynamic-symbol=duckdb_adbc_init"
"LINKER:--export-dynamic-symbol=PyInit__duckdb")
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/duckdb_packaging/setuptools_scm_version.py b/duckdb_packaging/setuptools_scm_version.py
index 1234567..abcdef0 100644
--- a/duckdb_packaging/setuptools_scm_version.py
+++ b/duckdb_packaging/setuptools_scm_version.py
@@ -53,7 +53,7 @@ def version_scheme(version: _VersionObject) -> str:

distance = int(version.distance or 0)
try:
- if distance == 0 and not version.dirty:
+ if distance == 0:
return _tag_to_version(str(version.tag))
return _bump_dev_version(str(version.tag), distance)
except Exception as e:
72 changes: 72 additions & 0 deletions recipes/recipes_emscripten/duckdb/recipe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
context:
version: 1.4.4

package:
name: python-duckdb
version: ${{ version }}

source:
git: https://github.com/duckdb/duckdb-python.git
tag: v${{ version }}
# expected_commit: a12f36ca411007f5eb48919448f61c7498112553
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you allow --experimental, this would work and "pin" the tag to a commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do allow experimental for a couple of recipes, you could add this package too

if (recipe == "arrow") or (recipe == "thrift"):
cmd.extend(["--experimental"])

patches:
- patches/emscripten-python-target.patch
- patches/fix-dirty-version-scheme.patch

build:
number: 0
script: build.sh

files:
exclude:
- '**/__pycache__/**'
- '**/*.pyc'
- '**/test_*.py'
- 'duckdb_build/**'
python:
skip_pyc_compilation:
- '**/*.py'

requirements:
build:
- python
- cross-python_${{ target_platform }}
- ${{ compiler('cxx') }}
- pip
- setuptools
- setuptools-scm
- pybind11
- scikit-build-core
- cmake
- ninja
host:
- python
- pybind11
run:
- python

tests:
- script: pytester
files:
recipe:
- test_duckdb.py
requirements:
build:
- pytester
run:
- pytester-run

about:
homepage: https://duckdb.org/
license: MIT
license_file: LICENSE
summary: DuckDB is an analytical in-process SQL database management system
description: |
DuckDB is an in-process SQL OLAP database management system.
It is designed to support analytical query workloads (OLAP) while
being embeddable in Python and other languages.
repository: https://github.com/duckdb/duckdb-python

extra:
recipe-maintainers:
- wolfv
26 changes: 26 additions & 0 deletions recipes/recipes_emscripten/duckdb/test_duckdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import duckdb


def test_import():
assert duckdb is not None


def test_basic_query():
con = duckdb.connect()
result = con.execute("SELECT 42 AS answer").fetchall()
assert result == [(42,)]


def test_create_table():
con = duckdb.connect()
con.execute("CREATE TABLE t (x INTEGER, y VARCHAR)")
con.execute("INSERT INTO t VALUES (1, 'hello'), (2, 'world')")
result = con.execute("SELECT * FROM t ORDER BY x").fetchall()
assert result == [(1, "hello"), (2, "world")]


def test_aggregation():
con = duckdb.connect()
con.execute("CREATE TABLE nums AS SELECT * FROM range(10) t(i)")
result = con.execute("SELECT SUM(i), COUNT(*) FROM nums").fetchone()
assert result == (45, 10)
Loading