run method

Future<void> run({
  1. required BuildInput input,
  2. required BuildOutputBuilder output,
})

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);
  }
}