crabidy/flake.nix

282 lines
12 KiB
Nix

{
description = "crabidy a client/server music player";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
crane.url = "github:ipetkov/crane";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
crane,
fenix,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
lib = pkgs.lib;
fenixPkgs = fenix.packages.${system};
# Source that keeps the .proto files crabidy-core/build.rs compiles —
# crane's default cargo-source cleaning would drop them.
src = lib.cleanSourceWith {
src = ./.;
name = "crabidy-source";
filter =
path: type:
(builtins.match ".*\\.proto$" path != null) || (crane.mkLib pkgs).filterCargoSources path type;
};
# trunk needs cbd-web's non-cargo assets (index.html, style.css,
# Trunk.toml) which filterCargoSources drops. Keep everything under
# cbd-web/ (except a stale prebuilt dist/) plus the usual cargo/proto
# sources.
webSrc = lib.cleanSourceWith {
src = ./.;
name = "crabidy-web-source";
filter =
path: type:
(
(builtins.match ".*\\.proto$" path != null)
|| (lib.hasInfix "/cbd-web/" path)
|| (crane.mkLib pkgs).filterCargoSources path type
)
&& !(lib.hasInfix "/cbd-web/dist" path);
};
# Only the real binary crates; never build cbd-web (a wasm crate) for a
# host or aarch64 target.
workspaceBins = "-p crabidy-server -p cbd -p cbd-tui";
# Headless: everything except the embedded web UI. Spelled out rather
# than "--no-default-features" alone, which means *no providers at
# all* (see docs/src/build-features.md) — the features must be named
# or this package ships an empty server. The
# aarch64 server (below) takes the full defaults and stages the wasm
# bundle. `notifications` and `mpris` are cbd-tui's own defaults, but
# naming the feature set at all replaces them, so they have to be
# listed or the installed client loses its desktop integration; both
# are pure-Rust D-Bus and add no buildInput. `opus-bundled` is
# deliberately absent: Opus decoding is on, but libopus comes from
# nixpkgs rather than a cmake build of the vendored source.
headlessFeatures = "all-providers,opus,spectrum,notifications,mpris";
# --- Web UI (wasm) bundle ---------------------------------------------
# cbd-web is compiled to wasm by trunk; the output dist/ is what
# crabidy-server's build.rs embeds (feature `web-ui`). The bundle is
# architecture-independent — the same wasm runs in the browser whatever
# the server's CPU — so one derivation feeds every server build.
#
# The wasm-bindgen CLI version must match the wasm-bindgen crate
# (0.2.126) exactly. nixpkgs ships an older CLI and, in the dev shell,
# trunk downloads the matching one at build time — impossible in a
# sealed Nix build, so we pin an overridden CLI here.
wasmBindgenCli = pkgs.wasm-bindgen-cli.overrideAttrs (_: rec {
version = "0.2.126";
src = pkgs.fetchCrate {
pname = "wasm-bindgen-cli";
inherit version;
hash = "sha256-H6Is3fiZVxZCfOMWK5dWMSrtn50VGv0sfdnsT+cTtyk=";
};
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
inherit src;
name = "wasm-bindgen-cli-${version}-vendor";
hash = "sha256-VucqkXbCi4qtQzY/HrXiDnbSURsagPsdNVMn1Tw3UiY=";
};
});
wasmToolchain = fenixPkgs.combine [
fenixPkgs.stable.rustc
fenixPkgs.stable.cargo
fenixPkgs.targets.wasm32-unknown-unknown.stable.rust-std
];
craneLibWasm = (crane.mkLib pkgs).overrideToolchain (_: wasmToolchain);
webArgs = {
src = webSrc;
strictDeps = true;
cargoExtraArgs = "--locked -p cbd-web";
CARGO_BUILD_TARGET = "wasm32-unknown-unknown";
doCheck = false;
# crabidy-core's build.rs (a transitive dep of cbd-web) needs protoc.
nativeBuildInputs = [ pkgs.protobuf ];
};
webDeps = craneLibWasm.buildDepsOnly webArgs;
webBundle = craneLibWasm.buildTrunkPackage (
webArgs
// {
pname = "cbd-web";
version = "0.1.0";
cargoArtifacts = webDeps;
wasm-bindgen-cli = wasmBindgenCli;
# wasm-opt for the release bundle.
wasm-opt = pkgs.binaryen;
# This is a virtual workspace, so trunk run from the root can't
# resolve the cbd-web member (it reports "could not find the root
# package"). Build from inside cbd-web, exactly like the dev-shell
# `build-web` script. The vendored cargo config already pins cargo
# to the offline vendor dir, so no network is needed.
buildPhaseCargoCommand = ''
( cd cbd-web && trunk build index.html --release )
'';
installPhaseCommand = ''
cp -r cbd-web/dist $out
'';
}
);
# --- Native toolchain / build (for `nix profile install` on x86_64,
# aarch64, etc.) ---
craneLib = (crane.mkLib pkgs).overrideToolchain (_: fenixPkgs.stable.toolchain);
nativeArgs = {
inherit src;
strictDeps = true;
cargoExtraArgs = "--locked --no-default-features --features ${headlessFeatures} ${workspaceBins}";
# protoc + pkg-config are build-host tools (run during the build);
# with strictDeps they must be nativeBuildInputs or alsa-sys cannot
# find pkg-config. alsa-lib and libopus are the actual linked
# libraries: `headlessFeatures` leaves `opus-bundled` off, so
# opusic-sys emits a plain `-lopus` instead of compiling the
# vendored copy with cmake, and nixpkgs' libopus satisfies it.
nativeBuildInputs = [
pkgs.protobuf
pkgs.pkg-config
];
buildInputs = [
pkgs.alsa-lib
pkgs.libopus
];
# audio-player links libasound; yt-dlp for /youtube stays a runtime
# dependency, not wired here.
};
nativeDeps = craneLib.buildDepsOnly nativeArgs;
crabidy = craneLib.buildPackage (
nativeArgs
// {
cargoArtifacts = nativeDeps;
doCheck = false; # tests need a config/network; `nix flake check` can run them later
# rustc stamps no RUNPATH of its own, and the `-L` that buildInputs
# contributes arrives through NIX_LDFLAGS, which ld-wrapper does not
# mirror into the binary. Without this the binaries link cleanly and
# then refuse to *start* — "libasound.so.2: cannot open shared
# object file" — anywhere the libraries are not already on
# LD_LIBRARY_PATH, which is to say everywhere outside this repo's
# dev shell. autoPatchelfHook walks the finished binaries and fills
# their RUNPATH from buildInputs, so an installed crabidy runs on a
# bare login shell. It also fails the build on any library it cannot
# resolve, which is how a missing buildInput should surface.
nativeBuildInputs = nativeArgs.nativeBuildInputs ++ [ pkgs.autoPatchelfHook ];
# Rust links libgcc_s for unwinding; it is the compiler's own
# runtime, so no crate declares it and the hook cannot guess it.
buildInputs = nativeArgs.buildInputs ++ [ (pkgs.lib.getLib pkgs.stdenv.cc.cc) ];
}
);
# --- Cross toolchain / build: static aarch64 musl (a portable binary
# that runs on any aarch64 Linux, incl. a Raspberry Pi) ---
muslTarget = "aarch64-unknown-linux-musl";
crossPkgs = pkgs.pkgsCross.aarch64-multiplatform-musl;
crossCc = "${crossPkgs.stdenv.cc}/bin/${crossPkgs.stdenv.cc.targetPrefix}cc";
# Static alsa-lib for the target, linked into the binary.
crossAlsa = crossPkgs.pkgsStatic.alsa-lib;
crossToolchain = fenixPkgs.combine [
fenixPkgs.stable.rustc
fenixPkgs.stable.cargo
fenixPkgs.targets.${muslTarget}.stable.rust-std
];
craneLibCross = (crane.mkLib pkgs).overrideToolchain (_: crossToolchain);
crossEnvTarget = "AARCH64_UNKNOWN_LINUX_MUSL";
crossArgs = {
inherit src;
strictDeps = true;
# Both builds take their name from the workspace root, so name this
# one for its target — otherwise the native and cross derivations are
# both "crabidy-0.1.0" and a build log cannot tell them apart.
pname = "crabidy-server-aarch64";
# web-ui is a default feature; keep it on so the embedded UI ships.
cargoExtraArgs = "--locked -p crabidy-server";
CARGO_BUILD_TARGET = muslTarget;
# crabidy-server's build.rs embeds cbd-web/dist (feature `web-ui`).
# Stage the prebuilt wasm bundle there before the crate compiles.
preBuild = ''
rm -rf cbd-web/dist
cp -rL --no-preserve=mode,ownership ${webBundle} cbd-web/dist
'';
# Build scripts / proc-macros compile for the build host; deps for the
# target use the cross toolchain. protoc, pkg-config, and cmake are
# host tools. Unlike the native build this one keeps the default
# `opus-bundled`: cmake cross-compiles the vendored libopus into the
# static binary, whereas an unbundled `-lopus` would need a static
# aarch64 libopus staged for the target the way alsa-lib is. cmake
# comes deliberately without ninja, whose mere presence flips cmake's
# generator and then clashes with a cached build dir.
nativeBuildInputs = [
pkgs.protobuf
pkgs.pkg-config
pkgs.cmake
];
buildInputs = [ crossAlsa ];
depsBuildBuild = [ crossPkgs.stdenv.cc ];
# cc-rs (aws-lc-sys, rustls's crypto backend) needs the cross C
# compiler; the linker is the same cross cc. Static CRT + -static so
# the binary has no interpreter and runs standalone.
"CC_aarch64_unknown_linux_musl" = crossCc;
"CARGO_TARGET_${crossEnvTarget}_LINKER" = crossCc;
"CARGO_TARGET_${crossEnvTarget}_RUSTFLAGS" = "-C target-feature=+crt-static -C link-arg=-static";
# pkg-config resolves the target's static alsa-lib for alsa-sys.
PKG_CONFIG_ALLOW_CROSS = "1";
PKG_CONFIG_PATH_aarch64_unknown_linux_musl = "${crossAlsa.dev}/lib/pkgconfig";
};
crossDeps = craneLibCross.buildDepsOnly crossArgs;
crabidy-server-aarch64 = craneLibCross.buildPackage (
crossArgs
// {
cargoArtifacts = crossDeps;
doCheck = false; # cannot run aarch64 tests on an x86_64 builder
}
);
in
{
packages = {
default = crabidy;
inherit crabidy crabidy-server-aarch64;
};
apps = {
cbd = flake-utils.lib.mkApp {
drv = crabidy;
name = "cbd";
};
cbd-tui = flake-utils.lib.mkApp {
drv = crabidy;
name = "cbd-tui";
};
crabidy-server = flake-utils.lib.mkApp {
drv = crabidy;
name = "crabidy-server";
};
default = flake-utils.lib.mkApp {
drv = crabidy;
name = "cbd";
};
};
}
);
}