Shared build and link hook infrastructure for Dart packages that distribute prebuilt native code assets (via GitHub Releases or bundled prebuilt/ directories), support compiling from source through a toolchain-agnostic buildFromSource callback (e.g. CBuilder from package:native_toolchain_c, CMake, or Cargo), and tree-shake static libraries in hook/link.dart via @RecordUse.
Modeled after CLibrary in package:native_toolchain_c, a single PrebuiltLibrary specification is defined once and shared across hook/build.dart, hook/link.dart, tool/precompile_binaries.dart, and tool/regenerate_hashes.dart.
- Unified
PrebuiltLibraryspecification:library.build(input: input, output: output)forhook/build.dart.library.link(input: input, output: output)forhook/link.dart.library.buildStandalone(...)(viaBuildInputBuilder) for standalone CI scripts.
- Standardized build modes (
buildModeunderhooks.user_defines.<package_name>):fetch(default): Uses a pub-bundledprebuilt/binary if present, or downloads the prebuilt binary, verifies its SHA-256 digest, and caches it inoutputDirectoryShared(in an ABI-specific subdirectory preserving the canonical OS library filename for iOS/macOS XCFrameworks). Downloads are streamed, retried on transient failures, and written atomically. Optionally falls back tobuildFromSourceif no binary is available.build(aliascheckout): Compiles from source usingbuildFromSource(optionally fromcheckoutPath).local: Bundles a pre-existing dynamic library fromlocalPath.
- Tree-shaking link hook (
treeshakeunderhooks.user_defines.<package_name>):- Resolves used symbols via
SymbolsResolvers.fromRecordUseMapping(ffigen) orSymbolsResolvers.fromMethodPrefix(e.g. Diplomat). auto(default): Tree-shakes when possible. If no static library is released for a target, or if linking fails infetchmode (e.g. no C toolchain for the target), prints a warning and bundles the prebuilt dynamic library instead.on: Always tree-shakes, and fails the build if that is not possible.off: Never tree-shakes; bundles the dynamic library directly without running the C linker.- On Windows, only exports symbols the
.libdefines, and switches to a.defmodule-definition file when/INCLUDE:<symbol>flags would exceed the 32k command-line limit. - On Windows, don't mark functions with
__declspec(dllexport)when compiling the static library: the linker then exports, and so keeps, every such function in each object file it links, even unused ones. The.deffile already determines the exports. - Any other assets routed to the package's link hook are forwarded unchanged.
- Resolves used symbols via
- Maintainer CLI runners (
package:prebuilt_code_assets/tools.dart):runPrecompileBinariesClifortool/precompile_binaries.dart.runRegenerateHashesClifortool/regenerate_hashes.dart(supports both local artifact directories and remote release URLs, and refuses to write an incomplete manifest when downloads fail).
import 'package:prebuilt_code_assets/prebuilt_code_assets.dart';
import 'package:record_use/record_use.dart' as record_use;
import '../bindings/record_use_mapping.g.dart';
import 'hashes.dart';
final myLibrary = PrebuiltLibrary(
name: 'my_lib',
assetName: 'my_package.dart',
releaseConfig: PrebuiltReleaseConfig.github(
owner: 'my-org',
repo: 'my_package',
version: version,
fileHashes: fileHashes,
libraryName: 'my_lib',
),
buildFromSource: (input, output, {required static, checkoutPath}) async {
// Compile with your toolchain (e.g. `CBuilder`, CMake, or Cargo), add the
// sources you read to `output.dependencies`, and return the Uri of the
// built static (if `static`) or dynamic library.
},
usedSymbols: SymbolsResolvers.fromRecordUseMapping(
const record_use.Library('package:my_package/src/bindings/bindings.g.dart'),
recordUseMapping,
),
allKnownSymbols: recordUseMapping.values.toSet(),
);For release layouts other than <repo>-<target>-<libraryFileName>, pass a custom resolveAssetName to PrebuiltReleaseConfig.github, or use the PrebuiltReleaseConfig constructor directly.
// hook/build.dart
import 'package:hooks/hooks.dart';
import 'package:my_package/src/hook_helpers/library.dart';
Future<void> main(List<String> args) async {
await build(args, (input, output) async {
await myLibrary.build(input: input, output: output);
});
}// hook/link.dart
import 'package:hooks/hooks.dart';
import 'package:my_package/src/hook_helpers/library.dart';
Future<void> main(List<String> args) async {
await link(args, (input, output) async {
await myLibrary.link(input: input, output: output);
});
}Both methods accept an optional Logger; by default messages are printed to the hook's stdout/stderr.
Consumers of your package configure how the native library is obtained and whether it is tree-shaken under hooks.user_defines.<package_name> in their workspace or application pubspec.yaml:
hooks:
user_defines:
my_package:
# 'fetch' (default), 'build' (alias 'checkout'), or 'local'
buildMode: fetch
# 'auto' (default), 'on', or 'off'
treeshake: auto
# Path to a local source checkout (used when buildMode is 'build')
# checkoutPath: ../path/to/checkout
# Path to a pre-existing dynamic library on disk (required when buildMode is 'local')
# localPath: /absolute/or/relative/path/to/libmy_lib.so| Key | Type | Description |
|---|---|---|
buildMode |
String |
'fetch' (default: use bundled prebuilt/ or download from release URL), 'build' / 'checkout' (compile from source), or 'local' (use a binary at localPath). |
treeshake |
String or bool |
'auto' (default), 'on' / true, or 'off' / false. See Features. |
local_build |
bool |
Shorthand used by dart-lang/native examples: local_build: true means buildMode: build. Ignored if buildMode is set. |
checkoutPath |
String (path) |
Optional path to a local source directory when buildMode is 'build'. |
localPath |
String (path) |
Path to a pre-built dynamic library file when buildMode is 'local'. |
Relative paths are resolved against the pubspec.yaml that defines them. Unknown values fail the build with an explanation (set strictBuildOptions: false on PrebuiltLibrary to fall back to the defaults instead).