dart-setup-ffi-assets

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Compiling C Code into Code Assets with Native Assets Hooks

使用Native Assets钩子将C代码编译为Code Assets

Integrate and automate the compilation and packaging of native C/C++ source code into Code Assets under Dart's overarching Native Assets feature using build and link hooks.
通过构建和链接钩子,在Dart的Native Assets功能框架下,集成并自动化原生C/C++源代码到Code Assets的编译与打包流程。

Contents

目录

Introduction

简介

Under Dart's Native Assets feature, packages can package native code (like C/C++ libraries) as Code Assets and bundle them automatically during standard development cycles (e.g.,
dart run
,
dart test
,
dart build
, and
flutter run
). The packaging of Code Assets is driven by two programmatic hook scripts placed inside a package's
hook/
folder:
  1. hook/build.dart
    : Compiles local C sources to machine code or bundles prebuilt native binaries as code assets for a specific host/target architecture.
  2. hook/link.dart
    : Links built code assets, applying advanced tree-shaking optimizations to strip unused native symbols and compress the runtime binary size.

在Dart的Native Assets功能下,包可以将原生代码(如C/C++库)打包为Code Assets,并在标准开发周期(例如
dart run
dart test
dart build
flutter run
)中自动捆绑。Code Assets的打包由放置在包的
hook/
文件夹中的两个程序化钩子脚本驱动:
  1. hook/build.dart
    :将本地C源代码编译为机器码,或将预构建原生二进制文件打包为特定主机/目标架构的Code Assets。
  2. hook/link.dart
    :链接已构建的Code Assets,应用高级摇树优化以剥离未使用的原生符号,压缩运行时二进制文件大小。

Constraints

约束条件

[!IMPORTANT] Keep all file resolving platform-independent. Never hardcode absolute target paths, shell scripts, or system command variables. Always use
Platform.script.resolve()
or
Uri
-based resolution to ensure scripts are fully portable.
  • Hook Locations: Compiling and packaging hooks must reside strictly inside the
    hook/
    directory at the package's root:
    • hook/build.dart
      (Build execution phase)
    • hook/link.dart
      (Optional packaging/linking/tree-shaking phase)
  • Compile Toolchain Standard: Use the programmatic APIs from
    package:native_toolchain_c
    (e.g.
    CBuilder
    and
    CLibrary
    ) to run compile toolchains. Never invoke raw
    gcc
    ,
    clang
    , or
    msvc
    via shell commands.
  • Preamble & License Headers: Every handcrafted and generated source file (including bindings, helpers, and hooks) must strictly contain the target package's copyright and licensing header.
  • Tree Shaking Mapping: If utilizing compiler tree-shaking, you must map the target Dart method names (e.g.
    Method.name
    ) back to their raw native C symbol names using a record use mapping generated by FFIgen. The mapping file must reside under
    lib/src/third_party/
    and strictly use the
    .g.dart
    extension (e.g.,
    sqlite3.record_use_mapping.g.dart
    ).
  • Integrity Safeguards for Precompiled Libraries: If adopting the dynamic download pattern:
    • Cryptographic Verification: Downloaded prebuilt binaries must be checked against preconfigured lookup tables containing MD5 or SHA-256 hashes to guarantee binary integrity and prevent tampering.
    • Graceful Recovery: Support offline developers by providing fallbacks (such as local compiler execution via flags like
      local_build
      ).

[!IMPORTANT] 确保所有文件解析与平台无关。切勿硬编码绝对目标路径、Shell脚本或系统命令变量。始终使用
Platform.script.resolve()
或基于
Uri
的解析方式,确保脚本完全可移植。
  • 钩子位置:编译和打包钩子必须严格放置在包根目录的
    hook/
    目录下:
    • hook/build.dart
      (构建执行阶段)
    • hook/link.dart
      (可选的打包/链接/摇树阶段)
  • 编译工具链标准:使用
    package:native_toolchain_c
    提供的程序化API(如
    CBuilder
    CLibrary
    )来运行编译工具链。切勿通过Shell命令直接调用原始的
    gcc
    clang
    msvc
  • 序言与许可证头:所有手工编写和生成的源代码文件(包括绑定、辅助工具和钩子)必须严格包含目标包的版权和许可证头。
  • 摇树映射:如果使用编译器摇树优化,必须通过FFigen生成的使用记录映射,将目标Dart方法名(如
    Method.name
    )映射回对应的原生C符号名。映射文件必须存放在
    lib/src/third_party/
    目录下,且严格使用
    .g.dart
    扩展名(例如
    sqlite3.record_use_mapping.g.dart
    )。
  • 预编译库的完整性保障:如果采用动态下载模式:
    • 加密验证:下载的预构建二进制文件必须与包含MD5或SHA-256哈希的预配置查找表进行校验,以保证二进制文件的完整性并防止篡改。
    • 优雅降级:为离线开发者提供回退方案(例如通过
      local_build
      标志启用本地编译)。

