From c8cd55fd93f48328342566ef3b4d854e8109d9af Mon Sep 17 00:00:00 2001 From: David Martos Date: Tue, 19 May 2026 17:31:05 +0200 Subject: [PATCH 01/37] sqlcipher target build for macOS openssl build android support sqlcipher output support ios disable linux 32 bits format fix download skip tests lint fix analyze no analyze no sanitizer build cleanup targetos crypto switch link with common crypto on macos/ios support tar xz build for iOS version 3.3.0 Revert "version 3.3.0" This reverts commit 4eb21c00edd24fd8cd5611200ef1a376f29cbb6f. sqlite3-sqlcipher tag errata only prepare release link android log good hashes ignore wasm test allow prebuilt sqlcipher hashes hashes trigger deploy hashes whitespace cleanup --- .github/workflows/compile_sqlite.yml | 4 +- .github/workflows/main.yml | 60 +-- .github/workflows/release.yml | 109 ++--- build_ssl.sh | 13 + sqlite3/hook/build.dart | 57 ++- sqlite3/lib/src/hook/android_ndk.dart | 77 ++++ sqlite3/lib/src/hook/asset_hashes.dart | 50 +-- sqlite3/lib/src/hook/assets.dart | 5 +- sqlite3/lib/src/hook/description.dart | 72 +++- sqlite3/lib/src/hook/openssl.dart | 462 +++++++++++++++++++++ sqlite3_connection_pool/lib/src/raw.dart | 3 +- sqlite3_web/lib/src/protocol/helper.g.dart | 3 +- tool/build_sqlite.dart | 45 +- tool/build_with_sanitizers.dart | 2 +- tool/download_sqlite.dart | 17 + 15 files changed, 831 insertions(+), 148 deletions(-) create mode 100755 build_ssl.sh create mode 100644 sqlite3/lib/src/hook/android_ndk.dart create mode 100644 sqlite3/lib/src/hook/openssl.dart diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index f20d29e3..76ce280e 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -48,8 +48,9 @@ jobs: build_sqlite: needs: [download_sqlite] strategy: + fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} name: Compile sqlite3 @@ -96,6 +97,7 @@ jobs: build_with_sanitizers: needs: [download_sqlite] + if: false runs-on: ubuntu-latest outputs: libs: ${{ steps.upload.outputs.artifact-id }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7bf50f56..fd07ff60 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,36 +44,36 @@ jobs: - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.dart }} - - uses: actions/cache@v5 - with: - path: "${{ env.PUB_CACHE }}" - key: dart-dependencies-${{ matrix.dart }}-${{ runner.os }} - restore-keys: | - dart-dependencies-${{ matrix.dart }}- - dart-dependencies- - - - name: Pub get - run: dart pub get - - - name: Format dart - run: | - dart format --set-exit-if-changed sqlite3 - dart format --set-exit-if-changed sqlite3_test - dart format --set-exit-if-changed sqlite3_web - - - name: Format native - run: clang-format --Werror --dry-run --style=google assets/*.h - working-directory: sqlite3 - - - name: Format native - run: clang-format --Werror --dry-run --style=google src/*{.h,.c} - working-directory: sqlite3_wasm_build - - - name: Analyze - run: dart analyze --fatal-infos sqlite3 sqlite3_web sqlite3_test + # - uses: actions/cache@v5 + # with: + # path: "${{ env.PUB_CACHE }}" + # key: dart-dependencies-${{ matrix.dart }}-${{ runner.os }} + # restore-keys: | + # dart-dependencies-${{ matrix.dart }}- + # dart-dependencies- + + # - name: Pub get + # run: dart pub get + + # - name: Format dart + # run: | + # dart format --set-exit-if-changed sqlite3 + # dart format --set-exit-if-changed sqlite3_test + # dart format --set-exit-if-changed sqlite3_web + + # - name: Format native + # run: clang-format --Werror --dry-run --style=google assets/*.h + # working-directory: sqlite3 + + # - name: Format native + # run: clang-format --Werror --dry-run --style=google src/*{.h,.c} + # working-directory: sqlite3_wasm_build + + # - name: Analyze + # run: dart analyze --fatal-infos sqlite3 sqlite3_web sqlite3_test test: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) + if: false && github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) timeout-minutes: 5 needs: [analyze, fetch_sqlite] strategy: @@ -185,7 +185,7 @@ jobs: upload_asset_hashes: if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) timeout-minutes: 5 - needs: [test, fetch_sqlite] + needs: [fetch_sqlite] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -219,7 +219,7 @@ jobs: retention-days: 1 integration_test_web: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) + if: false && github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) timeout-minutes: 10 needs: [analyze, fetch_sqlite] runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dac08026..8e7bae6b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: - 'sqlite3_test-[0-9]+.[0-9]+.[0-9]+*' - 'sqlite3_web-[0-9]+.[0-9]+.[0-9]+*' - 'sqlite3_connection_pool-[0-9]+.[0-9]+.[0-9]+*' + - 'sqlite3-sqlcipher-[0-9]+.[0-9]+.[0-9]+*' permissions: {} @@ -61,62 +62,62 @@ jobs: gh release upload "$tag" sqlite-compiled/* - publish_sqlite3: - needs: [prepare_release] - permissions: - id-token: write # Needed to create OIDC token for pub.dev - if: "${{ startsWith(github.ref_name, 'sqlite3-') }}" - uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 - with: - environment: 'pub.dev' - working-directory: sqlite3/ + # publish_sqlite3: + # needs: [prepare_release] + # permissions: + # id-token: write # Needed to create OIDC token for pub.dev + # if: "${{ startsWith(github.ref_name, 'sqlite3-') }}" + # uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + # with: + # environment: 'pub.dev' + # working-directory: sqlite3/ - publish_sqlite3_test: - needs: [prepare_release] - permissions: - id-token: write # Needed to create OIDC token for pub.dev - if: "${{ startsWith(github.ref_name, 'sqlite3_test-') }}" - uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 - with: - environment: 'pub.dev' - working-directory: sqlite3_test/ + # publish_sqlite3_test: + # needs: [prepare_release] + # permissions: + # id-token: write # Needed to create OIDC token for pub.dev + # if: "${{ startsWith(github.ref_name, 'sqlite3_test-') }}" + # uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + # with: + # environment: 'pub.dev' + # working-directory: sqlite3_test/ - publish_sqlite3_web: - needs: [prepare_release] - permissions: - id-token: write # Needed to create OIDC token for pub.dev - if: "${{ startsWith(github.ref_name, 'sqlite3_web-') }}" - uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 - with: - environment: 'pub.dev' - working-directory: sqlite3_web/ + # publish_sqlite3_web: + # needs: [prepare_release] + # permissions: + # id-token: write # Needed to create OIDC token for pub.dev + # if: "${{ startsWith(github.ref_name, 'sqlite3_web-') }}" + # uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + # with: + # environment: 'pub.dev' + # working-directory: sqlite3_web/ - publish_sqlite3_connection_pool: - needs: [fetch_sqlite, prepare_release] - permissions: - id-token: write # Needed to create OIDC token for pub.dev - if: "${{ startsWith(github.ref_name, 'sqlite3_connection_pool-') }}" - runs-on: ubuntu-latest - environment: 'pub.dev' - # This can't use the workflow because we need to download assets to include in - # the package. - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - uses: dart-lang/setup-dart@v1 # This will request an OIDC token for pub.dev + # publish_sqlite3_connection_pool: + # needs: [fetch_sqlite, prepare_release] + # permissions: + # id-token: write # Needed to create OIDC token for pub.dev + # if: "${{ startsWith(github.ref_name, 'sqlite3_connection_pool-') }}" + # runs-on: ubuntu-latest + # environment: 'pub.dev' + # # This can't use the workflow because we need to download assets to include in + # # the package. + # steps: + # - uses: actions/checkout@v6 + # with: + # persist-credentials: false + # - uses: dart-lang/setup-dart@v1 # This will request an OIDC token for pub.dev - - name: Download connection pool libraries - uses: actions/download-artifact@v8 - with: - artifact-ids: ${{ needs.fetch_sqlite.outputs.pool_libs_artifact_id }} - path: sqlite3_connection_pool/lib/src/precompiled/ + # - name: Download connection pool libraries + # uses: actions/download-artifact@v8 + # with: + # artifact-ids: ${{ needs.fetch_sqlite.outputs.pool_libs_artifact_id }} + # path: sqlite3_connection_pool/lib/src/precompiled/ - - name: Pub get - run: dart pub get - - name: Publish - dry run - run: dart pub publish --dry-run - working-directory: sqlite3_connection_pool - - name: Publish to pub.dev - run: dart pub publish -f - working-directory: sqlite3_connection_pool + # - name: Pub get + # run: dart pub get + # - name: Publish - dry run + # run: dart pub publish --dry-run + # working-directory: sqlite3_connection_pool + # - name: Publish to pub.dev + # run: dart pub publish -f + # working-directory: sqlite3_connection_pool diff --git a/build_ssl.sh b/build_ssl.sh new file mode 100755 index 00000000..17bca368 --- /dev/null +++ b/build_ssl.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu + + +pushd openssl + +BUILD_DIR=$(realpath ../openssl-build) +./Configure no-ssl no-tls no-dtls no-engine no-deprecated no-shared no-tests no-docs no-apps --prefix=$BUILD_DIR --openssldir=$BUILD_DIR \ + '-Wl,-rpath,$(LIBRPATH)' + +make -j 8 +make install +popd \ No newline at end of file diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 1697b4a8..c62c1563 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -6,6 +6,7 @@ import 'package:native_toolchain_c/native_toolchain_c.dart'; import 'package:path/path.dart' as p; import 'package:sqlite3/src/hook/description.dart'; +import 'package:sqlite3/src/hook/openssl.dart'; import 'package:sqlite3/src/hook/used_symbols.dart'; void main(List args) async { @@ -59,12 +60,57 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} '''); } + final targetOS = input.config.code.targetOS; + + final List includes = []; + final List libraryDirectories = []; + final List libraries = []; + final List flags = []; + + switch (targetOS) { + case OS.macOS: + case OS.iOS: + flags.addAll([ + '-framework', + 'Foundation', + '-framework', + 'Security', + ]); + break; + case OS.android: + case OS.linux: + case OS.windows: + final kOpenSSLBuiltDir = (await buildOpenSSL(input, output))!.path; + print("BUILT OPENSSL IN $kOpenSSLBuiltDir"); + + final cryptoStaticLib = File( + getStaticCryptoLib( + Directory(kOpenSSLBuiltDir), + input.config.code.targetOS, + input.config.code.targetArchitecture, + ), + ); + + includes.add('$kOpenSSLBuiltDir/include'); + libraryDirectories.add(cryptoStaticLib.parent.path); + libraries.add('crypto'); + default: + throw UnsupportedError( + 'Unsupported OS: ${input.config.code.targetOS}', + ); + } + + // final files = Directory(kOpenSSLBuiltDir).listSync(); + // print(files); + + final isMacLike = [OS.iOS, OS.macOS].contains(targetOS); + final library = CBuilder.library( name: 'sqlite3', packageName: 'sqlite3', assetName: name, sources: [sourceFile], - includes: [p.dirname(sourceFile)], + includes: [p.dirname(sourceFile), ...includes], defines: defines, flags: [ if (input.config.code.targetOS == OS.linux) ...[ @@ -78,7 +124,7 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} '-fdata-sections', '-Wl,--gc-sections', ], - if (input.config.code.targetOS case OS.iOS || OS.macOS) ...[ + if (isMacLike) ...[ '-headerpad_max_install_names', // clang would use the temporary directory passed by // native_toolchain_c otherwise. So this makes improves @@ -86,11 +132,16 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} '-install_name', '@rpath/libsqlite3.dylib', ], + ...flags, ], + libraryDirectories: [...libraryDirectories], libraries: [ - if (input.config.code.targetOS == OS.android) + if (targetOS == OS.android || targetOS == OS.linux) ...[ // We need to link the math library on Android. 'm', + ], + if (targetOS == OS.android) 'log', + ...libraries, ], ); diff --git a/sqlite3/lib/src/hook/android_ndk.dart b/sqlite3/lib/src/hook/android_ndk.dart new file mode 100644 index 00000000..7681cfc0 --- /dev/null +++ b/sqlite3/lib/src/hook/android_ndk.dart @@ -0,0 +1,77 @@ +// ignore_for_file: depend_on_referenced_packages, implementation_imports + +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:native_toolchain_c/src/native_toolchain/android_ndk.dart' + as ntc_ndk; +import 'package:native_toolchain_c/src/tool/tool_instance.dart'; +import 'package:native_toolchain_c/src/tool/tool_resolver.dart'; + +/// Resolve the Android NDK root, picking the latest installed version. +/// Uses the native_toolchain_cmake resolver first, then falls back to a local scan. +Future resolveAndroidNdkRoot() async { + try { + final resolver = ntc_ndk.androidNdk.defaultResolver; + if (resolver != null) { + final instances = await resolver.resolve( + ToolResolvingContext(logger: Logger.detached('android_ndk')), + ); + final ndkInstance = _pickNdkInstance(instances); + final ndkPath = Directory.fromUri(ndkInstance.uri).path; + if (await Directory(ndkPath).exists()) { + return ndkPath; + } + } + } catch (_) { + // fall through to legacy resolution + } + throw Exception( + 'Android NDK not found. Set ANDROID_NDK_ROOT or install it via Android Studio (SDK Manager > NDK).', + ); +} + +ToolInstance _pickNdkInstance(List instances) { + final ndkInstances = instances + .where((i) => i.tool.name == ntc_ndk.androidNdk.name) + .toList(); + if (ndkInstances.isEmpty) { + throw StateError('No Android NDK instance resolved'); + } + + ndkInstances.sort( + (a, b) => switch ((a.version, b.version)) { + (null, null) => 0, + (null, _) => 1, + (_, null) => -1, + (_, _) => -a.version!.compareTo(b.version!), + }, + ); + + return ndkInstances.first; +} + +String resolveAndroidToolchainBinDir(String ndkRoot) { + // Android NDK toolchain binaries live under toolchains/llvm/prebuilt//bin. + // Pick the first host tag that exists for the current platform. + final hostTags = switch (Platform.operatingSystem) { + 'macos' => const ['darwin-x86_64', 'darwin-arm64'], + 'linux' => const ['linux-x86_64'], + 'windows' => const ['windows-x86_64', 'windows-arm64'], + final os => throw UnsupportedError( + 'Unsupported host OS for Android NDK: $os', + ), + }; + + for (final host in hostTags) { + final binDir = Directory('$ndkRoot/toolchains/llvm/prebuilt/$host/bin'); + if (binDir.existsSync()) { + return binDir.path; + } + } + + throw StateError( + 'Android NDK toolchain bin directory not found under ' + '$ndkRoot/toolchains/llvm/prebuilt for host ${Platform.operatingSystem}.', + ); +} diff --git a/sqlite3/lib/src/hook/asset_hashes.dart b/sqlite3/lib/src/hook/asset_hashes.dart index 92591aae..086f16bf 100644 --- a/sqlite3/lib/src/hook/asset_hashes.dart +++ b/sqlite3/lib/src/hook/asset_hashes.dart @@ -6,43 +6,19 @@ // dart format off // ignore: unnecessary_nullable_for_final_variable_declarations -const String? releaseTag = 'sqlite3-3.3.1'; +const String? releaseTag = 'sqlite3-sqlcipher-3.3.2'; const Map assetNameToSha256Hash = { - 'libsqlite3.arm.android.so': '72d43c7c7119947ce2a7525bd63c4795b3cea250c09dba98c4fb9e52c7dbec82', - 'libsqlite3.arm.linux.so': '1d8b40878c73b8204333fa40c2d175c439a9e7a95e0644f7a14ed926cd748061', - 'libsqlite3.arm64.android.so': 'cf2378ec6fa20479184d6c44b03c332343d2e0f5c648591f5302dca27ed41a94', - 'libsqlite3.arm64.ios.dylib': '81d4c606fecdd06cac958c7ed4420ea274d94bcbf13704f84271920be6b8313d', - 'libsqlite3.arm64.ios_sim.dylib': '3b3ad68054d03bbd2ae3b12c1dc316e94bf8d30ec6712d3d9e17884894be9185', - 'libsqlite3.arm64.linux.so': '0541443d38cd79160f6262e6a59892657d640459fef8acdd1f4f29a4ed0d2612', - 'libsqlite3.arm64.macos.dylib': '44ae2c22adb504b50b9019535d5c7d901eed93cb380b526e710ecb64f529a742', - 'libsqlite3.ia32.android.so': 'eb28aed906d4b28ab81cac7742b37652e68b599bb95547cf3fcac456939b2606', - 'libsqlite3.ia32.linux.so': 'abfef4eee37a1a7f93c4234cfdb530872b489ebd6eb535b8a3fae9ce0f28805a', - 'libsqlite3.riscv64.linux.so': 'f478d471f6b466631cc91a9320f94f3bba9209e419f7ab344fd1ca7547df6839', - 'libsqlite3.x64.android.so': '2f235dff75b348ee62e5ae01177ea77ce1b1c9ebae7cb1a499319e7c82ec1358', - 'libsqlite3.x64.ios_sim.dylib': '6c15b38f4014cf581d0665c81860f2f1f81e7895878057ca93305b7112bf2b11', - 'libsqlite3.x64.linux.so': '148c51687ac3be3487873845690ddbf3d8ee099a5521a193c856008907fb7361', - 'libsqlite3.x64.macos.dylib': '0d62e4fe358da23494823804ed66e0dbed43d6f9a5ab71618fad713e4e6d1a38', - 'libsqlite3mc.arm.android.so': '7acdde3afc25684b7d14dc3b2af7bb56ccc78032413480b0036af3431c23f45c', - 'libsqlite3mc.arm.linux.so': 'cd960fa458519334c7d90ea7ffbe2e652f9e2960d707abd0c9e05b890f999eca', - 'libsqlite3mc.arm64.android.so': '858536d508bb0b60f8b06f6f6c1d4465c52431a043ad723f655ea0859e57965c', - 'libsqlite3mc.arm64.ios.dylib': '1a45e2fdfdd14eaf4e7f04f35ae4cec153c042b6cfef74a3e56001dd3d2dc589', - 'libsqlite3mc.arm64.ios_sim.dylib': 'b49353f173969715cd7dbadb311b22c5cba1d7701fdfd1e4721dadf4c792cc15', - 'libsqlite3mc.arm64.linux.so': '2bb3a48cc6baa6697f4a1e8989799ae852e05fc779d7cec0443f01f76a0787c4', - 'libsqlite3mc.arm64.macos.dylib': '1fb217fa065b1653b32257eb78e15bc92e5c29ba6ff983ea1b91b2b03e3d35ce', - 'libsqlite3mc.ia32.android.so': '23e0625f5df62059432c916c123af2e52547a28b1ef8a7b2ddd76db2d8c5e128', - 'libsqlite3mc.riscv64.linux.so': '93a8b841b07b411846dce367e6a9e2e6e56dad438579926f04b4e65e4e581b40', - 'libsqlite3mc.x64.android.so': 'a64a3bf30b951915fe196dad47733b8d94351b9711c845858e1079fa35906fef', - 'libsqlite3mc.x64.ios_sim.dylib': '1bc73ad3882ed5a179528dacdb9c4ec562780b500a5ea4d00cef460646ce6a9a', - 'libsqlite3mc.x64.linux.so': '9139316c6bffee12ea095c5353fa2a10362aa3698cf04cf12ffcf982e248dc1a', - 'libsqlite3mc.x64.macos.dylib': '21cd27136d567037eb039228c78c66e904bb20d953f630e2ad5e373ad9f69912', - 'sqlite3.arm64.windows.dll': 'eb4a75e3370afdf42046b8d87e3e1b0e6a6b9162f7706af58543457cdf4614ae', - 'sqlite3.debug.wasm': '20893f1746963efcd1e2ef03f5183b2b919f7454cc3419622818f9bea9c09fc8', - 'sqlite3.ia32.windows.dll': '63e90b2642a59cd4a10c6e47aa19cf3e304a30567f81548c01a5a3ad55ad6797', - 'sqlite3.wasm': '3c616bf0d51380daaed7871b3110a634a17c703c22d82cba7a2a73216769e680', - 'sqlite3.x64.windows.dll': '15b1e7bee3fede1c90eab94c7eb9bb36ae29c33aa2d61bdc0de546326ae6c089', - 'sqlite3mc.arm64.windows.dll': 'cf492f9f796b9e361497150c5bb681f897f1befab4abd1cbd4031a0c02d70515', - 'sqlite3mc.ia32.windows.dll': '4ed49dea8e787229d093e02fd0770c5ad7a6ad39f3fa1c48e9924893d6d831f8', - 'sqlite3mc.wasm': '79e8fa22aeec7f3ea18de0c54159418e7d23957dd583ba89500704c427329805', - 'sqlite3mc.x64.windows.dll': '310882a529f811d34d8b1e29e23f9a6147f6cb0e56bbc5586f2ce3acb00f76ab', + 'libsqlcipher.arm.android.so': 'f2dc15c3aebbbd3e34e7321eb89686e7ba46b61e25678592e82d4c1be4e0d4da', + 'libsqlcipher.arm64.android.so': 'ca0de9929c91722e6d066cb7d0a004e7f1d2c61d6f495a8500dff6ae7f8bf1b1', + 'libsqlcipher.arm64.ios.dylib': 'bd94a9cdb9a2d4a5312fbea169679aaa7d4186b73ff30e726aac04e84bf70e71', + 'libsqlcipher.arm64.ios_sim.dylib': '3a11f40c21c79e9d0cab6b084bc2764864f008b92baa8b6a330ad503816dd114', + 'libsqlcipher.arm64.macos.dylib': '2ce44e43da3f6e7ac0d0cd39bfaabc35dfe10f9e9db713ce587aa0d5446f302d', + 'libsqlcipher.ia32.android.so': '8d05e74c9aff67c21951aabf34933be61a9e3832f949198e580ba62231b36a3a', + 'libsqlcipher.x64.android.so': 'aabf285d4066bbb66ea02d85dbdf812917110c3b7eb94af77dc7cd76da5b79a5', + 'libsqlcipher.x64.ios_sim.dylib': '2b9dc11ba52bba90c7d0d933185462c9f9ca949d498b1e690852e6fccdf32933', + 'libsqlcipher.x64.linux.so': '7dfe264f34b599f1142cee90857da58f3d9f050d87676bcaae913e05b9915d2f', + 'sqlite3.debug.wasm': '9a942f7c42247e6463725b80bafc3a3f5d1ff6a6d2d27ef1ba1b06a313bf0a2e', + 'sqlite3.wasm': 'cb6b6b3a6d6cd912ef3b95ab995714a4e91694aa9a9d1cd15542314fb44982d0', + 'sqlite3mc.wasm': '4493f8b822215a3ac50364deb0e5a0491bd6ec537c0f79ee2ef1fb02575ffbdd', }; diff --git a/sqlite3/lib/src/hook/assets.dart b/sqlite3/lib/src/hook/assets.dart index 080c7a0f..fba5f6a9 100644 --- a/sqlite3/lib/src/hook/assets.dart +++ b/sqlite3/lib/src/hook/assets.dart @@ -8,11 +8,14 @@ enum LibraryType { /// SQLite multiple ciphers build, with sources taken from /// https://github.com/utelle/SQLite3MultipleCiphers. - sqlite3mc; + sqlite3mc, + + sqlcipher; String get basename => switch (this) { LibraryType.sqlite3 => 'sqlite3', LibraryType.sqlite3mc => 'sqlite3mc', + LibraryType.sqlcipher => 'sqlcipher', }; String filename(CodeConfig config) { diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index 304335fa..e1c1fe51 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -16,12 +16,18 @@ import 'utils.dart'; sealed class SqliteBinary { static SqliteBinary forBuild(BuildInput input) { final userDefines = input.userDefines; + // print("USERDEFINES"); + // print(userDefines['source']); switch (userDefines['source']) { case null: case 'sqlite3': return PrecompiledFromGithubAssets(LibraryType.sqlite3); case 'sqlite3mc': return PrecompiledFromGithubAssets(LibraryType.sqlite3mc); + case 'sqlcipher-folder': + return PrecompiledAtFolder(Directory("...."), LibraryType.sqlcipher); + case 'sqlcipher': + return PrecompiledFromGithubAssets(LibraryType.sqlcipher); case 'test-sqlite3': return PrecompiledForTesting(LibraryType.sqlite3); case 'test-sqlite3mc': @@ -98,6 +104,39 @@ enum SimpleBinary implements ExternalSqliteBinary { } } +final class PrecompiledAtFolder extends PrecompiledBinary { + final Directory folder; + + const PrecompiledAtFolder(this.folder, super.type) : super._(); + + @override + Stream _fetchFromSource( + BuildInput input, + BuildOutputBuilder output, + String filename, + ) { + final uri = folder.uri.resolve(filename); + output.dependencies.add(uri); + + return File(uri.toFilePath()).openRead().map( + (event) => switch (event) { + final Uint8List bytes => bytes, + _ => Uint8List.fromList(event), + }, + ); + } + + @override + Stream fetch( + BuildInput input, + BuildOutputBuilder output, + PrebuiltSqliteLibrary library, + ) { + final filename = library.sourceFilename; + return _fetchFromSource(input, output, filename); + } +} + sealed class PrecompiledBinary implements SqliteBinary { final LibraryType type; @@ -227,10 +266,15 @@ final class PrecompiledFromGithubAssets extends PrecompiledBinary { // environments where that's required // https://github.com/simolus3/sqlite3.dart/issues/335 ..findProxy = HttpClient.findProxyFromEnvironment; - final uri = Uri.https( - 'github.com', - 'simolus3/sqlite3.dart/releases/download/${releaseTag!}/$filename', - ); + final uri = type == LibraryType.sqlcipher + ? Uri.https( + 'github.com', + 'davidmartos96/sqlite3.dart/releases/download/${releaseTag!}/$filename', + ) + : Uri.https( + 'github.com', + 'simolus3/sqlite3.dart/releases/download/${releaseTag!}/$filename', + ); HttpClientResponse response; try { @@ -351,9 +395,19 @@ extension type const CompilerDefines(Map flags) }; final start = includeDefaults - ? CompilerDefines.defaults(targetOS == OS.windows) + ? CompilerDefines.defaults(targetOS) : const CompilerDefines({}); + print("----------------------"); + print("OBJ: $obj"); + print("DEFINES START:"); + print(start); + if (additionalDefines != null) { + print("DEFINES ADDITIONAL:"); + print(additionalDefines); + } + print("------------------------"); + return switch (additionalDefines) { final added? => start.overrideWith(added), null => start, @@ -388,11 +442,15 @@ extension type const CompilerDefines(Map flags) return CompilerDefines(entries); } - static CompilerDefines defaults(bool windows) { + static CompilerDefines defaults(OS targetOS) { final defines = _parseLines(const LineSplitter().convert(_defaultDefines)); - if (windows) { + if (targetOS == OS.windows) { defines['SQLITE_API'] = '__declspec(dllexport)'; } + + if (targetOS == OS.macOS || targetOS == OS.iOS) { + defines['SQLCIPHER_CRYPTO_CC'] = null; + } return defines; } } diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart new file mode 100644 index 00000000..8d6267c1 --- /dev/null +++ b/sqlite3/lib/src/hook/openssl.dart @@ -0,0 +1,462 @@ +import 'dart:io'; + +import 'package:code_assets/code_assets.dart'; +import 'package:hooks/hooks.dart'; +import 'package:path/path.dart'; +import 'package:sqlite3/src/hook/android_ndk.dart'; + +// Based from https://github.com/LucazzP/openssl_dart + +// import '../lib/src/android_ndk.dart'; + +const version = '3.6.2'; +const sourceCodeUrl = + 'https://github.com/openssl/openssl/releases/download/openssl-$version/openssl-$version.tar.gz'; +const openSslDirName = 'openssl-$version'; +const configArgs = [ + 'no-shared', + 'no-apps', + 'no-docs', + 'no-tests', + 'no-engine', + 'no-module', + 'no-ssl', + 'no-tls', + 'no-dtls', + 'no-comp', + 'no-legacy', + 'no-fips', + 'no-async', + '-Wl,-headerpad_max_install_names', +]; +// 'no-unit-test no-asm no-makedepend no-ssl no-apps -Wl,-headerpad_max_install_names' +const perlDownloadUrl = + 'https://strawberryperl.com/download/5.14.2.1/strawberry-perl-5.14.2.1-64bit-portable.zip'; +const jomDownloadUrl = + 'https://download.qt.io/official_releases/jom/jom_1_1_5.zip'; + +Map environment = {}; + +Future buildOpenSSL( + BuildInput input, + BuildOutputBuilder output, +) async { + if (!input.config.buildCodeAssets) return null; + + final workDir = input.outputDirectory; + final outputDir = join(input.outputDirectoryShared.toFilePath(), 'openssl'); + + // download source code from openssl + await downloadAndExtract( + sourceCodeUrl, + '$openSslDirName.tar.gz', + workDir, + createFolderForExtraction: false, + ); + + final openSslDir = workDir.resolve('$openSslDirName/'); + + // build source code, depends on the OS we are running on + // Read https://github.com/openssl/openssl/blob/openssl-3.5.4/INSTALL.md#building-openssl + final configName = resolveConfigName( + input.config.code.targetOS, + input.config.code.targetArchitecture, + input.config.code.targetOS == OS.iOS + ? input.config.code.iOS.targetSdk + : null, + ); + if (input.config.code.targetOS == OS.android) { + final ndkRoot = await resolveAndroidNdkRoot(); + environment['ANDROID_NDK_ROOT'] = ndkRoot; + environment['ANDROID_NDK_HOME'] = ndkRoot; + + final toolchainBin = resolveAndroidToolchainBinDir(ndkRoot); + final existingPath = Platform.environment['PATH'] ?? ''; + final pathSeparator = Platform.isWindows ? ';' : ':'; + environment['PATH'] = '$toolchainBin$pathSeparator$existingPath'; + + print(environment); + } + + final extraConfigureArgs = [ + '--prefix=$outputDir', + + '--openssldir=$outputDir', + ]; + + switch (OS.current) { + case OS.windows: + final msvcEnv = await resolveWindowsBuildEnvironment( + input.config.code.targetArchitecture, + ); + // should have perl and jom installed + final needDownloadPerl = !await isProgramInstalled('perl'); + final needDownloadJom = !await isProgramInstalled('jom'); + var perlProgram = 'perl'; + var jomProgram = 'jom'; + + if (needDownloadPerl) { + await downloadAndExtract(perlDownloadUrl, 'perl.zip', workDir); + perlProgram = workDir + .resolve('./perl/perl/bin/perl.exe') + .toFilePath(windows: Platform.isWindows); + } + if (needDownloadJom) { + await downloadAndExtract(jomDownloadUrl, 'jom.zip', workDir); + jomProgram = workDir + .resolve('./jom/jom.exe') + .toFilePath(windows: Platform.isWindows); + } + + // run ./Configure with the target OS and architecture + await runProcess( + perlProgram, + [ + 'Configure', + configName, + ...configArgs, + ...extraConfigureArgs, + // needed to build using multiple threads on Windows + '/FS', + ], + workingDirectory: openSslDir, + extraEnvironment: msvcEnv, + ); + + // run jom to build the library + await runProcess( + jomProgram, + ['-j', '${Platform.numberOfProcessors}'], + workingDirectory: openSslDir, + extraEnvironment: msvcEnv, + ); + + // delete perl and jom if downloaded + if (needDownloadPerl) { + await Directory( + workDir.resolve('perl').toFilePath(windows: Platform.isWindows), + ).delete(recursive: true); + } + if (needDownloadJom) { + await Directory( + workDir.resolve('jom').toFilePath(windows: Platform.isWindows), + ).delete(recursive: true); + } + break; + case OS.macOS: + case OS.linux: + final hasPerl = await isProgramInstalled('perl'); + if (!hasPerl) { + throw Exception( + 'perl is not installed, please install it to be able to build openssl.', + ); + } + + // run ./Configure with the target OS and architecture + await runProcess('./Configure', [ + configName, + ...configArgs, + ...extraConfigureArgs, + ], workingDirectory: openSslDir); + + // run make + await runProcess('make', [ + '-j', + '${Platform.numberOfProcessors}', + ], workingDirectory: openSslDir); + + await runProcess('make', ['install'], workingDirectory: openSslDir); + + break; + } + + // final filesInOut = Directory( + // openSslDir.toFilePath(), + // ).listSync(recursive: true); + + // copy the library to the output directory + // final String libPath = outputDir + // .resolve(libName) + // .toFilePath(windows: Platform.isWindows); + // await File( + // openSslDir.resolve(libName).toFilePath(windows: Platform.isWindows), + // ).copy(libPath); + + // delete the source code + // await Directory( + // openSslDir.toFilePath(windows: Platform.isWindows), + // ).delete(recursive: true); + + return Directory(outputDir); + + // determine the libName from OS and Link mode + /* final libName = switch (( + input.config.code.targetOS, + input.config.code.linkModePreference, + )) { + ( + OS.windows, + LinkModePreference.static || LinkModePreference.preferStatic, + ) => + 'libcrypto_static.lib', + ( + OS.macOS || OS.iOS, + LinkModePreference.static || LinkModePreference.preferStatic, + ) => + 'libcrypto.a', + ( + OS.linux || OS.android, + LinkModePreference.static || LinkModePreference.preferStatic, + ) => + 'libcrypto.a', + ( + OS.windows, + LinkModePreference.dynamic || LinkModePreference.preferDynamic, + ) => + 'libcrypto-3-${input.config.code.targetArchitecture.name}.dll', + ( + OS.macOS || OS.iOS, + LinkModePreference.dynamic || LinkModePreference.preferDynamic, + ) => + 'libcrypto.dylib', + ( + OS.linux || OS.android, + LinkModePreference.dynamic || LinkModePreference.preferDynamic, + ) => + 'libcrypto.so', + _ => throw UnsupportedError( + 'Unsupported target OS: ${input.config.code.targetOS.name} or link mode preference: ${input.config.code.linkModePreference.name}', + ), + }; + + // copy the library to the output directory + final String libPath = outputDir + .resolve(libName) + .toFilePath(windows: Platform.isWindows); + await File( + openSslDir.resolve(libName).toFilePath(windows: Platform.isWindows), + ).copy(libPath); + + // delete the source code + await Directory( + openSslDir.toFilePath(windows: Platform.isWindows), + ).delete(recursive: true); + + return null; */ + + // add the library to dart code assets + /* output.assets.code.add( + CodeAsset( + package: input.packageName, + name: 'src/third_party/openssl.g.dart', + linkMode: libName.linkMode, + file: outputDir.resolve(libName), + ), + ); */ +} + +/* extension on String { + LinkMode get linkMode { + if (endsWith('.dylib') || endsWith('.so') || endsWith('.dll')) { + return DynamicLoadingBundled(); + } + return StaticLinking(); + } +} */ + +String getStaticCryptoLib( + Directory opensslDir, + OS targetOS, + Architecture targetArch, +) { + final libName = switch ((targetOS, LinkModePreference.static)) { + ( + OS.windows, + LinkModePreference.static || LinkModePreference.preferStatic, + ) => + 'libcrypto_static.lib', + ( + OS.macOS || OS.iOS, + LinkModePreference.static || LinkModePreference.preferStatic, + ) => + 'libcrypto.a', + ( + OS.linux || OS.android, + LinkModePreference.static || LinkModePreference.preferStatic, + ) => + 'libcrypto.a', + ( + OS.windows, + LinkModePreference.dynamic || LinkModePreference.preferDynamic, + ) => + 'libcrypto-3-${targetArch.name}.dll', + ( + OS.macOS || OS.iOS, + LinkModePreference.dynamic || LinkModePreference.preferDynamic, + ) => + 'libcrypto.dylib', + ( + OS.linux || OS.android, + LinkModePreference.dynamic || LinkModePreference.preferDynamic, + ) => + 'libcrypto.so', + _ => throw UnsupportedError('Unsupported target OS: $targetOS'), + }; + + final String openSSLLibDir; + if (Directory(join(opensslDir.path, 'lib')).existsSync()) { + openSSLLibDir = join(opensslDir.path, 'lib'); + } else { + openSSLLibDir = join(opensslDir.path, 'lib64'); + } + + final libPath = join(openSSLLibDir, libName); + if (!File(libPath).existsSync()) { + throw Exception( + 'Could not find OpenSSL static library in ' + '$libPath', + ); + } + + return libPath; +} + +Future isProgramInstalled(String programName) async { + try { + await runProcess(programName, []); + return true; + } catch (e) { + return false; + } +} + +String resolveConfigName(OS os, Architecture architecture, IOSSdk? iosSdk) { + final isIosSimulator = iosSdk == IOSSdk.iPhoneSimulator; + return switch ((os, architecture)) { + (OS.android, Architecture.arm) => 'android-arm', + (OS.android, Architecture.arm64) => 'android-arm64', + (OS.android, Architecture.ia32) => 'android-x86', + (OS.android, Architecture.x64) => 'android-x86_64', + (OS.android, Architecture.riscv64) => 'android-riscv64', + + (OS.iOS, Architecture.arm) => 'ios-xcrun', + (OS.iOS, Architecture.arm64) => + isIosSimulator ? 'iossimulator-arm64-xcrun' : 'ios64-xcrun', + (OS.iOS, Architecture.ia32) => 'iossimulator-i386-xcrun', + (OS.iOS, Architecture.x64) => 'iossimulator-x86_64-xcrun', + + (OS.macOS, Architecture.arm64) => 'darwin64-arm64', + (OS.macOS, Architecture.x64) => 'darwin64-x86_64', + (OS.macOS, Architecture.ia32) => 'darwin-i386', + + (OS.linux, Architecture.arm) => 'linux-armv4', + (OS.linux, Architecture.arm64) => 'linux-aarch64', + (OS.linux, Architecture.ia32) => 'linux-x86', + (OS.linux, Architecture.x64) => 'linux-x86_64', + (OS.linux, Architecture.riscv32) => 'linux32-riscv32', + (OS.linux, Architecture.riscv64) => 'linux64-riscv64', + + (OS.windows, Architecture.arm) => 'VC-WIN32-ARM', + (OS.windows, Architecture.arm64) => 'VC-WIN64-ARM', + (OS.windows, Architecture.ia32) => 'VC-WIN32', + (OS.windows, Architecture.x64) => 'VC-WIN64A', + + _ => throw UnsupportedError( + 'Unsupported target combination: ${os.name}-${architecture.name}', + ), + }; +} + +Future> resolveWindowsBuildEnvironment( + Architecture architecture, +) async { + final result = await runProcess('cmd.exe', [ + '/c', + r'call C:\"Program Files"\"Microsoft Visual Studio"\2022\Community\VC\Auxiliary\Build\vcvars64.bat >nul && set', + ]); + + return Map.fromEntries( + result.trim().split('\n').map((line) { + final parts = line.split('='); + if (parts.length != 2) { + return null; + } + return MapEntry(parts[0].trim(), parts[1].trim()); + }).nonNulls, + ); +} + +Future runProcess( + String executable, + List arguments, { + Uri? workingDirectory, + Map? extraEnvironment, +}) async { + final processResult = await Process.run( + executable, + arguments, + workingDirectory: workingDirectory?.toFilePath(windows: Platform.isWindows), + environment: {...?extraEnvironment, ...environment}, + includeParentEnvironment: true, + ); + print(processResult.stdout); + if ((processResult.stderr as String).isNotEmpty) { + print(processResult.stderr); + } + if (processResult.exitCode != 0) { + throw ProcessException( + executable, + arguments, + processResult.stderr.toString(), + processResult.exitCode, + ); + } + return processResult.stdout.toString(); +} + +Future downloadAndExtract( + String url, + String outputFileName, + Uri workDir, { + bool createFolderForExtraction = true, +}) async { + // download the file + await runProcess('curl', [ + '-L', + url, + '-o', + outputFileName, + ], workingDirectory: workDir); + + final fileOut = await runProcess('file', [ + outputFileName, + ], workingDirectory: workDir); + print(fileOut); + + // unzip the file + final isTarGz = outputFileName.endsWith('.tar.gz'); + final isTarXz = outputFileName.endsWith('.tar.xz'); + final destinationPath = workDir.resolve( + outputFileName + .replaceAll('.tar.gz', '') + .replaceAll('.zip', '') + .replaceAll('.tar.xz', ''), + ); + print("destination $destinationPath"); + await Directory( + destinationPath.toFilePath(windows: Platform.isWindows), + ).create(recursive: true); + await runProcess('tar', [ + isTarGz ? '-xzf' : (isTarXz ? '-xJf' : '-xf'), + outputFileName, + if (createFolderForExtraction) ...[ + '-C', + destinationPath.toFilePath(windows: Platform.isWindows), + ], + ], workingDirectory: workDir); + + // remove the tar.gz file + await File( + workDir.resolve(outputFileName).toFilePath(windows: Platform.isWindows), + ).delete(); +} diff --git a/sqlite3_connection_pool/lib/src/raw.dart b/sqlite3_connection_pool/lib/src/raw.dart index ad571d41..0c330274 100644 --- a/sqlite3_connection_pool/lib/src/raw.dart +++ b/sqlite3_connection_pool/lib/src/raw.dart @@ -243,8 +243,7 @@ final class PoolConnections { extension type PoolConnectionRef( /// The pool connection, used to manage cached prepared statements. - Pointer - connection + Pointer connection ) { /// The `sqlite3*` connection pointer. Pointer get rawDatabase => connection.ref.raw; diff --git a/sqlite3_web/lib/src/protocol/helper.g.dart b/sqlite3_web/lib/src/protocol/helper.g.dart index 1662ae9c..189f3796 100644 --- a/sqlite3_web/lib/src/protocol/helper.g.dart +++ b/sqlite3_web/lib/src/protocol/helper.g.dart @@ -687,8 +687,7 @@ SharedCompatibilityCheck newSharedCompatibilityCheck({ @anonymous extension type _DedicatedInSharedCompatibilityCheck._( DedicatedInSharedCompatibilityCheck _ -) - implements DedicatedInSharedCompatibilityCheck { +) implements DedicatedInSharedCompatibilityCheck { external factory _DedicatedInSharedCompatibilityCheck({ @JS('d') required String? databaseName, @JS('i') required int requestId, diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 43f519b3..1cb81d6b 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -20,9 +20,15 @@ void main(List args) async { var operatingSystems = args.map(OS.fromString).toList(); if (operatingSystems.isEmpty) { if (Platform.isLinux) { - operatingSystems = [OS.linux, OS.android]; + operatingSystems = [ + OS.linux, + OS.android, + ]; } else if (Platform.isMacOS) { - operatingSystems = [OS.macOS, OS.iOS]; + operatingSystems = [ + OS.macOS, + OS.iOS, + ]; } else if (Platform.isWindows) { operatingSystems = [OS.windows]; } @@ -47,11 +53,21 @@ void main(List args) async { final buildTasks = >[]; - for (final mode in ['sqlite3', 'sqlite3mc']) { + for (final mode in [ + // 'sqlite3', + // 'sqlite3mc', + 'sqlcipher', + ]) { + final sourceFileName = switch (mode) { + "sqlite3" || "sqlcipher" => "sqlite3.c", + "sqlite3mc" => 'sqlite3mc_amalgamation.c', + _ => throw UnimplementedError(), + }; + final sourcePath = fs.currentDirectory.parent .childDirectory('sqlite-src') .childDirectory(mode) - .childFile(mode == 'sqlite3' ? 'sqlite3.c' : 'sqlite3mc_amalgamation.c') + .childFile(sourceFileName) .path; Future buildAndCopy(OS os, Architecture architecture, @@ -106,6 +122,14 @@ void main(List args) async { defines: { 'source': 'source', 'path': p.relative(sourcePath, from: fs.currentDirectory.path), + 'defines': { + 'defines': { + 'SQLITE_HAS_CODEC': null, + 'SQLITE_TEMP_STORE': "2", + 'SQLITE_EXTRA_INIT': 'sqlcipher_extra_init', + 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', + } + } }, basePath: fs.currentDirectory.uri, )), @@ -147,12 +171,13 @@ void main(List args) async { } const _osToAbis = { + // TODO: Recover linux 32 bits OS.linux: [ - Architecture.arm, - Architecture.arm64, - Architecture.ia32, + // Architecture.arm, + // Architecture.arm64, + // Architecture.ia32, Architecture.x64, - Architecture.riscv64, + // Architecture.riscv64, ], OS.android: [ Architecture.arm, @@ -167,10 +192,10 @@ const _osToAbis = { ], OS.macOS: [ Architecture.arm64, - Architecture.x64, + // Architecture.x64, ], OS.iOS: [ Architecture.arm64, - // Note: There's a special check to also compile simulator builds for x64 + // // Note: There's a special check to also compile simulator builds for x64 ], }; diff --git a/tool/build_with_sanitizers.dart b/tool/build_with_sanitizers.dart index f6d475b3..adcadd23 100644 --- a/tool/build_with_sanitizers.dart +++ b/tool/build_with_sanitizers.dart @@ -58,7 +58,7 @@ void main() async { includes: [p.dirname(sourceFile)], defines: { 'SQLITE_ENABLE_API_ARMOR': '1', - ...CompilerDefines.defaults(false), + ...CompilerDefines.defaults(input.config.code.targetOS), }, flags: [ '-fsanitize=$sanitizer', diff --git a/tool/download_sqlite.dart b/tool/download_sqlite.dart index ad6c6762..8f0a69ec 100644 --- a/tool/download_sqlite.dart +++ b/tool/download_sqlite.dart @@ -7,16 +7,25 @@ const sqliteSource = 'https://sqlite.org/2026/$sqlitePath.zip'; const sqliteMultipleCiphersSource = 'https://github.com/utelle/SQLite3MultipleCiphers/releases/download/v2.3.4/sqlite3mc-2.3.4-sqlite-3.53.1-amalgamation.zip'; +const sqlcipherVersion = '4.16.0'; +const sqlcipherPath = 'sqlcipher-amalgamation-$sqlcipherVersion'; +const sqlCipherSource = + 'https://github.com/chehrlic/sqlcipher-amalgamation/archive/refs/tags/v$sqlcipherVersion.zip'; + const tmpDir = 'tmp'; /// This runs as part of a GitHub actions workflow in this repository. It's not /// really supposed to be used outside of that, but can be used to reproduce /// what hooks are doing in the CI. void main(List args) async { + if (await Directory(tmpDir).exists()) { + await Directory(tmpDir).delete(recursive: true); + } await Directory(tmpDir).create(); await _downloadAndExtract(sqliteSource, 'sqlite3'); await _downloadAndExtract(sqliteMultipleCiphersSource, 'sqlite3mc'); + await _downloadAndExtract(sqlCipherSource, 'sqlcipher'); await Directory('sqlite-src').create(); await Directory('sqlite-src/sqlite3mc').create(); @@ -32,6 +41,14 @@ void main(List args) async { .copy('sqlite-src/sqlite3/sqlite3.c'); await File('$tmpDir/$sqlitePath/sqlite3ext.h') .copy('sqlite-src/sqlite3/sqlite3ext.h'); + + await Directory('sqlite-src/sqlcipher').create(); + await File('$tmpDir/$sqlcipherPath/sqlite3.h') + .copy('sqlite-src/sqlcipher/sqlite3.h'); + await File('$tmpDir/$sqlcipherPath/sqlite3.c') + .copy('sqlite-src/sqlcipher/sqlite3.c'); + await File('$tmpDir/$sqlcipherPath/sqlite3ext.h') + .copy('sqlite-src/sqlcipher/sqlite3ext.h'); } Future _downloadAndExtract(String url, String filename) async { From b38b0d20d2323334775be7106f7706fc630a0691 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 14:28:24 +0200 Subject: [PATCH 02/37] hashes --- sqlite3/lib/src/hook/asset_hashes.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sqlite3/lib/src/hook/asset_hashes.dart b/sqlite3/lib/src/hook/asset_hashes.dart index 086f16bf..ce21cd6e 100644 --- a/sqlite3/lib/src/hook/asset_hashes.dart +++ b/sqlite3/lib/src/hook/asset_hashes.dart @@ -9,15 +9,15 @@ const String? releaseTag = 'sqlite3-sqlcipher-3.3.2'; const Map assetNameToSha256Hash = { - 'libsqlcipher.arm.android.so': 'f2dc15c3aebbbd3e34e7321eb89686e7ba46b61e25678592e82d4c1be4e0d4da', - 'libsqlcipher.arm64.android.so': 'ca0de9929c91722e6d066cb7d0a004e7f1d2c61d6f495a8500dff6ae7f8bf1b1', + 'libsqlcipher.arm.android.so': '941ec977eef2d7e51fcf1005304069772612258dccd70edc2aacb829f47da138', + 'libsqlcipher.arm64.android.so': 'af77d7d8be5336d6768b1eb81f1f5b4e84f7cefd1c74d34d4904d31282fe67d3', 'libsqlcipher.arm64.ios.dylib': 'bd94a9cdb9a2d4a5312fbea169679aaa7d4186b73ff30e726aac04e84bf70e71', 'libsqlcipher.arm64.ios_sim.dylib': '3a11f40c21c79e9d0cab6b084bc2764864f008b92baa8b6a330ad503816dd114', 'libsqlcipher.arm64.macos.dylib': '2ce44e43da3f6e7ac0d0cd39bfaabc35dfe10f9e9db713ce587aa0d5446f302d', - 'libsqlcipher.ia32.android.so': '8d05e74c9aff67c21951aabf34933be61a9e3832f949198e580ba62231b36a3a', - 'libsqlcipher.x64.android.so': 'aabf285d4066bbb66ea02d85dbdf812917110c3b7eb94af77dc7cd76da5b79a5', + 'libsqlcipher.ia32.android.so': '9828f82ed144de73e15c9102e01d71e1cf736dd8ef67c3f82f723b2124617c2e', + 'libsqlcipher.x64.android.so': '15577d744643ba80f7578b47c6f1481957482faa2caf3dbadbeea99df08d066d', 'libsqlcipher.x64.ios_sim.dylib': '2b9dc11ba52bba90c7d0d933185462c9f9ca949d498b1e690852e6fccdf32933', - 'libsqlcipher.x64.linux.so': '7dfe264f34b599f1142cee90857da58f3d9f050d87676bcaae913e05b9915d2f', + 'libsqlcipher.x64.linux.so': 'a8f36a48002e202402cca11d1e22de08f40af0c893559640d9c8c4c27f8ea347', 'sqlite3.debug.wasm': '9a942f7c42247e6463725b80bafc3a3f5d1ff6a6d2d27ef1ba1b06a313bf0a2e', 'sqlite3.wasm': 'cb6b6b3a6d6cd912ef3b95ab995714a4e91694aa9a9d1cd15542314fb44982d0', 'sqlite3mc.wasm': '4493f8b822215a3ac50364deb0e5a0491bd6ec537c0f79ee2ef1fb02575ffbdd', From f0d680bc97b01572f820f2cf30635897be8dd07a Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 17:48:34 +0200 Subject: [PATCH 03/37] hashes update --- .github/workflows/compile_sqlite.yml | 2 +- sqlite3/lib/src/hook/asset_hashes.dart | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index 76ce280e..b0232b63 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -48,7 +48,7 @@ jobs: build_sqlite: needs: [download_sqlite] strategy: - fail-fast: false + fail-fast: true matrix: os: [ubuntu-latest, macos-latest] diff --git a/sqlite3/lib/src/hook/asset_hashes.dart b/sqlite3/lib/src/hook/asset_hashes.dart index ce21cd6e..a29ec520 100644 --- a/sqlite3/lib/src/hook/asset_hashes.dart +++ b/sqlite3/lib/src/hook/asset_hashes.dart @@ -9,15 +9,15 @@ const String? releaseTag = 'sqlite3-sqlcipher-3.3.2'; const Map assetNameToSha256Hash = { - 'libsqlcipher.arm.android.so': '941ec977eef2d7e51fcf1005304069772612258dccd70edc2aacb829f47da138', - 'libsqlcipher.arm64.android.so': 'af77d7d8be5336d6768b1eb81f1f5b4e84f7cefd1c74d34d4904d31282fe67d3', + 'libsqlcipher.arm.android.so': '2eadf86bc294585fada0ac9f4267e30c92cd13b0e1600633ea5aed2420a320cf', + 'libsqlcipher.arm64.android.so': '805cb1f8ca5e9bc358205d1d03f943601a172b2dfe01d3342de8bb6ff587f58b', 'libsqlcipher.arm64.ios.dylib': 'bd94a9cdb9a2d4a5312fbea169679aaa7d4186b73ff30e726aac04e84bf70e71', 'libsqlcipher.arm64.ios_sim.dylib': '3a11f40c21c79e9d0cab6b084bc2764864f008b92baa8b6a330ad503816dd114', 'libsqlcipher.arm64.macos.dylib': '2ce44e43da3f6e7ac0d0cd39bfaabc35dfe10f9e9db713ce587aa0d5446f302d', - 'libsqlcipher.ia32.android.so': '9828f82ed144de73e15c9102e01d71e1cf736dd8ef67c3f82f723b2124617c2e', - 'libsqlcipher.x64.android.so': '15577d744643ba80f7578b47c6f1481957482faa2caf3dbadbeea99df08d066d', + 'libsqlcipher.ia32.android.so': '26e9db6dfd182493adcb92602c0b7e5d411cafcc68bf2e5b5a4bcfe6cdabd18d', + 'libsqlcipher.x64.android.so': 'e7fd186037357a8421125b872873073f18b5277131c743ee5ce2594c3cecacc6', 'libsqlcipher.x64.ios_sim.dylib': '2b9dc11ba52bba90c7d0d933185462c9f9ca949d498b1e690852e6fccdf32933', - 'libsqlcipher.x64.linux.so': 'a8f36a48002e202402cca11d1e22de08f40af0c893559640d9c8c4c27f8ea347', + 'libsqlcipher.x64.linux.so': '74af90ec30afa80407ed702cf1d8ff163ec3b0d72c11651ddc5f34a400826314', 'sqlite3.debug.wasm': '9a942f7c42247e6463725b80bafc3a3f5d1ff6a6d2d27ef1ba1b06a313bf0a2e', 'sqlite3.wasm': 'cb6b6b3a6d6cd912ef3b95ab995714a4e91694aa9a9d1cd15542314fb44982d0', 'sqlite3mc.wasm': '4493f8b822215a3ac50364deb0e5a0491bd6ec537c0f79ee2ef1fb02575ffbdd', From 4005eb48ba1a9522424626335e9d95e461c692ea Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 18:10:52 +0200 Subject: [PATCH 04/37] Trigger Actions detection From 8d2983caa545c120f2326bdd42bf88d4eaa2aa00 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 19:12:33 +0200 Subject: [PATCH 05/37] cleanup --- build_ssl.sh | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100755 build_ssl.sh diff --git a/build_ssl.sh b/build_ssl.sh deleted file mode 100755 index 17bca368..00000000 --- a/build_ssl.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -eu - - -pushd openssl - -BUILD_DIR=$(realpath ../openssl-build) -./Configure no-ssl no-tls no-dtls no-engine no-deprecated no-shared no-tests no-docs no-apps --prefix=$BUILD_DIR --openssldir=$BUILD_DIR \ - '-Wl,-rpath,$(LIBRPATH)' - -make -j 8 -make install -popd \ No newline at end of file From b36ccb6b62ced5735fdcd7578c9c187d7aba39bb Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 19:30:10 +0200 Subject: [PATCH 06/37] recover jobs --- .github/workflows/compile_sqlite.yml | 1 - .github/workflows/main.yml | 60 ++++++++-------- .github/workflows/release.yml | 100 +++++++++++++-------------- 3 files changed, 80 insertions(+), 81 deletions(-) diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index b0232b63..1d22e9e3 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -97,7 +97,6 @@ jobs: build_with_sanitizers: needs: [download_sqlite] - if: false runs-on: ubuntu-latest outputs: libs: ${{ steps.upload.outputs.artifact-id }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fc77286d..08b314b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,36 +44,36 @@ jobs: - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.dart }} - # - uses: actions/cache@v5 - # with: - # path: "${{ env.PUB_CACHE }}" - # key: dart-dependencies-${{ matrix.dart }}-${{ runner.os }} - # restore-keys: | - # dart-dependencies-${{ matrix.dart }}- - # dart-dependencies- - - # - name: Pub get - # run: dart pub get - - # - name: Format dart - # run: | - # dart format --set-exit-if-changed sqlite3 - # dart format --set-exit-if-changed sqlite3_test - # dart format --set-exit-if-changed sqlite3_web - - # - name: Format native - # run: clang-format --Werror --dry-run --style=google assets/*.h - # working-directory: sqlite3 - - # - name: Format native - # run: clang-format --Werror --dry-run --style=google src/*{.h,.c} - # working-directory: sqlite3_wasm_build - - # - name: Analyze - # run: dart analyze --fatal-infos sqlite3 sqlite3_web sqlite3_test + - uses: actions/cache@v5 + with: + path: "${{ env.PUB_CACHE }}" + key: dart-dependencies-${{ matrix.dart }}-${{ runner.os }} + restore-keys: | + dart-dependencies-${{ matrix.dart }}- + dart-dependencies- + + - name: Pub get + run: dart pub get + + - name: Format dart + run: | + dart format --set-exit-if-changed sqlite3 + dart format --set-exit-if-changed sqlite3_test + dart format --set-exit-if-changed sqlite3_web + + - name: Format native + run: clang-format --Werror --dry-run --style=google assets/*.h + working-directory: sqlite3 + + - name: Format native + run: clang-format --Werror --dry-run --style=google src/*{.h,.c} + working-directory: sqlite3_wasm_build + + - name: Analyze + run: dart analyze --fatal-infos sqlite3 sqlite3_web sqlite3_test test: - if: false && github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) timeout-minutes: 5 needs: [analyze, fetch_sqlite] strategy: @@ -185,7 +185,7 @@ jobs: upload_asset_hashes: if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) timeout-minutes: 5 - needs: [fetch_sqlite] + needs: [test, fetch_sqlite] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -219,7 +219,7 @@ jobs: retention-days: 1 integration_test_web: - if: false && github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) timeout-minutes: 10 needs: [analyze, fetch_sqlite] runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00b2fb97..1019502b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,56 +62,56 @@ jobs: gh release upload "$tag" sqlite-compiled/* - # publish_sqlite3: - # needs: [prepare_release] - # permissions: - # id-token: write # Needed to create OIDC token for pub.dev - # if: "${{ startsWith(github.ref_name, 'sqlite3-') }}" - # uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 - # with: - # environment: 'pub.dev' - # working-directory: sqlite3/ - - # publish_sqlite3_test: - # needs: [prepare_release] - # permissions: - # id-token: write # Needed to create OIDC token for pub.dev - # if: "${{ startsWith(github.ref_name, 'sqlite3_test-') }}" - # uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 - # with: - # environment: 'pub.dev' - # working-directory: sqlite3_test/ - - # publish_sqlite3_web: - # needs: [prepare_release] - # permissions: - # id-token: write # Needed to create OIDC token for pub.dev - # if: "${{ startsWith(github.ref_name, 'sqlite3_web-') }}" - # uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 - # with: - # environment: 'pub.dev' - # working-directory: sqlite3_web/ - - # publish_sqlite3_connection_pool: - # needs: [fetch_sqlite, prepare_release] - # permissions: - # id-token: write # Needed to create OIDC token for pub.dev - # if: "${{ startsWith(github.ref_name, 'sqlite3_connection_pool-') }}" - # runs-on: ubuntu-latest - # environment: 'pub.dev' - # # This can't use the workflow because we need to download assets to include in - # # the package. - # steps: - # - uses: actions/checkout@v6 - # with: - # persist-credentials: false - # - uses: dart-lang/setup-dart@v1 # This will request an OIDC token for pub.dev - - # - name: Download connection pool libraries - # uses: actions/download-artifact@v8 - # with: - # artifact-ids: ${{ needs.fetch_sqlite.outputs.pool_libs_artifact_id }} - # path: sqlite3_connection_pool/lib/src/precompiled/ + publish_sqlite3: + needs: [prepare_release] + permissions: + id-token: write # Needed to create OIDC token for pub.dev + if: "${{ startsWith(github.ref_name, 'sqlite3-') }}" + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + environment: 'pub.dev' + working-directory: sqlite3/ + + publish_sqlite3_test: + needs: [prepare_release] + permissions: + id-token: write # Needed to create OIDC token for pub.dev + if: "${{ startsWith(github.ref_name, 'sqlite3_test-') }}" + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + environment: 'pub.dev' + working-directory: sqlite3_test/ + + publish_sqlite3_web: + needs: [prepare_release] + permissions: + id-token: write # Needed to create OIDC token for pub.dev + if: "${{ startsWith(github.ref_name, 'sqlite3_web-') }}" + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + environment: 'pub.dev' + working-directory: sqlite3_web/ + + publish_sqlite3_connection_pool: + needs: [fetch_sqlite, prepare_release] + permissions: + id-token: write # Needed to create OIDC token for pub.dev + if: "${{ startsWith(github.ref_name, 'sqlite3_connection_pool-') }}" + runs-on: ubuntu-latest + environment: 'pub.dev' + # This can't use the workflow because we need to download assets to include in + # the package. + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: dart-lang/setup-dart@v1 # This will request an OIDC token for pub.dev + + - name: Download connection pool libraries + uses: actions/download-artifact@v8 + with: + artifact-ids: ${{ needs.fetch_sqlite.outputs.pool_libs_artifact_id }} + path: sqlite3_connection_pool/lib/src/precompiled/ - name: Pub get run: dart pub get From 166208d86517edb3ccc6f46dfc615dd35c9ee973 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 19:32:11 +0200 Subject: [PATCH 07/37] cleanup --- .github/workflows/compile_sqlite.yml | 2 +- sqlite3/lib/src/hook/description.dart | 37 ++------------------------- 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index 1d22e9e3..0b16ca29 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -50,7 +50,7 @@ jobs: strategy: fail-fast: true matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} name: Compile sqlite3 diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index e1c1fe51..7b8283fb 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -24,14 +24,14 @@ sealed class SqliteBinary { return PrecompiledFromGithubAssets(LibraryType.sqlite3); case 'sqlite3mc': return PrecompiledFromGithubAssets(LibraryType.sqlite3mc); - case 'sqlcipher-folder': - return PrecompiledAtFolder(Directory("...."), LibraryType.sqlcipher); case 'sqlcipher': return PrecompiledFromGithubAssets(LibraryType.sqlcipher); case 'test-sqlite3': return PrecompiledForTesting(LibraryType.sqlite3); case 'test-sqlite3mc': return PrecompiledForTesting(LibraryType.sqlite3mc); + case 'test-sqlcipher': + return PrecompiledForTesting(LibraryType.sqlcipher); case 'system': final osSpecificNameKey = 'name_${input.config.code.targetOS.name}'; @@ -104,39 +104,6 @@ enum SimpleBinary implements ExternalSqliteBinary { } } -final class PrecompiledAtFolder extends PrecompiledBinary { - final Directory folder; - - const PrecompiledAtFolder(this.folder, super.type) : super._(); - - @override - Stream _fetchFromSource( - BuildInput input, - BuildOutputBuilder output, - String filename, - ) { - final uri = folder.uri.resolve(filename); - output.dependencies.add(uri); - - return File(uri.toFilePath()).openRead().map( - (event) => switch (event) { - final Uint8List bytes => bytes, - _ => Uint8List.fromList(event), - }, - ); - } - - @override - Stream fetch( - BuildInput input, - BuildOutputBuilder output, - PrebuiltSqliteLibrary library, - ) { - final filename = library.sourceFilename; - return _fetchFromSource(input, output, filename); - } -} - sealed class PrecompiledBinary implements SqliteBinary { final LibraryType type; From fc2df9cccb14375a0a8e257c8e1c76c43c9071a9 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 20:15:45 +0200 Subject: [PATCH 08/37] move flags --- sqlite3/hook/build.dart | 82 +++++++++++++++------------ sqlite3/lib/src/hook/assets.dart | 9 +++ sqlite3/lib/src/hook/description.dart | 39 +++++++++++-- sqlite3/lib/src/hook/openssl.dart | 5 +- tool/build_sqlite.dart | 27 ++++----- tool/build_with_sanitizers.dart | 6 +- 6 files changed, 109 insertions(+), 59 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index c62c1563..0556aa38 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -4,6 +4,7 @@ import 'package:code_assets/code_assets.dart'; import 'package:hooks/hooks.dart'; import 'package:native_toolchain_c/native_toolchain_c.dart'; import 'package:path/path.dart' as p; +import 'package:sqlite3/src/hook/assets.dart'; import 'package:sqlite3/src/hook/description.dart'; import 'package:sqlite3/src/hook/openssl.dart'; @@ -35,7 +36,9 @@ void main(List args) async { file: downloaded.uri, ), ); - case CompileSqlite(:final sourceFile, :final defines): + case CompileSqlite(:final libraryType, :final sourceFile, :final defines): + final targetOS = input.config.code.targetOS; + // With Flutter on Linux (which already dynamically links SQLite through // its libgtk dependency), we run into issues where loading our SQLite // build causes internal symbols to be resolved against the already @@ -47,7 +50,7 @@ void main(List args) async { // For the full discussion, see https://github.com/dart-lang/native/issues/2724 String? linkerScript; - if (input.config.code.targetOS == OS.linux) { + if (targetOS == OS.linux) { linkerScript = input.outputDirectory.resolve('sqlite.map').path; await File(linkerScript).writeAsString(''' @@ -60,44 +63,53 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} '''); } - final targetOS = input.config.code.targetOS; - final List includes = []; final List libraryDirectories = []; final List libraries = []; final List flags = []; - switch (targetOS) { - case OS.macOS: - case OS.iOS: - flags.addAll([ - '-framework', - 'Foundation', - '-framework', - 'Security', - ]); - break; - case OS.android: - case OS.linux: - case OS.windows: - final kOpenSSLBuiltDir = (await buildOpenSSL(input, output))!.path; - print("BUILT OPENSSL IN $kOpenSSLBuiltDir"); - - final cryptoStaticLib = File( - getStaticCryptoLib( - Directory(kOpenSSLBuiltDir), - input.config.code.targetOS, - input.config.code.targetArchitecture, - ), - ); - - includes.add('$kOpenSSLBuiltDir/include'); - libraryDirectories.add(cryptoStaticLib.parent.path); - libraries.add('crypto'); - default: - throw UnsupportedError( - 'Unsupported OS: ${input.config.code.targetOS}', - ); + if (libraryType == LibraryType.sqlcipher) { + switch (targetOS) { + case OS.macOS: + case OS.iOS: + // Link with CommonCrypto on Apple platforms, which is optimized + flags.addAll([ + '-framework', + 'Foundation', + '-framework', + 'Security', + ]); + break; + case OS.android: + case OS.linux: + case OS.windows: + // OpenSSL is downloaded next to the main source file + final openSslSrcDir = Directory( + p.join(File(sourceFile).parent.path, 'openssl-src'), + ); + final kOpenSSLBuiltDir = (await buildOpenSSL( + input, + output, + openSslSrcDir: openSslSrcDir, + ))!.path; + print("BUILT OPENSSL IN $kOpenSSLBuiltDir"); + + final cryptoStaticLib = File( + getStaticCryptoLib( + Directory(kOpenSSLBuiltDir), + input.config.code.targetOS, + input.config.code.targetArchitecture, + ), + ); + + includes.add('$kOpenSSLBuiltDir/include'); + libraryDirectories.add(cryptoStaticLib.parent.path); + libraries.add('crypto'); + default: + throw UnsupportedError( + 'Unsupported OS: ${input.config.code.targetOS}', + ); + } } // final files = Directory(kOpenSSLBuiltDir).listSync(); diff --git a/sqlite3/lib/src/hook/assets.dart b/sqlite3/lib/src/hook/assets.dart index fba5f6a9..b2f2f5e6 100644 --- a/sqlite3/lib/src/hook/assets.dart +++ b/sqlite3/lib/src/hook/assets.dart @@ -26,6 +26,15 @@ enum LibraryType { OS.android || OS.linux || _ => 'lib$basename.so', }; } + + static LibraryType fromName(String name) { + return switch (name) { + 'sqlite3' => LibraryType.sqlite3, + 'sqlite3mc' => LibraryType.sqlite3mc, + 'sqlcipher' => LibraryType.sqlcipher, + _ => throw ArgumentError('Unknown library type: $name'), + }; + } } enum TargetOperatingSystem { diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index 7b8283fb..9a2b00c8 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -44,9 +44,16 @@ sealed class SqliteBinary { case 'executable': return SimpleBinary.fromExecutable; case 'source': + final libraryTypeName = userDefines['library_type'] as String?; + final libraryType = libraryTypeName != null + ? LibraryType.fromName(libraryTypeName) + : LibraryType.sqlite3; + return CompileSqlite( sourceFile: userDefines.path('path')!.toFilePath(), + libraryType: libraryType, defines: CompilerDefines.parse( + libraryType, userDefines, input.config.code.targetOS, ), @@ -293,13 +300,20 @@ final class PrecompiledForTesting extends PrecompiledBinary { } final class CompileSqlite implements SqliteBinary { + /// The type of build + final LibraryType libraryType; + /// Path to the `sqlite3.c` source file to compile. final String sourceFile; /// User-defines for the SQLite compilation. final CompilerDefines defines; - CompileSqlite({required this.sourceFile, required this.defines}); + CompileSqlite({ + required this.libraryType, + required this.sourceFile, + required this.defines, + }); } /// If we're compiling SQLite from source, a way to obtain these sources. @@ -344,7 +358,11 @@ extension type const CompilerDefines(Map flags) return CompilerDefines({...flags, ...other.flags}); } - static CompilerDefines parse(HookInputUserDefines defines, OS targetOS) { + static CompilerDefines parse( + LibraryType libraryType, + HookInputUserDefines defines, + OS targetOS, + ) { final obj = defines['defines']; // Include default options when not explicitly disabled. @@ -362,7 +380,7 @@ extension type const CompilerDefines(Map flags) }; final start = includeDefaults - ? CompilerDefines.defaults(targetOS) + ? CompilerDefines.defaults(targetOS, libraryType) : const CompilerDefines({}); print("----------------------"); @@ -409,15 +427,24 @@ extension type const CompilerDefines(Map flags) return CompilerDefines(entries); } - static CompilerDefines defaults(OS targetOS) { + static CompilerDefines defaults(OS targetOS, LibraryType libraryType) { final defines = _parseLines(const LineSplitter().convert(_defaultDefines)); if (targetOS == OS.windows) { defines['SQLITE_API'] = '__declspec(dllexport)'; } - if (targetOS == OS.macOS || targetOS == OS.iOS) { - defines['SQLCIPHER_CRYPTO_CC'] = null; + // Minimum extra flags to build SQLCipher + if (libraryType == LibraryType.sqlcipher) { + defines.addAll({ + 'SQLITE_HAS_CODEC': null, + 'SQLITE_TEMP_STORE': "2", + 'SQLITE_EXTRA_INIT': 'sqlcipher_extra_init', + 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', + if (targetOS == OS.macOS || targetOS == OS.iOS) + 'SQLCIPHER_CRYPTO_CC': null, + }); } + return defines; } } diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart index 8d6267c1..2f36e016 100644 --- a/sqlite3/lib/src/hook/openssl.dart +++ b/sqlite3/lib/src/hook/openssl.dart @@ -39,8 +39,9 @@ Map environment = {}; Future buildOpenSSL( BuildInput input, - BuildOutputBuilder output, -) async { + BuildOutputBuilder output, { + required Directory openSslSrcDir, +}) async { if (!input.config.buildCodeAssets) return null; final workDir = input.outputDirectory; diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 1cb81d6b..1dff9065 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -12,6 +12,10 @@ import '../sqlite3/hook/build.dart' as hook; final _limitConcurrency = Pool(Platform.numberOfProcessors); +const _kSQLiteMode = 'sqlite'; +const _kSQLite3MCMode = 'sqlite3mc'; +const _kSQLCipherMode = 'sqlcipher'; + /// Invokes `package:sqlite3` build hooks for multiple operating systems and /// architectures, merging outputs into `sqlite3-compiled/`. void main(List args) async { @@ -54,17 +58,17 @@ void main(List args) async { final buildTasks = >[]; for (final mode in [ - // 'sqlite3', - // 'sqlite3mc', - 'sqlcipher', + _kSQLiteMode, + _kSQLite3MCMode, + _kSQLCipherMode, ]) { final sourceFileName = switch (mode) { - "sqlite3" || "sqlcipher" => "sqlite3.c", - "sqlite3mc" => 'sqlite3mc_amalgamation.c', + _kSQLiteMode || _kSQLCipherMode => "sqlite3.c", + _kSQLite3MCMode => 'sqlite3mc_amalgamation.c', _ => throw UnimplementedError(), }; - final sourcePath = fs.currentDirectory.parent + final sourceCFilePath = fs.currentDirectory.parent .childDirectory('sqlite-src') .childDirectory(mode) .childFile(sourceFileName) @@ -121,15 +125,8 @@ void main(List args) async { workspacePubspec: PackageUserDefinesSource( defines: { 'source': 'source', - 'path': p.relative(sourcePath, from: fs.currentDirectory.path), - 'defines': { - 'defines': { - 'SQLITE_HAS_CODEC': null, - 'SQLITE_TEMP_STORE': "2", - 'SQLITE_EXTRA_INIT': 'sqlcipher_extra_init', - 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', - } - } + 'path': p.relative(sourceCFilePath, from: fs.currentDirectory.path), + 'library_type': mode, }, basePath: fs.currentDirectory.uri, )), diff --git a/tool/build_with_sanitizers.dart b/tool/build_with_sanitizers.dart index adcadd23..04138209 100644 --- a/tool/build_with_sanitizers.dart +++ b/tool/build_with_sanitizers.dart @@ -8,6 +8,7 @@ import 'package:path/path.dart' as p; import 'package:pool/pool.dart'; import 'package:sqlite3/src/hook/description.dart'; +import 'package:sqlite3/src/hook/assets.dart'; import '../sqlite3/hook/build.dart' as hook; final _limitConcurrency = Pool(Platform.numberOfProcessors); @@ -58,7 +59,10 @@ void main() async { includes: [p.dirname(sourceFile)], defines: { 'SQLITE_ENABLE_API_ARMOR': '1', - ...CompilerDefines.defaults(input.config.code.targetOS), + ...CompilerDefines.defaults( + input.config.code.targetOS, + LibraryType.sqlite3, + ), }, flags: [ '-fsanitize=$sanitizer', From 0fb180f4c82e3a926849b64adbb6fd1ae47c3102 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 20:30:21 +0200 Subject: [PATCH 09/37] cleanup --- sqlite3/lib/src/hook/openssl.dart | 119 ++---------------------------- tool/build_sqlite.dart | 7 +- tool/download_sqlite.dart | 15 ++++ 3 files changed, 28 insertions(+), 113 deletions(-) diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart index 2f36e016..c05c0449 100644 --- a/sqlite3/lib/src/hook/openssl.dart +++ b/sqlite3/lib/src/hook/openssl.dart @@ -7,12 +7,8 @@ import 'package:sqlite3/src/hook/android_ndk.dart'; // Based from https://github.com/LucazzP/openssl_dart -// import '../lib/src/android_ndk.dart'; +// Build instructions: https://github.com/openssl/openssl/blob/openssl-3.6.2/INSTALL.md#building-openssl -const version = '3.6.2'; -const sourceCodeUrl = - 'https://github.com/openssl/openssl/releases/download/openssl-$version/openssl-$version.tar.gz'; -const openSslDirName = 'openssl-$version'; const configArgs = [ 'no-shared', 'no-apps', @@ -47,18 +43,8 @@ Future buildOpenSSL( final workDir = input.outputDirectory; final outputDir = join(input.outputDirectoryShared.toFilePath(), 'openssl'); - // download source code from openssl - await downloadAndExtract( - sourceCodeUrl, - '$openSslDirName.tar.gz', - workDir, - createFolderForExtraction: false, - ); - - final openSslDir = workDir.resolve('$openSslDirName/'); + final openSslSrcUri = openSslSrcDir.uri; - // build source code, depends on the OS we are running on - // Read https://github.com/openssl/openssl/blob/openssl-3.5.4/INSTALL.md#building-openssl final configName = resolveConfigName( input.config.code.targetOS, input.config.code.targetArchitecture, @@ -120,7 +106,7 @@ Future buildOpenSSL( // needed to build using multiple threads on Windows '/FS', ], - workingDirectory: openSslDir, + workingDirectory: openSslSrcUri, extraEnvironment: msvcEnv, ); @@ -128,7 +114,7 @@ Future buildOpenSSL( await runProcess( jomProgram, ['-j', '${Platform.numberOfProcessors}'], - workingDirectory: openSslDir, + workingDirectory: openSslSrcUri, extraEnvironment: msvcEnv, ); @@ -158,113 +144,22 @@ Future buildOpenSSL( configName, ...configArgs, ...extraConfigureArgs, - ], workingDirectory: openSslDir); + ], workingDirectory: openSslSrcUri); // run make await runProcess('make', [ '-j', '${Platform.numberOfProcessors}', - ], workingDirectory: openSslDir); + ], workingDirectory: openSslSrcUri); - await runProcess('make', ['install'], workingDirectory: openSslDir); + await runProcess('make', ['install'], workingDirectory: openSslSrcUri); break; } - // final filesInOut = Directory( - // openSslDir.toFilePath(), - // ).listSync(recursive: true); - - // copy the library to the output directory - // final String libPath = outputDir - // .resolve(libName) - // .toFilePath(windows: Platform.isWindows); - // await File( - // openSslDir.resolve(libName).toFilePath(windows: Platform.isWindows), - // ).copy(libPath); - - // delete the source code - // await Directory( - // openSslDir.toFilePath(windows: Platform.isWindows), - // ).delete(recursive: true); - return Directory(outputDir); - - // determine the libName from OS and Link mode - /* final libName = switch (( - input.config.code.targetOS, - input.config.code.linkModePreference, - )) { - ( - OS.windows, - LinkModePreference.static || LinkModePreference.preferStatic, - ) => - 'libcrypto_static.lib', - ( - OS.macOS || OS.iOS, - LinkModePreference.static || LinkModePreference.preferStatic, - ) => - 'libcrypto.a', - ( - OS.linux || OS.android, - LinkModePreference.static || LinkModePreference.preferStatic, - ) => - 'libcrypto.a', - ( - OS.windows, - LinkModePreference.dynamic || LinkModePreference.preferDynamic, - ) => - 'libcrypto-3-${input.config.code.targetArchitecture.name}.dll', - ( - OS.macOS || OS.iOS, - LinkModePreference.dynamic || LinkModePreference.preferDynamic, - ) => - 'libcrypto.dylib', - ( - OS.linux || OS.android, - LinkModePreference.dynamic || LinkModePreference.preferDynamic, - ) => - 'libcrypto.so', - _ => throw UnsupportedError( - 'Unsupported target OS: ${input.config.code.targetOS.name} or link mode preference: ${input.config.code.linkModePreference.name}', - ), - }; - - // copy the library to the output directory - final String libPath = outputDir - .resolve(libName) - .toFilePath(windows: Platform.isWindows); - await File( - openSslDir.resolve(libName).toFilePath(windows: Platform.isWindows), - ).copy(libPath); - - // delete the source code - await Directory( - openSslDir.toFilePath(windows: Platform.isWindows), - ).delete(recursive: true); - - return null; */ - - // add the library to dart code assets - /* output.assets.code.add( - CodeAsset( - package: input.packageName, - name: 'src/third_party/openssl.g.dart', - linkMode: libName.linkMode, - file: outputDir.resolve(libName), - ), - ); */ } -/* extension on String { - LinkMode get linkMode { - if (endsWith('.dylib') || endsWith('.so') || endsWith('.dll')) { - return DynamicLoadingBundled(); - } - return StaticLinking(); - } -} */ - String getStaticCryptoLib( Directory opensslDir, OS targetOS, diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 1dff9065..59cbea29 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -140,12 +140,17 @@ void main(List args) async { for (final os in operatingSystems) { for (final architecture in _osToAbis[os]!) { // Compiling sqlite3mc for x86 on Linux does not work. - if (mode == 'sqlite3mc' && + if (mode == _kSQLite3MCMode && os == OS.linux && architecture == Architecture.ia32) { continue; } + // TODO: Windows build for sqlcipher + if (mode == _kSQLCipherMode && os == OS.windows) { + continue; + } + scheduleTask(() => buildAndCopy(os, architecture, iOS: IOSCodeConfig(targetSdk: IOSSdk.iPhoneOS, targetVersion: 12))); } diff --git a/tool/download_sqlite.dart b/tool/download_sqlite.dart index 8f0a69ec..1626d2e3 100644 --- a/tool/download_sqlite.dart +++ b/tool/download_sqlite.dart @@ -12,6 +12,11 @@ const sqlcipherPath = 'sqlcipher-amalgamation-$sqlcipherVersion'; const sqlCipherSource = 'https://github.com/chehrlic/sqlcipher-amalgamation/archive/refs/tags/v$sqlcipherVersion.zip'; +const openSslVersion = '3.6.2'; +const openSslSource = + 'https://github.com/openssl/openssl/releases/download/openssl-$openSslVersion/openssl-$openSslVersion.tar.gz'; +const openSslPath = 'openssl-$openSslVersion'; + const tmpDir = 'tmp'; /// This runs as part of a GitHub actions workflow in this repository. It's not @@ -26,6 +31,7 @@ void main(List args) async { await _downloadAndExtract(sqliteSource, 'sqlite3'); await _downloadAndExtract(sqliteMultipleCiphersSource, 'sqlite3mc'); await _downloadAndExtract(sqlCipherSource, 'sqlcipher'); + await _downloadAndExtractTarGz(openSslSource, 'openssl'); await Directory('sqlite-src').create(); await Directory('sqlite-src/sqlite3mc').create(); @@ -49,6 +55,10 @@ void main(List args) async { .copy('sqlite-src/sqlcipher/sqlite3.c'); await File('$tmpDir/$sqlcipherPath/sqlite3ext.h') .copy('sqlite-src/sqlcipher/sqlite3ext.h'); + + // move openssl source inside sqlcipher source + await Directory('$tmpDir/$openSslPath') + .rename('sqlite-src/sqlcipher/openssl-src'); } Future _downloadAndExtract(String url, String filename) async { @@ -56,6 +66,11 @@ Future _downloadAndExtract(String url, String filename) async { await _run('unzip $filename.zip', workingDirectory: tmpDir); } +Future _downloadAndExtractTarGz(String url, String filename) async { + await _run('curl -L $url --output $filename.tar.gz', workingDirectory: tmpDir); + await _run('tar xzf $filename.tar.gz', workingDirectory: tmpDir); +} + Future _run(String command, {String? workingDirectory}) async { print('Running $command'); From 979bf55dff914128e08df1fab95652b6f4f73b84 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 20:33:50 +0200 Subject: [PATCH 10/37] add archs --- sqlite3/lib/src/hook/description.dart | 13 +------------ tool/build_sqlite.dart | 12 ++++++------ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index 9a2b00c8..20a9455d 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -16,8 +16,6 @@ import 'utils.dart'; sealed class SqliteBinary { static SqliteBinary forBuild(BuildInput input) { final userDefines = input.userDefines; - // print("USERDEFINES"); - // print(userDefines['source']); switch (userDefines['source']) { case null: case 'sqlite3': @@ -383,16 +381,6 @@ extension type const CompilerDefines(Map flags) ? CompilerDefines.defaults(targetOS, libraryType) : const CompilerDefines({}); - print("----------------------"); - print("OBJ: $obj"); - print("DEFINES START:"); - print(start); - if (additionalDefines != null) { - print("DEFINES ADDITIONAL:"); - print(additionalDefines); - } - print("------------------------"); - return switch (additionalDefines) { final added? => start.overrideWith(added), null => start, @@ -440,6 +428,7 @@ extension type const CompilerDefines(Map flags) 'SQLITE_TEMP_STORE': "2", 'SQLITE_EXTRA_INIT': 'sqlcipher_extra_init', 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', + // Link with CommonCrypto on Apple platforms if (targetOS == OS.macOS || targetOS == OS.iOS) 'SQLCIPHER_CRYPTO_CC': null, }); diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 59cbea29..510a43aa 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -175,11 +175,11 @@ void main(List args) async { const _osToAbis = { // TODO: Recover linux 32 bits OS.linux: [ - // Architecture.arm, - // Architecture.arm64, - // Architecture.ia32, + Architecture.arm, + Architecture.arm64, + Architecture.ia32, Architecture.x64, - // Architecture.riscv64, + Architecture.riscv64, ], OS.android: [ Architecture.arm, @@ -194,10 +194,10 @@ const _osToAbis = { ], OS.macOS: [ Architecture.arm64, - // Architecture.x64, + Architecture.x64, ], OS.iOS: [ Architecture.arm64, - // // Note: There's a special check to also compile simulator builds for x64 + // Note: There's a special check to also compile simulator builds for x64 ], }; From 257dbc3f07c2e854dafa533ebf59efc0620e2cf2 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 20:35:47 +0200 Subject: [PATCH 11/37] library type --- tool/build_sqlite.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 510a43aa..70a095a6 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -126,7 +126,12 @@ void main(List args) async { defines: { 'source': 'source', 'path': p.relative(sourceCFilePath, from: fs.currentDirectory.path), - 'library_type': mode, + 'library_type': switch (mode) { + _kSQLiteMode => 'sqlite3', + _kSQLite3MCMode => 'sqlite3mc', + _kSQLCipherMode => 'sqlcipher', + _ => throw UnimplementedError(), + }, }, basePath: fs.currentDirectory.uri, )), From 852d774748391d160f65e1ff8ead1cbcec46ddb2 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 21 May 2026 20:45:38 +0200 Subject: [PATCH 12/37] fix name --- tool/build_sqlite.dart | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 70a095a6..3f2203d4 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -12,7 +12,7 @@ import '../sqlite3/hook/build.dart' as hook; final _limitConcurrency = Pool(Platform.numberOfProcessors); -const _kSQLiteMode = 'sqlite'; +const _kSQLiteMode = 'sqlite3'; const _kSQLite3MCMode = 'sqlite3mc'; const _kSQLCipherMode = 'sqlcipher'; @@ -126,12 +126,7 @@ void main(List args) async { defines: { 'source': 'source', 'path': p.relative(sourceCFilePath, from: fs.currentDirectory.path), - 'library_type': switch (mode) { - _kSQLiteMode => 'sqlite3', - _kSQLite3MCMode => 'sqlite3mc', - _kSQLCipherMode => 'sqlcipher', - _ => throw UnimplementedError(), - }, + 'library_type': mode, }, basePath: fs.currentDirectory.uri, )), From e8cbac7d9b4bc6f575f5b2822babc530e570c895 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 07:36:43 +0200 Subject: [PATCH 13/37] copy clean openssl src dir for each arch --- sqlite3/hook/build.dart | 27 +++++++++++++++++++++------ sqlite3/lib/src/hook/utils.dart | 20 ++++++++++++++++++++ tool/download_sqlite.dart | 4 ++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 0556aa38..155e099d 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -9,6 +9,7 @@ import 'package:sqlite3/src/hook/assets.dart'; import 'package:sqlite3/src/hook/description.dart'; import 'package:sqlite3/src/hook/openssl.dart'; import 'package:sqlite3/src/hook/used_symbols.dart'; +import 'package:sqlite3/src/hook/utils.dart'; void main(List args) async { await build(args, (input, output) async { @@ -83,16 +84,30 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} case OS.android: case OS.linux: case OS.windows: - // OpenSSL is downloaded next to the main source file - final openSslSrcDir = Directory( - p.join(File(sourceFile).parent.path, 'openssl-src'), - ); + final Directory openSslSrcDirArch; + { + // OpenSSL is downloaded next to the main source file and we leave it untouched, copying the src + // into a separate folder for each architecture, so that they can be configured and built in parallel + final openSslSrcDirOrig = Directory( + p.join(File(sourceFile).parent.path, 'openssl-src'), + ); + + openSslSrcDirArch = Directory( + input.outputDirectory + .resolve( + 'openssl-src-${targetOS.name}-${input.config.code.targetArchitecture.name}', + ) + .path, + )..createSync(recursive: true); + + copyDirectory(openSslSrcDirOrig, openSslSrcDirArch); + } + final kOpenSSLBuiltDir = (await buildOpenSSL( input, output, - openSslSrcDir: openSslSrcDir, + openSslSrcDir: openSslSrcDirArch, ))!.path; - print("BUILT OPENSSL IN $kOpenSSLBuiltDir"); final cryptoStaticLib = File( getStaticCryptoLib( diff --git a/sqlite3/lib/src/hook/utils.dart b/sqlite3/lib/src/hook/utils.dart index 1b172ed0..a279828c 100644 --- a/sqlite3/lib/src/hook/utils.dart +++ b/sqlite3/lib/src/hook/utils.dart @@ -1,3 +1,7 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; + /// A sink that allows [add] being called exactly once, and then reports the /// value of the added event. final class OnceSink implements Sink { @@ -19,3 +23,19 @@ final class OnceSink implements Sink { } } } + +void copyDirectory(Directory source, Directory destination) => + source.listSync(recursive: false).forEach((var entity) { + if (entity is Directory) { + var newDirectory = Directory( + path.join(destination.absolute.path, path.basename(entity.path)), + ); + newDirectory.createSync(); + + copyDirectory(entity.absolute, newDirectory); + } else if (entity is File) { + entity.copySync( + path.join(destination.path, path.basename(entity.path)), + ); + } + }); diff --git a/tool/download_sqlite.dart b/tool/download_sqlite.dart index 1626d2e3..aca3458f 100644 --- a/tool/download_sqlite.dart +++ b/tool/download_sqlite.dart @@ -33,7 +33,11 @@ void main(List args) async { await _downloadAndExtract(sqlCipherSource, 'sqlcipher'); await _downloadAndExtractTarGz(openSslSource, 'openssl'); + if (await Directory('sqlite-src').exists()) { + await Directory('sqlite-src').delete(recursive: true); + } await Directory('sqlite-src').create(); + await Directory('sqlite-src/sqlite3mc').create(); await File('$tmpDir/sqlite3mc_amalgamation.h') .copy('sqlite-src/sqlite3mc/sqlite3mc_amalgamation.h'); From b4f6acb34ed192432a2d1b8a101439abfc73e470 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 07:56:41 +0200 Subject: [PATCH 14/37] openssl folder per abi --- sqlite3/hook/build.dart | 34 +++++++++-------------------- sqlite3/lib/src/hook/openssl.dart | 36 +++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 155e099d..04de17d0 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -84,40 +84,26 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} case OS.android: case OS.linux: case OS.windows: - final Directory openSslSrcDirArch; - { - // OpenSSL is downloaded next to the main source file and we leave it untouched, copying the src - // into a separate folder for each architecture, so that they can be configured and built in parallel - final openSslSrcDirOrig = Directory( - p.join(File(sourceFile).parent.path, 'openssl-src'), - ); - - openSslSrcDirArch = Directory( - input.outputDirectory - .resolve( - 'openssl-src-${targetOS.name}-${input.config.code.targetArchitecture.name}', - ) - .path, - )..createSync(recursive: true); - - copyDirectory(openSslSrcDirOrig, openSslSrcDirArch); - } - - final kOpenSSLBuiltDir = (await buildOpenSSL( + // OpenSSL is downloaded next to the main source file + final openSslSrcDir = Directory( + p.join(File(sourceFile).parent.path, 'openssl-src'), + ); + + final openSslBinariesDir = (await buildOpenSSL( input, output, - openSslSrcDir: openSslSrcDirArch, - ))!.path; + openSslSrcDir: openSslSrcDir, + ))!; final cryptoStaticLib = File( getStaticCryptoLib( - Directory(kOpenSSLBuiltDir), + openSslBinariesDir, input.config.code.targetOS, input.config.code.targetArchitecture, ), ); - includes.add('$kOpenSSLBuiltDir/include'); + includes.add(p.join(openSslBinariesDir.path, 'include')); libraryDirectories.add(cryptoStaticLib.parent.path); libraries.add('crypto'); default: diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart index c05c0449..cf55898f 100644 --- a/sqlite3/lib/src/hook/openssl.dart +++ b/sqlite3/lib/src/hook/openssl.dart @@ -41,9 +41,22 @@ Future buildOpenSSL( if (!input.config.buildCodeAssets) return null; final workDir = input.outputDirectory; - final outputDir = join(input.outputDirectoryShared.toFilePath(), 'openssl'); - final openSslSrcUri = openSslSrcDir.uri; + // We configure the project from a separate folder per ABI, to support parallel builds + final openSslBuildDir = Directory( + workDir.resolve('build-openssl').toFilePath(windows: Platform.isWindows), + )..createSync(recursive: true); + + final openSslBuildDirUri = openSslBuildDir.uri; + + // Directory where we install the OpenSSL binaries + final outputDir = join( + input.outputDirectoryShared.toFilePath(windows: Platform.isWindows), + 'openssl', + ); + + // Absolute path of the Configure program in the src folder + final String configureProgramPath = join(openSslSrcDir.path, 'Configure'); final configName = resolveConfigName( input.config.code.targetOS, @@ -62,12 +75,11 @@ Future buildOpenSSL( final pathSeparator = Platform.isWindows ? ';' : ':'; environment['PATH'] = '$toolchainBin$pathSeparator$existingPath'; - print(environment); + // print(environment); } final extraConfigureArgs = [ '--prefix=$outputDir', - '--openssldir=$outputDir', ]; @@ -99,14 +111,14 @@ Future buildOpenSSL( await runProcess( perlProgram, [ - 'Configure', + configureProgramPath, configName, ...configArgs, ...extraConfigureArgs, // needed to build using multiple threads on Windows '/FS', ], - workingDirectory: openSslSrcUri, + workingDirectory: openSslBuildDirUri, extraEnvironment: msvcEnv, ); @@ -114,7 +126,7 @@ Future buildOpenSSL( await runProcess( jomProgram, ['-j', '${Platform.numberOfProcessors}'], - workingDirectory: openSslSrcUri, + workingDirectory: openSslBuildDirUri, extraEnvironment: msvcEnv, ); @@ -140,19 +152,21 @@ Future buildOpenSSL( } // run ./Configure with the target OS and architecture - await runProcess('./Configure', [ + await runProcess(configureProgramPath, [ configName, ...configArgs, ...extraConfigureArgs, - ], workingDirectory: openSslSrcUri); + ], workingDirectory: openSslBuildDirUri); // run make await runProcess('make', [ '-j', '${Platform.numberOfProcessors}', - ], workingDirectory: openSslSrcUri); + ], workingDirectory: openSslBuildDirUri); - await runProcess('make', ['install'], workingDirectory: openSslSrcUri); + await runProcess('make', [ + 'install', + ], workingDirectory: openSslBuildDirUri); break; } From 42c78e7096d762f284bca73e2aafeb3ed3f3ffa8 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 07:57:14 +0200 Subject: [PATCH 15/37] cleanup --- sqlite3/hook/build.dart | 1 - sqlite3/lib/src/hook/utils.dart | 20 -------------------- 2 files changed, 21 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 04de17d0..788c0352 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -9,7 +9,6 @@ import 'package:sqlite3/src/hook/assets.dart'; import 'package:sqlite3/src/hook/description.dart'; import 'package:sqlite3/src/hook/openssl.dart'; import 'package:sqlite3/src/hook/used_symbols.dart'; -import 'package:sqlite3/src/hook/utils.dart'; void main(List args) async { await build(args, (input, output) async { diff --git a/sqlite3/lib/src/hook/utils.dart b/sqlite3/lib/src/hook/utils.dart index a279828c..1b172ed0 100644 --- a/sqlite3/lib/src/hook/utils.dart +++ b/sqlite3/lib/src/hook/utils.dart @@ -1,7 +1,3 @@ -import 'dart:io'; - -import 'package:path/path.dart' as path; - /// A sink that allows [add] being called exactly once, and then reports the /// value of the added event. final class OnceSink implements Sink { @@ -23,19 +19,3 @@ final class OnceSink implements Sink { } } } - -void copyDirectory(Directory source, Directory destination) => - source.listSync(recursive: false).forEach((var entity) { - if (entity is Directory) { - var newDirectory = Directory( - path.join(destination.absolute.path, path.basename(entity.path)), - ); - newDirectory.createSync(); - - copyDirectory(entity.absolute, newDirectory); - } else if (entity is File) { - entity.copySync( - path.join(destination.path, path.basename(entity.path)), - ); - } - }); From 7b1b6e7be9d8efc7d3da55d51c61d3b2fbcb37c6 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 10:20:00 +0200 Subject: [PATCH 16/37] improvements --- sqlite3/hook/build.dart | 17 +++++++------ sqlite3/lib/src/hook/openssl.dart | 42 +++++++++++++++++++++++++++++-- tool/build_sqlite.dart | 8 +++--- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 788c0352..f20eaf01 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -50,7 +50,7 @@ void main(List args) async { // For the full discussion, see https://github.com/dart-lang/native/issues/2724 String? linkerScript; - if (targetOS == OS.linux) { + if (targetOS == OS.linux && libraryType != LibraryType.sqlcipher) { linkerScript = input.outputDirectory.resolve('sqlite.map').path; await File(linkerScript).writeAsString(''' @@ -126,12 +126,15 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} defines: defines, flags: [ if (input.config.code.targetOS == OS.linux) ...[ - // This avoids loading issues on Linux, see comment above. - '-Wl,-Bsymbolic', - // And since we already have a designated list of symbols to - // export, we might as well strip the rest. - // TODO: Port this to other targets too. - '-Wl,--version-script=$linkerScript', + if (linkerScript != null) ...[ + // This avoids loading issues on Linux, see comment above. + '-Wl,-Bsymbolic', + // And since we already have a designated list of symbols to + // export, we might as well strip the rest. + // TODO: Port this to other targets too. + '-Wl,--version-script=$linkerScript', + ], + '-s', '-ffunction-sections', '-fdata-sections', '-Wl,--gc-sections', diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart index cf55898f..eaa14e93 100644 --- a/sqlite3/lib/src/hook/openssl.dart +++ b/sqlite3/lib/src/hook/openssl.dart @@ -23,9 +23,37 @@ const configArgs = [ 'no-legacy', 'no-fips', 'no-async', - '-Wl,-headerpad_max_install_names', + 'no-aria', + 'no-bf', + 'no-blake2', + 'no-camellia', + 'no-cast', + 'no-chacha', + 'no-cmac', + 'no-des', + 'no-dh', + 'no-dsa', + 'no-ec', + 'no-ecdh', + 'no-ecdsa', + 'no-md4', + 'no-mdc2', + 'no-ocsp', + 'no-poly1305', + 'no-rc2', + 'no-rc4', + 'no-rc5', + 'no-rmd160', + 'no-seed', + 'no-siphash', + 'no-sm2', + 'no-sm3', + 'no-sm4', + 'no-srp', + 'no-ts', + 'no-whirlpool', ]; -// 'no-unit-test no-asm no-makedepend no-ssl no-apps -Wl,-headerpad_max_install_names' + const perlDownloadUrl = 'https://strawberryperl.com/download/5.14.2.1/strawberry-perl-5.14.2.1-64bit-portable.zip'; const jomDownloadUrl = @@ -81,6 +109,16 @@ Future buildOpenSSL( final extraConfigureArgs = [ '--prefix=$outputDir', '--openssldir=$outputDir', + if (input.config.code.targetOS == OS.linux) ...[ + '-fPIC', + '-ffunction-sections', + '-fdata-sections', + '-fvisibility=hidden', + ], + if (input.config.code.targetOS == OS.macOS || + input.config.code.targetOS == OS.iOS) ...[ + '-Wl,-headerpad_max_install_names', + ], ]; switch (OS.current) { diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 3f2203d4..b99dab95 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -146,9 +146,11 @@ void main(List args) async { continue; } - // TODO: Windows build for sqlcipher - if (mode == _kSQLCipherMode && os == OS.windows) { - continue; + if (mode == _kSQLCipherMode) { + // TODO: Windows build for sqlcipher + if (os == OS.windows) continue; + // TODO: Linux arm 32 + if (os == OS.linux && architecture == Architecture.arm) continue; } scheduleTask(() => buildAndCopy(os, architecture, From 9e2f295ac141c4400d495c84f676175d215ade46 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 10:29:11 +0200 Subject: [PATCH 17/37] perl Configure instead of ./Configure --- sqlite3/lib/src/hook/openssl.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart index eaa14e93..1e8b0e95 100644 --- a/sqlite3/lib/src/hook/openssl.dart +++ b/sqlite3/lib/src/hook/openssl.dart @@ -190,7 +190,8 @@ Future buildOpenSSL( } // run ./Configure with the target OS and architecture - await runProcess(configureProgramPath, [ + await runProcess('perl', [ + configureProgramPath, configName, ...configArgs, ...extraConfigureArgs, From 7f2a9947ad25427d7f819925eb9de7844ad616a6 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 10:42:10 +0200 Subject: [PATCH 18/37] exclude some linux archs --- tool/build_sqlite.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index b99dab95..4c23173e 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -149,8 +149,11 @@ void main(List args) async { if (mode == _kSQLCipherMode) { // TODO: Windows build for sqlcipher if (os == OS.windows) continue; - // TODO: Linux arm 32 + // TODO: Other linux archs if (os == OS.linux && architecture == Architecture.arm) continue; + if (os == OS.linux && architecture == Architecture.arm64) continue; + if (os == OS.linux && architecture == Architecture.ia32) continue; + if (os == OS.linux && architecture == Architecture.riscv64) continue; } scheduleTask(() => buildAndCopy(os, architecture, From 628a08ac4c374e33d309a779b7fd3f516ad56ece Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 11:33:51 +0200 Subject: [PATCH 19/37] encryption test --- .github/workflows/main.yml | 8 ++++ sqlite3/test/ffi/encryption_test.dart | 66 +++++++++++++++++++++++++++ tool/hook_overrides.dart | 10 ++++ 3 files changed, 84 insertions(+) create mode 100644 sqlite3/test/ffi/encryption_test.dart diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08b314b7..16fa3ec7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -157,6 +157,14 @@ jobs: dart test --test-randomize-ordering-seed "random" -P ci working-directory: sqlite3/ + - name: Enable sqlcipher + run: | + dart run tool/hook_overrides.dart compiled-sqlcipher + - name: Test sqlite3 package with sqlcipher + run: | + dart test --test-randomize-ordering-seed "random" -P ci + working-directory: sqlite3/ + - name: Prepare for tests with system SQLite run: | dart run tool/hook_overrides.dart system-os-specific diff --git a/sqlite3/test/ffi/encryption_test.dart b/sqlite3/test/ffi/encryption_test.dart new file mode 100644 index 00000000..26ef44f6 --- /dev/null +++ b/sqlite3/test/ffi/encryption_test.dart @@ -0,0 +1,66 @@ +@Tags(['ffi']) +library; + +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite3/src/hook/assets.dart'; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +import '../common/utils.dart'; + +void main() { + test('encryption', () { + final LibraryType libraryType = _inferLibraryType(); + + if (libraryType == LibraryType.sqlcipher || + libraryType == LibraryType.sqlite3mc) { + testEncryptAndOpenEncrypted(); + } + }); +} + +void testEncryptAndOpenEncrypted() { + final path = d.path('test.db'); + final db = sqlite3.open(path); + addTearDown(() { + db.close(); + }); + + final key = 'my_secret'; + db.execute("PRAGMA key = '$key'"); + db.execute("CREATE TABLE users (id INTEGER, username TEXT)"); + db.close(); + + final dbAfterEnc = sqlite3.open(path); + addTearDown(() => dbAfterEnc.close()); + expect( + () => dbAfterEnc.select('SELECT * FROM sqlite_master'), + throwsSqlError(SqlError.SQLITE_NOTADB, SqlError.SQLITE_NOTADB), + ); + dbAfterEnc.execute("PRAGMA key = '$key'"); + + // Reads the db after setting the key + expect(dbAfterEnc.select('SELECT * FROM sqlite_master'), isNotEmpty); +} + +LibraryType _inferLibraryType() { + final db = sqlite3.openInMemory(); + + try { + // Sqlcipher can check PRAGMA cipher_version + final cipherVersionRows = db.select('PRAGMA cipher_version'); + if (cipherVersionRows.isNotEmpty) { + return LibraryType.sqlcipher; + } + + // PRAGMA cipher is available in sqlite3mc + final cipherRows = db.select('PRAGMA cipher'); + if (cipherRows.isNotEmpty) { + return LibraryType.sqlite3mc; + } + + return LibraryType.sqlite3; + } finally { + db.close(); + } +} diff --git a/tool/hook_overrides.dart b/tool/hook_overrides.dart index a155c20b..f660ce79 100644 --- a/tool/hook_overrides.dart +++ b/tool/hook_overrides.dart @@ -48,6 +48,16 @@ hooks: sqlite3: source: test-sqlite3mc directory: $outPath/ +'''); + case 'compiled-sqlcipher': + final outPath = p.relative('sqlite-compiled', from: p.dirname(path)); + + out.write(''' +hooks: + user_defines: + sqlite3: + source: test-sqlcipher + directory: $outPath/ '''); default: throw 'Unsupported mode, can use system, system-os-specific, ' From 0eed2b2c75e99e7daa186cf00b982997f9ba48ff Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 11:42:35 +0200 Subject: [PATCH 20/37] cleanup --- sqlite3/hook/build.dart | 4 +++- sqlite3/lib/src/hook/description.dart | 13 ++++--------- tool/build_sqlite.dart | 1 - 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index f20eaf01..fe693b3d 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -105,6 +105,9 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} includes.add(p.join(openSslBinariesDir.path, 'include')); libraryDirectories.add(cryptoStaticLib.parent.path); libraries.add('crypto'); + + // The android library is needed when linking + libraries.add('log'); default: throw UnsupportedError( 'Unsupported OS: ${input.config.code.targetOS}', @@ -155,7 +158,6 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} // We need to link the math library on Android. 'm', ], - if (targetOS == OS.android) 'log', ...libraries, ], ); diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index 20a9455d..03bc67a3 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -238,15 +238,10 @@ final class PrecompiledFromGithubAssets extends PrecompiledBinary { // environments where that's required // https://github.com/simolus3/sqlite3.dart/issues/335 ..findProxy = HttpClient.findProxyFromEnvironment; - final uri = type == LibraryType.sqlcipher - ? Uri.https( - 'github.com', - 'davidmartos96/sqlite3.dart/releases/download/${releaseTag!}/$filename', - ) - : Uri.https( - 'github.com', - 'simolus3/sqlite3.dart/releases/download/${releaseTag!}/$filename', - ); + final uri = Uri.https( + 'github.com', + 'simolus3/sqlite3.dart/releases/download/${releaseTag!}/$filename', + ); HttpClientResponse response; try { diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 4c23173e..efbd2b8f 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -178,7 +178,6 @@ void main(List args) async { } const _osToAbis = { - // TODO: Recover linux 32 bits OS.linux: [ Architecture.arm, Architecture.arm64, From 81f85cae0117f6dc77977de79c078619f821da22 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 11:51:06 +0200 Subject: [PATCH 21/37] exclude sqlcipher tests on windows --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16fa3ec7..5e7d9dd1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -163,6 +163,7 @@ jobs: - name: Test sqlite3 package with sqlcipher run: | dart test --test-randomize-ordering-seed "random" -P ci + if: ${{ runner.os != 'windows' }} # TODO: SQLCipher Windows not supported yet working-directory: sqlite3/ - name: Prepare for tests with system SQLite From 6223d81142c6683b62447a0cfb03963f667f0819 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 12:11:59 +0200 Subject: [PATCH 22/37] fix log link --- sqlite3/hook/build.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index fe693b3d..1cd2b7eb 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -106,8 +106,10 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} libraryDirectories.add(cryptoStaticLib.parent.path); libraries.add('crypto'); - // The android library is needed when linking - libraries.add('log'); + if (targetOS == OS.android) { + // The android library is needed when linking + libraries.add('log'); + } default: throw UnsupportedError( 'Unsupported OS: ${input.config.code.targetOS}', From 9c4def4d177b2442288597ab3d06d1a4c1a24cb1 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 15:01:08 +0200 Subject: [PATCH 23/37] extra flags --- sqlite3/lib/src/hook/description.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index 03bc67a3..bdde325a 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -423,6 +423,19 @@ extension type const CompilerDefines(Map flags) 'SQLITE_TEMP_STORE': "2", 'SQLITE_EXTRA_INIT': 'sqlcipher_extra_init', 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', + + // Most modern unix systems support nanosleep, but if it wouldn't be available + // we want to fallback to usleep (microseconds) instead of sleep (seconds) + 'HAVE_USLEEP': null, + + // SQLCipher enables this by default, so better keep compatibility + 'SQLITE_USE_URI ': null, + + // SQLCipher uses it in their Community builds. + // Not clear if it has an impact in all applications + // https://github.com/sqlcipher/sqlcipher-android/blob/7fab57af75039e5004b087086142b11a9d2a2380/sqlcipher/src/main/jni/sqlcipher/Android.mk#L9 + 'SQLITE_ENABLE_MEMORY_MANAGEMENT': null, + // Link with CommonCrypto on Apple platforms if (targetOS == OS.macOS || targetOS == OS.iOS) 'SQLCIPHER_CRYPTO_CC': null, From 5596169224d95e98ad5b7c0e25c0b785bc03bcec Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 22 May 2026 15:51:53 +0200 Subject: [PATCH 24/37] format --- sqlite3/lib/src/hook/description.dart | 4 ++-- tool/download_sqlite.dart | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index bdde325a..77aa7fc0 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -425,7 +425,7 @@ extension type const CompilerDefines(Map flags) 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', // Most modern unix systems support nanosleep, but if it wouldn't be available - // we want to fallback to usleep (microseconds) instead of sleep (seconds) + // we want to fallback to usleep (microseconds) instead of sleep (seconds) 'HAVE_USLEEP': null, // SQLCipher enables this by default, so better keep compatibility @@ -435,7 +435,7 @@ extension type const CompilerDefines(Map flags) // Not clear if it has an impact in all applications // https://github.com/sqlcipher/sqlcipher-android/blob/7fab57af75039e5004b087086142b11a9d2a2380/sqlcipher/src/main/jni/sqlcipher/Android.mk#L9 'SQLITE_ENABLE_MEMORY_MANAGEMENT': null, - + // Link with CommonCrypto on Apple platforms if (targetOS == OS.macOS || targetOS == OS.iOS) 'SQLCIPHER_CRYPTO_CC': null, diff --git a/tool/download_sqlite.dart b/tool/download_sqlite.dart index aca3458f..88053f40 100644 --- a/tool/download_sqlite.dart +++ b/tool/download_sqlite.dart @@ -37,7 +37,7 @@ void main(List args) async { await Directory('sqlite-src').delete(recursive: true); } await Directory('sqlite-src').create(); - + await Directory('sqlite-src/sqlite3mc').create(); await File('$tmpDir/sqlite3mc_amalgamation.h') .copy('sqlite-src/sqlite3mc/sqlite3mc_amalgamation.h'); @@ -71,7 +71,8 @@ Future _downloadAndExtract(String url, String filename) async { } Future _downloadAndExtractTarGz(String url, String filename) async { - await _run('curl -L $url --output $filename.tar.gz', workingDirectory: tmpDir); + await _run('curl -L $url --output $filename.tar.gz', + workingDirectory: tmpDir); await _run('tar xzf $filename.tar.gz', workingDirectory: tmpDir); } From 88d953058342a7c7219f593a9ee3544c56d603be Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 18:28:44 +0200 Subject: [PATCH 25/37] cleanup after merge --- sqlite3/hook/build.dart | 16 +++++--- sqlite3/lib/src/hook/assets.dart | 9 ----- sqlite3/lib/src/hook/description.dart | 53 ++++++++++----------------- tool/build_sqlite.dart | 15 -------- tool/build_with_sanitizers.dart | 4 +- 5 files changed, 32 insertions(+), 65 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index b9b5d6b4..c7f1404c 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -36,9 +36,7 @@ void main(List args) async { file: downloaded.uri, ), ); - case CompileSqlite(:final libraryType, :final sourceFile, :final defines): - final targetOS = input.config.code.targetOS; - + case CompileSqlite(:final sourceFile, :final defines): // With Flutter on Linux (which already dynamically links SQLite through // its libgtk dependency), we run into issues where loading our SQLite // build causes internal symbols to be resolved against the already @@ -50,7 +48,7 @@ void main(List args) async { // For the full discussion, see https://github.com/dart-lang/native/issues/2724 String? linkerScript; - if (targetOS == OS.linux && libraryType != LibraryType.sqlcipher) { + if (input.config.code.targetOS == OS.linux) { linkerScript = input.outputDirectory.resolve('sqlite.map').path; await File(linkerScript).writeAsString(''' @@ -67,11 +65,16 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} final targetOS = input.config.code.targetOS; final isAppleTarget = targetOS == OS.iOS || targetOS == OS.macOS; + final List includes = []; + final List libraryDirectories = []; + final List libraries = []; + final List flags = []; + final library = CBuilder.library( name: 'sqlite3', packageName: 'sqlite3', assetName: name, - sources: [sourceFile], + sources: [sourceFile, ...includes], includes: [p.dirname(sourceFile), ...includes], defines: defines, flags: [ @@ -111,9 +114,10 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} ], libraryDirectories: [...libraryDirectories], libraries: [ - if (targetOS == OS.android || targetOS == OS.linux) ...[ + if (targetOS == OS.android) ...[ // We need to link the math library on Android. 'm', + if (isSqlcipher) 'log', ], ...libraries, ], diff --git a/sqlite3/lib/src/hook/assets.dart b/sqlite3/lib/src/hook/assets.dart index cb00f1b1..5c0fdff4 100644 --- a/sqlite3/lib/src/hook/assets.dart +++ b/sqlite3/lib/src/hook/assets.dart @@ -27,15 +27,6 @@ enum LibraryType { OS.android || OS.linux || _ => 'lib$basename.so', }; } - - static LibraryType fromName(String name) { - return switch (name) { - 'sqlite3' => LibraryType.sqlite3, - 'sqlite3mc' => LibraryType.sqlite3mc, - 'sqlcipher' => LibraryType.sqlcipher, - _ => throw ArgumentError('Unknown library type: $name'), - }; - } } enum TargetOperatingSystem { diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index f249e34b..d9af6e8c 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -42,18 +42,13 @@ sealed class SqliteBinary { case 'executable': return SimpleBinary.fromExecutable; case 'source': - final libraryTypeName = userDefines['library_type'] as String?; - final libraryType = libraryTypeName != null - ? LibraryType.fromName(libraryTypeName) - : LibraryType.sqlite3; - + final isSqlcipher = userDefines['is_sqlcipher'] as bool? ?? false; return CompileSqlite( sourceFile: userDefines.path('path')!.toFilePath(), - libraryType: libraryType, defines: CompilerDefines.parse( - libraryType, userDefines, - input.config.code.targetOS, + targetOS: input.config.code.targetOS, + isSqlcipher: isSqlcipher, ), ); default: @@ -299,20 +294,13 @@ final class PrecompiledForTesting extends PrecompiledBinary { } final class CompileSqlite implements SqliteBinary { - /// The type of build - final LibraryType libraryType; - /// Path to the `sqlite3.c` source file to compile. final String sourceFile; /// User-defines for the SQLite compilation. final CompilerDefines defines; - CompileSqlite({ - required this.libraryType, - required this.sourceFile, - required this.defines, - }); + CompileSqlite({required this.sourceFile, required this.defines}); } /// If we're compiling SQLite from source, a way to obtain these sources. @@ -357,11 +345,7 @@ extension type const CompilerDefines(Map flags) return CompilerDefines({...flags, ...other.flags}); } - static CompilerDefines parse( - LibraryType libraryType, - HookInputUserDefines defines, - OS targetOS, - ) { + static CompilerDefines parse(HookInputUserDefines defines, {required OS targetOS, required bool isSqlcipher}) { final obj = defines['defines']; // Include default options when not explicitly disabled. @@ -379,7 +363,7 @@ extension type const CompilerDefines(Map flags) }; final start = includeDefaults - ? CompilerDefines.defaults(targetOS, libraryType) + ? CompilerDefines.defaults(targetOS: targetOS, isSqlcipher: isSqlcipher) : const CompilerDefines({}); return switch (additionalDefines) { @@ -416,31 +400,34 @@ extension type const CompilerDefines(Map flags) return CompilerDefines(entries); } - static CompilerDefines defaults(OS targetOS, LibraryType libraryType) { + static CompilerDefines defaults({required OS targetOS, required bool isSqlcipher}) { final defines = _parseLines(const LineSplitter().convert(_defaultDefines)); if (targetOS == OS.windows) { defines['SQLITE_API'] = '__declspec(dllexport)'; } // Minimum extra flags to build SQLCipher - if (libraryType == LibraryType.sqlcipher) { + if (isSqlcipher) { defines.addAll({ 'SQLITE_HAS_CODEC': null, 'SQLITE_TEMP_STORE': "2", 'SQLITE_EXTRA_INIT': 'sqlcipher_extra_init', 'SQLITE_EXTRA_SHUTDOWN': 'sqlcipher_extra_shutdown', - // Most modern unix systems support nanosleep, but if it wouldn't be available - // we want to fallback to usleep (microseconds) instead of sleep (seconds) - 'HAVE_USLEEP': null, + // Default flags from SQLCipher community builds to keep compatibility with the old sqlcipher_flutter_libs + // which was using SQLCipher Community binaries under the hood + // https://github.com/sqlcipher/sqlcipher-android/blob/7fab57af75039e5004b087086142b11a9d2a2380/sqlcipher/src/main/jni/sqlcipher/Android.mk#L9 + ...{ + // Most modern unix systems support nanosleep, but if it wouldn't be available + // we want to fallback to usleep (microseconds) instead of sleep (seconds) + 'HAVE_USLEEP': null, - // SQLCipher enables this by default, so better keep compatibility - 'SQLITE_USE_URI ': null, + // URI support + 'SQLITE_USE_URI ': null, - // SQLCipher uses it in their Community builds. - // Not clear if it has an impact in all applications - // https://github.com/sqlcipher/sqlcipher-android/blob/7fab57af75039e5004b087086142b11a9d2a2380/sqlcipher/src/main/jni/sqlcipher/Android.mk#L9 - 'SQLITE_ENABLE_MEMORY_MANAGEMENT': null, + // Not clear if it has an impact in all applications + 'SQLITE_ENABLE_MEMORY_MANAGEMENT': null, + }, // Link with CommonCrypto on Apple platforms if (targetOS == OS.macOS || targetOS == OS.iOS) diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 800155e0..a2f5f362 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -117,21 +117,6 @@ void main(List args) async { 'source': 'source', 'path': p.relative(sourcePath, from: fs.currentDirectory.path), if (mode == SqliteFork.sqlcipher) ...{ - 'defines': { - 'default_options': true, - 'defines': [ - 'SQLITE_HAS_CODEC=1', - 'SQLITE_EXTRA_INIT=sqlcipher_extra_init', - 'SQLITE_EXTRA_SHUTDOWN=sqlcipher_extra_shutdown', - // SQLCipher uses it in their Community builds. - // Not clear if it has an impact in all applications - // https://github.com/sqlcipher/sqlcipher-android/blob/7fab57af75039e5004b087086142b11a9d2a2380/sqlcipher/src/main/jni/sqlcipher/Android.mk#L9 - 'SQLITE_ENABLE_MEMORY_MANAGEMENT=1', - if (os case OS.iOS || OS.macOS) - // Link with CommonCrypto on Apple platforms - 'SQLCIPHER_CRYPTO_CC=1', - ] - }, 'is_sqlcipher': true, } }, diff --git a/tool/build_with_sanitizers.dart b/tool/build_with_sanitizers.dart index 04138209..04e3307d 100644 --- a/tool/build_with_sanitizers.dart +++ b/tool/build_with_sanitizers.dart @@ -60,8 +60,8 @@ void main() async { defines: { 'SQLITE_ENABLE_API_ARMOR': '1', ...CompilerDefines.defaults( - input.config.code.targetOS, - LibraryType.sqlite3, + targetOS: input.config.code.targetOS, + isSqlcipher: false, ), }, flags: [ From 0c86241f8379efb67c5d0f373bb72162bae1ea3e Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 19:27:09 +0200 Subject: [PATCH 26/37] link statically --- .github/workflows/compile_sqlite.yml | 7 +++ sqlite3/hook/build.dart | 81 +++++++++++++++++++++++----- tool/build_sqlite.dart | 7 +++ 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index df0b4c7e..ffd32bed 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -124,6 +124,13 @@ jobs: name: sqlite-src path: sqlite-src + - name: Download compiled OpenSSL + if: steps.cache_build.outputs.cache-hit != 'true' + uses: actions/download-artifact@v8 + with: + name: openssl-libs-${{ runner.os }} + path: openssl-compiled + - uses: dart-lang/setup-dart@v1 if: steps.cache_build.outputs.cache-hit != 'true' diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index c7f1404c..50c000da 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -4,10 +4,8 @@ import 'package:code_assets/code_assets.dart'; import 'package:hooks/hooks.dart'; import 'package:native_toolchain_c/native_toolchain_c.dart'; import 'package:path/path.dart' as p; -import 'package:sqlite3/src/hook/assets.dart'; import 'package:sqlite3/src/hook/description.dart'; -import 'package:sqlite3/src/hook/openssl.dart'; import 'package:sqlite3/src/hook/used_symbols.dart'; void main(List args) async { @@ -62,20 +60,69 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} } final isSqlcipher = input.userDefines['is_sqlcipher'] as bool? ?? false; + final targetOS = input.config.code.targetOS; + final targetArchitecture = input.config.code.targetArchitecture; final isAppleTarget = targetOS == OS.iOS || targetOS == OS.macOS; - final List includes = []; - final List libraryDirectories = []; - final List libraries = []; - final List flags = []; + // Directory where the architecture compiled OpenSSL is located. Null if OpenSSL is not used + Directory? openSslCompileDir; + File? openSslStaticLib; + if (isSqlcipher) { + final linksWithOpenSSL = + targetOS == OS.android || + targetOS == OS.linux || + targetOS == OS.windows; + if (linksWithOpenSSL) { + const openSSLCompiledRootKey = 'openssl_compiled_root'; + final Uri? opensslCompiledRoot = + input.userDefines.path(openSSLCompiledRootKey); + + if (opensslCompiledRoot == null) { + throw StateError( + 'Target $targetOS needs OpenSSL compiled dir root with \'openSSLCompiledRootKey\'', + ); + } + + openSslCompileDir = Directory( + p.join( + opensslCompiledRoot.toFilePath(), + "${targetOS.name}-${targetArchitecture.name}", + ), + ); + + if (!await openSslCompileDir.exists()) { + throw StateError( + 'Expected OpenSSL compiled directory at ${openSslCompileDir.path}', + ); + } + + openSslStaticLib = File( + p.join( + openSslCompileDir.path, + _getOpenSslLibFolderName(targetOS, targetArchitecture), + targetOS.staticlibFileName('crypto'), + ), + ); + + if (!await openSslStaticLib.exists()) { + throw StateError( + 'Expected OpenSSL static library at ${openSslStaticLib.path}', + ); + } + } + } final library = CBuilder.library( name: 'sqlite3', packageName: 'sqlite3', assetName: name, - sources: [sourceFile, ...includes], - includes: [p.dirname(sourceFile), ...includes], + sources: [sourceFile], + includes: [ + p.dirname(sourceFile), + if (openSslCompileDir != null) + p.join(openSslCompileDir.path, 'include'), + ], defines: defines, flags: [ if (input.config.code.targetOS == OS.linux) ...[ @@ -91,9 +138,6 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} '-ffunction-sections', '-fdata-sections', '-Wl,--gc-sections', - if (isSqlcipher) - // TODO: Link OpenSSL statically? - '-lcrypto', ], if (isAppleTarget) ...[ '-headerpad_max_install_names', @@ -110,16 +154,18 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} '-framework', 'Security', ], ], - ...flags, ], - libraryDirectories: [...libraryDirectories], + libraryDirectories: [ + if (openSslStaticLib != null) openSslStaticLib.parent.path, + ], libraries: [ if (targetOS == OS.android) ...[ // We need to link the math library on Android. 'm', if (isSqlcipher) 'log', ], - ...libraries, + // Link with OpenSSL (SQLCipher builds) + if (openSslCompileDir != null) 'crypto', ], ); @@ -136,5 +182,12 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} }); } +String _getOpenSslLibFolderName(OS os, Architecture architecture) { + return switch ((os, architecture)) { + (OS.linux, Architecture.x64) => 'lib64', + _ => 'lib', + }; +} + const package = 'sqlite3'; const name = 'src/ffi/libsqlite3.g.dart'; diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index a2f5f362..800b75e7 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -118,6 +118,13 @@ void main(List args) async { 'path': p.relative(sourcePath, from: fs.currentDirectory.path), if (mode == SqliteFork.sqlcipher) ...{ 'is_sqlcipher': true, + // This is the folder where all the openssl compiled archs are located + 'openssl_compiled_root': p.relative( + fs.currentDirectory.parent + .childDirectory('openssl-compiled') + .path, + from: fs.currentDirectory.path, + ), } }, basePath: fs.currentDirectory.uri, From 58a7d4441f5f43946c6f02f4239143637c10df5a Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 19:39:02 +0200 Subject: [PATCH 27/37] analyze --- sqlite3/hook/build.dart | 5 +++-- sqlite3/lib/src/hook/description.dart | 13 ++++++++++--- tool/build_sqlite.dart | 14 -------------- tool/build_with_sanitizers.dart | 1 - 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 50c000da..65030b83 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -75,8 +75,9 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} targetOS == OS.windows; if (linksWithOpenSSL) { const openSSLCompiledRootKey = 'openssl_compiled_root'; - final Uri? opensslCompiledRoot = - input.userDefines.path(openSSLCompiledRootKey); + final Uri? opensslCompiledRoot = input.userDefines.path( + openSSLCompiledRootKey, + ); if (opensslCompiledRoot == null) { throw StateError( diff --git a/sqlite3/lib/src/hook/description.dart b/sqlite3/lib/src/hook/description.dart index d9af6e8c..69db2cef 100644 --- a/sqlite3/lib/src/hook/description.dart +++ b/sqlite3/lib/src/hook/description.dart @@ -42,7 +42,7 @@ sealed class SqliteBinary { case 'executable': return SimpleBinary.fromExecutable; case 'source': - final isSqlcipher = userDefines['is_sqlcipher'] as bool? ?? false; + final isSqlcipher = userDefines['is_sqlcipher'] as bool? ?? false; return CompileSqlite( sourceFile: userDefines.path('path')!.toFilePath(), defines: CompilerDefines.parse( @@ -345,7 +345,11 @@ extension type const CompilerDefines(Map flags) return CompilerDefines({...flags, ...other.flags}); } - static CompilerDefines parse(HookInputUserDefines defines, {required OS targetOS, required bool isSqlcipher}) { + static CompilerDefines parse( + HookInputUserDefines defines, { + required OS targetOS, + required bool isSqlcipher, + }) { final obj = defines['defines']; // Include default options when not explicitly disabled. @@ -400,7 +404,10 @@ extension type const CompilerDefines(Map flags) return CompilerDefines(entries); } - static CompilerDefines defaults({required OS targetOS, required bool isSqlcipher}) { + static CompilerDefines defaults({ + required OS targetOS, + required bool isSqlcipher, + }) { final defines = _parseLines(const LineSplitter().convert(_defaultDefines)); if (targetOS == OS.windows) { defines['SQLITE_API'] = '__declspec(dllexport)'; diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 800b75e7..8ea6e876 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -12,10 +12,6 @@ import '../sqlite3/hook/build.dart' as hook; final _limitConcurrency = Pool(Platform.numberOfProcessors); -const _kSQLiteMode = 'sqlite3'; -const _kSQLite3MCMode = 'sqlite3mc'; -const _kSQLCipherMode = 'sqlcipher'; - /// Invokes `package:sqlite3` build hooks for multiple operating systems and /// architectures, merging outputs into `sqlite3-compiled/`. void main(List args) async { @@ -140,16 +136,6 @@ void main(List args) async { for (final architecture in _osToAbis[os]!) { if (_skipBuild(os, architecture, mode)) continue; - if (mode == _kSQLCipherMode) { - // TODO: Windows build for sqlcipher - if (os == OS.windows) continue; - // TODO: Other linux archs - if (os == OS.linux && architecture == Architecture.arm) continue; - if (os == OS.linux && architecture == Architecture.arm64) continue; - if (os == OS.linux && architecture == Architecture.ia32) continue; - if (os == OS.linux && architecture == Architecture.riscv64) continue; - } - scheduleTask(() => buildAndCopy(os, architecture, iOS: IOSCodeConfig(targetSdk: IOSSdk.iPhoneOS, targetVersion: 12))); } diff --git a/tool/build_with_sanitizers.dart b/tool/build_with_sanitizers.dart index 04e3307d..56f04c8f 100644 --- a/tool/build_with_sanitizers.dart +++ b/tool/build_with_sanitizers.dart @@ -8,7 +8,6 @@ import 'package:path/path.dart' as p; import 'package:pool/pool.dart'; import 'package:sqlite3/src/hook/description.dart'; -import 'package:sqlite3/src/hook/assets.dart'; import '../sqlite3/hook/build.dart' as hook; final _limitConcurrency = Pool(Platform.numberOfProcessors); From 2788cb6d5044a7744decdfac0acf7c924dff79d1 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 19:39:58 +0200 Subject: [PATCH 28/37] fix --- .github/workflows/compile_sqlite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index ffd32bed..21665c3d 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -66,7 +66,7 @@ jobs: if: steps.cache_build.outputs.cache-hit != 'true' - name: Download OpenSSL - if: steps.cache_build.outputs.cache-hit != 'true' + if: runner.os == 'Linux' && steps.cache_build.outputs.cache-hit != 'true' run: | curl -L https://github.com/openssl/openssl/releases/download/openssl-3.6.2/openssl-3.6.2.tar.gz | tar --extract --gzip mv openssl-3.6.2 openssl-src From 632b96d62d46dae827ebe2ae3afbf8f2adb5c837 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 19:49:14 +0200 Subject: [PATCH 29/37] build openssl refactor --- tool/build_openssl.dart | 50 ++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/tool/build_openssl.dart b/tool/build_openssl.dart index e8a901b9..1964c5e3 100644 --- a/tool/build_openssl.dart +++ b/tool/build_openssl.dart @@ -24,30 +24,43 @@ void main(List args) async { var hadFailure = false; for (final platform in args) { + final OS targetOS; + final List targetArchs; + switch (platform) { case 'linux': - for (final arch in _linuxArchitectures) { - try { - await _buildOpenSSL( - targetOS: OS.linux, - targetArchitecture: arch, - sharedOutputDirectory: target, - openSslSrcDir: src, - ); - } catch (e, s) { - hadFailure = true; - print('Build failed for $arch: $e'); - print(s); - } - } - case 'windows': + targetArchs = _linuxArchitectures; + targetOS = OS.linux; break; case 'android': + targetArchs = _androidArchitectures; + targetOS = OS.android; + break; + case 'windows': + targetArchs = [ + // TODO: Windows + ]; + targetOS = OS.windows; break; default: throw UnsupportedError( 'Unsupported target OS, expected linux, windows or android.'); } + + for (final arch in targetArchs) { + try { + await _buildOpenSSL( + targetOS: targetOS, + targetArchitecture: arch, + sharedOutputDirectory: target, + openSslSrcDir: src, + ); + } catch (e, s) { + hadFailure = true; + print('Build failed for $platform-$arch: $e'); + print(s); + } + } } if (hadFailure) exit(1); @@ -224,3 +237,10 @@ const _linuxArchitectures = [ Architecture.x64, //Architecture.riscv64, ]; + +const _androidArchitectures = [ + Architecture.arm, + Architecture.arm64, + Architecture.ia32, + Architecture.x64, +]; From 80d2d0b8a1c8af5b140879663e3c753299e1d781 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 20:08:48 +0200 Subject: [PATCH 30/37] compile android --- tool/build_openssl.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/tool/build_openssl.dart b/tool/build_openssl.dart index 1964c5e3..eb4db7ca 100644 --- a/tool/build_openssl.dart +++ b/tool/build_openssl.dart @@ -90,9 +90,6 @@ Future _buildOpenSSL({ targetOS, targetArchitecture, ); - if (targetOS == OS.android) { - throw 'TODO: Android'; - } final extraConfigureArgs = [ '--prefix=${outputDirectory.path}', From 2d5bb2fd4939945f3275a166ad4f0d311332d047 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 20:23:20 +0200 Subject: [PATCH 31/37] ndk root --- tool/build_openssl.dart | 66 ++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/tool/build_openssl.dart b/tool/build_openssl.dart index eb4db7ca..c5387ce5 100644 --- a/tool/build_openssl.dart +++ b/tool/build_openssl.dart @@ -91,6 +91,19 @@ Future _buildOpenSSL({ targetArchitecture, ); + final Map extraEnv = {}; + if (targetOS == OS.android) { + final String? ndkRoot = Platform.environment['ANDROID_NDK_ROOT']; + + if (ndkRoot == null) { + throw Exception('Android NDK not found. Set ANDROID_NDK_ROOT'); + } + + final existingPath = Platform.environment['PATH'] ?? ''; + extraEnv['PATH'] = + '$ndkRoot/toolchains/llvm/prebuilt/linux-x86_64/bin:$existingPath'; + } + final extraConfigureArgs = [ '--prefix=${outputDirectory.path}', '--openssldir=${outputDirectory.path}', @@ -108,31 +121,37 @@ Future _buildOpenSSL({ case OS.linux: // run ./Configure with the target OS and architecture await _run( - 'perl', - [ - configureProgramPath, - configName, - ..._configArgs, - ...extraConfigureArgs, - ], - workingDirectory: openSslBuildDirPath); + 'perl', + [ + configureProgramPath, + configName, + ..._configArgs, + ...extraConfigureArgs, + ], + workingDirectory: openSslBuildDirPath, + environment: extraEnv, + ); // Build static libraries await _run( - 'make', - [ - '-j', - '${Platform.numberOfProcessors}', - ], - workingDirectory: openSslBuildDirPath); + 'make', + [ + '-j', + '${Platform.numberOfProcessors}', + ], + workingDirectory: openSslBuildDirPath, + environment: extraEnv, + ); // Copy compiled libraries into output directory await _run( - 'make', - [ - 'install', - ], - workingDirectory: openSslBuildDirPath); + 'make', + [ + 'install', + ], + workingDirectory: openSslBuildDirPath, + environment: extraEnv, + ); break; } @@ -140,13 +159,18 @@ Future _buildOpenSSL({ await tmp.delete(recursive: true); } -Future _run(String executable, List args, - {String? workingDirectory}) async { +Future _run( + String executable, + List args, { + String? workingDirectory, + Map? environment, +}) async { final proc = await Process.start( executable, args, mode: ProcessStartMode.inheritStdio, workingDirectory: workingDirectory, + environment: environment, ); final exitCode = await proc.exitCode; From f5d4f1c6f4bb062fe04f8d72b0a517046bdf465a Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 20:37:44 +0200 Subject: [PATCH 32/37] download openssl --- .github/workflows/compile_sqlite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index 21665c3d..2e444e0c 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -125,7 +125,7 @@ jobs: path: sqlite-src - name: Download compiled OpenSSL - if: steps.cache_build.outputs.cache-hit != 'true' + if: runner.os == 'Linux' && steps.cache_build.outputs.cache-hit != 'true' uses: actions/download-artifact@v8 with: name: openssl-libs-${{ runner.os }} From 662a4078a683b4bd5c0738bd1d174d503a86bc79 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 20:44:17 +0200 Subject: [PATCH 33/37] cleanup --- .github/workflows/compile_sqlite.yml | 2 +- sqlite3/hook/build.dart | 15 +- sqlite3/lib/src/hook/android_ndk.dart | 77 ----- sqlite3/lib/src/hook/openssl.dart | 411 -------------------------- 4 files changed, 8 insertions(+), 497 deletions(-) delete mode 100644 sqlite3/lib/src/hook/android_ndk.dart delete mode 100644 sqlite3/lib/src/hook/openssl.dart diff --git a/.github/workflows/compile_sqlite.yml b/.github/workflows/compile_sqlite.yml index 2e444e0c..22b00bd0 100644 --- a/.github/workflows/compile_sqlite.yml +++ b/.github/workflows/compile_sqlite.yml @@ -66,7 +66,7 @@ jobs: if: steps.cache_build.outputs.cache-hit != 'true' - name: Download OpenSSL - if: runner.os == 'Linux' && steps.cache_build.outputs.cache-hit != 'true' + if: steps.cache_build.outputs.cache-hit != 'true' run: | curl -L https://github.com/openssl/openssl/releases/download/openssl-3.6.2/openssl-3.6.2.tar.gz | tar --extract --gzip mv openssl-3.6.2 openssl-src diff --git a/sqlite3/hook/build.dart b/sqlite3/hook/build.dart index 65030b83..d0af4fbb 100644 --- a/sqlite3/hook/build.dart +++ b/sqlite3/hook/build.dart @@ -127,14 +127,13 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')} defines: defines, flags: [ if (input.config.code.targetOS == OS.linux) ...[ - if (linkerScript != null) ...[ - // This avoids loading issues on Linux, see comment above. - '-Wl,-Bsymbolic', - // And since we already have a designated list of symbols to - // export, we might as well strip the rest. - // TODO: Port this to other targets too. - '-Wl,--version-script=$linkerScript', - ], + // This avoids loading issues on Linux, see comment above. + '-Wl,-Bsymbolic', + // And since we already have a designated list of symbols to + // export, we might as well strip the rest. + // TODO: Port this to other targets too. + '-Wl,--version-script=$linkerScript', + // Strip symbols '-s', '-ffunction-sections', '-fdata-sections', diff --git a/sqlite3/lib/src/hook/android_ndk.dart b/sqlite3/lib/src/hook/android_ndk.dart deleted file mode 100644 index 7681cfc0..00000000 --- a/sqlite3/lib/src/hook/android_ndk.dart +++ /dev/null @@ -1,77 +0,0 @@ -// ignore_for_file: depend_on_referenced_packages, implementation_imports - -import 'dart:io'; - -import 'package:logging/logging.dart'; -import 'package:native_toolchain_c/src/native_toolchain/android_ndk.dart' - as ntc_ndk; -import 'package:native_toolchain_c/src/tool/tool_instance.dart'; -import 'package:native_toolchain_c/src/tool/tool_resolver.dart'; - -/// Resolve the Android NDK root, picking the latest installed version. -/// Uses the native_toolchain_cmake resolver first, then falls back to a local scan. -Future resolveAndroidNdkRoot() async { - try { - final resolver = ntc_ndk.androidNdk.defaultResolver; - if (resolver != null) { - final instances = await resolver.resolve( - ToolResolvingContext(logger: Logger.detached('android_ndk')), - ); - final ndkInstance = _pickNdkInstance(instances); - final ndkPath = Directory.fromUri(ndkInstance.uri).path; - if (await Directory(ndkPath).exists()) { - return ndkPath; - } - } - } catch (_) { - // fall through to legacy resolution - } - throw Exception( - 'Android NDK not found. Set ANDROID_NDK_ROOT or install it via Android Studio (SDK Manager > NDK).', - ); -} - -ToolInstance _pickNdkInstance(List instances) { - final ndkInstances = instances - .where((i) => i.tool.name == ntc_ndk.androidNdk.name) - .toList(); - if (ndkInstances.isEmpty) { - throw StateError('No Android NDK instance resolved'); - } - - ndkInstances.sort( - (a, b) => switch ((a.version, b.version)) { - (null, null) => 0, - (null, _) => 1, - (_, null) => -1, - (_, _) => -a.version!.compareTo(b.version!), - }, - ); - - return ndkInstances.first; -} - -String resolveAndroidToolchainBinDir(String ndkRoot) { - // Android NDK toolchain binaries live under toolchains/llvm/prebuilt//bin. - // Pick the first host tag that exists for the current platform. - final hostTags = switch (Platform.operatingSystem) { - 'macos' => const ['darwin-x86_64', 'darwin-arm64'], - 'linux' => const ['linux-x86_64'], - 'windows' => const ['windows-x86_64', 'windows-arm64'], - final os => throw UnsupportedError( - 'Unsupported host OS for Android NDK: $os', - ), - }; - - for (final host in hostTags) { - final binDir = Directory('$ndkRoot/toolchains/llvm/prebuilt/$host/bin'); - if (binDir.existsSync()) { - return binDir.path; - } - } - - throw StateError( - 'Android NDK toolchain bin directory not found under ' - '$ndkRoot/toolchains/llvm/prebuilt for host ${Platform.operatingSystem}.', - ); -} diff --git a/sqlite3/lib/src/hook/openssl.dart b/sqlite3/lib/src/hook/openssl.dart deleted file mode 100644 index 1e8b0e95..00000000 --- a/sqlite3/lib/src/hook/openssl.dart +++ /dev/null @@ -1,411 +0,0 @@ -import 'dart:io'; - -import 'package:code_assets/code_assets.dart'; -import 'package:hooks/hooks.dart'; -import 'package:path/path.dart'; -import 'package:sqlite3/src/hook/android_ndk.dart'; - -// Based from https://github.com/LucazzP/openssl_dart - -// Build instructions: https://github.com/openssl/openssl/blob/openssl-3.6.2/INSTALL.md#building-openssl - -const configArgs = [ - 'no-shared', - 'no-apps', - 'no-docs', - 'no-tests', - 'no-engine', - 'no-module', - 'no-ssl', - 'no-tls', - 'no-dtls', - 'no-comp', - 'no-legacy', - 'no-fips', - 'no-async', - 'no-aria', - 'no-bf', - 'no-blake2', - 'no-camellia', - 'no-cast', - 'no-chacha', - 'no-cmac', - 'no-des', - 'no-dh', - 'no-dsa', - 'no-ec', - 'no-ecdh', - 'no-ecdsa', - 'no-md4', - 'no-mdc2', - 'no-ocsp', - 'no-poly1305', - 'no-rc2', - 'no-rc4', - 'no-rc5', - 'no-rmd160', - 'no-seed', - 'no-siphash', - 'no-sm2', - 'no-sm3', - 'no-sm4', - 'no-srp', - 'no-ts', - 'no-whirlpool', -]; - -const perlDownloadUrl = - 'https://strawberryperl.com/download/5.14.2.1/strawberry-perl-5.14.2.1-64bit-portable.zip'; -const jomDownloadUrl = - 'https://download.qt.io/official_releases/jom/jom_1_1_5.zip'; - -Map environment = {}; - -Future buildOpenSSL( - BuildInput input, - BuildOutputBuilder output, { - required Directory openSslSrcDir, -}) async { - if (!input.config.buildCodeAssets) return null; - - final workDir = input.outputDirectory; - - // We configure the project from a separate folder per ABI, to support parallel builds - final openSslBuildDir = Directory( - workDir.resolve('build-openssl').toFilePath(windows: Platform.isWindows), - )..createSync(recursive: true); - - final openSslBuildDirUri = openSslBuildDir.uri; - - // Directory where we install the OpenSSL binaries - final outputDir = join( - input.outputDirectoryShared.toFilePath(windows: Platform.isWindows), - 'openssl', - ); - - // Absolute path of the Configure program in the src folder - final String configureProgramPath = join(openSslSrcDir.path, 'Configure'); - - final configName = resolveConfigName( - input.config.code.targetOS, - input.config.code.targetArchitecture, - input.config.code.targetOS == OS.iOS - ? input.config.code.iOS.targetSdk - : null, - ); - if (input.config.code.targetOS == OS.android) { - final ndkRoot = await resolveAndroidNdkRoot(); - environment['ANDROID_NDK_ROOT'] = ndkRoot; - environment['ANDROID_NDK_HOME'] = ndkRoot; - - final toolchainBin = resolveAndroidToolchainBinDir(ndkRoot); - final existingPath = Platform.environment['PATH'] ?? ''; - final pathSeparator = Platform.isWindows ? ';' : ':'; - environment['PATH'] = '$toolchainBin$pathSeparator$existingPath'; - - // print(environment); - } - - final extraConfigureArgs = [ - '--prefix=$outputDir', - '--openssldir=$outputDir', - if (input.config.code.targetOS == OS.linux) ...[ - '-fPIC', - '-ffunction-sections', - '-fdata-sections', - '-fvisibility=hidden', - ], - if (input.config.code.targetOS == OS.macOS || - input.config.code.targetOS == OS.iOS) ...[ - '-Wl,-headerpad_max_install_names', - ], - ]; - - switch (OS.current) { - case OS.windows: - final msvcEnv = await resolveWindowsBuildEnvironment( - input.config.code.targetArchitecture, - ); - // should have perl and jom installed - final needDownloadPerl = !await isProgramInstalled('perl'); - final needDownloadJom = !await isProgramInstalled('jom'); - var perlProgram = 'perl'; - var jomProgram = 'jom'; - - if (needDownloadPerl) { - await downloadAndExtract(perlDownloadUrl, 'perl.zip', workDir); - perlProgram = workDir - .resolve('./perl/perl/bin/perl.exe') - .toFilePath(windows: Platform.isWindows); - } - if (needDownloadJom) { - await downloadAndExtract(jomDownloadUrl, 'jom.zip', workDir); - jomProgram = workDir - .resolve('./jom/jom.exe') - .toFilePath(windows: Platform.isWindows); - } - - // run ./Configure with the target OS and architecture - await runProcess( - perlProgram, - [ - configureProgramPath, - configName, - ...configArgs, - ...extraConfigureArgs, - // needed to build using multiple threads on Windows - '/FS', - ], - workingDirectory: openSslBuildDirUri, - extraEnvironment: msvcEnv, - ); - - // run jom to build the library - await runProcess( - jomProgram, - ['-j', '${Platform.numberOfProcessors}'], - workingDirectory: openSslBuildDirUri, - extraEnvironment: msvcEnv, - ); - - // delete perl and jom if downloaded - if (needDownloadPerl) { - await Directory( - workDir.resolve('perl').toFilePath(windows: Platform.isWindows), - ).delete(recursive: true); - } - if (needDownloadJom) { - await Directory( - workDir.resolve('jom').toFilePath(windows: Platform.isWindows), - ).delete(recursive: true); - } - break; - case OS.macOS: - case OS.linux: - final hasPerl = await isProgramInstalled('perl'); - if (!hasPerl) { - throw Exception( - 'perl is not installed, please install it to be able to build openssl.', - ); - } - - // run ./Configure with the target OS and architecture - await runProcess('perl', [ - configureProgramPath, - configName, - ...configArgs, - ...extraConfigureArgs, - ], workingDirectory: openSslBuildDirUri); - - // run make - await runProcess('make', [ - '-j', - '${Platform.numberOfProcessors}', - ], workingDirectory: openSslBuildDirUri); - - await runProcess('make', [ - 'install', - ], workingDirectory: openSslBuildDirUri); - - break; - } - - return Directory(outputDir); -} - -String getStaticCryptoLib( - Directory opensslDir, - OS targetOS, - Architecture targetArch, -) { - final libName = switch ((targetOS, LinkModePreference.static)) { - ( - OS.windows, - LinkModePreference.static || LinkModePreference.preferStatic, - ) => - 'libcrypto_static.lib', - ( - OS.macOS || OS.iOS, - LinkModePreference.static || LinkModePreference.preferStatic, - ) => - 'libcrypto.a', - ( - OS.linux || OS.android, - LinkModePreference.static || LinkModePreference.preferStatic, - ) => - 'libcrypto.a', - ( - OS.windows, - LinkModePreference.dynamic || LinkModePreference.preferDynamic, - ) => - 'libcrypto-3-${targetArch.name}.dll', - ( - OS.macOS || OS.iOS, - LinkModePreference.dynamic || LinkModePreference.preferDynamic, - ) => - 'libcrypto.dylib', - ( - OS.linux || OS.android, - LinkModePreference.dynamic || LinkModePreference.preferDynamic, - ) => - 'libcrypto.so', - _ => throw UnsupportedError('Unsupported target OS: $targetOS'), - }; - - final String openSSLLibDir; - if (Directory(join(opensslDir.path, 'lib')).existsSync()) { - openSSLLibDir = join(opensslDir.path, 'lib'); - } else { - openSSLLibDir = join(opensslDir.path, 'lib64'); - } - - final libPath = join(openSSLLibDir, libName); - if (!File(libPath).existsSync()) { - throw Exception( - 'Could not find OpenSSL static library in ' - '$libPath', - ); - } - - return libPath; -} - -Future isProgramInstalled(String programName) async { - try { - await runProcess(programName, []); - return true; - } catch (e) { - return false; - } -} - -String resolveConfigName(OS os, Architecture architecture, IOSSdk? iosSdk) { - final isIosSimulator = iosSdk == IOSSdk.iPhoneSimulator; - return switch ((os, architecture)) { - (OS.android, Architecture.arm) => 'android-arm', - (OS.android, Architecture.arm64) => 'android-arm64', - (OS.android, Architecture.ia32) => 'android-x86', - (OS.android, Architecture.x64) => 'android-x86_64', - (OS.android, Architecture.riscv64) => 'android-riscv64', - - (OS.iOS, Architecture.arm) => 'ios-xcrun', - (OS.iOS, Architecture.arm64) => - isIosSimulator ? 'iossimulator-arm64-xcrun' : 'ios64-xcrun', - (OS.iOS, Architecture.ia32) => 'iossimulator-i386-xcrun', - (OS.iOS, Architecture.x64) => 'iossimulator-x86_64-xcrun', - - (OS.macOS, Architecture.arm64) => 'darwin64-arm64', - (OS.macOS, Architecture.x64) => 'darwin64-x86_64', - (OS.macOS, Architecture.ia32) => 'darwin-i386', - - (OS.linux, Architecture.arm) => 'linux-armv4', - (OS.linux, Architecture.arm64) => 'linux-aarch64', - (OS.linux, Architecture.ia32) => 'linux-x86', - (OS.linux, Architecture.x64) => 'linux-x86_64', - (OS.linux, Architecture.riscv32) => 'linux32-riscv32', - (OS.linux, Architecture.riscv64) => 'linux64-riscv64', - - (OS.windows, Architecture.arm) => 'VC-WIN32-ARM', - (OS.windows, Architecture.arm64) => 'VC-WIN64-ARM', - (OS.windows, Architecture.ia32) => 'VC-WIN32', - (OS.windows, Architecture.x64) => 'VC-WIN64A', - - _ => throw UnsupportedError( - 'Unsupported target combination: ${os.name}-${architecture.name}', - ), - }; -} - -Future> resolveWindowsBuildEnvironment( - Architecture architecture, -) async { - final result = await runProcess('cmd.exe', [ - '/c', - r'call C:\"Program Files"\"Microsoft Visual Studio"\2022\Community\VC\Auxiliary\Build\vcvars64.bat >nul && set', - ]); - - return Map.fromEntries( - result.trim().split('\n').map((line) { - final parts = line.split('='); - if (parts.length != 2) { - return null; - } - return MapEntry(parts[0].trim(), parts[1].trim()); - }).nonNulls, - ); -} - -Future runProcess( - String executable, - List arguments, { - Uri? workingDirectory, - Map? extraEnvironment, -}) async { - final processResult = await Process.run( - executable, - arguments, - workingDirectory: workingDirectory?.toFilePath(windows: Platform.isWindows), - environment: {...?extraEnvironment, ...environment}, - includeParentEnvironment: true, - ); - print(processResult.stdout); - if ((processResult.stderr as String).isNotEmpty) { - print(processResult.stderr); - } - if (processResult.exitCode != 0) { - throw ProcessException( - executable, - arguments, - processResult.stderr.toString(), - processResult.exitCode, - ); - } - return processResult.stdout.toString(); -} - -Future downloadAndExtract( - String url, - String outputFileName, - Uri workDir, { - bool createFolderForExtraction = true, -}) async { - // download the file - await runProcess('curl', [ - '-L', - url, - '-o', - outputFileName, - ], workingDirectory: workDir); - - final fileOut = await runProcess('file', [ - outputFileName, - ], workingDirectory: workDir); - print(fileOut); - - // unzip the file - final isTarGz = outputFileName.endsWith('.tar.gz'); - final isTarXz = outputFileName.endsWith('.tar.xz'); - final destinationPath = workDir.resolve( - outputFileName - .replaceAll('.tar.gz', '') - .replaceAll('.zip', '') - .replaceAll('.tar.xz', ''), - ); - print("destination $destinationPath"); - await Directory( - destinationPath.toFilePath(windows: Platform.isWindows), - ).create(recursive: true); - await runProcess('tar', [ - isTarGz ? '-xzf' : (isTarXz ? '-xJf' : '-xf'), - outputFileName, - if (createFolderForExtraction) ...[ - '-C', - destinationPath.toFilePath(windows: Platform.isWindows), - ], - ], workingDirectory: workDir); - - // remove the tar.gz file - await File( - workDir.resolve(outputFileName).toFilePath(windows: Platform.isWindows), - ).delete(); -} From ce4184bea2495d13cf5a0ba6f5d0c67279f80f58 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 21:17:35 +0200 Subject: [PATCH 34/37] integration tests --- .github/workflows/main.yml | 26 ++++++++++ .../integration_test/integration_test.dart | 52 +++++++++++++++++++ .../flutter_integration_tests/pubspec.yaml | 1 + tool/build_sqlite.dart | 2 - 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bfa584bf..622963c5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -441,3 +441,29 @@ jobs: emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none script: flutter test integration_test -Dsqlite3.multipleciphers=true working-directory: "examples/flutter_integration_tests" + + - name: Enable sqlcipher + run: | + dart run tool/hook_overrides.dart compiled-sqlcipher + + - name: Flutter sqlcipher tests on macOS + if: runner.os == 'macos' + working-directory: examples/flutter_integration_tests + run: | + flutter config --enable-swift-package-manager + flutter test integration_test -Dsqlite3.sqlcipher=true + flutter test integration_test -Dsqlite3.sqlcipher=true -d macos + + - name: sqlcipher Android emulator tests + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 + if: runner.os == 'linux' + with: + api-level: 34 + force-avd-creation: false + target: google_apis + arch: x86_64 + disable-animations: false + avd-name: $AVD_NAME + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + script: flutter test integration_test -Dsqlite3.sqlcipher=true + working-directory: "examples/flutter_integration_tests" diff --git a/examples/flutter_integration_tests/integration_test/integration_test.dart b/examples/flutter_integration_tests/integration_test/integration_test.dart index 563e4f4a..bdcc8647 100644 --- a/examples/flutter_integration_tests/integration_test/integration_test.dart +++ b/examples/flutter_integration_tests/integration_test/integration_test.dart @@ -1,7 +1,10 @@ // ignore_for_file: avoid_print +import 'dart:io'; + import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart'; import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3_connection_pool/sqlite3_connection_pool.dart'; @@ -118,6 +121,55 @@ void main() { print(db.select('select sqlite3mc_config(?)', ['cipher'])); }); } + + const sqlcipher = bool.fromEnvironment('sqlite3.sqlcipher'); + if (sqlcipher) { + test('cipher_version', () { + final db = sqlite3.openInMemory()..closeWhenDone(); + print(db.select('select sqlite3mc_config(?)', ['cipher'])); + }); + } + + if (ciphers || sqlcipher) { + test('encryption', () { + final dir = Directory.systemTemp.createTempSync(); + final path = join(dir.path, 'test.db'); + final db = sqlite3.open(path); + addTearDown(() { + db.close(); + }); + + final key = 'my_secret'; + db.execute("PRAGMA key = '$key'"); + db.execute("CREATE TABLE users (id INTEGER, username TEXT)"); + db.close(); + + final dbAfterEnc = sqlite3.open(path); + addTearDown(() => dbAfterEnc.close()); + expect( + () => dbAfterEnc.select('SELECT * FROM sqlite_master'), + throwsSqlError(SqlError.SQLITE_NOTADB, SqlError.SQLITE_NOTADB), + ); + dbAfterEnc.execute("PRAGMA key = '$key'"); + + // Reads the db after setting the key + expect(dbAfterEnc.select('SELECT * FROM sqlite_master'), isNotEmpty); + }); + } +} + +void testEncryptAndOpenEncrypted() {} + +Matcher throwsSqlError(int resultCode, int extendedResultCode) { + return throwsA( + isA() + .having( + (e) => e.extendedResultCode, + 'extendedResultCode', + extendedResultCode, + ) + .having((e) => e.resultCode, 'resultCode', resultCode), + ); } extension on Database { diff --git a/examples/flutter_integration_tests/pubspec.yaml b/examples/flutter_integration_tests/pubspec.yaml index 7890bce9..4b6b049f 100644 --- a/examples/flutter_integration_tests/pubspec.yaml +++ b/examples/flutter_integration_tests/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: sdk: flutter sqlite3: sqlite3_connection_pool: + path: ^1.8.3 dev_dependencies: flutter_test: diff --git a/tool/build_sqlite.dart b/tool/build_sqlite.dart index 8ea6e876..40ae1804 100644 --- a/tool/build_sqlite.dart +++ b/tool/build_sqlite.dart @@ -168,8 +168,6 @@ bool _skipBuild(OS targetOS, Architecture targetArch, SqliteFork type) { case SqliteFork.sqlcipher: // TODO: Build for Windows if (targetOS == OS.windows) return true; - // TODO: Build for Android - if (targetOS == OS.android) return true; // TODO: Other Linux architectures if (targetOS == OS.linux) { return targetArch != Architecture.x64; From 49bf0c5e1bf42b413d70797b89db6fc1b2cc5c5a Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 29 May 2026 22:40:02 +0200 Subject: [PATCH 35/37] fix test --- .../integration_test/integration_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/flutter_integration_tests/integration_test/integration_test.dart b/examples/flutter_integration_tests/integration_test/integration_test.dart index bdcc8647..6442ff0a 100644 --- a/examples/flutter_integration_tests/integration_test/integration_test.dart +++ b/examples/flutter_integration_tests/integration_test/integration_test.dart @@ -126,7 +126,9 @@ void main() { if (sqlcipher) { test('cipher_version', () { final db = sqlite3.openInMemory()..closeWhenDone(); - print(db.select('select sqlite3mc_config(?)', ['cipher'])); + final cipherVersionRows = db.select('PRAGMA cipher_version'); + print(cipherVersionRows); + expect(cipherVersionRows, isNotEmpty); }); } From 4a247e43c2dca7387cb414c03b76a56ca9393aef Mon Sep 17 00:00:00 2001 From: David Martos Date: Sun, 31 May 2026 10:51:27 +0200 Subject: [PATCH 36/37] Trigger Actions detection From 960df550661b999463d8d6cddbc9fb03f04fc5f3 Mon Sep 17 00:00:00 2001 From: David Martos Date: Sun, 31 May 2026 10:59:49 +0200 Subject: [PATCH 37/37] cleanup --- .../integration_test/integration_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/flutter_integration_tests/integration_test/integration_test.dart b/examples/flutter_integration_tests/integration_test/integration_test.dart index 6442ff0a..bf141bfc 100644 --- a/examples/flutter_integration_tests/integration_test/integration_test.dart +++ b/examples/flutter_integration_tests/integration_test/integration_test.dart @@ -160,8 +160,6 @@ void main() { } } -void testEncryptAndOpenEncrypted() {} - Matcher throwsSqlError(int resultCode, int extendedResultCode) { return throwsA( isA()