blob: e47ac0f3546b1089b22f71093ceb4364d97e6554 [file] [log] [blame] [edit]
// 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:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import 'run_cbuilder.dart';
abstract class Builder {
Future<(List<Asset> assets, Set<Uri> dependencies)> run({
required HookConfig hookConfig,
required Logger? logger,
});
}
/// A programming language that can be selected for compilation of source files.
///
/// See [CBuilder.language] for more information.
class Language {
/// The name of the language.
final String name;
const Language._(this.name);
static const Language c = Language._('c');
static const Language cpp = Language._('c++');
/// Known values for [Language].
static const List<Language> values = [c, cpp];
@override
String toString() => name;
}
/// Specification for building an artifact with a C compiler.
class CBuilder implements Builder {
/// What kind of artifact to build.
final _CBuilderType _type;
/// Name of the library or executable to build.
///
/// The filename will be decided by [BuildConfig.targetOS] and
/// [OS.libraryFileName] or [OS.executableFileName].
///
/// File will be placed in [BuildConfig.outputDirectory].
final String name;
/// Asset identifier.
///
/// Used to output the [BuildOutput.assets].
///
/// If omitted, no asset will be added to the build output.
final String? assetName;
/// Sources to build the library or executable.
///
/// Resolved against [BuildConfig.packageRoot].
///
/// Used to output the [BuildOutput.dependencies].
final List<String> sources;
/// Include directories to pass to the compiler.
///
/// Resolved against [BuildConfig.packageRoot].
///
/// Used to output the [BuildOutput.dependencies].
final List<String> includes;
/// The dart files involved in building this artifact.
///
/// Resolved against [BuildConfig.packageRoot].
///
/// Used to output the [BuildOutput.dependencies].
final List<String> dartBuildFiles;
/// TODO(https://github.com/dart-lang/native/issues/54): Move to [BuildConfig]
/// or hide in public API.
@visibleForTesting
final Uri? installName;
/// Flags to pass to the compiler.
final List<String> flags;
/// Definitions of preprocessor macros.
///
/// When the value is `null`, the macro is defined without a value.
final Map<String, String?> defines;
/// 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;
/// Whether the compiler will emit position independent code.
///
/// When set to `true`, libraries will be compiled with `-fPIC` and
/// executables with `-fPIE`. Accordingly the corresponding parameter of the
/// [CBuilder.executable] constructor is named `pie`.
///
/// When set to `null`, the default behavior of the compiler will be used.
///
/// This option has no effect when building for Windows, where generation of
/// position independent code is not configurable.
///
/// Defaults to `true` for libraries and `false` for executables.
final bool? pic;
/// The language standard to use.
///
/// When set to `null`, the default behavior of the compiler will be used.
final String? std;
/// The language to compile [sources] as.
///
/// [cppLinkStdLib] only has an effect when this option is set to
/// [Language.cpp].
final Language language;
/// The C++ standard library to link against.
///
/// This option has no effect when [language] is not set to [Language.cpp] or
/// when compiling for Windows.
///
/// When set to `null`, the following defaults will be used, based on the
/// target OS:
///
/// | OS | Library |
/// | :------ | :----------- |
/// | Android | `c++_shared` |
/// | iOS | `c++` |
/// | Linux | `stdc++` |
/// | macOS | `c++` |
/// | Fuchsia | `c++` |
final String? cppLinkStdLib;
/// If the code asset should be a dynamic or static library.
///
/// This determines whether to produce a dynamic or static library. If null,
/// the value is instead retrieved from the [BuildConfig].
final LinkModePreference? linkModePreference;
CBuilder.library({
required this.name,
required this.assetName,
this.sources = const [],
this.includes = const [],
required this.dartBuildFiles,
@visibleForTesting this.installName,
this.flags = const [],
this.defines = const {},
this.buildModeDefine = true,
this.ndebugDefine = true,
this.pic = true,
this.std,
this.language = Language.c,
this.cppLinkStdLib,
this.linkModePreference,
}) : _type = _CBuilderType.library;
CBuilder.executable({
required this.name,
this.sources = const [],
this.includes = const [],
required this.dartBuildFiles,
this.flags = const [],
this.defines = const {},
this.buildModeDefine = true,
this.ndebugDefine = true,
bool? pie = false,
this.std,
this.language = Language.c,
this.cppLinkStdLib,
}) : _type = _CBuilderType.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.
@override
Future<(List<Asset> assets, Set<Uri> dependencies)> run({
required HookConfig hookConfig,
required Logger? logger,
}) async {
final outDir = hookConfig.outputDirectory;
final packageRoot = hookConfig.packageRoot;
await Directory.fromUri(outDir).create(recursive: true);
final linkMode =
_linkMode(linkModePreference ?? hookConfig.linkModePreference);
final libUri =
outDir.resolve(hookConfig.targetOS.libraryFileName(name, linkMode));
final exeUri = outDir.resolve(hookConfig.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 dartBuildFiles = [
for (final source in this.dartBuildFiles) packageRoot.resolve(source),
];
if (!hookConfig.dryRun) {
final task = RunCBuilder(
hookConfig: hookConfig,
logger: logger,
sources: sources,
includes: includes,
dynamicLibrary: _type == _CBuilderType.library &&
linkMode == DynamicLoadingBundled()
? libUri
: null,
staticLibrary:
_type == _CBuilderType.library && linkMode == StaticLinking()
? libUri
: null,
executable: _type == _CBuilderType.executable ? exeUri : null,
installName: installName,
flags: flags,
defines: {
...defines,
if (buildModeDefine) hookConfig.buildMode.name.toUpperCase(): null,
if (ndebugDefine && hookConfig.buildMode != BuildMode.debug)
'NDEBUG': null,
},
pic: pic,
std: std,
language: language,
cppLinkStdLib: cppLinkStdLib,
);
await task.run();
}
List<NativeCodeAsset> assets;
if (assetName != null) {
assets = [
NativeCodeAsset(
package: hookConfig.packageName,
name: assetName!,
file: libUri,
linkMode: linkMode,
os: hookConfig.targetOS,
architecture:
hookConfig.dryRun ? null : hookConfig.targetArchitecture,
)
];
} else {
assets = [];
}
Set<Uri> dependencies;
if (!hookConfig.dryRun) {
final includeFiles = await Stream.fromIterable(includes)
.asyncExpand(
(include) => Directory(include.toFilePath())
.list(recursive: true)
.where((entry) => entry is File)
.map((file) => file.uri),
)
.toList();
dependencies = {
// Note: We use a Set here to deduplicate the dependencies.
...sources,
...includeFiles,
...dartBuildFiles,
};
} else {
dependencies = {};
}
return (assets, dependencies);
}
}
enum _CBuilderType {
executable,
library,
}
LinkMode _linkMode(LinkModePreference preference) {
if (preference == LinkModePreference.dynamic ||
preference == LinkModePreference.preferDynamic) {
return DynamicLoadingBundled();
}
assert(preference == LinkModePreference.static ||
preference == LinkModePreference.preferStatic);
return StaticLinking();
}