Native Interop Packages

原生互操作包

Programmatic build and link hooks for Code Assets leverage three specialized native interop packages:
DependencyPurposeKey API Abstractions
package:hooks
Main orchestrator defining execution bounds.
build(args, callback)
,
link(args, callback)
package:native_toolchain_c
Detects local compilers (MSVC, Xcode/Clang, GCC) and executes build toolchains.
CLibrary
,
CBuilder
,
LinkerOptions.treeshake
package:code_assets
Models code metadata records passed to dynamic loaders.
CodeAsset
,
DynamicLoadingBundled

Code Assets的程序化构建和链接钩子依赖三个专门的原生互操作包:
依赖包用途核心API抽象
package:hooks
主协调器,定义执行边界。
build(args, callback)
,
link(args, callback)
package:native_toolchain_c
检测本地编译器(MSVC、Xcode/Clang、GCC)并执行构建工具链。
CLibrary
,
CBuilder
,
LinkerOptions.treeshake
package:code_assets
定义传递给动态加载器的代码元数据记录。
CodeAsset
,
DynamicLoadingBundled

Step-by-Step Workflow

分步工作流

Step 1: Add Dependencies

步骤1:添加依赖

Add Code Assets hook and toolchain dependencies to your package. You must fetch these dependencies directly from pub.dev.
You can add it automatically using the CLI:
bash
dart pub add code_assets hooks native_toolchain_c record_use dev:ffigen
Or manually declare them in your target package's
pubspec.yaml
:
yaml
dependencies:
  code_assets: ^1.0.0
  hooks: ^0.1.0
  native_toolchain_c: ^0.1.0
  record_use: ^0.6.0

dev_dependencies:
  ffigen: ^20.1.1
将Code Assets钩子和工具链依赖添加到你的包中。必须直接从pub.dev获取这些依赖。
你可以使用CLI自动添加:
bash
dart pub add code_assets hooks native_toolchain_c record_use dev:ffigen
或者在目标包的
pubspec.yaml
中手动声明:
yaml
dependencies:
  code_assets: ^1.0.0
  hooks: ^0.1.0
  native_toolchain_c: ^0.1.0
  record_use: ^0.6.0

dev_dependencies:
  ffigen: ^20.1.1

Step 2: Define C Specifications

步骤2:定义C规范

Define your target C library compilation metadata inside
lib/src/c_library.dart
. This lets both the build and link hooks share a single source of truth for assets, names, and sources.
lib/src/c_library.dart
中定义目标C库的编译元数据。这样构建和链接钩子可以共享资产、名称和源代码的单一数据源。

Step 3: Implement Build and Link Hook Scripts

步骤3:实现构建与链接钩子脚本

Write the compilation orchestration script inside
hook/build.dart
and the dead-code elimination logic inside
hook/link.dart
.
hook/build.dart
中编写编译编排脚本,在
hook/link.dart
中编写死码消除逻辑。

Step 4: Run the Hook Cycle

步骤4:运行钩子周期

Running standard test suites dynamically launches the build and link hook lifecycle in the background:
bash
dart test

运行标准测试套件会在后台自动启动构建和链接钩子的生命周期:
bash
dart test

Choosing an Integration Approach

选择集成方案

There are two primary methods for integrating and delivering C/C++ native assets in Dart. Select the one that matches your project requirements:
AspectMethod 1: Local Compilation & Tree-ShakingMethod 2: Precompiled Downloads
Primary Use CaseWhen C/C++ source code is included directly in the package and you want maximum size optimization.When compiling locally is slow/complex, or when avoiding developer host toolchain requirements.
Host Toolchain RequirementsRequires pre-installed platform C compiler (Xcode tools, MSVC, GCC).Zero compiler setup required on developer/user machines.
Binary OptimizationPremium. Unused symbols are completely tree-shaken, decreasing library size.Standard. Standard compiled binaries are shipped as-is.
Offline SetupFully compliant. Works completely offline.Requires network access to download libraries, with offline fallback.

