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.
258 lines
11 KiB
Nix
258 lines
11 KiB
Nix
{
|
||
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.
|
||
};
|
||
}
|
||
);
|
||
}
|