1818const ZSTD_COMPRESSION_LEVEL : i32 = 19 ;
1919const 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" ) ) ]
2226const 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+
369416fn 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