Skip to content

[prebuilt_code_assets] Add package:prebuilt_code_assets - #3719

Draft
mosuem wants to merge 6 commits into
ntc-windows-export-defined-symbolsfrom
add-prebuilt-code-assets
Draft

mosuem wants to merge 6 commits into
ntc-windows-export-defined-symbolsfrom
add-prebuilt-code-assets

Conversation

@mosuem

@mosuem mosuem commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Adds package:prebuilt_code_assets (currently at mosuem/prebuilt_code_assets) to this repository as pkgs/prebuilt_code_assets.

The package covers the boilerplate that packages shipping prebuilt native libraries (like package:icu4x and package:boring) currently each write by hand in their hooks:

  • hook/build.dart: fetches the prebuilt dynamic or static library for the target from a release (e.g. GitHub Releases) or a pub-bundled prebuilt/ directory, verifies its SHA-256, and caches it. Alternatively, builds from source through a toolchain-agnostic callback (CBuilder, CMake, Cargo, ...), or bundles a local binary. Selected via hooks.user_defines.<package>.buildMode (fetch/build/local).
  • hook/link.dart: tree-shakes the static library with CLinker down to the functions in recordedUses, and falls back to the prebuilt dynamic library if linking fails (e.g. no C toolchain) in treeshake: auto mode.
  • Maintainer tools: runPrecompileBinariesCli and runRegenerateHashesCli for producing release binaries and the hash manifest.

Changes in this PR:

  • pkgs/prebuilt_code_assets, at 0.2.0-wip (see its CHANGELOG for the breaking changes since 0.1.2 on pub.dev), with resolution: workspace.
  • Adds the package to the pub workspace, the native.yaml CI path filters, the labeler, the PR title prefixes, and the README.

The history of the package is not preserved (single commit). It's relicensed to BSD-3-Clause with Dart project authors headers in the source repository before the import.

Stacked on #3726, with which native_toolchain_c only exports symbols that the input archives define on Windows. Together with #3718 (merged), this lets this package drop its own COFF archive parsing and Windows command-line length workaround. Requires native_toolchain_c 0.19.6.

PR Checklist

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
  • I've run dart tool/ci.dart --all locally and resolved all issues identified. (Ran the --license --format --dependency-validator --workspace tasks, dart analyze --fatal-infos, and the package tests.)
  • All existing and new tests are passing. I added new tests to check the change I am making. (56 tests; CI in the source repository passes on Linux, macOS, and Windows with Dart 3.13.0 and stable.)
  • The PR is actually solving the issue.
  • I have updated CHANGELOG.md for the relevant packages.
  • I have updated the pubspec package version if necessary.

@mosuem
mosuem added this pull request to stack #3720 September 30, 2026 14:32
@github-actions github-actions Bot added the type-infra A repository infrastructure change or enhancement label Sep 30, 2026
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

PR Health

Breaking changes ✔️
Package Change Current Version New Version Needed Version Looking good?
prebuilt_code_assets Breaking 0.1.2 0.2.0-wip 0.2.0-wip ✔️

This check can be disabled by tagging the PR with skip-breaking-check.

API leaks ✔️

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

Package Leaked API symbol Leaking sources

This check can be disabled by tagging the PR with skip-leaking-check.

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

@dcharkes dcharkes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @goderbauer @liamappelbe

I really like the one PrebuiltLibrary object that you use from hook/build.dart hook/link.dart and tool/prebuilt.dart!

However, I'm not yet sure that we have considered enough the alternative ways of doing things to be shipping this package as Dart team endorsed, signaling it's the best way.

  • Which decisions this package makes are opinionated and could be made differently?
  • Is the API is flexible enough to accommodate a bunch of alternative ways to do prebuilt libraries.

