buildSlintAot function
App build hook: AOT-compiles every ui/**.slint of the package into one
staticlib — slint-build codegen plus generated C ABI glue, no
slint-interpreter — and routes it to the app's link hook (ToLinkHook),
which links the final dylib keeping only the components the app uses (see
aot_link.dart). One code asset per .slint file, with id
package:<app>/<path>.aot.g.dart matching the wrappers the
slint_compiler builder generates.
Wire it up as the app's hook/build.dart (with hook/link.dart calling
linkSlintAot):
import 'package:hooks/hooks.dart';
import 'package:slint_compiler/aot_build.dart';
void main(List<String> args) => build(args, buildSlintAot);
Implementation
Future<void> buildSlintAot(BuildInput input, BuildOutputBuilder output) async {
if (!input.config.buildCodeAssets) return;
// The AOT dylib ships only in release/profile builds; debug builds (incl.
// `flutter test`) use the slint_interpreter package. In Flutter,
// linkingEnabled == true exactly for the non-debug (AOT) modes.
if (!input.config.linkingEnabled) return;
// `.slint` files live under `ui/`, not `lib/`: they are not Dart, and the
// release bundle must not ship them — see `slint_generator`'s builder.
final uiDir = Directory.fromUri(input.packageRoot.resolve('ui/'));
final slintFiles = !uiDir.existsSync()
? <File>[]
: (uiDir
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.where((f) => f.path.endsWith('.slint'))
.toList()
..sort((a, b) => a.path.compareTo(b.path)));
if (slintFiles.isEmpty) return;
final packageConfig = findPackageConfig(input);
final compilerRoot = packageRootFromConfig(packageConfig, 'slint_compiler');
final generatorRoot = packageRootFromConfig(packageConfig, 'slint_generator');
final slintCoreCrate =
packageRootFromConfig(packageConfig, 'slint').resolve('rust/');
final files = <SlintAotFile>[];
final assetNames = <String>[];
final uiPath = uiDir.uri.toFilePath();
for (final f in slintFiles) {
final relative = f.path.substring(uiPath.length);
final stemBase = relative.substring(0, relative.length - '.slint'.length);
final stem = stemBase.replaceAll(Platform.pathSeparator, '_');
// `a/b.slint` and `a_b.slint` map to the same module stem and would
// silently overwrite each other's generated sources in the AOT crate.
if (files.any((e) => e.stem == stem)) {
throw StateError(
'duplicate AOT module stem "$stem" from "$relative" — '
'rename one of the .slint files',
);
}
final schema = await introspectSlint(
f.path,
introspectManifest: generatorRoot.resolve('rust/Cargo.toml'),
);
files.add(SlintAotFile(
stem: stem,
source: f.readAsStringSync(),
schema: schema,
));
assetNames.add('$stemBase.aot.g.dart'.replaceAll(Platform.pathSeparator, '/'));
}
final crateDir =
Directory.fromUri(input.outputDirectoryShared.resolve('slint_aot/'));
emitAotCrate(
crateDir,
files: files,
slintCoreCratePath: slintCoreCrate.toFilePath(),
// Present inside this repo (the Cargo workspace root); absent for a pub
// consumer, which then resolves afresh as before.
lockfile: File.fromUri(compilerRoot.resolve('../../Cargo.lock')),
);
final build = await runCargoBuild(
input,
output,
crateName: 'slint-dart-aot',
manifestPath: crateDir.uri.resolve('Cargo.toml'),
artifactKind: 'staticlib',
// Makes rustc print the `native-static-libs:` note — the exact linker
// line the staticlib needs — which the link hook replays when it links
// the final dylib.
extraEnv: const {'RUSTFLAGS': '--print=native-static-libs'},
sourceDirs: [
input.packageRoot.resolve('ui/'),
// Regenerate when the glue generator or the introspect tool change.
compilerRoot.resolve('lib/'),
generatorRoot.resolve('rust/'),
],
);
final linkFlags = _persistedNativeLinkFlags(
crateDir: crateDir,
triple: rustTriple(input.config.code),
cargoOutput: build.cargoOutput,
);
// Everything the link hook needs, written next to the routed staticlib.
final manifest = {
'assetIds': assetNames,
'sharedSymbols': aotSharedSymbols,
'linkFlags': linkFlags,
'components': [
for (var i = 0; i < files.length; i++)
for (final c in files[i].schema.components)
{
'name': c.name,
'library': 'package:${input.packageName}/${assetNames[i]}',
'newExtern': aotNewExternName(c.name),
'symbols': aotComponentSymbols(c.name),
},
],
};
File.fromUri(input.outputDirectory.resolve(aotLinkManifestName))
.writeAsStringSync(jsonEncode(manifest));
output.assets.code.add(
CodeAsset(
package: input.packageName,
name: assetNames.first,
linkMode: StaticLinking(),
file: build.artifact,
),
routing: ToLinkHook(input.packageName),
);
}