Skip to content

Commit f629ae5

Browse files
committed
Add compile-time options
1 parent c6c3972 commit f629ae5

File tree

8 files changed

+590
-96
lines changed

8 files changed

+590
-96
lines changed

sqlite3_native_assets/README.md

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ __Like native assets, this package is experimental.__
33
This package provides SQLite as a [native code asset](https://dart.dev/interop/c-interop#native-assets).
44

55
It has the same functionality as the `sqlite3_flutter_libs` package,
6-
except that it works without Flutter.
6+
except that it also works without Flutter.
77

88
## Getting started
99

@@ -28,3 +28,131 @@ void main() {
2828
print('Using sqlite3 ${sqlite3.version}');
2929
}
3030
```
31+
32+
## Options
33+
34+
This package supports [user-defines](https://github.com/dart-lang/native/pull/2165)
35+
to customize how SQLite is built.
36+
You can configure this with an entry in your pubspec:
37+
38+
```yaml
39+
hooks:
40+
user_defines:
41+
sqlite3_native_assets:
42+
# Your options here
43+
```
44+
45+
> [!NOTE]
46+
> As of 2025-04-10, support for user-defines in native assets is a _very_ recent feature
47+
> that's still being rolled out to Flutter and Dart SDKs.
48+
49+
### Configuring SQLite sources
50+
51+
By default, `sqlite3_native_assets` will download and compile SQLite. It's possible to customize:
52+
53+
__The download URL__
54+
55+
```yaml
56+
hooks:
57+
user_defines:
58+
sqlite3_native_assets:
59+
source:
60+
amalgamation: https://sqlite.org/2025/sqlite-amalgamation-3490100.zip
61+
```
62+
63+
__Using a local `sqlite3.c`__
64+
65+
If you already have a `sqlite3.c` file in your project that you want to use instead of downloading
66+
SQLite, you can use the `local` option:
67+
68+
```yaml
69+
hooks:
70+
user_defines:
71+
sqlite3_native_assets:
72+
source:
73+
local: third_party/sqlite3/sqlite3.c
74+
```
75+
76+
__Load dynamically__
77+
78+
You can also instruct this package to attempt to load SQLite from the operating system (if it's in `$PATH`)
79+
or by looking up symbols in the executable / the running process:
80+
81+
```yaml
82+
hooks:
83+
user_defines:
84+
sqlite3_native_assets:
85+
source:
86+
system: # can also use process: or executable: here
87+
```
88+
89+
__Skip build__
90+
91+
You can also instruct `sqlite3_native_assets` to not build, link or configure SQLite in any way.
92+
This is useful as an escape hatch if you have a custom SQLite build that you would like to apply
93+
instead:
94+
95+
```yaml
96+
hooks:
97+
user_defines:
98+
sqlite3_native_assets:
99+
source: false
100+
```
101+
102+
In this case, you can write your own build hooks. When compiling SQLite to a library, add a derived
103+
`CodeAssets` to your outputs like this:
104+
105+
```dart
106+
CodeAsset(
107+
package: 'sqlite3_native_assets',
108+
name: 'sqlite3_native_assets.dart',
109+
linkMode: linkMode,
110+
os: input.config.code.targetOS,
111+
architecture: input.config.code.targetArchitecture,
112+
)
113+
```
114+
115+
If the `package` and `name` match, the custom library will get used.
116+
117+
### Configuring compile-time options
118+
119+
You can override the [compile-time options](https://sqlite.org/compile.html) this
120+
package uses to compile SQLite.
121+
By default, a reasonable set of compile-time options including the recommended options and
122+
some others to enable useful features and performance enhancements are enabled.
123+
124+
You can add additional entries to this list, either as a map:
125+
126+
```yaml
127+
hooks:
128+
user_defines:
129+
sqlite3_native_assets:
130+
defines:
131+
defines:
132+
SQLITE_LIKE_DOESNT_MATCH_BLOBS:
133+
SQLITE_MAX_SCHEMA_RETRY: 2
134+
```
135+
136+
or as a list:
137+
138+
```yaml
139+
hooks:
140+
user_defines:
141+
sqlite3_native_assets:
142+
defines:
143+
defines:
144+
- SQLITE_LIKE_DOESNT_MATCH_BLOBS
145+
- SQLITE_MAX_SCHEMA_RETRY=2
146+
```
147+
148+
You can also disable the default compile-time options:
149+
150+
```yaml
151+
hooks:
152+
user_defines:
153+
sqlite3_native_assets:
154+
defines:
155+
default_options: false
156+
defines:
157+
# Your preferred options here
158+
```

sqlite3_native_assets/hook/build.dart

Lines changed: 3 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,9 @@
1-
import 'dart:convert';
2-
import 'dart:io';
3-
4-
import 'package:archive/archive.dart';
5-
import 'package:http/http.dart';
6-
import 'package:logging/logging.dart';
7-
import 'package:path/path.dart';
81
import 'package:native_assets_cli/native_assets_cli.dart';
9-
import 'package:native_toolchain_c/native_toolchain_c.dart';
2+
import 'package:sqlite3_native_assets/src/build.dart';
103

114
void main(List<String> args) async {
125
await build(args, (input, output) async {
13-
final source = input.outputDirectory.resolve('sqlite3.c');
14-
final response = await get(
15-
Uri.parse('https://sqlite.org/2025/sqlite-amalgamation-3490100.zip'),
16-
);
17-
if (response.statusCode != 200) {
18-
throw 'Could not download sqlite3: ${response.statusCode} ${response.reasonPhrase} ${response.body}';
19-
}
20-
final archive = ZipDecoder().decodeBytes(response.bodyBytes);
21-
for (final file in archive) {
22-
if (posix.basename(file.name) == 'sqlite3.c') {
23-
await File(source.toFilePath()).writeAsBytes(file.content);
24-
}
25-
}
26-
27-
final builder = CBuilder.library(
28-
name: 'sqlite3',
29-
assetName: 'sqlite3_native_assets.dart',
30-
sources: [source.toFilePath()],
31-
defines: collectDefines(),
32-
);
33-
34-
await builder.run(
35-
input: input,
36-
// This adds a dependency on sqlite3.c, which confuses dependency tracking
37-
// because that file was also changed by this build script.
38-
output: _IgnoreSourceDependency(output),
39-
logger:
40-
Logger('')
41-
..level = Level.ALL
42-
..onRecord.listen((record) => print(record.message)),
43-
);
6+
final build = SqliteBuild(input, output);
7+
await build.runBuild();
448
});
459
}
46-
47-
final class _IgnoreSourceDependency extends BuildOutputBuilder {
48-
final BuildOutputBuilder _inner;
49-
50-
_IgnoreSourceDependency(this._inner);
51-
52-
@override
53-
EncodedAssetBuildOutputBuilder get assets => _inner.assets;
54-
55-
@override
56-
void addDependencies(Iterable<Uri> uris) {
57-
super.addDependencies(uris.where((e) => url.extension(e.path) != '.c'));
58-
}
59-
}
60-
61-
// Note: Keep in sync with https://github.com/simolus3/sqlite-native-libraries/blob/master/sqlite3-native-library/cpp/CMakeLists.txt
62-
const _defines = '''
63-
SQLITE_ENABLE_DBSTAT_VTAB
64-
SQLITE_ENABLE_FTS5
65-
SQLITE_ENABLE_RTREE
66-
SQLITE_ENABLE_MATH_FUNCTIONS
67-
SQLITE_DQS=0
68-
SQLITE_DEFAULT_MEMSTATUS=0
69-
SQLITE_TEMP_STORE=2
70-
SQLITE_MAX_EXPR_DEPTH=0
71-
SQLITE_STRICT_SUBTYPE=1
72-
SQLITE_OMIT_AUTHORIZATION
73-
SQLITE_OMIT_DECLTYPE
74-
SQLITE_OMIT_DEPRECATED
75-
SQLITE_OMIT_PROGRESS_CALLBACK
76-
SQLITE_OMIT_SHARED_CACHE
77-
SQLITE_OMIT_TCL_VARIABLE
78-
SQLITE_OMIT_TRACE
79-
SQLITE_USE_ALLOCA
80-
SQLITE_UNTESTABLE
81-
SQLITE_HAVE_ISNAN
82-
SQLITE_HAVE_LOCALTIME_R
83-
SQLITE_HAVE_LOCALTIME_S
84-
SQLITE_HAVE_MALLOC_USABLE_SIZE
85-
SQLITE_HAVE_STRCHRNUL
86-
''';
87-
88-
Map<String, String?> collectDefines() {
89-
final entries = <String, String?>{};
90-
for (final line in const LineSplitter().convert(_defines)) {
91-
if (line.contains('=')) {
92-
final [key, value] = line.trim().split('=');
93-
entries[key] = value;
94-
} else {
95-
entries[line.trim()] = null;
96-
}
97-
}
98-
return entries;
99-
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import 'dart:io';
2+
3+
import 'package:archive/archive.dart';
4+
import 'package:http/http.dart';
5+
import 'package:logging/logging.dart';
6+
import 'package:native_assets_cli/code_assets_builder.dart';
7+
import 'package:native_assets_cli/native_assets_cli.dart';
8+
import 'package:native_toolchain_c/native_toolchain_c.dart';
9+
import 'package:path/path.dart';
10+
11+
import 'defines.dart';
12+
import 'source.dart';
13+
import 'user_defines.dart';
14+
15+
final class SqliteBuild {
16+
final SqliteSource source;
17+
final CompilerDefines defines;
18+
final BuildInput input;
19+
final BuildOutputBuilder output;
20+
21+
bool trackSourcesAsDependencyForCompilation = true;
22+
23+
SqliteBuild(this.input, this.output)
24+
: source = SqliteSource.parse(UserDefinesOptions.fromHooks(input)),
25+
defines = CompilerDefines.parse(UserDefinesOptions.fromHooks(input));
26+
27+
Future<String?> _prepareBuild() async {
28+
switch (source) {
29+
case DownloadAmalgamation(:final uri):
30+
// Don't track the source because we're the ones creating the local
31+
// copy.
32+
final response = await get(Uri.parse(uri));
33+
if (response.statusCode != 200) {
34+
throw 'Could not download sqlite3: ${response.statusCode} ${response.reasonPhrase} ${response.body}';
35+
}
36+
final archive = ZipDecoder().decodeBytes(response.bodyBytes);
37+
final filepath =
38+
input.outputDirectory.resolve('sqlite3.c').toFilePath();
39+
for (final file in archive) {
40+
if (posix.basename(file.name) == 'sqlite3.c') {
41+
await File(filepath).writeAsBytes(file.content);
42+
}
43+
}
44+
trackSourcesAsDependencyForCompilation = false;
45+
return filepath;
46+
case ExistingAmalgamation(:final sqliteSource):
47+
return sqliteSource;
48+
case UseFromSystem():
49+
case UseFromExecutable():
50+
case UseFromProcess():
51+
case DontLinkSqlite():
52+
return null;
53+
}
54+
}
55+
56+
Future<void> _compile(String source) async {
57+
final builder = CBuilder.library(
58+
name: 'sqlite3',
59+
assetName: 'sqlite3_native_assets.dart',
60+
sources: [source],
61+
defines: defines,
62+
);
63+
64+
await builder.run(
65+
input: input,
66+
output:
67+
trackSourcesAsDependencyForCompilation
68+
? output
69+
: _IgnoreSourceDependency(output),
70+
logger:
71+
Logger('')
72+
..level = Level.ALL
73+
..onRecord.listen((record) => print(record.message)),
74+
);
75+
}
76+
77+
Future<void> runBuild() async {
78+
final resolvedSourceFile = await _prepareBuild();
79+
80+
if (resolvedSourceFile != null) {
81+
await _compile(resolvedSourceFile);
82+
} else if (source case final DontCompileSqlite dontCompile) {
83+
if (dontCompile.resolveLinkMode(input) case final linkMode?) {
84+
output.assets.addEncodedAsset(
85+
CodeAsset(
86+
package: input.packageName,
87+
name: 'sqlite3_native_assets.dart',
88+
linkMode: linkMode,
89+
os: input.config.code.targetOS,
90+
architecture: input.config.code.targetArchitecture,
91+
).encode(),
92+
);
93+
}
94+
}
95+
}
96+
}
97+
98+
final class _IgnoreSourceDependency extends BuildOutputBuilder {
99+
final BuildOutputBuilder _inner;
100+
101+
_IgnoreSourceDependency(this._inner);
102+
103+
@override
104+
EncodedAssetBuildOutputBuilder get assets => _inner.assets;
105+
106+
@override
107+
void addDependencies(Iterable<Uri> uris) {
108+
super.addDependencies(uris.where((e) => url.extension(e.path) != '.c'));
109+
}
110+
}

0 commit comments

Comments
 (0)