execute method
Execute this build step.
The context provides access to the build configuration and logger.
Returns a NativeStepResult containing any artifacts produced by this step.
Implementation
@override
Future<NativeStepResult> execute(
NativeBuildContext context,
ResolvedSource source,
) async {
final logger = context.logger;
final r = runner ?? ProcessRunner(logger: logger);
final expandedSourceDirectory = expandRecipeValue(
sourceDirectory,
context,
source,
);
final srcDir = p.isAbsolute(expandedSourceDirectory)
? expandedSourceDirectory
: p.join(source.directory.path, expandedSourceDirectory);
final buildDir = buildDirectory != null
? (() {
final expandedBuildDirectory = expandRecipeValue(
buildDirectory!,
context,
source,
);
return p.isAbsolute(expandedBuildDirectory)
? expandedBuildDirectory
: p.join(source.directory.path, expandedBuildDirectory);
})()
: p.join(srcDir, 'build');
final buildDirEntity = Directory(buildDir);
buildDirEntity.createSync(recursive: true);
// Detect stale CMakeCache.txt from a previous source path
final cacheFile = File(p.join(buildDir, 'CMakeCache.txt'));
if (cacheFile.existsSync()) {
final cacheContent = cacheFile.readAsStringSync();
final homeDirMatch = RegExp(
r'CMAKE_HOME_DIRECTORY:INTERNAL=(.+)',
).firstMatch(cacheContent);
if (homeDirMatch != null) {
final cachedSource = homeDirMatch.group(1);
if (cachedSource != srcDir) {
logger?.info(
'[cmake_configure] Stale cache detected '
'(cached source: $cachedSource, current: $srcDir). '
'Cleaning build directory.',
);
buildDirEntity.deleteSync(recursive: true);
buildDirEntity.createSync(recursive: true);
}
}
}
final args = <String>['-S', srcDir, '-B', buildDir];
if (generator != null) {
args.addAll(['-G', expandRecipeValue(generator!, context, source)]);
}
String? effectiveToolchain = toolchainFile;
// Host execution must not use target toolchain
final isHost = execution == 'host';
if (effectiveToolchain == null && !isHost) {
final resolver = const NativeToolchainResolver();
effectiveToolchain = resolver.cmakeToolchainFile(context.target);
if (effectiveToolchain != null) {
logger?.info('[$id] Auto toolchain: $effectiveToolchain');
}
}
if (effectiveToolchain != null) {
final expandedToolchain = expandRecipeValue(
effectiveToolchain,
context,
source,
);
args.addAll(['-DCMAKE_TOOLCHAIN_FILE=$expandedToolchain']);
}
// Auto-inject Android defaults when target is Android and not overridden.
// Skip for host execution — host code generators run on the host compiler.
if (!isHost && context.target.os.name == 'android') {
final arch = context.target.architecture;
final resolver = const NativeToolchainResolver();
final abi = NativeToolchainResolver.androidAbiFor(arch);
if (!defines.containsKey('ANDROID_ABI')) {
args.add('-DANDROID_ABI=$abi');
}
if (!defines.containsKey('ANDROID_PLATFORM')) {
args.add('-DANDROID_PLATFORM=android-24');
}
if (!defines.containsKey('ANDROID_STL')) {
args.add('-DANDROID_STL=c++_static');
}
// OPENSSL_ROOT_DIR auto if not set and resolver knows NDK layout?
if (!defines.containsKey('OPENSSL_ROOT_DIR') && resolver.hasAndroidNdk) {
// Leave to recipe's {{ dependencies.openssl.prefix }} if present;
// no default injection to avoid false paths.
}
}
for (final entry in defines.entries) {
args.add(
'-D${entry.key}=${expandRecipeValue(entry.value, context, source)}',
);
}
logger?.info('[$id] Running: cmake ${args.join(' ')}');
await r.runStreaming('cmake', args, workingDirectory: Directory(buildDir));
// Validate expected targets immediately after configure
if (expectTargets.isNotEmpty) {
// Check that CMakeCache mentions those targets or that the build files were generated
// We do a lightweight check: look for build.ninja or Makefile that would contain the target
final buildNinja = File(p.join(buildDir, 'build.ninja'));
final makefile = File(p.join(buildDir, 'Makefile'));
String? buildFileContent;
if (buildNinja.existsSync()) {
buildFileContent = buildNinja.readAsStringSync();
} else if (makefile.existsSync()) {
buildFileContent = makefile.readAsStringSync();
}
if (buildFileContent != null) {
for (final t in expectTargets) {
if (!buildFileContent.contains(t)) {
throw StateError(
'CMake configuration completed but required target "$t" was not generated.\n'
'Relevant dependency errors may be in the log above.\n'
'Possible missing configuration: OPENSSL_ROOT_DIR or toolchain.',
);
}
}
logger?.info(
'[$id] Verified expected targets: ${expectTargets.join(', ')}',
);
} else {
logger?.warning(
'[$id] Could not verify expected targets ${expectTargets.join(', ')}: no build.ninja/Makefile found at $buildDir',
);
}
}
return const NativeStepResult();
}