nix: build nova-plugin via crane to cache deps separately from crate src

This commit is contained in:
Damocles 2026-05-03 20:01:47 +02:00
parent 2a691aa66f
commit bf5cb913fc
4 changed files with 88 additions and 44 deletions

16
flake.lock generated
View file

@ -1,5 +1,20 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1777830388,
"narHash": "sha256-2uoQAqUk2H0ijQtGiWAyNeQYGYc6yfAcRRLlJAz4Gp8=",
"owner": "ipetkov",
"repo": "crane",
"rev": "d459c1350e96ce1a7e3859c513ef5e9869d67d6f",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1776169885,
@ -38,6 +53,7 @@
},
"root": {
"inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs",
"quickshell": "quickshell",
"treefmt-nix": "treefmt-nix"

View file

@ -13,6 +13,8 @@
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
crane.url = "github:ipetkov/crane";
};
outputs =
@ -21,6 +23,7 @@
nixpkgs,
quickshell,
treefmt-nix,
crane,
...
}:
let
@ -57,6 +60,7 @@
fn rec {
rawPkgs = nixpkgs.legacyPackages.${system};
pkgs = rawPkgs.extend qtbaseOverlay;
craneLib = crane.mkLib pkgs;
treefmt-eval = treefmt-nix.lib.evalModule rawPkgs treefmt-config;
}
);
@ -65,7 +69,7 @@
formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper);
packages = forAllSystems (
{ pkgs, rawPkgs, ... }:
{ pkgs, rawPkgs, craneLib, ... }:
let
# Rebuild quickshell against patched Qt via its overlay
qs = (pkgs.extend quickshell.overlays.default).quickshell.override {
@ -74,7 +78,7 @@
};
nova-stats = pkgs.callPackage ./nix/stats-daemon.nix { };
nova-shaders = pkgs.callPackage ./nix/shaders.nix { };
nova-plugin = pkgs.callPackage ./nix/plugin.nix { };
nova-plugin = pkgs.callPackage ./nix/plugin.nix { inherit craneLib; };
in
rec {
inherit nova-stats nova-shaders nova-plugin;
@ -122,12 +126,13 @@
withX11 = false;
withI3 = false;
};
craneLibRaw = crane.mkLib rawPkgs;
in
rawPkgs.callPackage ./nix/package.nix {
quickshell = qsUnpatched;
nova-stats = rawPkgs.callPackage ./nix/stats-daemon.nix { };
nova-shaders = rawPkgs.callPackage ./nix/shaders.nix { };
nova-plugin = rawPkgs.callPackage ./nix/plugin.nix { };
nova-plugin = rawPkgs.callPackage ./nix/plugin.nix { craneLib = craneLibRaw; };
};
}
);

View file

@ -1,6 +1,6 @@
{
lib,
rustPlatform,
craneLib,
pkg-config,
qt6,
writeShellScript,
@ -33,47 +33,62 @@ let
fi
exec ${qt6.qtbase}/bin/qmake6 "$@"
'';
in
rustPlatform.buildRustPackage {
pname = "nova-plugin";
version = "0.1.0";
src = lib.cleanSource ../plugin;
cargoLock.lockFile = ../plugin/Cargo.lock;
nativeBuildInputs = [
pkg-config
qt6.qtbase
qt6.qtdeclarative
];
# Args shared between the deps-only build (cached on Cargo.lock changes only)
# and the final crate build. cxx-qt-build runs during the deps build too, so it
# needs the Qt env to find qmltyperegistrar/qmlcachegen.
commonArgs = {
pname = "nova-plugin";
version = "0.1.0";
src = craneLib.cleanCargoSource ../plugin;
strictDeps = true;
buildInputs = [
qt6.qtbase
qt6.qtdeclarative
];
nativeBuildInputs = [
pkg-config
qt6.qtbase
qt6.qtdeclarative
];
buildInputs = [
qt6.qtbase
qt6.qtdeclarative
];
dontWrapQtApps = true;
dontWrapQtApps = true;
# qt6.qtbase's setup hook overrides QMAKE after derivation attrs are set,
# so re-assert it in preBuild which runs after all setup hooks.
preBuild = ''
export QMAKE=${qmakeWrapper}
'';
installPhase = ''
runHook preInstall
qml_dir="$out/lib/qt-6/qml/NovaStats"
mkdir -p "$qml_dir"
install -m755 target/*/release/libnova_plugin.so "$qml_dir/libNovaStats.so"
install -m644 target/*/cxxqt/qml_modules/NovaStats/qmldir "$qml_dir/"
install -m644 target/*/cxxqt/qml_modules/NovaStats/plugin.qmltypes "$qml_dir/" 2>/dev/null || true
runHook postInstall
'';
meta = {
description = "In-process system stats QML plugin for nova-shell";
platforms = lib.platforms.linux;
# qt6.qtbase's setup hook overrides QMAKE after derivation attrs are set,
# so re-assert it in preBuild which runs after all setup hooks.
preBuild = ''
export QMAKE=${qmakeWrapper}
'';
};
}
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
in
craneLib.buildPackage (
commonArgs
// {
inherit cargoArtifacts;
# Crane's default cargo build doesn't set CARGO_BUILD_TARGET, so artifacts land
# in `target/release/` and cxx-qt files in `target/cxxqt/...` (no triple subdir,
# unlike nixpkgs rustPlatform). Use `find` to stay agnostic to either layout.
installPhaseCommand = ''
qml_dir="$out/lib/qt-6/qml/NovaStats"
mkdir -p "$qml_dir"
so=$(find target -name 'libnova_plugin.so' -path '*/release/*' | head -1)
install -m755 "$so" "$qml_dir/libNovaStats.so"
qmldir=$(find target -path '*/cxxqt/qml_modules/NovaStats/qmldir' | head -1)
install -m644 "$qmldir" "$qml_dir/"
qmltypes=$(find target -path '*/cxxqt/qml_modules/NovaStats/plugin.qmltypes' | head -1)
[ -n "$qmltypes" ] && install -m644 "$qmltypes" "$qml_dir/" || true
'';
meta = {
description = "In-process system stats QML plugin for nova-shell";
platforms = lib.platforms.linux;
};
}
)

View file

@ -8,7 +8,15 @@ fn main() {
// plugin loader finds nothing exported. --export-dynamic re-exposes them.
println!("cargo:rustc-link-arg=-Wl,--export-dynamic");
// Crane's deps-only build stubs out lib.rs and removes our bridge sources to
// build a dummy crate that only compiles dependencies. Skip cxx-qt codegen in
// that case - we just want cxx-qt-lib (the heavy dep) cached, not our own glue.
let bridge_files = ["src/system_stats.rs", "src/cpu_service.rs"];
if !bridge_files.iter().all(|p| std::path::Path::new(p).exists()) {
return;
}
CxxQtBuilder::new_qml_module(QmlModule::new("NovaStats").plugin_type(PluginType::Dynamic))
.files(["src/system_stats.rs", "src/cpu_service.rs"])
.files(bridge_files)
.build();
}