在Dart中集成和交付C/C++原生资产有两种主要方法。请选择符合项目需求的方案:
维度方法1:本地编译与摇树优化方法2:预编译二进制下载
主要适用场景当C/C++源代码直接包含在包中,且需要最大化尺寸优化时。当本地编译缓慢/复杂,或希望避免开发者主机工具链要求时。
主机工具链要求需要预先安装平台C编译器(Xcode工具、MSVC、GCC)。开发者/用户机器无需任何编译器配置。
二进制优化高级优化。未使用的符号会被完全摇树剥离,减小库体积。标准优化。直接交付标准编译的二进制文件。
离线支持完全兼容。可完全离线工作。需要网络下载库文件,提供离线回退方案。

Method 1: Local Compilation with Linker Tree-Shaking (Recommended)

方法1:本地编译与链接器摇树优化(推荐)

In this approach, the build hook invokes local toolchains (GCC, Clang, MSVC) to compile source files directly. The link hook subsequently filters output symbols utilizing compiler options, retaining only target methods invoked in user code. This represents the standard, robust SQLite pattern under
pkgs/code_assets/example/sqlite
.
此方案中,构建钩子调用本地工具链(GCC、Clang、MSVC)直接编译源代码。链接钩子随后利用编译器选项过滤输出符号,仅保留用户代码中调用的目标方法。这是
pkgs/code_assets/example/sqlite
下的标准、可靠的SQLite模式。

Prerequisite Host Compiler Toolchains

前置主机编译器工具链

Since
package:native_toolchain_c
delegates actual dynamic compilation to the host operating system's default toolchain, the development machine must have one of the following compiler packages pre-installed:
  • macOS: Xcode Command Line Tools. Install via:
    bash
    xcode-select --install
  • Linux: GCC or Clang. Install via:
    bash
    sudo apt install build-essential
  • Windows: MSVC (Microsoft Visual C++). Install the Visual Studio Installer and select the Desktop development with C++ workload.
Note: If no compatible toolchain is discovered on the host path, the build hook script will throw a compilation execution exception. Ensure to specify compiler constraints or adopt Method 2 if toolchains cannot be guaranteed.
由于
package:native_toolchain_c
将实际的动态编译委托给主机操作系统的默认工具链,开发机器必须预先安装以下编译器包之一:
  • macOS:Xcode命令行工具。通过以下命令安装:
    bash
    xcode-select --install
  • Linux:GCC或Clang。通过以下命令安装:
    bash
    sudo apt install build-essential
  • Windows:MSVC(Microsoft Visual C++)。安装Visual Studio安装程序并选择使用C++的桌面开发工作负载。
注意:如果在主机路径中未发现兼容的工具链,构建钩子脚本将抛出编译执行异常。如果无法保证工具链可用,请指定编译器约束或采用方法2。

C Source and Bindings Setup

C源代码与绑定配置

Assume a C source defining simple math functions at
third_party/sqlite/sqlite3.c
with its entry point header at
third_party/sqlite/sqlite3.h
:
c
#ifndef SQLITE3_H_
#define SQLITE3_H_

const char *sqlite3_libversion(void);

#endif // SQLITE3_H_
We utilize a programmatic FFIgen script (
tool/ffigen.dart
) to create FFI bindings in
lib/src/third_party/sqlite3.g.dart
, enabling recorded usage tracking and producing the lookup metadata map in
lib/src/third_party/sqlite3.record_use_mapping.g.dart
:
dart
// AUTO-GENERATED FILE - DO NOT MODIFY.
// Generated via ffigen.

const recordUseMapping = {
  'sqlite3_libversion': 'sqlite3_libversion',
};
假设在
third_party/sqlite/sqlite3.c
中有一个定义简单数学函数的C源代码,其入口头文件位于
third_party/sqlite/sqlite3.h
c
#ifndef SQLITE3_H_
#define SQLITE3_H_

const char *sqlite3_libversion(void);

