| // 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', |
| }; |