run method
Runs the CMake build and emits the produced library as a bundled
CodeAsset.
No-op when HookConfigCodeConfig.buildCodeAssets is false.
Implementation
Future<void> run({
required BuildInput input,
required BuildOutputBuilder output,
}) async {
if (!input.config.buildCodeAssets) {
return;
}
final targetOS = input.config.code.targetOS;
final packageName = input.packageName;
final effectiveAssetPackage = assetPackage ?? packageName;
final libBaseName = libName ?? packageName;
final sourceRoot = sourceDir == null
? input.packageRoot
: input.packageRoot.resolve('$sourceDir/');
final buildDir = input.outputDirectory.resolve('dcb_build/');
Directory.fromUri(buildDir).createSync(recursive: true);
// Infer debug/release: explicit override > linkingEnabled heuristic.
// linkingEnabled is true for AOT (release) builds, false for JIT (debug).
final isDebug = buildOptions.debug ?? !input.config.linkingEnabled;
final buildType = isDebug ? 'Debug' : 'Release';
// Resolve link mode from the hooks system preference.
// Flutter sets this per-platform: iOS → static, others → dynamic.
final linkMode = switch (input.config.code.linkModePreference) {
LinkModePreference.static ||
LinkModePreference.preferStatic => StaticLinking(),
_ => DynamicLoadingBundled(),
};
final isDynamic = linkMode is DynamicLoadingBundled;
// Resolve platform-specific settings.
final String cmake;
final configureArgs = <String>[];
Map<String, String>? processEnvironment;
switch (config) {
case WindowsConfig cfg:
final resolved = _resolveWindows(cfg, buildType);
cmake = resolved.cmakePath;
configureArgs.addAll(resolved.configureArgs);
processEnvironment = resolved.environment;
case LinuxConfig cfg:
cmake = cfg.cmake;
configureArgs.addAll(_resolveLinuxArgs(cfg, buildType));
case AndroidConfig cfg:
final abi = _resolveAndroidAbi(
input.config.code.targetArchitecture,
cfg.abi,
);
cmake = _resolveAndroidCmake(cfg);
configureArgs.addAll(_resolveAndroidArgs(cfg, buildType, abi));
// Ninja generator needs ninja.exe on PATH; inject VS Ninja dir.
if (cfg.generator == CmakeGenerator.ninja && Platform.isWindows) {
processEnvironment = _ensureNinjaOnPath(cmake);
}
case MacosConfig cfg:
cmake = cfg.cmake;
configureArgs.addAll(
_resolveMacosArgs(
cfg,
buildType,
input.config.code.targetArchitecture,
),
);
case IosConfig cfg:
cmake = cfg.cmake;
configureArgs.addAll(
_resolveIosArgs(cfg, buildType, input.config.code),
);
// DEVELOPER_DIR environment variable for xcrun.
if (cfg.developerDir != null) {
processEnvironment = Map<String, String>.from(Platform.environment);
processEnvironment['DEVELOPER_DIR'] = cfg.developerDir!;
}
}
// 1. Configure.
// Invalidate the build directory when configure arguments change
// (e.g. switching between c++_static and c++_shared on Android).
final allConfigureArgs = [
'-DBUILD_SHARED_LIBS=${isDynamic ? 'ON' : 'OFF'}',
if (buildOptions.copyCompileCommands)
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
...configureArgs,
...extraDefines,
];
_invalidateBuildOnConfigChange(buildDir, allConfigureArgs);
await _runProcess(cmake, [
'-S',
sourceRoot.toFilePath(),
'-B',
buildDir.toFilePath(),
...allConfigureArgs,
], environment: processEnvironment);
// 2. Build.
await _runProcess(cmake, [
'--build',
buildDir.toFilePath(),
if (!_isSingleConfigGenerator(config)) ...['--config', buildType],
if (buildOptions.parallel) '--parallel',
], environment: processEnvironment);
// 3. Copy compile_commands.json for LSP / clangd.
if (buildOptions.copyCompileCommands) {
_copyCompileCommandsIfPresent(
buildDir: buildDir,
buildType: buildType,
packageRoot: input.packageRoot,
relativeDest: buildOptions.compileCommandsPath,
);
}
// 4. Locate the produced library.
final libFileName = targetOS.libraryFileName(libBaseName, linkMode);
final libFile = _locateArtifact(buildDir, libFileName, buildType);
// 4. Bundle CRT DLLs if requested (Windows /MD, dynamic only).
if (config case WindowsConfig cfg) {
if (isDynamic && cfg.dynamicCrt && cfg.bundleCrt) {
_bundleWindowsCrt(cfg, libFile);
}
}
// 5. Declare cache dependencies (CMakeLists + native sources).
_declareDependencies(sourceRoot, output);
// 6. Emit the code asset with the resolved link mode.
output.assets.code.add(
CodeAsset(
package: effectiveAssetPackage,
name: assetName,
linkMode: linkMode,
file: libFile.uri,
),
);
// 7. Bundle libc++_shared.so when using dynamic STL on Android.
// Without this, dlopen fails at runtime because the shared C++ runtime
// is not in the APK. AGP's externalNativeBuild handles this
// automatically, but Native Assets hooks bypass AGP's native build
// system, so we must register the dependency explicitly.
if (config case AndroidConfig cfg when !cfg.staticStl) {
final abi = _resolveAndroidAbi(
input.config.code.targetArchitecture,
cfg.abi,
);
_bundleAndroidSharedStl(cfg, effectiveAssetPackage, output, abi);
}
}