#endif // SQLITE3_H_
我们使用程序化FFigen脚本(
tool/ffigen.dart
)在
lib/src/third_party/sqlite3.g.dart
中创建FFI绑定,启用使用记录跟踪,并在
lib/src/third_party/sqlite3.record_use_mapping.g.dart
中生成查找元数据映射:
dart
// AUTO-GENERATED FILE - DO NOT MODIFY.
// Generated via ffigen.

const recordUseMapping = {
  'sqlite3_libversion': 'sqlite3_libversion',
};

Defining the C Library Build Spec

定义C库构建规范

Define the centralized library specification in
lib/src/c_library.dart
:
dart
import 'package:native_toolchain_c/native_toolchain_c.dart';

/// The C build specification for the sqlite library.
final cLibrary = CLibrary(
  name: 'sqlite3',
  assetName: 'src/third_party/sqlite3.g.dart',
  sources: ['third_party/sqlite/sqlite3.c'],
);
lib/src/c_library.dart
中定义集中式库规范:
dart
import 'package:native_toolchain_c/native_toolchain_c.dart';

/// The C build specification for the sqlite library.
final cLibrary = CLibrary(
  name: 'sqlite3',
  assetName: 'src/third_party/sqlite3.g.dart',
  sources: ['third_party/sqlite/sqlite3.c'],
);

Implementing
hook/build.dart

实现
hook/build.dart

Implement
hook/build.dart
using
CLibrary.build
. This builds the library to a dynamic library (e.g.
.so
,
.dylib
, or
.dll
) inside the hook's target directory:
dart
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:sqlite/src/c_library.dart';

void main(List<String> args) async {
  await build(args, (input, output) async {
    if (input.config.buildCodeAssets) {
      await cLibrary.build(
        input: input,
        output: output,
        defines: {
          if (input.config.code.targetOS == OS.windows)
            // Ensure C functions are explicitly exported in the Windows DLL
            'SQLITE_API': '__declspec(dllexport)',
        },
      );
    }
  });
}
使用
CLibrary.build
实现
hook/build.dart
。这会将库编译为动态库(例如
.so
.dylib
.dll
)并存储在钩子的目标目录中:
dart
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:sqlite/src/c_library.dart';

void main(List<String> args) async {
  await build(args, (input, output) async {
    if (input.config.buildCodeAssets) {
      await cLibrary.build(
        input: input,
        output: output,
        defines: {
          if (input.config.code.targetOS == OS.windows)
            // Ensure C functions are explicitly exported in the Windows DLL
            'SQLITE_API': '__declspec(dllexport)',
        },
      );
    }
  });
}

Implementing
hook/link.dart

实现
hook/link.dart

Implement the link optimization phase in
hook/link.dart
. This utilizes compiler tree-shaking options (
LinkerOptions.treeshake
) to compile a minimized, dead-code-eliminated binary based on symbol usage records:
dart
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:record_use/record_use.dart';
import 'package:sqlite/src/c_library.dart';
import 'package:sqlite/src/third_party/sqlite3.record_use_mapping.g.dart';

void main(List<String> arguments) async {
  await link(arguments, (input, output) async {
    await cLibrary.link(
      input: input,
      output: output,
      linkerOptions: LinkerOptions.treeshake(
        // Map Dart Method references back to raw C symbol names
        symbolsToKeep: input.recordedUses?.calls.keys.cast<Method>().map(
          (e) => recordUseMapping[e.name]!,
        ),
      ),
    );
  });
}

hook/link.dart
中实现链接优化阶段。这利用编译器摇树选项(
LinkerOptions.treeshake
),基于符号使用记录编译一个最小化、消除死码的二进制文件:
dart
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:record_use/record_use.dart';
import 'package:sqlite/src/c_library.dart';
import 'package:sqlite/src/third_party/sqlite3.record_use_mapping.g.dart';

void main(List<String> arguments) async {
  await link(arguments, (input, output) async {
    await cLibrary.link(
      input: input,
      output: output,
      linkerOptions: LinkerOptions.treeshake(
        // Map Dart Method references back to raw C symbol names
        symbolsToKeep: input.recordedUses?.calls.keys.cast<Method>().map(
          (e) => recordUseMapping[e.name]!,
        ),
      ),
    );
  });
}

Method 2: Downloading Precompiled Dynamic Libraries

方法2:下载预编译动态库