One thing that is very baked in to this design is this idea that the hook downloads from a third-party CDN. I think that's not the right way.

  • For small dylibs, for now the preferred way of doing things is to bundle them in the package tar (@jonasfj @sigurdm)
    • This avoids having to compute hashes for downloads, pub takes care of this.
    • This avoids downloading from a third-party CDN which can be down or of which we can violate fair-use-policies by pulling things very often on CI runs.
  • For larger dylibs, we ideally want a larger package limit in the short term. And a way to lazily download individual artifacts with provenance checked via a pub API and the artifacts stored in the pub cache.
    • This would limit bandwidth use.
    • And enable sharing of binaries across projects on disk.

How to use the current package for uploading your dylibs inside the package tar?

If we add support to pub for downloading invidual binary blobs and putting them in the pub cache, how does that change the API?

  1. The flow for how to publish your package might change. E.g. How do you declare the individual binary blobs for dart pub publish.
  2. The download flow might change. E.g. We will probably have some pub-team owned package that has an async API that returns you a file path on disk in the pub cache for your downloaded artifact after you asked for it. Then pub will do the hashes checking so you can simply rely on that.

I don't feel comfortable shipping this package as dart interop team endorsed until we explore the design space of precompiled dylibs more.

return symbols;
}

/// Returns the subset of [candidateSymbols] that the COFF [archive] defines.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this kind of logic be in package:native_toolchain_c somewhere?

@sigurdm

sigurdm commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

I agree with @dcharkes that we should think through how this fits with pub caching and signed package provenance before locking in the public API.

