Skip to content

Floresta distros: declarative, per-host build matrix - #66

Merged
jaoleal merged 8 commits into
getfloresta:masterfrom
jaoleal:organize-to-tag
Sep 29, 2026
Merged

jaoleal merged 8 commits into
getfloresta:masterfrom
jaoleal:organize-to-tag

Conversation

@jaoleal

@jaoleal jaoleal commented Sep 29, 2026

Copy link
Copy Markdown
Member

Until now, the flake exported one ad-hoc output per release (.#master, .#v0_9_1, …), with Android bolted onto master by a separate module (android-outputs.nix) and its own toolchain (fenix). Adding a new target meant new wiring in flake.nix, CI and the attestation code. Nothing was shared between releases and targets except by accident.

This PR adds tooling to declare Floresta distributions (distros). A distro is florestad + floresta-cli built for one place to run, from one release, by one build host. Each distro spells out:

  • the build host: which packages.<host> lists it;
  • the target: the package set it builds in (native pkgs or a pkgsCross.* set) and the Rust triple;
  • the linkage: dynamic by default, or static (checked by an installCheck);
  • the patches the target needs, as reusable modules (e.g. lib/patches/android-patches.nix).

Every distro goes through the same builder, so they share one dependency graph:

  • Toolchain: built from rust-overlay, reading each release's rust-toolchain.toml.
  • Dependencies: vendored crates are fetched as separate per-crate derivations, so releases pinning the same crate versions share them.
  • Workspace build: florestad, floresta-cli and libfloresta build in one cargo invocation, so libbitcoinkernel compiles once per build.

Because of this, adding a distro or a release only adds entries: it reuses what is already built and cached instead of adding new wiring.

What changes

Build library

  • floresta-build: packageName becomes packageSet, a list of components built in one cargo run. The per-target patches (buildPhase, installPhase, extraEnvVars, extraBuildInputs) become mkFloresta options instead of import-time arguments.
  • lib/mkFlorestaDistro.nix (new):
    • mkFlorestaDistro validates a distro spec with the module system and builds it.
    • mkFlorestaDistroForEachTag releases (release: { … }) stamps out one package per release.
    • Both are exposed as lib.mkFlorestaDistro / lib.targetsFor.

Outputs

  • lib/targets.nix is the single catalogue: the releases (an ordered list, so releasesSince "0.10.0" is a range) and, per host, the distros it builds. Packages are named florestad-<distro>-v<release>, e.g. florestad-aarch64-darwin-v0_9_1.

  • Current matrix:

    Distro Built on Releases
    x86_64-linux (dynamic, glibc) x86_64-linux 0.9.0, 0.9.1, 0.10.0
    aarch64-linux (dynamic, glibc) aarch64-linux 0.9.0, 0.9.1, 0.10.0
    aarch64-darwin aarch64-darwin 0.9.0, 0.9.1, 0.10.0
    aarch64-android (NDK) x86_64-linux 0.10.0
  • Naming convention: a distro is dynamic unless its name ends in -static. Only static binaries run outside Nix/NixOS, because dynamic ones load their interpreter and libraries from /nix/store.

CI and release

  • ci.yml reads the matrix off nix eval .#packages and builds each package on its host's runner, so a new distro or release needs no workflow edit. It absorbs android.yml.
  • build-and-attest.yml builds one release on every host (x86_64-linux, aarch64-linux, aarch64-darwin) and names artifacts <file>-<rustTarget>, matching the attestation manifests.
  • just build <distro> <release> builds one package. just build-and-package-all [release] collects every distro (or one release) into artifacts/<release>/, and fails if an existing artifact's bytes differ.

Breaking changes

  • The outputs .#master, .#v0_9_0, .#v0_9_1 and .#attestation-manifest-* are gone. Manifests now live at legacyPackages.<host>.attestation-manifests."<version>".
  • The floresta-master and fenix inputs are removed, along with the update-master sync workflow.
  • The v0.9.0 and v0.9.1 attestations in contrib/sigs/ are dropped. They signed the old workspace builds, which the distro manifests no longer reproduce, so these releases need to be re-attested.
  • x86_64-darwin no longer has packages (see below).

Not in this PR / known issues

  • Static musl Linux (x86_64-linux-static) is written out but commented: rustc links it as static-pie, and pkgsStatic's libstdc++.a (pulled in by libbitcoinkernel-sys) isn't built with -fPIE.
  • Other ABIs: armv7a-android, x86_64-android and x86_64-darwin are commented out until they've been built once.
  • Releases are currently fetched from jaoleal/FlorestaBA, and 0.10.0 points at v0.10.0-preview. That tag's Cargo.toml still says 0.9.0, so its store paths are named …-0.9.0. This is temporary until we have 0.10.0 on upstream floresta.

Testing

  • CI built every package in the matrix on its native runner (x86_64-linux, aarch64-linux, aarch64-darwin), including the Android distro.
  • nix flake check and nix flake check ./examples --no-build pass.
  • The refactor keeps the derivations stable: aarch64-darwin and Android drvPaths are unchanged by the mkFlorestaDistroForEachTag rewrite.

packageName becomes packageSet: a list of florestad / floresta-cli /
libfloresta built in one cargo invocation, so the shared dependency graph
(libbitcoinkernel included) compiles once. The default is florestad and
floresta-cli; the package is named after its component when it has one,
floresta otherwise, and runs as its first binary.

The import-time patch arguments become mkFloresta options: extraEnvVars,
buildPhase and installPhase (each replacing its cargo hook), and the
Android SDK goes through extraBuildInputs. android-outputs.nix passes
them per build; the examples follow the packageSet name.
A distro is floresta-build specialised for one place to run: one release,
one package set, one Rust target. mkFlorestaDistro validates a distro spec
(name, description, release, pkgs, static, rustTarget, toolchain,
mkFloresta) with the module system and builds it through floresta-build.

`pkgs` is the package set the build runs in (a pkgsCross set for cross
builds); rustTarget defaults to its triple. Static distros are checked by
an installCheck. The toolchain comes from rust-overlay: the release's
rust-toolchain.toml, stable when absent, carrying rust-std for the target.
mkFloresta takes floresta-build's options, and patch modules read the
distro's pkgs and rustTarget as module arguments.

mkFlorestaDistroForEachTag builds one distro from each release of a list,
the spec a function of the release, named florestad-<distro>-v<release>.

The flake's pkgs now carry the rust-overlay overlay, and
lib.mkFlorestaDistro exposes the builder per system.
…ost)

