blob: 74bef090470233469738f8911a60bfad2242aade [file]
// 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 'config.dart';
import 'link_mode.dart';
import 'os.dart';
import 'syntax.g.dart';
/// An asset containing executable code which respects the native application
/// binary interface (ABI).
///
/// Typical languages which produce code assets that respect the native ABI
/// include C, C++ (with `extern "C"`), Rust (with `extern "C"`), and a subset
/// of language features of Objective-C.
///
/// Native code assets can be accessed at runtime through native external
/// functions via their asset [id]:
///
/// <!-- file://./../../../example/api/code_asset_snippet.dart -->
/// ```dart
/// import 'dart:ffi';
///
/// void main() {
/// final result = add(14, 28);
/// print(result);
/// }
///
/// @Native<Int Function(Int, Int)>(assetId: 'package:my_package/add.dart')
/// external int add(int a, int b);
/// ```
///
/// There are several types of native code assets:
/// * Assets which designate symbols present in the target system
/// ([DynamicLoadingSystem]), process ([LookupInProcess]), or executable
/// ([LookupInExecutable]). These assets do not have a [file].
/// * Dynamic libraries bundled into the application ([DynamicLoadingBundled]).
/// These assets must provide a [file] to be bundled.
///
/// An application is compiled to run on a specific target [CodeConfig.targetOS]
/// and [CodeConfig.targetArchitecture]. Different targets require different
/// assets, so the package developer must specify which asset to bundle for
/// which target.
///
/// An asset has different ways of being accessible in the final application. It
/// is either brought in "manually" by having the package developer specify a
/// [file] path of the asset on the current system, it can be part of the Dart
/// or Flutter SDK ([LookupInProcess]), or it can be already present in the
/// target system ([DynamicLoadingSystem]). If the asset is bundled "manually",
/// the Dart or Flutter SDK will take care of copying the asset [file] from its
/// specified location on the current system into the application bundle.
final class CodeAsset {
/// The id of this code asset.
///
/// The identifier is a uri `package:<package>/<name>`. In build hooks,
/// `<package>` must be the name of the package containing the build hook.
/// Code assets are name-spaced in their own package to avoid naming
/// conflicts.
final String id;
/// The link mode for this native code.
///
/// Either dynamic loading or static linking.
final LinkMode linkMode;
/// The native library to be bundled with the Dart or Flutter application.
///
/// If the [linkMode] is [DynamicLoadingBundled], the file must be provided
/// and exist. The path must be an absolute path. Prefer constructing the path
/// via [HookInput.outputDirectoryShared] or [HookInput.outputDirectory].
///
/// If the [linkMode] is [DynamicLoadingSystem], the file must be provided,
/// and not exist.
///
/// If the [linkMode] is [LookupInProcess], or [LookupInExecutable] the file
/// must be omitted in the [BuildOutput].
///
/// Code assets with [DynamicLoadingBundled] may not have conflicting file
/// names, the dynamic linker treats them as a single namespace.
final Uri? file;
/// Constructs a native code asset.
///
/// The [id] of this asset is a uri `package:<package>/<name>` from [package]
/// and [name]. In build hooks, [package] must be the name of the package
/// containing the build hook. Code assets are name-spaced in their own
/// package to avoid naming conflicts.
CodeAsset({
required String package,
required String name,
required this.linkMode,
this.file,
}) : id = 'package:$package/$name';
CodeAsset._({required this.id, required this.linkMode, required this.file});
/// Creates a [CodeAsset] from an [EncodedAsset].
factory CodeAsset.fromEncoded(EncodedAsset asset) {
assert(asset.isCodeAsset);
final syntaxNode = NativeCodeAssetEncodingSyntax.fromJson(
asset.encoding,
path: asset.encodingJsonPath ?? [],
);
return CodeAsset._(
id: syntaxNode.id,
linkMode: LinkModeSyntaxExtension.fromSyntax(syntaxNode.linkMode),
file: syntaxNode.file,
);
}
/// Creates a copy of this [CodeAsset] with the given fields replaced.
CodeAsset copyWith({LinkMode? linkMode, String? id, Uri? file}) =>
CodeAsset._(
id: id ?? this.id,
linkMode: linkMode ?? this.linkMode,
file: file ?? this.file,
);
@override
bool operator ==(Object other) {
if (other is! CodeAsset) {
return false;
}
return other.id == id && other.linkMode == linkMode && other.file == file;
}
@override
int get hashCode => Object.hash(id, linkMode, file);
/// Encodes this [CodeAsset] into an [EncodedAsset].
EncodedAsset encode() {
final encoding = NativeCodeAssetEncodingSyntax(
file: file,
id: id,
linkMode: linkMode.toSyntax(),
);
return EncodedAsset(CodeAssetType.type, encoding.json);
}
}
/// Extension for identifying the type of a [CodeAsset].
extension CodeAssetType on CodeAsset {
/// The type identifier for [CodeAsset]s in a hook input or output.
static const String type = NativeCodeAssetNewSyntax.typeValue;
}
/// Methods on [EncodedAsset] for [CodeAsset]s.
extension EncodedCodeAsset on EncodedAsset {
/// Whether this [EncodedAsset] represents a [CodeAsset].
bool get isCodeAsset => type == CodeAssetType.type;
/// Converts this [EncodedAsset] to a [CodeAsset].
CodeAsset get asCodeAsset => CodeAsset.fromEncoded(this);
}
/// Provides OS-specific library naming conventions.
extension OSLibraryNaming on OS {
/// The default dynamic library file name on this os.
String dylibFileName(String name) {
final prefix = _dylibPrefix[this]!;
final extension = _dylibExtension[this]!;
return '$prefix$name.$extension';
}
/// The default static library file name on this os.
String staticlibFileName(String name) {
final prefix = _staticlibPrefix[this]!;
final extension = _staticlibExtension[this]!;
return '$prefix$name.$extension';
}
/// The default library file name on this os.
String libraryFileName(String name, LinkMode linkMode) {
if (linkMode is DynamicLoading) {
return dylibFileName(name);
}
assert(linkMode is StaticLinking);
return staticlibFileName(name);
}
/// The default executable file name on this os.
String executableFileName(String name) {
final extension = _executableExtension[this]!;
final dot = extension.isNotEmpty ? '.' : '';
return '$name$dot$extension';
}
}
/// The default name prefix for dynamic libraries per [OS].
const _dylibPrefix = {
OS.android: 'lib',
OS.fuchsia: 'lib',
OS.iOS: 'lib',
OS.linux: 'lib',
OS.macOS: 'lib',
OS.windows: '',
};
/// The default extension for dynamic libraries per [OS].
const _dylibExtension = {
OS.android: 'so',
OS.fuchsia: 'so',
OS.iOS: 'dylib',
OS.linux: 'so',
OS.macOS: 'dylib',
OS.windows: 'dll',
};
/// The default name prefix for static libraries per [OS].
const _staticlibPrefix = _dylibPrefix;
/// The default extension for static libraries per [OS].
const _staticlibExtension = {
OS.android: 'a',
OS.fuchsia: 'a',
OS.iOS: 'a',
OS.linux: 'a',
OS.macOS: 'a',
OS.windows: 'lib',
};
/// The default extension for executables per [OS].
const _executableExtension = {
OS.android: '',
OS.fuchsia: '',
OS.iOS: '',
OS.linux: '',
OS.macOS: '',
OS.windows: 'exe',
};