Skip to content

Commit 3faa0cb

Browse files
sd2kdicej
authored andcommitted
Add SQLite3 support to CPython WASI build
Downloads and builds SQLite 3.51.2 as a static library, then links it into libpython3.14.so. The _sqlite3 module is enabled via Modules/Setup.local. WASI-specific SQLite configuration: - SQLITE_OMIT_WAL (no mmap in WASI preview1) - SQLITE_OMIT_LOAD_EXTENSION (no dlopen) - SQLITE_THREADSAFE=0 (single-threaded WASM) Adds ~500KB to libpython3.14.so.zst (7.3MB → 7.8MB).
1 parent 4f8e446 commit 3faa0cb

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

build.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ use {
1818
const ZSTD_COMPRESSION_LEVEL: i32 = 19;
1919
const DEFAULT_SDK_VERSION: &str = "27";
2020

21+
// SQLite version to build - 3.51.2 (latest as of Jan 2026)
22+
const SQLITE_VERSION: &str = "3510200";
23+
const SQLITE_YEAR: &str = "2026";
24+
2125
#[cfg(any(target_os = "macos", target_os = "windows"))]
2226
const PYTHON_EXECUTABLE: &str = "python.exe";
2327
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
@@ -299,13 +303,17 @@ fn maybe_make_cpython(repo_dir: &Path, wasi_sdk: &Path) -> Result<()> {
299303
let lib_install_dir = cpython_wasi_dir.join("deps");
300304
build_zlib(wasi_sdk, &lib_install_dir)?;
301305

306+
build_sqlite(wasi_sdk, &lib_install_dir)?;
307+
302308
let config_guess =
303309
run(Command::new("../../config.guess").current_dir(&cpython_wasi_dir))?;
304310

305311
let dir = cpython_wasi_dir
306312
.to_str()
307313
.ok_or_else(|| anyhow!("non-utf8 path: {}", cpython_wasi_dir.display()))?;
308314

315+
// Configure CPython with SQLite support
316+
// The CFLAGS and LDFLAGS now include paths to both zlib AND sqlite
309317
run(Command::new("../../Tools/wasm/wasi-env")
310318
.env(
311319
"CONFIG_SITE",
@@ -335,11 +343,16 @@ fn maybe_make_cpython(repo_dir: &Path, wasi_sdk: &Path) -> Result<()> {
335343
"--enable-ipv6",
336344
]))?;
337345

346+
// Write Modules/Setup.local to force-enable _sqlite3
347+
// This ensures the module is built even if configure doesn't auto-detect it
348+
write_setup_local(&cpython_wasi_dir)?;
349+
338350
run(Command::new("make")
339351
.current_dir(&cpython_wasi_dir)
340352
.args(["build_all", "install"]))?;
341353
}
342354

355+
// Link libpython3.14.so - now includes libsqlite3.a
343356
run(Command::new(wasi_sdk.join("bin/clang"))
344357
.arg("--target=wasm32-wasip2")
345358
.arg("-shared")
@@ -357,6 +370,7 @@ fn maybe_make_cpython(repo_dir: &Path, wasi_sdk: &Path) -> Result<()> {
357370
.arg(cpython_wasi_dir.join("Modules/_decimal/libmpdec/libmpdec.a"))
358371
.arg(cpython_wasi_dir.join("Modules/expat/libexpat.a"))
359372
.arg(cpython_wasi_dir.join("deps/lib/libz.a"))
373+
.arg(cpython_wasi_dir.join("deps/lib/libsqlite3.a"))
360374
.arg("-lwasi-emulated-signal")
361375
.arg("-lwasi-emulated-getpid")
362376
.arg("-lwasi-emulated-process-clocks")
@@ -366,6 +380,39 @@ fn maybe_make_cpython(repo_dir: &Path, wasi_sdk: &Path) -> Result<()> {
366380
Ok(())
367381
}
368382

383+
/// Write Modules/Setup.local to enable _sqlite3 module
384+
///
385+
/// CPython's configure may not auto-detect sqlite3 for WASI cross-compilation,
386+
/// so we explicitly enable it here.
387+
fn write_setup_local(cpython_wasi_dir: &Path) -> Result<()> {
388+
let setup_local_path = cpython_wasi_dir.join("Modules/Setup.local");
389+
let deps_dir = cpython_wasi_dir.join("deps");
390+
391+
// The _sqlite3 module source files (relative to Modules/)
392+
// These are the files that make up the _sqlite3 extension in CPython 3.14
393+
// Note: blob.c is required - it defines pysqlite_close_all_blobs and pysqlite_blob_setup_types
394+
let setup_local_content = format!(
395+
r#"# Auto-generated by build.rs for SQLite support
396+
# Enable _sqlite3 module with statically linked SQLite
397+
398+
_sqlite3 _sqlite/blob.c _sqlite/connection.c _sqlite/cursor.c _sqlite/microprotocols.c _sqlite/module.c _sqlite/prepare_protocol.c _sqlite/row.c _sqlite/statement.c _sqlite/util.c -I{include} -L{lib} -lsqlite3
399+
"#,
400+
include = deps_dir.join("include").display(),
401+
lib = deps_dir.join("lib").display(),
402+
);
403+
404+
// Create the Modules directory if it doesn't exist
405+
fs::create_dir_all(cpython_wasi_dir.join("Modules"))?;
406+
fs::write(&setup_local_path, setup_local_content)?;
407+
408+
println!(
409+
"cargo:warning=Wrote Modules/Setup.local to enable _sqlite3: {}",
410+
setup_local_path.display()
411+
);
412+
413+
Ok(())
414+
}
415+
369416
fn run(command: &mut Command) -> Result<Vec<u8>> {
370417
let command_string = iter::once(command.get_program())
371418
.chain(command.get_args())
@@ -557,3 +604,114 @@ fn build_zlib(wasi_sdk: &Path, install_dir: &Path) -> Result<()> {
557604

558605
Ok(())
559606
}
607+
608+
/// Build SQLite for WASI
609+
///
610+
/// Downloads the SQLite amalgamation source and builds it as a static library
611+
/// for WASI. Key configuration:
612+
/// - SQLITE_OMIT_WAL: WAL requires mmap which isn't available in WASI preview1
613+
/// - SQLITE_OMIT_LOAD_EXTENSION: No dlopen in WASI
614+
/// - SQLITE_THREADSAFE=0: Single-threaded for WASM
615+
fn build_sqlite(wasi_sdk: &Path, install_dir: &Path) -> Result<()> {
616+
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
617+
618+
// Check if already built
619+
if install_dir.join("lib/libsqlite3.a").exists() {
620+
println!("cargo:warning=SQLite already built, skipping");
621+
return Ok(());
622+
}
623+
624+
println!("cargo:warning=Building SQLite {SQLITE_VERSION} for WASI...");
625+
626+
// Download SQLite amalgamation
627+
let url = format!("https://sqlite.org/{SQLITE_YEAR}/sqlite-autoconf-{SQLITE_VERSION}.tar.gz");
628+
fetch_extract(&url, &out_dir)?;
629+
630+
let src_dir = out_dir.join(format!("sqlite-autoconf-{SQLITE_VERSION}"));
631+
632+
// Ensure install directories exist
633+
fs::create_dir_all(install_dir.join("lib"))?;
634+
fs::create_dir_all(install_dir.join("include"))?;
635+
636+
let sysroot = wasi_sdk.join("share/wasi-sysroot");
637+
let sysroot_str = sysroot.to_string_lossy();
638+
639+
// SQLite-specific CFLAGS for WASI compatibility
640+
// Note: Don't set SQLITE_THREADSAFE here - let --disable-threadsafe handle it
641+
// to avoid macro redefinition warnings
642+
let sqlite_cflags = format!(
643+
"--target=wasm32-wasi \
644+
--sysroot={sysroot} \
645+
-I{sysroot}/include/wasm32-wasip1 \
646+
-D_WASI_EMULATED_SIGNAL \
647+
-D_WASI_EMULATED_PROCESS_CLOCKS \
648+
-fPIC \
649+
-O2 \
650+
-DSQLITE_OMIT_WAL \
651+
-DSQLITE_OMIT_LOAD_EXTENSION \
652+
-DSQLITE_OMIT_LOCALTIME \
653+
-DSQLITE_OMIT_RANDOMNESS \
654+
-DSQLITE_OMIT_SHARED_CACHE",
655+
sysroot = sysroot_str
656+
);
657+
658+
// Configure SQLite
659+
let mut configure = Command::new("./configure");
660+
configure
661+
.current_dir(&src_dir)
662+
.env("AR", wasi_sdk.join("bin/ar"))
663+
.env("CC", wasi_sdk.join("bin/clang"))
664+
.env("RANLIB", wasi_sdk.join("bin/ranlib"))
665+
.env("CFLAGS", &sqlite_cflags)
666+
.env(
667+
"LDFLAGS",
668+
format!(
669+
"--target=wasm32-wasip2 --sysroot={sysroot} -L{sysroot}/lib",
670+
sysroot = sysroot_str
671+
),
672+
)
673+
.arg("--host=wasm32-wasi")
674+
.arg(format!("--prefix={}", install_dir.display()))
675+
.arg("--disable-shared")
676+
.arg("--enable-static")
677+
.arg("--disable-readline")
678+
.arg("--disable-threadsafe")
679+
.arg("--disable-load-extension");
680+
681+
run(&mut configure)?;
682+
683+
// Build only the static library (not the shell, which fails to link on WASI)
684+
let mut make = Command::new("make");
685+
make.current_dir(&src_dir)
686+
.env("AR", wasi_sdk.join("bin/ar"))
687+
.env("CC", wasi_sdk.join("bin/clang"))
688+
.env("RANLIB", wasi_sdk.join("bin/ranlib"))
689+
.env("CFLAGS", &sqlite_cflags)
690+
.arg(format!("AR={}", wasi_sdk.join("bin/ar").display()))
691+
.arg("ARFLAGS=rcs")
692+
.arg("libsqlite3.a"); // Build only the static library
693+
run(&mut make)?;
694+
695+
// Manual install since we didn't build everything
696+
// Copy the library
697+
fs::copy(
698+
src_dir.join("libsqlite3.a"),
699+
install_dir.join("lib/libsqlite3.a"),
700+
)?;
701+
// Copy the headers
702+
fs::copy(
703+
src_dir.join("sqlite3.h"),
704+
install_dir.join("include/sqlite3.h"),
705+
)?;
706+
fs::copy(
707+
src_dir.join("sqlite3ext.h"),
708+
install_dir.join("include/sqlite3ext.h"),
709+
)?;
710+
711+
println!(
712+
"cargo:warning=SQLite built successfully: {}",
713+
install_dir.join("lib/libsqlite3.a").display()
714+
);
715+
716+
Ok(())
717+
}

0 commit comments

Comments
 (0)