Replace the master/v0_9_x release outputs with distros: florestad and
floresta-cli built for one place to run, from one release, as one host
builds it.

lib/targets.nix lists every package, per host, with
mkFlorestaDistroForEachTag: packages.<host>.florestad-<distro>-v<release>.
A host lists a distro only after that combination was built once.
Releases are a list, oldest first, so releasesSince "0.10.0" is a range;
0.9.0, 0.9.1 and 0.10.0 (v0.10.0-preview) are fetched from
jaoleal/FlorestaBA.

Distros: x86_64-linux and aarch64-linux (native, dynamic, glibc),
aarch64-darwin, and aarch64-android from 0.10.0 (x86_64-linux). A distro
is dynamic unless its name says -static; only static binaries run outside
Nix or NixOS.

The static musl x86_64-linux-static distro (from x86_64-linux and
aarch64-linux) is written out but commented: rustc links it as static-pie, and
pkgsStatic's libstdc++.a, which libbitcoinkernel-sys links, is not built
with -fPIE.

lib/patches/android-patches.nix is the NDK build as a mkFloresta module,
reading the distro's pkgs and rustTarget as module arguments. It replaces
lib/android-outputs.nix, and with it the fenix input.

`packages` holds the whole matrix for the host; the `master` release and
the floresta-master input are gone. Attestation manifests move to
legacyPackages.<host>.attestation-manifests."<version>", grouping the
host's packages by release; `nix run .#attest` builds them from there.
lib.targetsFor exposes the matrix per system.
They covered the old workspace builds, which are gone: the distro
manifests of those releases no longer reproduce them.
The floresta-master input is gone, so there is nothing for the
update-master workflow to bump, nor for dependabot to leave to it.
ci.yml evaluates packages.<host> for every host and builds each package
on that host's runner, so a distro or release added to lib/targets.nix is
built with no edit here. It absorbs android.yml.

build-and-attest publishes one release, taken from the dispatch input or
the pushed tag, building each host's packages of that release and naming
artifacts <file>-<rustTarget> as lib/attestation.nix does, on
x86_64-linux, aarch64-linux and aarch64-darwin. One host per distro: two
copies of one artifact cannot both be published, so only one Linux host
may build x86_64-linux-static once it is enabled.
`just build <distro> <release>` builds one package, and
build-and-package-all builds every distro this host produces, of every
release or only the one given, into artifacts/<release>/ named
<file>-<triple>. An artifact already there is compared, never
overwritten: the recipe fails if the build's bytes differ.
@jaoleal
jaoleal merged commit c581721 into getfloresta:master Sep 29, 2026
13 checks passed
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.

1 participant