An alternative approach compiles binaries beforehand on a central build machine, archives them, and downloads the target binary during the build hook execution. This matches the paradigm demonstrated in the
download_asset
hook package.
另一种方案是预先在中央构建机器上编译二进制文件,归档后在构建钩子执行期间下载目标二进制文件。这与
download_asset
钩子包展示的范式一致。

Why Download Precompiled Binaries?

为什么选择预编译二进制下载?

  • Host Constraints: Compiling large C/C++ libraries locally requires a complete compiler setup (GCC, Xcode/SDKs, Visual Studio) that the end-developer's host machine may not possess.
  • Compile Speed: Precompiled downloads execute in milliseconds compared to potentially long multi-minute compilation processes.
  • Platform Bridging: Allows cross-compiling constraints to be avoided if host architectures are limited.

  • 主机约束:本地编译大型C/C++库需要完整的编译器配置(GCC、Xcode/SDKs、Visual Studio),而终端开发者的主机机器可能不具备这些条件。
  • 编译速度:预编译下载只需数毫秒,相比之下本地编译可能需要数分钟。
  • 平台桥接:如果主机架构有限,可以避免交叉编译约束。

Implementing Precompiled Dynamic Downloads

实现预编译动态库下载

We configure our build hook to detect local compiler flags (e.g.
local_build
). If not specified, the hook utilizes
HttpClient
to pull down platform-specific libraries, calculates the MD5 hash to confirm download safety against a configured hashes lookup table, and registers the binary file as a
CodeAsset
:
我们配置构建钩子以检测本地编译器标志(例如
local_build
)。如果未指定该标志,钩子将使用
HttpClient
拉取特定平台的库,计算MD5哈希以与配置的哈希查找表确认下载安全性,并将二进制文件注册为
CodeAsset

1. Defining Target Hashes (
lib/src/hook_helpers/hashes.dart
)

