From 9cd0e995bd0d354c64978e9f41c5de4fbf101e98 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Tue, 19 May 2026 00:26:53 +0900 Subject: [PATCH] Add initial support for building with extensions --- flake.nix | 61 ++++++++++++++++++++++--- sqlite-example-exts/addext.c | 38 ++++++++++++++++ sqlite-example-exts/subext.c | 38 ++++++++++++++++ src/amalgamation.nix | 6 ++- src/core-init-ext.nix | 87 ++++++++++++++++++++++++++++++++++++ src/mk-sqlite.nix | 51 +++++++++++++++++---- src/sqlite-cli.nix | 14 +++--- src/sqlite.nix | 15 ++++--- 8 files changed, 281 insertions(+), 29 deletions(-) create mode 100644 sqlite-example-exts/addext.c create mode 100644 sqlite-example-exts/subext.c create mode 100644 src/core-init-ext.nix diff --git a/flake.nix b/flake.nix index 3e19706..5fdeb8b 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,7 @@ enableDebug = [ true false ]; enableInteractive = [ true false ]; }; - in lib.mergeAttrsList (lib.mapCartesianProduct (args@{ + in lib.mergeAttrsList (lib.mapCartesianProduct ({ enableMinimal, enableDebug, enableInteractive, @@ -35,11 +35,60 @@ enableInteractive; }; in { - "sqlite${result.suffix}" = result.sqlite; - "sqlite-amalgamation${result.suffix}" = result.amalgamation; - "sqlite-cli${result.suffix}" = result.sqlite-cli; - "sqlite-static${result.suffix}" = result.sqlite-static; + "sqlite${result._suffix}" = result.sqlite; + "sqlite-amalgamation${result._suffix}" = result.amalgamation; + "sqlite-cli${result._suffix}" = result.sqlite-cli; + "sqlite-static${result._suffix}" = result.sqlite-static; }) productVars)); + + checks = forAllSystems (system: pkgs: let + sqlite-example-exts-static = pkgs.stdenv.mkDerivation { + name = "sqlite-example-exts-static"; + src = ./sqlite-example-exts; + + env.NIX_CFLAGS_COMPILE = "-DSQLITE_CORE"; + + buildInputs = with pkgs; [ sqlite.dev ]; + buildPhase = '' + runHook preBuild + + "$CC" -c addext.c + "$AR" rcs libaddext.a addext.o + + "$CC" -c subext.c + "$AR" rcs libsubext.a subext.o + + runHook postBuild + ''; + + + installPhase = '' + runHook preInstall + + install -Dm444 *.a -t "$out/lib" + + runHook postInstall + ''; + }; + + sqlite-with-static-exts = self.mkSqlite { + inherit pkgs; + + extensions = [ + { + library = "${sqlite-example-exts-static}/lib/libaddext.a"; + init = "sqlite3_addext_init"; + } + { + library = "${sqlite-example-exts-static}/lib/libsubext.a"; + init = "sqlite3_subext_init"; + } + ]; + }; + in { + inherit sqlite-example-exts-static; + sqlite-with-static-ext = sqlite-with-static-exts.sqlite; + sqlite-cli-with-static-ext = sqlite-with-static-exts.sqlite-cli; + }); }; } - diff --git a/sqlite-example-exts/addext.c b/sqlite-example-exts/addext.c new file mode 100644 index 0000000..39b5788 --- /dev/null +++ b/sqlite-example-exts/addext.c @@ -0,0 +1,38 @@ +#include + +SQLITE_EXTENSION_INIT1 + +static void add_func( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +) { + if (argc != 2) { + sqlite3_result_null(ctx); + return; + } + + int a = sqlite3_value_int(argv[0]); + int b = sqlite3_value_int(argv[1]); + + sqlite3_result_int(ctx, a + b); +} + +int sqlite3_addext_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +) { + SQLITE_EXTENSION_INIT2(pApi); + + return sqlite3_create_function( + db, + "add", + 2, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, + 0, + add_func, + 0, + 0 + ); +} diff --git a/sqlite-example-exts/subext.c b/sqlite-example-exts/subext.c new file mode 100644 index 0000000..7e9686d --- /dev/null +++ b/sqlite-example-exts/subext.c @@ -0,0 +1,38 @@ +#include + +SQLITE_EXTENSION_INIT1 + +static void sub_func( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +) { + if (argc != 2) { + sqlite3_result_null(ctx); + return; + } + + int a = sqlite3_value_int(argv[0]); + int b = sqlite3_value_int(argv[1]); + + sqlite3_result_int(ctx, a - b); +} + +int sqlite3_subext_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +) { + SQLITE_EXTENSION_INIT2(pApi); + + return sqlite3_create_function( + db, + "sub", + 2, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, + 0, + sub_func, + 0, + 0 + ); +} diff --git a/src/amalgamation.nix b/src/amalgamation.nix index 360e384..b07cdfa 100644 --- a/src/amalgamation.nix +++ b/src/amalgamation.nix @@ -11,8 +11,10 @@ zlib, icu, - features, - featureFlags, + extensions ? [ ], + extraLibraries ? [ ], + features ? { }, + featureFlags ? [ ], enableInteractive ? false, ... diff --git a/src/core-init-ext.nix b/src/core-init-ext.nix new file mode 100644 index 0000000..b77b613 --- /dev/null +++ b/src/core-init-ext.nix @@ -0,0 +1,87 @@ +{ + lib, + + extensions, + nameSuffix, + version, + + stdenv, + writeText, + + ... +}: + +assert lib.assertMsg (extensions != [ ]) "This derivation should not be evaluated if there are no extensions to build."; + +# TODO: assert no colliding init/shutdown symbols across extensions. + +let + initSymbols = builtins.catAttrs "init" extensions; + shutdownSymbols = builtins.catAttrs "shutdown" extensions; + + coreInitHeaders = writeText "sqlite-core-init.h" '' + int core_init(const char *dummy); + void core_shutdown(void); + + ${lib.concatMapStringsSep "\n" (sym: "int ${sym}(const char *dummy);") initSymbols} + ${lib.concatMapStringsSep "\n" (sym: "void ${sym}(void);") shutdownSymbols} + ''; + + coreInitImpl = writeText "sqlite-core-init.c" '' + #include "${coreInitHeaders}" + + int core_init(const char *dummy) { + int err = 0; + ${lib.concatMapStringsSep "\n" (sym: "err += ${sym}(dummy);") initSymbols} + return err; + } + + void core_shutdown(void) { + ${lib.concatMapStringsSep "\n" (sym: "${sym}();") shutdownSymbols} + } + ''; +in stdenv.mkDerivation (finalAttrs: { + pname = "sqlite-core-init${nameSuffix}"; + version = version; + + src = null; + dontUnpack = true; + + buildPhase = '' + runHook preBuild + "$CC" -c '${coreInitImpl}' -o sqlite-core-init.o + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + install -Dm644 '${coreInitHeaders}' "$out/include/sqlite-core-init.h" + mkdir -p "$out/lib" + "$AR" rcs "$out/lib/libsqlite-core-init.a" sqlite-core-init.o + + runHook postInstall + ''; + + # TODO: Add a checkphase that runs nm on the provided extensions to ensure the expected symbols are present. + # Also verify that the `sqlite_api` symbol is not present, and give a nice warning about using -DSQLITE_CORE + # if it is. + + passthru = { + extraLDFLAGS = [ + "-L${finalAttrs.finalPackage}/lib" + "-lsqlite-core-init" + ] ++ lib.concatMap (ext: [ + "-L${lib.dirOf ext.library}" + "-l${lib.removeSuffix ".so" (lib.removeSuffix ".a" (lib.removePrefix "lib" (lib.baseNameOf ext.library)))}" + ]) extensions; + + extraMakeFlags = let + opts = [ + "-DSQLITE_EXTRA_INIT=core_init" + "-DSQLITE_EXTRA_SHUTDOWN=core_shutdown" + "-DSQLITE_CUSTOM_INCLUDE=${placeholder "out"}/include/sqlite-core-init.h" + ]; + in map (opt: "OPTIONS+=${lib.escapeShellArg opt}") opts; + }; +}) diff --git a/src/mk-sqlite.nix b/src/mk-sqlite.nix index e761e05..a02f49a 100644 --- a/src/mk-sqlite.nix +++ b/src/mk-sqlite.nix @@ -25,12 +25,21 @@ features ? { }, # Custom SQLite extensions and their entrypoints. - # extensions ? [ ], + # + # extensions :: [{ library :: str, init :: str, shutdown :: nullOr str }] + extensions ? [ ], # Additional libraries to link against, useful when the extensions # use external libraries. - # extraLibraries ? [ ], -}: let + extraLibraries ? [ ], +}: + +assert lib.assertMsg (extensions == [ ] -> extraLibraries == [ ]) "If no extensions are specified, extraLibraries must be empty."; + +# TODO: fix compiling with extensions with amalgamation turned off +assert lib.assertMsg (extensions != [ ] -> amalgamate) "Building with extensions without amalgamation is not currently supported."; + +let config = (lib.evalModules { modules = [ ./build-config.nix @@ -53,32 +62,56 @@ (lib.concatStringsSep "-") (x: if x == "" then "" else "-${x}") ]; + + coreInitExt = if extensions == [ ] + then null + else pkgs.callPackage ./core-init-ext.nix ({ + inherit version src; + inherit extensions; + inherit extraLibraries; + nameSuffix = suffix; + } // config); in { - inherit config; - inherit suffix; + _config = config; + _suffix = suffix; + _coreInitExt = coreInitExt; sqlite = pkgs.callPackage ./sqlite.nix ({ - inherit (pkgs.sqlite) version src; + inherit version src; inherit amalgamate; + inherit features; + inherit extensions; + inherit extraLibraries; + inherit coreInitExt; nameSuffix = suffix; static = false; } // config); sqlite-static = pkgs.callPackage ./sqlite.nix ({ - inherit (pkgs.sqlite) version src; + inherit version src; inherit amalgamate; + inherit features; + inherit extensions; + inherit extraLibraries; + inherit coreInitExt; nameSuffix = suffix; static = true; } // config); sqlite-cli = pkgs.callPackage ./sqlite-cli.nix ({ - inherit (pkgs.sqlite) version src; + inherit version src; inherit amalgamate; + inherit features; + inherit extensions; + inherit extraLibraries; nameSuffix = suffix; } // config); } // lib.optionalAttrs amalgamate { amalgamation = pkgs.callPackage ./amalgamation.nix ({ - inherit (pkgs.sqlite) version src; + inherit version src; + inherit features; + inherit extensions; + inherit extraLibraries; nameSuffix = suffix; } // config); } diff --git a/src/sqlite-cli.nix b/src/sqlite-cli.nix index 411867e..0f4e6ba 100644 --- a/src/sqlite-cli.nix +++ b/src/sqlite-cli.nix @@ -11,9 +11,10 @@ zlib, icu, - amalgamate, - features, - featureFlags, + amalgamate ? true, + coreInitExt ? null, + features ? { }, + featureFlags ? [ ], enableInteractive ? false, ... @@ -57,8 +58,9 @@ stdenv.mkDerivation (finalAttrs: { "--with-icu-config=${lib.getExe' icu.dev "icu-config"}" ]; - env.NIX_CFLAGS_COMPILE = lib.concatStringsSep " " featureFlags; - env.NIX_CFLAGS_LINK = lib.optionalString features.ENABLE_FTS5 "-lm"; - makeTarget = "sqlite3"; + makeFlags = (coreInitExt.passthru.makeFlags or [ ]) ++ map (opt: "OPTIONS+=${lib.escapeShellArg opt}") featureFlags; + + env.NIX_CFLAGS_LINK = lib.optionalString features.ENABLE_FTS5 "-lm"; + env.NIX_LDFLAGS = lib.concatStringsSep " " (coreInitExt.passthru.extraLDFLAGS or [ ]); }) diff --git a/src/sqlite.nix b/src/sqlite.nix index ed4b054..4e7738a 100644 --- a/src/sqlite.nix +++ b/src/sqlite.nix @@ -13,9 +13,10 @@ zlib, icu, - amalgamate, - features, - featureFlags, + amalgamate ? true, + coreInitExt ? null, + features ? { }, + featureFlags ? [ ], enableInteractive ? false, ... @@ -54,13 +55,15 @@ stdenv.mkDerivation (finalAttrs: { "--${if static then "disable" else "enable"}-shared" "--${if static then "enable" else "disable"}-static" "--${if amalgamate then "enable" else "disable"}-amalgamation" + ] ++ lib.optional features.ENABLE_ICU [ "--enable-icu-collations" "--with-icu-config=${lib.getExe' icu.dev "icu-config"}" ]; - env.NIX_CFLAGS_COMPILE = lib.concatStringsSep " " featureFlags; - env.NIX_CFLAGS_LINK = lib.optionalString features.ENABLE_FTS5 "-lm"; + makeTarget = if static then "lib" else "so"; + makeFlags = (coreInitExt.passthru.makeFlags or [ ]) ++ map (opt: "OPTIONS+=${lib.escapeShellArg opt}") featureFlags; - makeTarget = "sqlite3.la"; + env.NIX_CFLAGS_LINK = lib.optionalString features.ENABLE_FTS5 "-lm"; + env.NIX_LDFLAGS = lib.concatStringsSep " " (coreInitExt.passthru.extraLDFLAGS or [ ]); })