blob: b714d6e2554f05819c104ea5e49204144804615a [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'build_mode.dart';
import 'ctool.dart';
import 'language.dart';
import 'linkmode.dart';
import 'logger.dart';
import 'optimization_level.dart';
import 'output_type.dart';
import 'run_cbuilder.dart';
/// Specification for building an artifact with a C compiler.
class CBuilder extends CTool implements Builder {
/// The dart files involved in building this artifact.
///
/// Resolved against [BuildInput.packageRoot].
///
/// Used to output the [BuildOutput.dependencies].
@Deprecated(
'Newer Dart and Flutter SDKs automatically add the Dart hook '
'sources as dependencies.',
)
final List<String> dartBuildFiles;
/// Whether to define a macro for the current [BuildMode].
///
/// The macro name is the uppercase name of the build mode and does not have a
/// value.
///
/// Defaults to `true`.
final bool buildModeDefine;
/// Whether to define the standard `NDEBUG` macro when _not_ building with
/// [BuildMode.debug].
///
/// When `NDEBUG` is defined, the C/C++ standard library
/// [`assert` macro in `assert.h`](https://en.wikipedia.org/wiki/Assert.h)
/// becomes a no-op. Other C/C++ code commonly use `NDEBUG` to disable debug
/// features, as well.
///
/// Defaults to `true`.
final bool ndebugDefine;
final BuildMode buildMode;
CBuilder.library({
required super.name,
super.packageName,
super.assetName,
super.sources = const [],
super.includes = const [],
super.forcedIncludes = const [],
super.frameworks = CTool.defaultFrameworks,
super.libraries = const [],
super.libraryDirectories = CTool.defaultLibraryDirectories,
@Deprecated(
'Newer Dart and Flutter SDKs automatically add the Dart hook '
'sources as dependencies.',
)
this.dartBuildFiles = const [],
@visibleForTesting super.installName,
super.flags = const [],
super.defines = const {},
this.buildModeDefine = true,
this.ndebugDefine = true,
super.pic = true,
super.std,
super.language = Language.c,
super.cppLinkStdLib,
super.linkModePreference,
super.optimizationLevel = OptimizationLevel.o3,
this.buildMode = BuildMode.release,
}) : super(type: OutputType.library);
CBuilder.executable({
required super.name,
super.packageName,
super.sources = const [],
super.includes = const [],
super.forcedIncludes = const [],
super.frameworks = CTool.defaultFrameworks,
super.libraries = const [],
super.libraryDirectories = CTool.defaultLibraryDirectories,
@Deprecated(
'Newer Dart and Flutter SDKs automatically add the Dart hook '
'sources as dependencies.',
)
this.dartBuildFiles = const [],
super.flags = const [],
super.defines = const {},
this.buildModeDefine = true,
this.ndebugDefine = true,
bool? pie = false,
super.std,
super.language = Language.c,
super.cppLinkStdLib,
super.optimizationLevel = OptimizationLevel.o3,
this.buildMode = BuildMode.release,
}) : super(
type: OutputType.executable,
assetName: null,
installName: null,
pic: pie,
linkModePreference: null,
);
/// Runs the C Compiler with on this C build spec.
///
/// Completes with an error if the build fails.
///
/// If provided, uses [logger] to output logs. Otherwise, uses a default
/// logger that streams [Level.WARNING] to stdout and higher levels to stderr.
@override
Future<void> run({
required BuildInput input,
required BuildOutputBuilder output,
Logger? logger,
List<AssetRouting> routing = const [ToAppBundle()],
}) async {
logger ??= createDefaultLogger();
if (!input.config.buildCodeAssets) {
logger.info(
'config.buildAssetTypes did not contain CodeAssets, '
'skipping CodeAsset $assetName build.',
);
return;
}
assert(
input.config.linkingEnabled || routing.whereType<ToLinkHook>().isEmpty,
'ToLinker can only be provided if input.config.linkingEnabled'
' is true.',
);
final outDir = input.outputDirectory;
final packageRoot = input.packageRoot;
await Directory.fromUri(outDir).create(recursive: true);
final linkMode = getLinkMode(
linkModePreference ?? input.config.code.linkModePreference,
);
final libUri = outDir.resolve(
input.config.code.targetOS.libraryFileName(name, linkMode),
);
final exeUri = outDir.resolve(
input.config.code.targetOS.executableFileName(name),
);
final sources = [
for (final source in this.sources)
packageRoot.resolveUri(Uri.file(source)),
];
final includes = [
for (final directory in this.includes)
packageRoot.resolveUri(Uri.file(directory)),
];
final forcedIncludes = [
for (final file in this.forcedIncludes)
packageRoot.resolveUri(Uri.file(file)),
];
final dartBuildFiles = [
// ignore: deprecated_member_use_from_same_package
for (final source in this.dartBuildFiles) packageRoot.resolve(source),
];
final libraryDirectories = [
for (final directory in this.libraryDirectories)
outDir.resolveUri(Uri.file(directory)),
];
final task = RunCBuilder(
input: input,
codeConfig: input.config.code,
logger: logger,
sources: sources,
includes: includes,
forcedIncludes: forcedIncludes,
frameworks: frameworks,
libraries: libraries,
libraryDirectories: libraryDirectories,
dynamicLibrary:
type == OutputType.library && linkMode == DynamicLoadingBundled()
? libUri
: null,
staticLibrary: type == OutputType.library && linkMode == StaticLinking()
? libUri
: null,
executable: type == OutputType.executable ? exeUri : null,
// ignore: invalid_use_of_visible_for_testing_member
installName: installName,
flags: flags,
defines: {
...defines,
if (buildModeDefine) buildMode.name.toUpperCase(): null,
if (ndebugDefine && buildMode != BuildMode.debug) 'NDEBUG': null,
},
pic: pic,
std: std,
language: language,
cppLinkStdLib: cppLinkStdLib,
optimizationLevel: optimizationLevel,
);
await task.run();
if (assetName != null) {
for (final route in routing) {
output.assets.code.add(
CodeAsset(
package: packageName ?? input.packageName,
name: assetName!,
file: libUri,
linkMode: linkMode,
),
routing: route,
);
}
}
final includeFiles = await Stream.fromIterable(includes)
.asyncExpand(
(include) => Directory(include.toFilePath())
.list(recursive: true)
.where((entry) => entry is File)
.map((file) => file.uri),
)
.toList();
output.dependencies.addAll({
// Note: We use a Set here to deduplicate the dependencies.
...sources,
...includeFiles,
...forcedIncludes,
...dartBuildFiles,
});
}
}