Files
Greg Shuflin 2888741fa1 test env: a node with a deliberately wrong clock, via the flake
Exercising the timestamp bound in `p2p::wire_time` needs a peer whose clock
is actually wrong, and the honest way to get one is to move the whole
process's wall clock — so the peer's signed gossip `announced_at_secs` and
every `modified_at` it writes are wrong together, which is the real failure
rather than a simulation of one.

libfaketime comes from the flake's devShell rather than a system package, so
the scenario reproduces on any machine with the flake and is pinned to the
same nixpkgs as everything else.

The recipe builds first and fakes only the built binary: running cargo itself
under a future clock would corrupt its mtime-based freshness check. `-m`
selects libfaketime's thread-safe build, since the daemon is thoroughly
multi-threaded, and FAKETIME_DONT_FAKE_MONOTONIC keeps QUIC's own timers on
the real monotonic clock — without it iroh's timeouts move too and the nodes
fail to connect for reasons unrelated to the test.
2026-08-30 03:22:40 -07:00

258 lines
11 KiB
Nix
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
description = "Synchronicity personal server runtime";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
crane.url = "github:ipetkov/crane";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
self,
nixpkgs,
rust-overlay,
crane,
flake-utils,
}: let
rust-version = "1.92.0";
# Must match ndkVersion in android/app/build.gradle.kts (and ANDROID_NDK_VERSION in justfile).
android-ndk-version = "28.0.13004108";
in
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system;
config = {
allowUnfree = true;
android_sdk.accept_license = true;
};
overlays = [rust-overlay.overlays.default];
};
androidComposition = pkgs.androidenv.composeAndroidPackages {
# Must cover compileSdk/targetSdk in android/app/build.gradle.kts —
# Gradle resolves the platform when configuring the app variant, so a
# missing version breaks every compile task under `nix develop`
# (the read-only Nix store can't auto-install the SDK component).
platformVersions = ["35" "37"];
buildToolsVersions = ["35.0.0" "36.0.0"];
ndkVersions = [android-ndk-version];
includeNDK = true;
includeSystemImages = false;
includeEmulator = false;
};
androidSdk = androidComposition.androidsdk;
rustToolchain = pkgs.rust-bin.stable."${rust-version}".default.override {
targets = ["aarch64-linux-android"];
};
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
# Source filtering — include Cargo sources, .slint UI files (the archived
# gui-slint app), and data files referenced by include_str!() / include_bytes!():
# .txt (lib's wordlist), .svg (gui-app's icons, baked in via assets.rs), and
# .cbr (comics.rs's cfg(test)-only RAR fixture).
rustSrc = pkgs.lib.cleanSourceWith {
src = ./rust;
filter = path: type:
(craneLib.filterCargoSources path type)
|| (builtins.match ".*\\.slint$" path != null)
|| (builtins.match ".*\\.txt$" path != null)
|| (builtins.match ".*\\.svg$" path != null)
|| (builtins.match ".*\\.cbr$" path != null);
};
# Common arguments shared across all builds
commonArgs = {
src = rustSrc;
pname = "synchronicity";
version = "0.1.0";
strictDeps = true;
nativeBuildInputs = [
pkgs.pkg-config
];
buildInputs = [
pkgs.openssl
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.wayland
pkgs.libxkbcommon
pkgs.fontconfig
];
};
# Native libraries the gpui desktop app needs on top of `commonArgs`.
# Shared with `devShells.default` so a plain `cargo check -p gui-app`
# works there too — without these a local build dies on fontconfig or
# alsa, which is exactly the thing that forces a full hermetic rebuild
# just to type-check a GUI change.
guiBuildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.libX11
pkgs.libxcursor
pkgs.libxrandr
pkgs.libxi
pkgs.libGL
pkgs.alsa-lib
pkgs.vulkan-loader
pkgs.libxcb
];
# Build workspace deps once (shared across all binaries)
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
# Helper to build a single binary from the workspace
mkBin = name: extraArgs: craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
cargoExtraArgs = "--bin ${name}";
# Only install the specific binary
doInstallCargoArtifacts = false;
} // extraArgs);
syn-cli = mkBin "syn-cli" {};
syn-tui = mkBin "syn-tui" {};
syn-gui = mkBin "syn-gui" {
# gui-app is excluded from the workspace's `default-members` (see
# justfile) so a bare `--bin syn-gui` can't find it — cargo only
# resolves anonymous `--bin` lookups against default members.
cargoExtraArgs = "-p gui-app --bin syn-gui";
# mupdf-sys's build.rs copies its bundled mupdf C sources into
# $OUT_DIR via `std::fs::copy`, which preserves the *source* file's
# permission bits. Files under crane's vendored-deps directory live
# in the Nix store, where every file is mode 444 (Nix strips write
# bits from everything it registers) — so the OUT_DIR copies come
# out unwritable too, and mupdf-sys's build.rs then edits a couple
# of those files in place (a Noto font fallback and a store.c
# refcounting fix), which fails with EACCES. The vendor directory
# itself can't be made writable (it's a read-only Nix store path),
# so instead give cargo a writable copy of just this one crate and
# override its source via `[patch.crates-io]`. Plain `cp` followed
# by `chmod` doesn't work here — chmod on the copy fails with
# "Operation not permitted" inside the Nix sandbox — so mirror
# crane's own inheritCargoArtifactsHook, which reaches for
# `rsync --chmod=u+w` for exactly this read-only-store-copying
# problem.
postPatch = ''
mupdfSysVendored=$(find -L "$cargoVendorDir" -maxdepth 2 -type d -iname 'mupdf-sys-*' | head -n1)
mupdfSysWritable="$PWD/.mupdf-sys-writable-vendor"
mkdir -p "$mupdfSysWritable"
rsync -a --chmod=u+w "$mupdfSysVendored"/ "$mupdfSysWritable"/
mkdir -p .cargo
cat >> .cargo/config.toml <<EOF
[patch.crates-io]
mupdf-sys = { path = "$mupdfSysWritable" }
EOF
'';
# bindgenHook wires up LIBCLANG_PATH etc. for mupdf-sys's build.rs (it
# uses bindgen to generate FFI bindings against the bundled mupdf C sources).
nativeBuildInputs = commonArgs.nativeBuildInputs ++ [
pkgs.rustPlatform.bindgenHook
];
buildInputs = commonArgs.buildInputs ++ guiBuildInputs;
# gpui needs to find wayland/X11/Vulkan/ALSA libs at runtime
postFixup = pkgs.lib.optionalString pkgs.stdenv.isLinux ''
patchelf --add-rpath ${pkgs.lib.makeLibraryPath [
pkgs.wayland
pkgs.libxkbcommon
pkgs.libGL
pkgs.libX11
pkgs.libxcursor
pkgs.libxcb
pkgs.libxrandr
pkgs.libxi
pkgs.fontconfig
pkgs.alsa-lib
pkgs.vulkan-loader
]} $out/bin/syn-gui
'';
};
# CI scripts — source lives in scripts/ for proper shell highlighting
createReleaseCi = pkgs.writeShellScriptBin "create-release-ci" ''
export JQ_BIN="${pkgs.jq}/bin/jq"
exec bash ${./scripts/create-release-ci.sh} "$@"
'';
buildApkCi = pkgs.writeShellScriptBin "build-apk-ci" ''
export SCCACHE_BIN="${pkgs.sccache}/bin/sccache"
export CARGO_SWEEP_BIN="${pkgs.cargo-sweep}/bin/cargo-sweep"
export ANDROID_SDK_DIR="${androidSdk}/libexec/android-sdk"
export AAPT2_PATH="${androidSdk}/libexec/android-sdk/build-tools/35.0.0/aapt2"
exec bash ${./scripts/build-apk-ci.sh} "$@"
'';
in {
packages = {
inherit syn-cli syn-tui syn-gui;
default = syn-cli;
};
devShells.default = pkgs.mkShell {
# `bindgenHook` sets LIBCLANG_PATH etc. for mupdf-sys's build.rs. It
# belongs in nativeBuildInputs so its setup hook actually runs.
nativeBuildInputs = [
pkgs.pkg-config
pkgs.rustPlatform.bindgenHook
];
# commonArgs.buildInputs + guiBuildInputs mirror what the `syn-gui`
# derivation links against, so `cargo build`/`cargo check` inside this
# shell finds the same native libraries the hermetic build does. The
# payoff is incremental compilation: the hermetic build starts from a
# clean sandbox every time, so type-checking a one-line GUI change
# costs a full release rebuild of the workspace.
buildInputs = [
buildApkCi
createReleaseCi
rustToolchain
pkgs.cargo-nextest
androidSdk
pkgs.jdk17
pkgs.just
# `just extension build` packages the browser extension with it.
pkgs.zip
# `just test-env-daemon-b-skewed` runs a test node under a
# deliberately wrong wall clock, to exercise the timestamp bound in
# `p2p::wire_time` and the peer-health reporting built on it. Here
# rather than as a system package so the scenario is reproducible
# on any machine with the flake, and pinned to the same nixpkgs as
# everything else.
pkgs.libfaketime
] ++ commonArgs.buildInputs ++ guiBuildInputs;
# gpui dlopen()s wayland/X11/Vulkan/ALSA at runtime rather than
# linking them, so a binary built in this shell needs them on the
# library path to actually run (the packaged build patchelfs an rpath
# in instead).
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (commonArgs.buildInputs ++ guiBuildInputs);
ANDROID_HOME = "${androidSdk}/libexec/android-sdk";
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
NDK_HOME = "${androidSdk}/libexec/android-sdk/ndk/${android-ndk-version}";
JAVA_HOME = "${pkgs.jdk17}";
AAPT2_PATH = "${androidSdk}/libexec/android-sdk/build-tools/35.0.0/aapt2";
# Deliberately doesn't write android/local.properties: that file is
# also what Android Studio uses interactively, and clobbering it
# with this ephemeral Nix store path broke Studio's own SDK/AVD
# resolution ("No target device found") the moment someone ran a
# Nix recipe from this shell. AGP already falls back to
# ANDROID_HOME/ANDROID_SDK_ROOT (set above) when local.properties
# has no sdk.dir, and the `gradlew`/`android-build`/`lint-kotlin*`
# just recipes pass AAPT2_PATH through as
# -Pandroid.aapt2FromMavenOverride when it's set — so no file needs
# to change on disk for a `nix develop` build to work.
};
}
);
}