linkSlintAot function
App link hook: links the final slint-dart-aot dylib from the staticlib
the build hook (aot_build.dart) routed here, keeping only the components
the app's Dart code actually uses.
Liveness comes from @RecordUse() on each component's generated _new
extern: the AOT compiler records tear-offs that survive Dart tree-shaking,
and this hook drops the native code of every component without one. When
the toolchain provides no recordings (LinkInput.recordedUses is null),
every component is kept — the link then matches what a plain cdylib build
would have produced.
Wire it up as the app's hook/link.dart:
import 'package:hooks/hooks.dart';
import 'package:slint_compiler/aot_link.dart';
void main(List<String> args) => link(args, linkSlintAot);
Implementation
Future<void> linkSlintAot(LinkInput input, LinkOutputBuilder output) async {
final staticLibs = [
for (final a in input.assets.code)
if (a.linkMode is StaticLinking && a.file != null) a,
];
if (staticLibs.isEmpty) return; // nothing routed (e.g. debug build)
if (staticLibs.length > 1) {
throw StateError(
'expected one routed slint staticlib, got '
'${[for (final a in staticLibs) a.id]}',
);
}
final staticLib = staticLibs.single;
final manifest = jsonDecode(
File.fromUri(staticLib.file!.resolve(aotLinkManifestName))
.readAsStringSync(),
) as Map<String, Object?>;
final components =
(manifest['components'] as List).cast<Map<String, Object?>>();
final assetIds = (manifest['assetIds'] as List).cast<String>();
final sharedSymbols = (manifest['sharedSymbols'] as List).cast<String>();
final linkFlags = (manifest['linkFlags'] as List).cast<String>();
final used = usedComponentNames(input.recordedUses, components);
final dropped = [
for (final c in components)
if (!used.contains(c['name'])) c['name'] as String,
];
stderr.writeln(
'slint_aot link: keeping ${used.length}/${components.length} components'
'${dropped.isEmpty ? '' : ', dropping $dropped'}'
'${input.recordedUses == null ? ' (no recorded usages from toolchain)' : ''}',
);
final keepSymbols = [
...sharedSymbols,
for (final c in components)
if (used.contains(c['name']))
...(c['symbols'] as List).cast<String>(),
];
final flags = partitionLinkFlags(linkFlags);
final os = input.config.code.targetOS;
await CLinker.library(
name: aotLibraryName,
sources: [staticLib.file!.toFilePath()],
// RunCBuilder only emits `-framework` flags for Language.objectiveC
// (documented on CTool.frameworks); it changes nothing else about a
// pure link step, and without it the rustc-reported frameworks are
// silently dropped — undefined CoreText/CoreFoundation symbols.
language: Language.objectiveC,
frameworks: flags.frameworks,
libraries: flags.libraries,
flags: flags.other,
linkerOptions: LinkerOptions.treeshake(
symbolsToKeep: keepSymbols,
// ld's -x drops local symbols — the moral equivalent of cargo's
// strip = "symbols" on the old cdylib path (2 MB per arch on the
// example). Not a flag MSVC's LINK.exe knows.
flags: [if (os != OS.windows) '-x'],
),
).run(input: input, output: output);
// CLinker linked one dylib; every .slint file's asset id points at it.
final libUri = input.outputDirectory
.resolve(os.libraryFileName(aotLibraryName, DynamicLoadingBundled()));
for (final id in assetIds) {
output.assets.code.add(
CodeAsset(
package: input.packageName,
name: id,
linkMode: DynamicLoadingBundled(),
file: libUri,
),
);
}
output.dependencies.add(staticLib.file!.resolve(aotLinkManifestName));
}