build method

Future<NativeBuildResult> build({
  1. required NativeTarget target,
  2. required Directory outputDir,
  3. Directory? workDir,
  4. LinkMode? linkMode,
})

Build for a specific target.

outputDir is where artifacts are staged (e.g., built-library/linux-x64/). workDir is the working directory for build commands.

Implementation

Future<NativeBuildResult> build({
  required NativeTarget target,
  required Directory outputDir,
  Directory? workDir,
  LinkMode? linkMode,
}) async {
  logger?.info('Building ${project.name} for ${target.label}...');

  // 1. Resolve recipe
  final recipe = project.build.recipeFor(target);
  if (recipe == null) {
    logger?.info('No declarative recipe found; invoking hook/build.dart.');
    return _buildViaHookCli(
      target: target,
      outputDir: outputDir,
      linkMode: linkMode ?? project.asset.linkMode,
      packageRoot: source?.directory ?? Directory.current,
    );
  }

  // 2. Resolve source if not provided
  var resolvedSource = source;
  if (resolvedSource == null) {
    // When no explicit source or sourceFallback is given, fall back
    // to project.sources so that CLI callers (and any executor user)
    // can build from the project's declared sources automatically.
    final effectiveFallback =
        sourceFallback ??
        (project.sources.isNotEmpty
            ? SourceFallback(
                sources: project.sources,
                builder: const NoOpSourceBuilder(),
                preparation: [],
              )
            : null);
    if (effectiveFallback == null) {
      throw StateError(
        'No source provided and no source fallback configured for '
        '${project.name} on ${target.label}.',
      );
    }

    logger?.info('Resolving source...');
    final sourceResult = await SourceFallbackResolver().resolve(
      fallback: effectiveFallback,
      packageRoot: Directory.current,
      sourceCacheRoot: Directory(
        p.join(
          Directory.current.path,
          '.dart_tool',
          'native_prebuilt',
          'sources',
        ),
      ),
      input: _dummyInput(),
      output: _dummyOutput(),
      logger: logger,
    );

    if (sourceResult == null) {
      throw StateError(
        'Failed to resolve source for ${project.name} on ${target.label}.',
      );
    }

    resolvedSource = sourceResult.source;
    workDir ??= sourceResult.workDirectory;
  }

  // 3. Create build context
  final context = NativeBuildContext(
    target: target,
    hook: NativeHookConfiguration(
      packageName: project.name,
      assetName: project.asset.assetName,
      libraryStem: project.asset.libraryStem,
      linkMode: linkMode ?? project.asset.linkMode,
    ),
    directories: NativeBuildDirectories(
      source: resolvedSource.directory,
      output: outputDir,
      cache:
          workDir ??
          Directory(
            p.join(
              resolvedSource.directory.path,
              '.dart_tool',
              'native_prebuilt',
            ),
          ),
      work:
          workDir ??
          Directory(
            p.join(
              resolvedSource.directory.path,
              '.dart_tool',
              'native_prebuilt',
            ),
          ),
    ),
    toolchains: const ToolchainRegistry(),
    environment: Platform.environment,
    options: project.build.options,
    variables: project.build.variables,
    logger: logger,
  );

  // 4. Execute the recipe
  logger?.info('Executing recipe: ${recipe.runtimeType}...');

  // Inject cache into StepBuildRecipe if available
  NativeBuildRecipe effectiveRecipe = recipe;
  if (cache != null && recipe is StepBuildRecipe) {
    effectiveRecipe = StepBuildRecipe(
      steps: recipe.steps,
      needsById: recipe.needsById,
      cache: cache,
    );
  }

  final result = await effectiveRecipe.execute(context, resolvedSource);

  // 5. Stage artifact bundle
  await _stageArtifacts(context, result);

  // 6. Write metadata
  await _writeMetadata(context, result, target);

  logger?.info(
    'Build completed: ${result.artifacts.length} artifact(s) staged '
    'to ${outputDir.path}',
  );

  return result;
}