native_prebuilt 0.0.13
native_prebuilt: ^0.0.13 copied to clipboard
Reusable infrastructure for Dart packages that ship prebuilt native libraries from GitHub or GitLab releases. Provides build-hook integration, manifest generation, download/verify/cache pipelines, and [...]
native_prebuilt #
Reusable infrastructure for Dart packages that ship prebuilt native libraries from GitHub or GitLab releases.
What it provides #
PrebuiltCodeAssetBuilderforhook/build.dartPrebuiltManifest/PrebuiltArtifactimmutable release metadataArtifactInstallerfor download → verify → extract → validate → cachePrebuiltResolverchain for override / local / cached / downloaded prebuiltsSourceFallback,SourceProvider, andSourceBuilderfor local / archive / git source buildsNativeBinaryInspectorand library-name helpers- CLI tooling for manifest generation, fetch, verification, and workflow templates
- Reusable GitHub Actions and pub.dev workflow templates
Install #
dependencies:
native_prebuilt: ^0.0.13
Hook usage #
import 'package:hooks/hooks.dart';
import 'package:logging/logging.dart';
import 'package:native_prebuilt/hooks.dart';
import 'package:my_package/src/hook/my_package_prebuilts.g.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
await PrebuiltCodeAssetBuilder(
assetName: 'my_bindings.dart',
libraryStem: 'mylib',
manifest: myPrebuilts,
linkModeResolver: (_) => DynamicLoadingBundled(),
sourceFallback: SourceFallback(
sources: [
LocalSource(paths: ['.']),
],
builder: CallbackSourceBuilder(
callback: ({
required source,
required input,
required output,
required logger,
}) async {
// build from source.directory
},
),
),
).run(input: input, output: output, logger: Logger.root);
});
}
Source fallback pipeline #
When no prebuilt is available, you can let native_prebuilt resolve source and build from it instead of failing immediately.
import 'package:logging/logging.dart';
import 'package:native_prebuilt/hooks.dart';
await PrebuiltCodeAssetBuilder(
assetName: 'src/my_package.dart',
libraryStem: 'my_package',
manifest: myPackagePrebuilts,
linkModeResolver: (_) => DynamicLoadingBundled(),
sourceFallback: SourceFallback(
sources: [
LocalSource(paths: ['.']),
ArchiveSource(
uri: Uri.parse('https://example.com/my_package.tar.gz'),
sha256: '...',
),
GitSource(
repository: Uri.parse('https://github.com/myorg/my_package.git'),
revision: 'FULL_COMMIT_SHA',
),
],
preparation: [
ApplyPatches(paths: ['patches/fix.patch']),
],
builder: CallbackSourceBuilder(
callback: ({
required source,
required input,
required output,
required logger,
}) async {
// compile source.directory here
},
),
),
).run(
input: input,
output: output,
logger: Logger.root
..level = Level.INFO
..onRecord.listen((record) => print(record.message)),
);
The resolution order is:
hooks.user_defines- local
.prebuilt/ - shared cache / release download
sourceFallback(local / archive / git → prepare → build)
Pass a Logger to see progress messages for cache hits, downloads, source acquisition, patching, and builds.
Manifest format #
native_prebuilt expects a YAML config file like:
schema: 1
package: my_package
asset_name: my_bindings.dart
library_stem: mylib
release:
provider: github
repository: myorg/myrepo
tag: mylib-v1.0.0
artifacts:
linux-x64:
archive: mylib-linux-x64.tar.gz
payload:
type: dynamic_library
Use:
dart run native_prebuilt manifest update \
--config native_prebuilt.yaml \
--output lib/src/hook/my_package_prebuilts.g.dart \
--built-library-dir built-library \
--release-assets-dir release-assets
dart run native_prebuilt manifest verify \
--config native_prebuilt.yaml \
--output lib/src/hook/my_package_prebuilts.g.dart \
--built-library-dir built-library \
--release-assets-dir release-assets
The generated manifest file is the only supported place for archiveSha256
and payloadSha256. Do not edit those hashes by hand; rerun manifest update
when the release assets change.
Fetch locally #
dart run native_prebuilt fetch --config native_prebuilt.yaml --platform linux-x64
GitHub Actions #
dart run native_prebuilt workflow init
Generates into .github/workflows/:
| File | Purpose |
|---|---|
prebuilt.yml |
Builds native libraries on every platform, generates manifest + release assets on tag pushes |
publish.yml |
Publishes to pub.dev on v* tags |
native-prebuilt-build.yml |
Reusable build workflow (called by prebuilt.yml) |
native-prebuilt-release.yml |
Reusable release workflow |
native-prebuilt-update-manifest.yml |
Reusable manifest generation workflow |
Tag triggers are package-specific (e.g. my_package-v*). On tag push:
build-linux,build-windows,build-macosjobs compile native code and upload artifactsupdate-manifestdownloads all artifacts, generates the manifest, and creates release archivesreleasepublishes archives to GitHub Releases viasoftprops/action-gh-release
The set of build jobs is automatically filtered to match the platforms declared in native_prebuilt.yaml.
Releasing #
git tag my_package-v1.0.0
git push origin my_package-v1.0.0
This triggers prebuilt.yml which builds, generates the manifest, and publishes release assets.
GitLab CI #
dart run native_prebuilt workflow init --gitlab
Generates into .gitlab-ci.yml and .gitlab/ci/:
| File | Purpose |
|---|---|
.gitlab-ci.yml |
Root pipeline — stages platform build jobs, manifest generation, and release |
.gitlab/ci/native-prebuilt-build-<platform>.yml |
Per-platform build job |
.gitlab/ci/native-prebuilt-release.yml |
Uploads release assets to GitLab Generic Package Registry |
.gitlab/ci/native-prebuilt-update-manifest.yml |
Generates manifest and release archives |
Platforms are determined by the artifacts: section in native_prebuilt.yaml. Only declared platforms get build jobs.
GitLab release source #
For packages hosted on GitLab, set the release source in native_prebuilt.yaml:
release:
provider: gitlab
project: mygroup/myproject
tag: my_package-v1.0.0
Releasing on GitLab #
git tag my_package-v1.0.0
git push origin my_package-v1.0.0
This triggers the GitLab pipeline which builds, generates the manifest, and uploads release assets to the GitLab Generic Package Registry.
Local overrides #
Resolution chain: user_defines → .prebuilt/ directory → shared cache →
download from release. The first hit wins.
Option 1: hooks.user_defines (per-project override) #
In your consumer package's pubspec.yaml:
hooks:
user_defines:
my_package:
prebuilt_path: /absolute/path/to/libmy_package.so
The key is the package name. prebuilt_path is the default key read by
UserDefinePrebuiltResolver. If the file exists, it is used directly.
Option 2: .prebuilt/ directory (per-build override) #
Drop a library into .prebuilt/<platform>/ next to your pubspec.yaml:
my_package/
.prebuilt/
linux-x64/
libmy_package.so
windows-x64/
my_package.dll
macos-arm64/
libmy_package.dylib
pubspec.yaml
The subdirectory must match the platform label from your manifest (e.g.
linux-x64, macos-arm64). LocalPrebuiltResolver picks up any matching
library without extra config.
Platforms #
Platforms are declared in native_prebuilt.yaml under artifacts:. Each key is
a platform label in the form <os>-<arch> (e.g. linux-x64, macos-arm64,
windows-x64).
Minimal form — archive and payload.type default automatically:
artifacts:
linux-x64:
linux-arm64:
macos-arm64:
Defaults:
archive→<package>-<platform>.tar.gz(e.g.my_package-linux-x64.tar.gz)payload.type→dynamic_library
Explicit form when you need custom names:
artifacts:
linux-x64:
archive: custom-name-linux-x64.tar.gz
payload:
type: dynamic_library
linux-arm64:
archive: custom-name-linux-arm64.tar.gz
payload:
type: static_library
Platform labels use the format <os>-<arch> and follow the values defined in
package:code_assets (OS
and Architecture).
Any combination supported by code_assets is valid (e.g. linux-x64,
linux-arm64, linux-riscv64, macos-arm64, android-arm64, ios-arm64,
windows-x64, etc.).
Adding a platform #
Add an entry to artifacts: and regenerate:
artifacts:
linux-x64:
archive: my_package-linux-x64.tar.gz
payload:
type: dynamic_library
linux-arm64: # ← new
archive: my_package-linux-arm64.tar.gz
payload:
type: dynamic_library
Then regenerate workflows and the manifest:
dart run native_prebuilt workflow init --config native_prebuilt.yaml
dart run native_prebuilt manifest update \
--config native_prebuilt.yaml \
--output lib/src/hook/my_package_prebuilts.g.dart \
--built-library-dir .dart_tool/lib
The regenerated prebuilt.yml will automatically include a build-linux-arm64
job. GitLab CI will include a native-prebuilt-build-linux.yml job.
Removing a platform #
Delete the entry from artifacts: and regenerate. The build job for that
platform will be removed from the CI configs.
Filtering platforms at generation time #
Use --platform to scaffold only a subset of the declared platforms:
dart run native_prebuilt workflow init --platform linux --platform macos
This is useful when you only want to generate CI for the platforms you can
test locally. The manifest still contains all platforms — --platform only
affects which CI jobs are generated.
Source build helpers #
sourceFallback.builder can invoke any build system. Common patterns are C and
Rust builds, but you can also call a custom script.
CBuilder (C/C++) #
Uses native_toolchain_c:
import 'package:hooks/hooks.dart';
import 'package:native_prebuilt/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:my_package/src/hook/my_package_prebuilts.g.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
final cBuilder = CBuilder.library(
name: 'my_package',
packageName: input.packageName,
assetName: 'src/my_package.dart',
sources: const ['src/native/my_package.c'],
);
await PrebuiltCodeAssetBuilder(
assetName: 'src/my_package.dart',
libraryStem: 'my_package',
manifest: myPackagePrebuilts,
linkModeResolver: (code) => DynamicLoadingBundled(),
sourceFallback: SourceFallback(
sources: [LocalSource(paths: ['.'])],
builder: CallbackSourceBuilder(
callback: ({
required source,
required input,
required output,
required logger,
}) async {
await cBuilder.run(input: input, output: output, logger: logger);
},
),
),
).run(input: input, output: output, logger: Logger.root);
});
}
RustBuilder #
Uses native_toolchain_rust.
Requires a Cargo.toml with crate-type = ["staticlib", "cdylib"] and a
rust-toolchain.toml pinned to a specific version.
import 'package:hooks/hooks.dart';
import 'package:native_prebuilt/hooks.dart';
import 'package:native_toolchain_rust/native_toolchain_rust.dart';
import 'package:my_package/src/hook/my_package_prebuilts.g.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
final rustBuilder = RustBuilder(
assetName: 'src/my_package.dart',
);
await PrebuiltCodeAssetBuilder(
assetName: 'src/my_package.dart',
libraryStem: 'my_package',
manifest: myPackagePrebuilts,
linkModeResolver: (code) => DynamicLoadingBundled(),
sourceFallback: SourceFallback(
sources: [LocalSource(paths: ['.'])],
builder: CallbackSourceBuilder(
callback: ({
required source,
required input,
required output,
required logger,
}) async {
await rustBuilder.run(input: input, output: output, logger: logger);
},
),
),
).run(input: input, output: output, logger: Logger.root);
});
}
Notes #
- Use
release.provider: gitlabandrelease.projectfor GitLab release assets. workflow init --platform ...is repeatable; omit it to scaffold the platforms declared in the manifest.- Generated GitHub workflows derive filenames and tag prefixes from
package:innative_prebuilt.yaml. - Generated GitLab scaffolds stage built libraries in
built-library/and use the manifest to decide which platform jobs to emit. - The package exports
OS,Architecture, andIOSSdkfromcode_assets. - The cache and installer are designed for repeated hook runs and concurrent builds.