Skip to content

Conversation

@BitcoinZavior
Copy link

@BitcoinZavior BitcoinZavior commented Jan 18, 2026

Precompiled Binaries Feature

Resolves #20
This PR introduces a comprehensive precompiled binaries system that significantly improves the developer experience by reducing build times and providing secure, signed artifacts.

Key Features

Secure Distribution

  • Ed25519-signed precompiled binaries with public key verification
  • Automated signing and verification pipeline
  • Secure artifact hosting via GitHub Releases

Performance Improvements

  • Skip local Rust compilation for supported platforms
  • Automatic fallback to local builds when precompiled binaries unavailable
  • Configurable modes: auto, always, or never

Developer Experience

  • Zero-configuration setup for most users
  • Comprehensive CLI tooling for maintainers
  • Detailed documentation and troubleshooting guides

Configuration

Users can configure precompiled binaries in their pubspec.yaml:
Defaulting to auto which most users would want.

bdk_dart:
  precompiled_binaries:
    mode: auto  # auto | always | never

@reez
Copy link
Collaborator

reez commented Jan 22, 2026

This is looking nice, added a bunch of comments/questions, and if you can run dart format to fix the ci failure too.

I’ll set up the signing secret later, let me know if any other maintainer setup is needed.

on:
push:
branches: [main, "**"]
branches: [main]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you remove ** to reduce duplicate CI runs on branch pushes?

Copy link
Author

@BitcoinZavior BitcoinZavior Jan 22, 2026

Choose a reason for hiding this comment

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

Yes, but this is something you can assess and configure.

GITHUB_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
Copy link
Collaborator

Choose a reason for hiding this comment

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

This uses toolchain: stable, but native/rust-toolchain.toml pins 1.85.1 and the CLI reads that channel when running rustup run. Should we switch to toolchain-file: native/rust-toolchain.toml (or set toolchain: 1.85.1) so precompiled artifacts are built with the pinned toolchain? Same applies to the Android job.

Copy link
Author

Choose a reason for hiding this comment

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

We switched both precompile jobs from stable to the pinned 1.85.1 toolchain. Pinning to 1.85.1 keeps a single source of truth and avoids subtle build/compatibility drift.

README.md Outdated
```

`mode` controls when the precompiled path is used:
- `auto` prefers precompiled binaries when available, otherwise builds locally
Copy link
Collaborator

Choose a reason for hiding this comment

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

auto is described as preferring precompiled, but the hook currently disables precompiled when rustup is present (UserOptions.defaultUsePrecompiledBinaries). Should we update this wording to reflect the rustup heuristic, or change the behavior to match the doc

Copy link
Author

Choose a reason for hiding this comment

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

I suggest updating the wording in README.md. It now explicitly states that auto prefers local builds when the Rust toolchain (rustup) is detected; otherwise, it uses precompiled binaries.

- CI builds and uploads precompiled binaries via `.github/workflows/precompile_binaries.yml`.
- Artifacts are tagged by the crate hash and uploaded to a GitHub release.
- Each binary is signed with an Ed25519 key; the public key is embedded in `pubspec.yaml`.
- The build hook tries to download a verified binary first and falls back to a local build if needed.
Copy link
Collaborator

Choose a reason for hiding this comment

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

The doc says ‘download verified binary first and fall back’; should we note that mode: always is intended to disable fallback (once that behavior is enforced), or explicitly call out the rustup heuristic for auto?

Copy link
Author

Choose a reason for hiding this comment

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

Added a “Mode behavior” section. It clarifies that always currently falls back but is intended to fail if a verified binary isn’t present, and reiterates how never operates.

);
}

if (precompiled.mode == PrecompiledBinaryMode.never) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

mode: always currently just returns null on download/verify failure, which triggers fallback in PrecompiledBuilder. Should always fail the build instead so it truly requires precompiled binaries?

Copy link
Author

Choose a reason for hiding this comment

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

lib/src/precompiled/artifacts_provider.dart now tracks when mode == PrecompiledBinaryMode.always and throws PrecompiledBinaryRequiredException on any download/verification failure instead of returning null. Since PrecompiledBuilder does not catch that exception, the build fails rather than falling back whenever mode=always cannot obtain a signed artifact—so the builder enforces the “require precompiled” promise.

exitCode = 2;
return;
}
if (repository == null || repository.trim().isEmpty) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

--repository is required but never used in any gh release calls; should we pass --repo $repository to view/create/upload (or drop the flag if it’s not needed)?

Copy link
Author

Choose a reason for hiding this comment

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

Forgot to call it out earlier: --repository was left unused in the release commands by mistake, but we’ve since wired it back in. This lets us rerun the tool against another repo (e.g., for testing or experimentation) without editing the workflow. In production it still defaults to the current repo, so the rest of the release flow behaves as before.

'--package',
cratePackage,
'--release',
'--locked',
Copy link
Collaborator

Choose a reason for hiding this comment

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

We pass --locked to cargo, but there’s no committed native/Cargo.lock in this repo; cargo build --locked will fail without it. Should we add native/Cargo.lock or remove --locked?

Copy link
Author

Choose a reason for hiding this comment

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

Lock file added and .gitignore updated to allow lock file changes.

final output = AccumulatorSink<Digest>();
final input = sha256.startChunkedConversion(output);

void addTextFile(File file) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We concatenate lines from multiple files without a delimiter or filename marker which could (rarely) lead to hash collisions if file boundaries change. Should we consider prefixing each file with its path (or a separator) before hashing to make the hash more robust?

Copy link
Author

Choose a reason for hiding this comment

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

That scenario is extremely rare isn't it? we would have to arrange contents to collide via a prefix/suffix shift. I believe a lightweight fix ( a separator before its contents) should be sufficient.

);

final downloaded = await provider.tryGetPrecompiledArtifact();
if (downloaded != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we enforce mode: always, should we be surfacing a hard failure here (or in PrecompiledArtifactProvider) so the build doesn’t silently fall back when always is set?

Copy link
Author

@BitcoinZavior BitcoinZavior Jan 24, 2026

Choose a reason for hiding this comment

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

PrecompiledArtifactProvider now throws PrecompiledBinaryRequiredException on any download/verify failure when mode == always. Since PrecompiledBuilder already propagates provider exceptions, the build will now fail fast instead of silently falling back—honoring the “require precompiled binaries” promise.

@BitcoinZavior
Copy link
Author

Thanks for the review 🧡 PR updated to address review comments and to provide more info.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support prebuilt binaries as default (with opt-in source builds)

2 participants