With the package attestation work landing in pub (dart-lang/pub#4875) and setup-dart (dart-lang/setup-dart#199), there is a natural design that works with GitHub Releases today and gives us a clean path to hosting blobs on pub.dev later without changing how hooks are written:

  1. Include prebuilt binaries as subjects in the package's Sigstore attestation bundle (instead of a generated hashes.dart):
    Even if a package tarball is signed with SLSA provenance, committing a generated hashes.dart only proves that the hash list was in the git repo—not that the binaries on GitHub Releases were actually built from that commit in CI.
    In a Sigstore bundle, the signature is over the JSON statement, whose subject array can list multiple artifacts (package.tar.gz plus every prebuilt binary from the matrix build) while still allowing verification of individual artifacts. If the publishing workflow passes both package.tar.gz and the built binaries to actions/attest, the single attestation bundle uploaded to pub.dev with the package already maps every binary filename to its sha256, making runRegenerateHashesCli / hashes.dart unnecessary.

  2. Store verified binaries in $PUB_CACHE via a dart pub plumbing command:
    Currently fetch.dart caches in .dart_tool/hooks_runner/shared/, which means binaries are re-downloaded per workspace and aren't covered by standard CI $PUB_CACHE caching. At the same time, hooks shouldn't mutate $PUB_CACHE directly (concurrency, read-only caches).
    Instead, dart pub get already verifies the package's attestation bundle once and can record the verified subject map (name -> sha256) in $PUB_CACHE. pub can then expose a command (e.g. dart pub cache fetch-blob or similar) that:

    • Checks if $PUB_CACHE/blobs/sha256/<hash>/<fileName> already exists (a fast-path existence check needs no re-hashing if pub only moves files into place after verification).
    • On a miss, looks up the expected sha256 from the package's verified provenance in $PUB_CACHE, downloads the binary, verifies its sha256 digest, and moves it into $PUB_CACHE/blobs/sha256/<hash>/<fileName>.
    • Returns the path in $PUB_CACHE.

    Doing this inside pub also avoids a bootstrap cycle where a hook would otherwise need package:sigstore (and its own libsigstore_ffi binary) to verify attestations. And because the package's provenance statically lists all valid sha256 digests for that version, dart pub cache gc can trace active packages via active_roots and garbage-collect unused binaries automatically.

  3. Path to hosting blobs on pub.dev later:
    If PrebuiltLibrary / PrebuiltReleaseConfig doesn't hardcode fileHashes + URL construction at the top level, and instead delegates "give me the verified local file for target asset X" to an abstraction (which initially shells out to pub or reads the attestation bundle + a download URL template), then if/when pub.dev supports uploading sidecar binary blobs directly, pub can switch to fetching from PUB_HOSTED_URL transparently without breaking hooks using package:prebuilt_code_assets.

Concretely for this PR: could we decouple PrebuiltLibrary from the hashes.dart / runRegenerateHashesCli workflow, so we don't have to deprecate fileHashes and tools.dart once pub lands binary provenance + cache support?

@dcharkes

dcharkes commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Another high level comment after discussion with @goderbauer and @liamappelbe, this package might not fit on this repo:

  • The dart-lang/native repo provides the interop building blocks.
  • The dart-lang/pub repo provides packaging/CDN building blocks (which you can use or not depending on whether you prefer to host binaries somewhere else)
  • The package in this PR builds on top of those building blocks a higher level workflow.

Maybe this package would fit better in the dart-lang/ecosystem repo.

We on dart-lang/native we are actively trying to have less packages, and less maintenance burden. (We'd love for package:native_toolchain_c to be community owned. It's a higher level building block that we as interop team don't need to own. We own enough lower level building blocks (the hooks and code_assets) to make native_toolchain_c by the community possible. We would prefer to not have to have ownership of this package.

(My previous comments still apply, whether we host this in dart-lang/native or somewhere else.)

Base automatically changed from ntc-windows-treeshake to main October 2, 2026 09:33
@mosuem
mosuem force-pushed the add-prebuilt-code-assets branch from 524d272 to 1947841 Compare October 2, 2026 09:33
@codecov

codecov Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.53012% with 87 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.85%. Comparing base (c684cbd) to head (1cebc59).

Files with missing lines Patch % Lines
...prebuilt_code_assets/lib/src/prebuilt_library.dart 83.42% 31 Missing ⚠️
...code_assets/lib/src/tools/precompile_binaries.dart 55.10% 22 Missing ⚠️
...t_code_assets/lib/src/tools/regenerate_hashes.dart 80.00% 17 Missing ⚠️
.../prebuilt_code_assets/lib/src/source_builders.dart 0.00% 8 Missing ⚠️
pkgs/prebuilt_code_assets/lib/src/targets.dart 50.00% 4 Missing ⚠️
pkgs/prebuilt_code_assets/lib/src/fetch.dart 96.47% 3 Missing ⚠️
...s/prebuilt_code_assets/lib/src/release_config.dart 83.33% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                          Coverage Diff                           @@
##           ntc-windows-export-defined-symbols    #3719      +/-   ##
======================================================================
- Coverage                               87.96%   87.85%   -0.11%     
======================================================================
  Files                                     311      321      +10     
  Lines                                   19849    20353     +504     
======================================================================
+ Hits                                    17461    17882     +421     
- Misses                                   2388     2471      +83     
Flag Coverage Δ
native_pkgs_macos 86.46% <82.32%> (-0.23%) ⬇️
native_pkgs_ubuntu 73.17% <82.12%> (+0.74%) ⬆️
native_pkgs_windows 75.92% <82.12%> (+0.44%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
code_assets 94.96% <ø> (ø)
data_assets 91.56% <ø> (ø)
ffi 100.00% <ø> (ø)
ffigen 91.58% <ø> (ø)
hooks 86.69% <ø> (+0.29%) ⬆️
hooks_runner 89.72% <ø> (ø)
jni 66.34% <ø> (ø)
jni_flutter ∅ <ø> (∅)
jni_util 25.00% <ø> (ø)
jnigen 82.85% <ø> (ø)
json_syntax_generator ∅ <ø> (∅)
native_test_helpers 85.71% <ø> (ø)
native_toolchain_c 96.27% <ø> (+0.19%) ⬆️
objective_c 79.53% <ø> (ø)
pub_formats ∅ <ø> (∅)
record_use 81.48% <ø> (ø)
snippet_tool 66.23% <ø> (ø)
swift2objc 92.95% <ø> (ø)
swiftgen 80.41% <ø> (ø)
test_case_selector 97.28% <ø> (ø)
Files with missing lines Coverage Δ
...gs/prebuilt_code_assets/lib/src/build_options.dart 100.00% <100.00%> (ø)
pkgs/prebuilt_code_assets/lib/src/logging.dart 100.00% <100.00%> (ø)
...prebuilt_code_assets/lib/src/symbols_resolver.dart 100.00% <100.00%> (ø)
...s/prebuilt_code_assets/lib/src/release_config.dart 83.33% <83.33%> (ø)
pkgs/prebuilt_code_assets/lib/src/fetch.dart 96.47% <96.47%> (ø)
pkgs/prebuilt_code_assets/lib/src/targets.dart 50.00% <50.00%> (ø)
.../prebuilt_code_assets/lib/src/source_builders.dart 0.00% <0.00%> (ø)
...t_code_assets/lib/src/tools/regenerate_hashes.dart 80.00% <80.00%> (ø)
...code_assets/lib/src/tools/precompile_binaries.dart 55.10% <55.10%> (ø)
...prebuilt_code_assets/lib/src/prebuilt_library.dart 83.42% <83.42%> (ø)

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

mosuem added 3 commits October 2, 2026 10:19
…ls on Windows

With the native_toolchain_c fix below in the stack, LinkerOptions.treeshake
no longer passes an /INCLUDE flag per symbol, so the command-line length
fallback to a hand-written .def file is no longer needed.
native_toolchain_c now only exports the symbols that the input archives
define on Windows. Without recorded uses, export allKnownSymbols on Windows,
or bundle the prebuilt dynamic library in fetch mode if that is null too.
@mosuem
mosuem force-pushed the add-prebuilt-code-assets branch from 1947841 to d77a88d Compare October 2, 2026 10:25
@mosuem
mosuem removed this pull request from stack #3720 October 2, 2026 10:26
@mosuem
mosuem changed the base branch from main to ntc-windows-export-defined-symbols October 2, 2026 10:26
@mosuem
mosuem added this pull request to stack #3727 October 2, 2026 10:26
Without `usedSymbols`, the library is never tree-shaken: `build` bundles
the dynamic library directly instead of routing a static library to the
link hook.

Without recorded uses (record use disabled), nothing can be tree-shaken
either. Then `link` bundles the prebuilt dynamic library in the `fetch`
build mode. In other build modes, it links the static library keeping all
functions, except on Windows, where a DLL only exports the functions it
lists, so it throws a `BuildError` suggesting to enable record use or to
set `treeshake: off`.

`treeshake: on` fails the build in both cases, because tree-shaking is not
possible.
mosuem added 2 commits October 5, 2026 12:50
CI runs the tests from the workspace root, where `Directory.current` is
not the package root, so the warm-up `pub get` failed to resolve the
`prebuilt_code_assets` path dependency. Use `findPackageRoot` from
`package:native_test_helpers` like the other packages in this repository.
…unctions

The docs and messages said "no recorded uses" for two different cases:

- Record use is disabled (`input.recordedUses` is `null`): it is unknown
  which functions the application uses. They now say "record use is
  disabled".
- The application uses none of the functions: `link` now explicitly
  bundles no library and returns before setting up the linker, instead of
  relying on `CLinker` skipping an empty `symbolsToKeep`. This also holds
  with `treeshake: on`.

Adds unit tests for the second case on Linux and Windows in the `auto` and
`on` modes, and an integration test that a `dart build cli` app that calls
none of the functions runs without the library.
user_defines:
my_package:
# 'fetch' (default), 'build' (alias 'checkout'), or 'local'
buildMode: fetch

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
buildMode: fetch
build_mode: fetch

What style do we want here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-infra A repository infrastructure change or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants