Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c8cd55f
sqlcipher target
davidmartos96 May 19, 2026
b38b0d2
hashes
davidmartos96 May 21, 2026
f0d680b
hashes update
davidmartos96 May 21, 2026
4005eb4
Trigger Actions detection
davidmartos96 May 21, 2026
8d2983c
cleanup
davidmartos96 May 21, 2026
6fdb82a
Merge branch 'main' into sqlcipher_pr
davidmartos96 May 21, 2026
b36ccb6
recover jobs
davidmartos96 May 21, 2026
166208d
cleanup
davidmartos96 May 21, 2026
fc2df9c
move flags
davidmartos96 May 21, 2026
0fb180f
cleanup
davidmartos96 May 21, 2026
979bf55
add archs
davidmartos96 May 21, 2026
257dbc3
library type
davidmartos96 May 21, 2026
852d774
fix name
davidmartos96 May 21, 2026
e8cbac7
copy clean openssl src dir for each arch
davidmartos96 May 22, 2026
b4f6acb
openssl folder per abi
davidmartos96 May 22, 2026
42c78e7
cleanup
davidmartos96 May 22, 2026
7b1b6e7
improvements
davidmartos96 May 22, 2026
9e2f295
perl Configure instead of ./Configure
davidmartos96 May 22, 2026
7f2a994
exclude some linux archs
davidmartos96 May 22, 2026
628a08a
encryption test
davidmartos96 May 22, 2026
0eed2b2
cleanup
davidmartos96 May 22, 2026
81f85ca
exclude sqlcipher tests on windows
davidmartos96 May 22, 2026
6223d81
fix log link
davidmartos96 May 22, 2026
9c4def4
extra flags
davidmartos96 May 22, 2026
5596169
format
davidmartos96 May 22, 2026
b31853d
Merge remote-tracking branch 'origin/sqlcipher' into sqlcipher_pr
davidmartos96 May 29, 2026
88d9530
cleanup after merge
davidmartos96 May 29, 2026
0c86241
link statically
davidmartos96 May 29, 2026
58a7d44
analyze
davidmartos96 May 29, 2026
2788cb6
fix
davidmartos96 May 29, 2026
632b96d
build openssl refactor
davidmartos96 May 29, 2026
80d2d0b
compile android
davidmartos96 May 29, 2026
2d5bb2f
ndk root
davidmartos96 May 29, 2026
f5d4f1c
download openssl
davidmartos96 May 29, 2026
662a407
cleanup
davidmartos96 May 29, 2026
ce4184b
integration tests
davidmartos96 May 29, 2026
49bf0c5
fix test
davidmartos96 May 29, 2026
4a247e4
Trigger Actions detection
davidmartos96 May 31, 2026
960df55
cleanup
davidmartos96 May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/compile_sqlite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ jobs:
build_sqlite:
needs: [download_sqlite, build_openssl]
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

Expand All @@ -123,6 +124,13 @@ jobs:
name: sqlite-src
path: sqlite-src

- name: Download compiled OpenSSL
if: runner.os == 'Linux' && 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'

Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ 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
if: ${{ runner.os != 'windows' }} # TODO: SQLCipher Windows not supported yet
working-directory: sqlite3/

- name: Prepare for tests with system SQLite
run: |
dart run tool/hook_overrides.dart system-os-specific
Expand Down Expand Up @@ -432,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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Booting an emulator is quite slow. I wonder if the Android emulator tests should be a separate job now, where we might spawn a single Android emulator only and use a script: that runs multiple flutter test calls with different build options in sequence?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Should we create a bash script for the ci tests? Or would you like placing the "&&"s in the workflow yaml directly?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Directly in the yaml should work

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"
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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();
final cipherVersionRows = db.select('PRAGMA cipher_version');
print(cipherVersionRows);
expect(cipherVersionRows, isNotEmpty);
});
}

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);
});
}
}

Matcher throwsSqlError(int resultCode, int extendedResultCode) {
return throwsA(
isA<SqliteException>()
.having(
(e) => e.extendedResultCode,
'extendedResultCode',
extendedResultCode,
)
.having((e) => e.resultCode, 'resultCode', resultCode),
);
}

