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;
String? windowsArchitecture;
switch (config) {
case WindowsConfig cfg:
final resolved = _resolveWindows(
cfg,
buildType,
input.config.code.targetArchitecture,
);
cmake = resolved.cmakePath;
configureArgs.addAll(resolved.configureArgs);
processEnvironment = resolved.environment;
windowsArchitecture = resolved.architecture;
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!;
}
}
// Resolve package_config rootUri with Dart's URI implementation. CMake's
// string slicing leaves percent escapes in filesystem paths, which breaks
// projects installed under spaces or non-ASCII directories. Passing the
// decoded absolute path also lets dcb_find_package.cmake use its fast path.
final dcbPackagePath = _resolveDartCppBridgePackagePath(input.packageRoot);
final dcbPackageDefine = dcbPackagePath == null
? const <String>[]
: ['-DDCB_PKG_PATH=$dcbPackagePath'];
// 1. Configure.
// Invalidate the build directory when configure arguments change
// (e.g. switching between c++_static and c++_shared on Android).
final allConfigureArgs = [
if (useDefaultCmakeArgs) ...[
'-DBUILD_SHARED_LIBS=${isDynamic ? 'ON' : 'OFF'}',
if (buildOptions.copyCompileCommands)
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
...configureArgs,
] else ...[
..._platformExtraDefines(config),
],
...dcbPackageDefine,
...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 runtime DLLs if requested (Windows /MD, dynamic only).
// - MSVC / clang-cl: the MSVC CRT DLLs (MSVCP140.dll etc.).
// - MSYS2 toolchains with staticRuntime=false: the MSYS2 runtime DLLs
// (libgcc_s_seh-1.dll etc.). With staticRuntime=true (default) the
// runtime is statically linked, so nothing needs bundling.
final bundledWindowsRuntime = <File>[];
if (config case WindowsConfig cfg) {
final isMsys2 =
cfg.compiler == WindowsCompiler.msys2Clang ||
cfg.compiler == WindowsCompiler.msys2Gcc;
if (isMsys2) {
if (isDynamic && !cfg.staticRuntime && cfg.bundleCrt) {
bundledWindowsRuntime.addAll(_bundleMsys2Runtime(cfg, libFile));
}
} else if (isDynamic && cfg.dynamicCrt && cfg.bundleCrt) {
bundledWindowsRuntime.addAll(
_bundleWindowsCrt(cfg, libFile, windowsArchitecture!),
);
}
}
// 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,
),
);
// A DLL copied next to the main library is not discovered by the Native
// Assets toolchain automatically. Register every bundled runtime DLL as
// its own code asset so Flutter includes it in the final application.
for (final runtimeDll in bundledWindowsRuntime) {
output.assets.code.add(
CodeAsset(
package: effectiveAssetPackage,
name: runtimeDll.uri.pathSegments.last,
linkMode: DynamicLoadingBundled(),
file: runtimeDll.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);
}
}