blob: 875cb6b102ac70871af998761cdd06af27f04573 [file] [log] [blame] [edit]
// Copyright (c) 2024, 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 'package:hooks/hooks.dart';
import 'architecture.dart';
import 'c_compiler_config.dart';
import 'code_asset.dart';
import 'ios_sdk.dart';
import 'link_mode_preference.dart';
import 'os.dart';
import 'syntax.g.dart';
/// Extension to the [HookConfig] providing access to configuration specific
/// to code assets (only available if code assets are supported).
extension HookConfigCodeConfig on HookConfig {
/// Code asset specific configuration.
CodeConfig get code => CodeConfig._fromJson(json, path);
/// Whether the hook invoker (e.g. the Dart or Flutter SDK) expects this
/// hook to build code assets.
bool get buildCodeAssets => buildAssetTypes.contains(CodeAssetType.type);
}
/// Extension to the [LinkInput] providing access to configuration specific to
/// code assets as well as code asset inputs to the linker (only available if
/// code assets are supported).
extension LinkInputCodeAssets on LinkInputAssets {
/// The [CodeAsset]s in this [LinkInputAssets.encodedAssets].
///
/// NOTE: If the linker implementation depends on the contents of the files
/// the code assets refer (e.g. looks at static archives and links them) then
/// the linker script has to add those files as dependencies via
/// [HookOutputBuilder.dependencies] to ensure the linker script will be
/// re-run if the content of the files changes.
Iterable<CodeAsset> get code =>
encodedAssets.where((e) => e.isCodeAsset).map(CodeAsset.fromEncoded);
}
/// The configuration for [CodeAsset]s in [HookConfig].
///
/// Available via [HookConfigCodeConfig.code].
final class CodeConfig {
final CodeConfigSyntax _syntax;
CodeConfig._fromJson(Map<String, Object?> json, List<Object> path)
: _syntax = ConfigSyntax.fromJson(json, path: path).extensions!.codeAssets!;
/// The architecture the code code asset should be built for.
///
/// The build and link hooks are invoked once per [targetArchitecture]. If the
/// invoker produces multi-architecture applications, the invoker is
/// responsible for combining the [CodeAsset]s for individual architectures
/// into a universal binary. So, the build and link hook implementations are
/// not responsible for providing universal binaries.
Architecture get targetArchitecture =>
ArchitectureSyntaxExtension.fromSyntax(_syntax.targetArchitecture);
/// The preferred link for [CodeAsset]s.
LinkModePreference get linkModePreference =>
LinkModePreferenceSyntaxExtension.fromSyntax(_syntax.linkModePreference);
/// A compiler toolchain able to target [targetOS] with [targetArchitecture].
CCompilerConfig? get cCompiler => switch (_syntax.cCompiler) {
null => null,
final c => CCompilerConfigSyntaxExtension.fromSyntax(c),
};
/// The operating system being compiled for.
OS get targetOS => OSSyntaxExtension.fromSyntax(_syntax.targetOs);
/// Configuration provided when [CodeConfig.targetOS] is [OS.macOS].
IOSCodeConfig get iOS => switch (_syntax.iOS) {
null => throw StateError('Cannot access iOSConfig if targetOS is not iOS.'),
final c => IOSCodeConfig._(c),
};
/// Configuration provided when [CodeConfig.targetOS] is [OS.android].
AndroidCodeConfig get android => switch (_syntax.android) {
null => throw StateError(
'Cannot access androidConfig if targetOS is not android.',
),
final c => AndroidCodeConfig._(c),
};
/// Configuration provided when [CodeConfig.targetOS] is [OS.macOS].
MacOSCodeConfig get macOS => switch (_syntax.macOS) {
null => throw StateError(
'Cannot access macOSConfig if targetOS is not MacOS.',
),
final c => MacOSCodeConfig._(c),
};
}
/// The configuration for [CodeAsset]s for target OS [OS.iOS] in
/// [CodeConfig.iOS].
final class IOSCodeConfig {
final IOSCodeConfigSyntax _syntax;
IOSCodeConfig._(this._syntax);
/// Whether to target device or simulator.
IOSSdk get targetSdk => IOSSdk.fromString(_syntax.targetSdk);
/// The lowest iOS version that the compiled code will be compatible with.
int get targetVersion => _syntax.targetVersion;
/// Constructs a new [IOSCodeConfig].
IOSCodeConfig({required IOSSdk targetSdk, required int targetVersion})
: _syntax = IOSCodeConfigSyntax(
targetSdk: targetSdk.type,
targetVersion: targetVersion,
);
}
/// The configuration for [CodeAsset]s for target OS [OS.android] in
/// [CodeConfig.android].
final class AndroidCodeConfig {
final AndroidCodeConfigSyntax _syntax;
AndroidCodeConfig._(this._syntax);
/// The minimum Android SDK API version to that the compiled code will be
/// compatible with.
int get targetNdkApi => _syntax.targetNdkApi;
/// Constructs a new [AndroidCodeConfig].
AndroidCodeConfig({required int targetNdkApi})
: _syntax = AndroidCodeConfigSyntax(targetNdkApi: targetNdkApi);
}
/// The configuration for [CodeAsset]s for target OS [OS.macOS] in
/// [CodeConfig.macOS].
final class MacOSCodeConfig {
final MacOSCodeConfigSyntax _syntax;
MacOSCodeConfig._(this._syntax);
/// The lowest MacOS version that the compiled code will be compatible with.
int get targetVersion => _syntax.targetVersion;
/// Constructs a new [MacOSCodeConfig].
MacOSCodeConfig({required int targetVersion})
: _syntax = MacOSCodeConfigSyntax(targetVersion: targetVersion);
}
/// Extension on [BuildOutputBuilder] to add [CodeAsset]s.
extension BuildOutputAssetsBuilderCode on BuildOutputAssetsBuilder {
/// Provides access to emitting code assets.
BuildOutputCodeAssetBuilder get code => BuildOutputCodeAssetBuilder._(this);
}
/// Extension on [BuildOutputBuilder] to add [CodeAsset]s.
final class BuildOutputCodeAssetBuilder {
/// Provides access to emitting code assets.
final BuildOutputAssetsBuilder _output;
BuildOutputCodeAssetBuilder._(this._output);
/// Adds the given [asset] to the hook output with [routing].
void add(CodeAsset asset, {AssetRouting routing = const ToAppBundle()}) =>
_output.addEncodedAsset(asset.encode(), routing: routing);
/// Adds the given [assets] to the hook output with [routing].
void addAll(
Iterable<CodeAsset> assets, {
AssetRouting routing = const ToAppBundle(),
}) {
for (final asset in assets) {
add(asset, routing: routing);
}
}
}
/// Extension on [LinkOutputBuilder] to add [CodeAsset]s.
extension LinkOutputAssetsBuilderCode on LinkOutputAssetsBuilder {
/// Provides access to emitting code assets.
LinkOutputCodeAssetBuilder get code => LinkOutputCodeAssetBuilder._(this);
}
/// Extension on [LinkOutputBuilder] to add [CodeAsset]s.
final class LinkOutputCodeAssetBuilder {
final LinkOutputAssetsBuilder _output;
LinkOutputCodeAssetBuilder._(this._output);
/// Adds the given [asset] to the hook output with [routing].
void add(CodeAsset asset, {LinkAssetRouting routing = const ToAppBundle()}) =>
_output.addEncodedAsset(asset.encode(), routing: routing);
/// Adds the given [assets] to the hook output with [routing].
void addAll(
Iterable<CodeAsset> assets, {
LinkAssetRouting routing = const ToAppBundle(),
}) {
for (final asset in assets) {
add(asset, routing: routing);
}
}
}
/// Extension to initialize code specific configuration on link/build inputs.
extension CodeAssetBuildInputBuilder on HookConfigBuilder {
/// Sets up the code asset specific configuration for a build or link hook
/// input.
void setupCode({
required Architecture targetArchitecture,
required OS targetOS,
required LinkModePreference linkModePreference,
CCompilerConfig? cCompiler,
AndroidCodeConfig? android,
IOSCodeConfig? iOS,
MacOSCodeConfig? macOS,
}) {
final codeConfig = CodeConfigSyntax(
linkModePreference: linkModePreference.toSyntax(),
targetArchitecture: targetArchitecture.toSyntax(),
targetOs: targetOS.toSyntax(),
cCompiler: cCompiler?.toSyntax(),
android: android?.toSyntax(),
iOS: iOS?.toSyntax(),
macOS: macOS?.toSyntax(),
);
final baseHookConfig = ConfigSyntax.fromJson(json);
baseHookConfig.extensions ??= ConfigExtensionsSyntax.fromJson({});
final hookConfig = ConfigSyntax.fromJson(baseHookConfig.json);
hookConfig.extensions!.codeAssets = codeConfig;
}
}
/// The [CodeAsset]s in [BuildOutputAssets.encodedAssets].
// TODO: Add access to assetsForBuild or assetsForLinking.
extension BuildOutputCodeAssets on BuildOutputAssets {
/// The [CodeAsset]s in this [BuildOutputAssets.encodedAssets].
List<CodeAsset> get code => encodedAssets
.where((asset) => asset.isCodeAsset)
.map(CodeAsset.fromEncoded)
.toList();
}
/// The [CodeAsset]s in [LinkOutputAssets.encodedAssets].
extension LinkOutputCodeAssets on LinkOutputAssets {
/// The [CodeAsset]s in this [LinkOutputAssets.encodedAssets].
List<CodeAsset> get code => encodedAssets
.where((asset) => asset.isCodeAsset)
.map(CodeAsset.fromEncoded)
.toList();
}
/// Extension methods for [MacOSCodeConfig] to convert to and from the syntax
/// model.
extension MacOSCodeConfigSyntaxExtension on MacOSCodeConfig {
/// Converts this [MacOSCodeConfig] to its corresponding
/// [MacOSCodeConfigSyntax].
MacOSCodeConfigSyntax toSyntax() =>
MacOSCodeConfigSyntax(targetVersion: targetVersion);
}
/// Extension methods for [IOSCodeConfig] to convert to and from the syntax
/// model.
extension IOSCodeConfigSyntaxExtension on IOSCodeConfig {
/// Converts this [IOSCodeConfig] to its corresponding [IOSCodeConfigSyntax].
IOSCodeConfigSyntax toSyntax() => IOSCodeConfigSyntax(
targetSdk: targetSdk.type,
targetVersion: targetVersion,
);
}
/// Extension methods for [AndroidCodeConfig] to convert to and from the syntax
/// model.
extension AndroidCodeConfigSyntaxExtension on AndroidCodeConfig {
/// Converts this [AndroidCodeConfig] to its corresponding
/// [AndroidCodeConfigSyntax].
AndroidCodeConfigSyntax toSyntax() =>
AndroidCodeConfigSyntax(targetNdkApi: targetNdkApi);
}