extension on Database {
Expand Down
1 change: 1 addition & 0 deletions examples/flutter_integration_tests/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies:
sdk: flutter
sqlite3:
sqlite3_connection_pool:
path: ^1.8.3

dev_dependencies:
flutter_test:
Expand Down
78 changes: 73 additions & 5 deletions sqlite3/hook/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,70 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')}
}

final isSqlcipher = input.userDefines['is_sqlcipher'] as bool? ?? false;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'm to blame for this, but there should be a comment here explaining that these options are internal, undocumented and only meant to be set via tool/build_sqlite.dart


final targetOS = input.config.code.targetOS;
final targetArchitecture = input.config.code.targetArchitecture;
final isAppleTarget = targetOS == OS.iOS || targetOS == OS.macOS;

// 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),
Comment thread
simolus3 marked this conversation as resolved.
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: [p.dirname(sourceFile)],
includes: [
p.dirname(sourceFile),
if (openSslCompileDir != null)
p.join(openSslCompileDir.path, 'include'),
],
defines: defines,
flags: [
if (input.config.code.targetOS == OS.linux) ...[
Expand All @@ -78,12 +133,11 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')}
// 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',
'-Wl,--gc-sections',
if (isSqlcipher)
// TODO: Link OpenSSL statically?
'-lcrypto',
],
if (isAppleTarget) ...[
'-headerpad_max_install_names',
Expand All @@ -101,10 +155,17 @@ ${usedSqliteSymbols.map((symbol) => ' $symbol;').join('\n')}
],
],
],
libraryDirectories: [
if (openSslStaticLib != null) openSslStaticLib.parent.path,
],
libraries: [
if (input.config.code.targetOS == OS.android)
if (targetOS == OS.android) ...[
// We need to link the math library on Android.
'm',
if (isSqlcipher) 'log',
],
// Link with OpenSSL (SQLCipher builds)
if (openSslCompileDir != null) 'crypto',
],
);

Expand All @@ -121,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';
53 changes: 48 additions & 5 deletions sqlite3/lib/src/hook/description.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ sealed class SqliteBinary {
return PrecompiledFromGithubAssets(LibraryType.sqlite3);
case 'sqlite3mc':
return PrecompiledFromGithubAssets(LibraryType.sqlite3mc);
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}';

Expand All @@ -38,11 +42,13 @@ sealed class SqliteBinary {
case 'executable':
return SimpleBinary.fromExecutable;
case 'source':
final isSqlcipher = userDefines['is_sqlcipher'] as bool? ?? false;
return CompileSqlite(
sourceFile: userDefines.path('path')!.toFilePath(),
defines: CompilerDefines.parse(
userDefines,
input.config.code.targetOS,
targetOS: input.config.code.targetOS,
isSqlcipher: isSqlcipher,
),
);
default:
Expand Down Expand Up @@ -339,7 +345,11 @@ extension type const CompilerDefines(Map<String, String?> flags)
return CompilerDefines({...flags, ...other.flags});
}

static CompilerDefines parse(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.
Expand All @@ -357,7 +367,7 @@ extension type const CompilerDefines(Map<String, String?> flags)
};

final start = includeDefaults
? CompilerDefines.defaults(targetOS == OS.windows)
? CompilerDefines.defaults(targetOS: targetOS, isSqlcipher: isSqlcipher)
: const CompilerDefines({});

return switch (additionalDefines) {
Expand Down Expand Up @@ -394,11 +404,44 @@ extension type const CompilerDefines(Map<String, String?> flags)
return CompilerDefines(entries);
}

static CompilerDefines defaults(bool windows) {
static CompilerDefines defaults({
required OS targetOS,
required bool isSqlcipher,
Comment thread
simolus3 marked this conversation as resolved.
}) {
final defines = _parseLines(const LineSplitter().convert(_defaultDefines));
if (windows) {
if (targetOS == OS.windows) {
defines['SQLITE_API'] = '__declspec(dllexport)';
}

// Minimum extra flags to build 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',

// 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,

// URI support
'SQLITE_USE_URI ': 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)
'SQLCIPHER_CRYPTO_CC': null,
});
}

return defines;
}
}
Expand Down
Loading