Add Guix-based reproducible build pipeline under contrib/guix#1
Closed
kim0 wants to merge 15 commits into
Closed
Conversation
added 14 commits
May 17, 2026 15:32
- channels.scm: pinned commit 2f4f5d74fb... does not exist upstream
(404 on both git.savannah.gnu.org and codeberg). Repin to the
v1.5.0 tag commit 230aa373f3..., which actually exists.
- manifest.scm: there is no standalone `rust-cargo` Guix package.
In Guix v1.5.0 the rust package has outputs ("out" "cargo"), so
the right spec is `rust:cargo`. `rust-cargo` would fail
`guix shell -m manifest.scm` with "no such package".
- libexec/patch-randomx.sh:
* regenerate `.cargo-checksum.json` after rewriting build.rs;
cargo --frozen verifies vendored-crate file hashes and would
otherwise refuse with "checksum has changed".
* collapse the duplicated ARCH/DARCH branches into one replacement
string (the old DARCH branch wrote `"ARCH"` but the surrounding
comment said DARCH — confusing; net effect was correct because
upstream only has DARCH today, but the script would silently
double-emit `"ARCH"` if upstream ever adds a real ARCH define).
* treat an empty RANDOMX_ARCH / RANDOMX_DARCH as unset so the
"default" fallback actually fires (guix-build exports
RANDOMX_ARCH="" by default; env::var on an empty var returns
Ok("") which previously skipped the or_else fallback).
* fail the patch if neither ARCH nor DARCH define is found,
instead of silently producing an unpatched vendored crate.
guix time-machine v1.5.0 refuses to authenticate a channel that lacks an (introduction ...) field when the underlying git repo carries a .guix-authorizations file (the official Guix repo does). Without this, the canonical error is: guix time-machine: error: channel 'guix' lacks an introduction and cannot be authenticated The values added are the canonical introduction commit + OpenPGP fingerprint for the official Guix channel as documented upstream (guix/channels.scm: %default-channels).
`guix shell --container --pure` mounts only the manifest's profile bin on PATH; it does NOT provide /usr/bin/env or /bin/bash, so the `#!/usr/bin/env bash` shebangs on mk-distsrc / build.sh / package.sh / patch-randomx.sh make those scripts fail with 'command not found' when guix-shell tries to execve them as a direct path. Fix by invoking the inner scripts via the `bash` interpreter (which IS in PATH via the manifest's bash package), bypassing the shebang. Four call sites need this: guix-mk-distsrc invokes mk-distsrc guix-build invokes libexec/build.sh mk-distsrc invokes libexec/patch-randomx.sh libexec/build.sh invokes libexec/package.sh Outside of guix-shell --container the original shebang still works unchanged on any normal Linux host with /usr/bin/env.
`guix shell --container --pure` mounts the host worktree with stat metadata that differs from what the index recorded on the host, so `git diff-index --quiet` falsely reports the tree as dirty even when content matches and `git status --porcelain` is empty. Refresh the stat cache (`git update-index -q --refresh`) immediately before the check, which is the canonical fix. Also dump `git status --porcelain` and `git diff-index --name-only HEAD` on genuine failure so a real divergence can be diagnosed without re-running.
`guix shell --container --pure` runs commands as a mapped user without privilege to chown into uid 0/gid 0. The unsuffixed `tar -xf - -C` in mk-distsrc therefore fails on every entry with tar: <path>: Cannot change ownership to uid 0, gid 0: Invalid argument eventually aborting with 'Exiting with failure status due to previous errors'. Add --no-same-owner --no-same-permissions so tar uses the extracting user's identity and ignores the archive's stored ownership metadata (which is what we want here — the distsrc only cares about file content and mtime, not who owned them at archive time).
build.sh extracts the deterministic source archive inside the same guix-shell --container --pure environment that bit mk-distsrc, so it needs the same --no-same-owner flag to avoid the chown EINVAL on every entry.
cuprate's workspace deps require rustc up to 1.91 (fjall, lsm-tree,
typed-index-collections, monero-daemon-rpc, etc). Guix v1.5.0 only
packages rust up to 1.88, so the build aborts before any work with:
error: rustc 1.85.1 is not supported by the following packages:
fjall@3.0.4 requires rustc 1.91.0
lsm-tree@3.0.4 requires rustc 1.91.0
...
Bump the channel pin to a recent master commit (7041be9c11, 2026-05-17)
that ships rust-1.93 as the default. Reproducibility is still preserved
because the pin is concrete.
Guix's `gcc-toolchain` profile only installs `gcc`/`g++`/`ar` etc.; it doesn't ship the legacy `cc` alias. cc-rs (used transitively by libsqlite3-sys, openssl-sys, randomx-rs, ring, and most other -sys crates) defaults to `cc` and aborts with ToolNotFound: failed to find tool "cc": No such file or directory before any C source is compiled. Set the canonical compiler env vars explicitly so cc-rs picks up gcc/g++/ar/etc. from the manifest profile.
…penssl
Two related failures hit at the cargo build step:
randomx-rs: CMake Error: unable to find build program 'Unix Makefiles'
(manifest had cmake but not make)
openssl-sys: openssl-src tried to build OpenSSL from source and failed
with 'Command "make" not found'
Both need `make` in the profile. Add gnu-make to manifest.scm.
For openssl-sys, also set OPENSSL_NO_VENDOR=1 so it links against the
openssl package already in the manifest via pkg-config instead of
recompiling OpenSSL via the openssl-src vendored copy on every build
(faster + smaller artifact + actually uses the audited Guix openssl).
Hint OPENSSL_DIR and PKG_CONFIG_PATH from $GUIX_ENVIRONMENT for
crates that don't use pkg-config.
s/gnu-make/make/ — the Guix package is named `make` (gnu/packages/base.scm) not gnu-make, so the previous commit's manifest entry failed with 'gnu-make: unknown package'.
…c 15 GCC 15.2 (now the default rust toolchain dep) rejects the unqualified `fesetround(mode)` call at instructions_portable.cpp:87 because the only relevant include is <cfenv>, which scopes fesetround to std::. RandomX upstream still expects the global-namespace version. Inject an extra `#include <fenv.h>` after <cfenv> to put fesetround back in the global namespace; behaviour is unchanged on older toolchains. Extend the .cargo-checksum.json regeneration to cover the new patched file as well.
The previous attempt (adding #include <fenv.h>) was insufficient under libstdc++ + gcc 15: even with the C header included, the global-namespace fesetround declaration is not reliably emitted, so compilation still fails with instructions_portable.cpp: error: 'fesetround' was not declared in this scope <cfenv> guarantees std::fesetround, so qualify the single call site to std::fesetround(mode) and stop trying to coax the global-namespace version into existence.
Root-caused the persistent RandomX 'fesetround was not declared' / 'fesetround is not a member of std' failures. The Guix gcc-15.2 libstdc++ ships with both _GLIBCXX_HAVE_FENV_H and _GLIBCXX_USE_C99_FENV undefined in bits/c++config.h. As a result <cfenv> never include_next's glibc's <fenv.h>, the std:: namespace gets none of the fenv functions, and the libstdc++ <fenv.h> compat wrapper is also a no-op. There is no way to call fesetround from C++ in this toolchain without these defines. Set both macros via CXXFLAGS so RandomX's CMake build (and any other C++ caller of <cfenv>) compiles. Verified locally: a minimal `std::fesetround(0)` translation unit fails to compile without the defines and succeeds with them.
`guix` is intentionally not in manifest.scm — the build container is supposed to be isolated from host tooling — so the unconditional `guix describe` call at the tail of build.sh aborted the build with `guix: command not found` AFTER cargo had already compiled cuprated. Fall back to a JSON stub that records the profile path and points at channels.scm as the deterministic source of truth, which is what verification consumers actually need. Keep the real `guix describe` when guix IS available (e.g. someone running the script directly on a Guix System host).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
cupratedand pin the Guix environment and toolchain.randomx-rsbehavior by patching vendored code to allowRANDOMX_ARCH/RANDOMX_DARCHto be honored during builds.Description
contrib/guixdirectory includingREADME.md,channels.scm, andmanifest.scmto pin Guix channels and the runtime manifest.guix-mk-distsrcto create a deterministic source archive,guix-buildto run a Guix time-machine build,guix-checksums,guix-attest, andguix-verifyfor checksums/attestation/verification workflows.contrib/guix/libexec:build.shto drive the deterministic build inside the container,package.shto create reproducibletar.gzarchives, andpatch-randomx.shto patch vendoredrandomx-rsbuild scripts to respectRANDOMX_ARCH/RANDOMX_DARCH.mk-distsrcto vendor Cargo dependencies, create.cuprate-distsrc.json, and produce a signed SHA256 summary, and addsmoke-reproducible.shas a local reproducibility smoke test.SOURCE_DATE_EPOCH, path remapping (--remap-path-prefix/-ffile-prefix-map),CARGO_NET_OFFLINE, and fixed tar/gzip options to produce deterministic archives and metadata files likebuild-metadata.json,guix-describe.json, and toolchain versions.Testing
contrib/guix/smoke-reproducible.shthat clones the repository, runsguix-mk-distsrcandguix-build, and compares resulting artifact checksums.contrib/guix/guix-checksums,contrib/guix/guix-verify, andcontrib/guix/guix-attestfor automated checksum generation, verification, and attestation signing.