1. 定义目标哈希(
lib/src/hook_helpers/hashes.dart

Define target MD5 hash checks per platform file in your package sources:
dart
const assetHashes = {
  'libnative_add_macos_arm64.dylib': '4a88f50438a98402db2dbd47b59eb412',
  'libnative_add_linux_x64.so': '9f5e15043aa98402dcdbbd47b59ea520',
  'native_add_windows_x64.dll': 'a881e5043ba98402acdebd47b59fa321',
};
在包源代码中为每个平台文件定义目标MD5哈希校验:
dart
const assetHashes = {
  'libnative_add_macos_arm64.dylib': '4a88f50438a98402db2dbd47b59eb412',
  'libnative_add_linux_x64.so': '9f5e15043aa98402dcdbbd47b59ea520',
  'native_add_windows_x64.dll': 'a881e5043ba98402acdebd47b59fa321',
};

2. Hook Downloader Helper (
lib/src/hook_helpers/download.dart
)

2. 钩子下载器辅助工具(
lib/src/hook_helpers/download.dart

Implement the downloading and integrity check logic using dynamic target filename matching:
dart
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:crypto/crypto.dart';

const version = '1.0.0';

Uri downloadUri(String target) => Uri.parse(
  'https://github.com/my-org/my-native-repo/releases/download/$version/$target',
);

Future<File> downloadAsset(
  OS targetOS,
  Architecture targetArchitecture,
  Directory outputDir,
) async {
  final fileName = targetOS.dylibFileName('native_add_${targetOS.name}_${targetArchitecture.name}');
  final uri = downloadUri(fileName);
  
  final client = HttpClient()..findProxy = HttpClient.findProxyFromEnvironment;
  final request = await client.getUrl(uri);
  final response = await request.close();
  
  if (response.statusCode != 200) {
    throw ArgumentError('Download target $uri failed: Code ${response.statusCode}');
  }
  
  final targetFile = File.fromUri(outputDir.uri.resolve(fileName));
  await targetFile.create(recursive: true);
  await response.pipe(targetFile.openWrite());
  
  return targetFile;
}

Future<String> hashAsset(File file) async {
  return md5.convert(await file.readAsBytes()).toString();
}
使用动态目标文件名匹配实现下载和完整性检查逻辑:
dart
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:crypto/crypto.dart';

const version = '1.0.0';

Uri downloadUri(String target) => Uri.parse(
  'https://github.com/my-org/my-native-repo/releases/download/$version/$target',
);

Future<File> downloadAsset(
  OS targetOS,
  Architecture targetArchitecture,
  Directory outputDir,
) async {
  final fileName = targetOS.dylibFileName('native_add_${targetOS.name}_${targetArchitecture.name}');
  final uri = downloadUri(fileName);
  
  final client = HttpClient()..findProxy = HttpClient.findProxyFromEnvironment;
  final request = await client.getUrl(uri);
  final response = await request.close();
  
  if (response.statusCode != 200) {
    throw ArgumentError('Download target $uri failed: Code ${response.statusCode}');
  }
  
  final targetFile = File.fromUri(outputDir.uri.resolve(fileName));
  await targetFile.create(recursive: true);
  await response.pipe(targetFile.openWrite());
  
  return targetFile;
}

Future<String> hashAsset(File file) async {
  return md5.convert(await file.readAsBytes()).toString();
}

3. Implementing
hook/build.dart

3. 实现
hook/build.dart

Write the final download build hook incorporating local compilation fallback:
dart
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:my_download_package/src/hook_helpers/hashes.dart';
import 'package:my_download_package/src/hook_helpers/download.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';

void main(List<String> args) async {
  await build(args, (input, output) async {
    final localBuild = input.userDefines['local_build'] as bool? ?? false;

    if (localBuild) {
      final name = 'native_add_${input.config.code.targetOS.name}_${input.config.code.targetArchitecture.name}';
      final builder = CBuilder.library(
        name: name,
        assetName: 'native_add.dart',
        sources: ['src/native_add.c'],
      );
      await builder.run(input: input, output: output);
    } else {
      final targetOS = input.config.code.targetOS;
      final targetArch = input.config.code.targetArchitecture;
      final outputDir = Directory.fromUri(input.outputDirectory);

      final file = await downloadAsset(targetOS, targetArch, outputDir);

      final fileHash = await hashAsset(file);
      final expectedFileName = targetOS.dylibFileName('native_add_${targetOS.name}_${targetArch.name}');
      final expectedHash = assetHashes[expectedFileName];

      if (fileHash != expectedHash) {
        throw Exception(
          'Security Mismatch: File $expectedFileName hash verification failed! '
          'Found hash: $fileHash, expected: $expectedHash.'
        );
      }

      output.assets.code.add(
        CodeAsset(
          package: input.packageName,
          name: 'native_add.dart',
          linkMode: DynamicLoadingBundled(),
          file: file.uri,
        ),
      );
    }
  });
}

编写包含本地编译回退的最终下载构建钩子:
dart
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:my_download_package/src/hook_helpers/hashes.dart';
import 'package:my_download_package/src/hook_helpers/download.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';

void main(List<String> args) async {
  await build(args, (input, output) async {
    final localBuild = input.userDefines['local_build'] as bool? ?? false;

    if (localBuild) {
      final name = 'native_add_${input.config.code.targetOS.name}_${input.config.code.targetArchitecture.name}';
      final builder = CBuilder.library(
        name: name,
        assetName: 'native_add.dart',
        sources: ['src/native_add.c'],
      );
      await builder.run(input: input, output: output);
    } else {
      final targetOS = input.config.code.targetOS;
      final targetArch = input.config.code.targetArchitecture;
      final outputDir = Directory.fromUri(input.outputDirectory);

      final file = await downloadAsset(targetOS, targetArch, outputDir);

      final fileHash = await hashAsset(file);
      final expectedFileName = targetOS.dylibFileName('native_add_${targetOS.name}_${targetArch.name}');
      final expectedHash = assetHashes[expectedFileName];

      if (fileHash != expectedHash) {
        throw Exception(
          'Security Mismatch: File $expectedFileName hash verification failed! '
          'Found hash: $fileHash, expected: $expectedHash.'
        );
      }

      output.assets.code.add(
        CodeAsset(
          package: input.packageName,
          name: 'native_add.dart',
          linkMode: DynamicLoadingBundled(),
          file: file.uri,
        ),
      );
    }
  });
}

Verification Checklist

验证检查清单

Before declaring a build or link hook implementation complete, always perform the following checks:
在宣布构建或链接钩子实现完成之前,请务必执行以下检查:

1. Local Execution Sandbox

1. 本地执行沙箱

Run unit tests and confirm the native assets compile/link process completes with no runtime or build tool exceptions:
bash
dart test
运行单元测试,确认原生资产的编译/链接过程完成且无运行时或构建工具异常:
bash
dart test

2. Verify Target Outputs

2. 验证目标输出

Navigate to your package target directory and verify that dynamic binary assets are created for the host system:
  • macOS: Verify
    .dart_tool/resources/
    or target directories contain
    .dylib
    files.
  • Linux: Verify
    .dart_tool/resources/
    or target directories contain
    .so
    files.
  • Windows: Verify
    .dart_tool/resources/
    or target directories contain
    .dll
    files.
导航到包目标目录,确认主机系统的动态二进制资产已创建:
  • macOS:验证
    .dart_tool/resources/
    或目标目录中包含
    .dylib
    文件。
  • Linux:验证
    .dart_tool/resources/
    或目标目录中包含
    .so
    文件。
  • Windows:验证
    .dart_tool/resources/
    或目标目录中包含
    .dll
    文件。

3. Verify Tree-Shaking Stripping

3. 验证摇树剥离效果

To ensure the link hook is actually stripping unused native symbols and compressing binary packaging, perform the following validation:
  1. Compile a production bundle of the CLI/app:
    bash
    dart build cli bin/main.dart
  2. Navigate to the compiled build directory containing the dynamic library.
  3. Query the exported dynamic symbol tables:
    • macOS:
      bash
      nm -gU build/cli/lib/libsqlite3.dylib
    • Linux:
      bash
      nm -D build/cli/lib/libsqlite3.so
    • Windows (using MSVC Developer Command Prompt):
      cmd
      dumpbin /EXPORTS build\cli\lib\sqlite3.dll
  4. Confirm Target Exports: Verify that the command outputs only the explicitly kept entry point functions (e.g.
    sqlite3_libversion
    ) and does not output any unreferenced/stripped symbols.
  5. No Bundle Scenario: If the application does not import or invoke any methods from the native library:
    • Verify that the link hook logs:
      Skipping linking as no symbols are to be kept.
    • Verify that no library was built/placed in the production bundle (the
      .dylib
      /
      .so
      /
      .dll
      file is not generated, saving bundle size).
为确保链接钩子确实在剥离未使用的原生符号并压缩二进制包大小,请执行以下验证:
  1. 编译CLI/应用的生产包:
    bash
    dart build cli bin/main.dart
  2. 导航到包含动态库的编译构建目录。
  3. 查询导出的动态符号表:
    • macOS:
      bash
      nm -gU build/cli/lib/libsqlite3.dylib
    • Linux:
      bash
      nm -D build/cli/lib/libsqlite3.so
    • Windows(使用MSVC开发者命令提示符):
      cmd
      dumpbin /EXPORTS build\cli\lib\sqlite3.dll
  4. 确认目标导出:验证命令输出仅包含明确保留的入口点函数(例如
    sqlite3_libversion
    ),不包含任何未引用/已剥离的符号。
  5. 无捆绑场景:如果应用未导入或调用原生库的任何方法:
    • 验证链接钩子日志显示:
      Skipping linking as no symbols are to be kept.
    • 验证生产包中未生成/放置库文件(
      .dylib
      /
      .so
      /
      .dll
      文件未生成,节省包体积)。

4. Verify Offline Compliance (User Defines)

4. 验证离线兼容性(用户定义)

Confirm offline compliance is fully active and the download fallback executes perfectly offline:
  1. Configure the
    local_build: true
    define for your package in the package's
    pubspec.yaml
    (or the workspace root
    pubspec.yaml
    ):
    yaml
    hooks:
      user_defines:
        <your_package_name>:
          local_build: true
  2. Disable the machine's network adapter or run in a sandboxed offline shell.
  3. Launch unit tests:
    bash
    dart test
  4. Verify the test suite successfully compiles local source files using host compilers, has no compile errors, and never attempts network download requests.
确认离线兼容性完全生效,且下载回退方案可在完全离线环境中正常执行:
  1. 在包的
    pubspec.yaml
    (或工作区根目录的
    pubspec.yaml
    )中配置
    local_build: true
    定义:
    yaml
    hooks:
      user_defines:
        <your_package_name>:
          local_build: true
  2. 禁用机器的网络适配器或在沙箱化的离线Shell中运行。
  3. 启动单元测试:
    bash
    dart test
  4. 验证测试套件成功使用主机编译器编译本地源代码,无编译错误,且从未尝试网络下载请求。