Patching a rust package in NixOS
A small note about weirdnesses of overrideAttrs in NixOS
I’ve been playing a fair bit of Hollow Knight: Silksong
recently (and can only recommend it). After playing the main story for a
while and getting closer to Act 3, i’ve been wanting to play with a
couple of mods. So i installed gale, a mod
manager that also supports Silksong. Since i am on a bit of an older
commit of nixpkgs/nixos-unstable and the older version of
gale (my tree of nixos-unstable has version
1.9.6) does not support Silksong yet, i thought i’d just
patch gale via NixOS’ overrideAttrs
mechanism, which is where this rabbithole begins.
My first attempt included just looking at the source and swapping out hashes and versions to match the newer version.
The original source is listed like so:
pname = "gale";
version = "1.10.0";
src = fetchFromGitHub {
owner = "Kesomannen";
repo = "gale";
tag = finalAttrs.version;
hash = "sha256-SnPYuMYdoY69CWMztuDxw0ohRDU2uECNhBs46hLg+eA=";
};
# ...
pnpmDeps = pnpm_10.fetchDeps {
inherit (finalAttrs) pname version src;
fetcherVersion = 1;
hash = "sha256-DYhPe59qfsSjyMIN31RL0mrHfmE6/I1SF+XutettkO8=";
};
# ...
cargoHash = "sha256-tWQRYD6hMU7cvtelGryLdpfoEnUKYt7yYNwHTFZ4pLw=";And, just like any other nix package you can override attributes used for constructing a package while you consume it like so:
(gale.overrideAttrs (oldAttrs: rec {
version = "1.10.0";
name = "${oldAttrs.pname}-${version}";
src = fetchFromGitHub {
owner = "Kesomannen";
repo = "gale";
tag = version;
hash = "sha256-SnPYuMYdoY69CWMztuDxw0ohRDU2uECNhBs46hLg+eA=";
};
pnpmDeps = pnpm_10.fetchDeps {
inherit version src;
inherit (oldAttrs) pname;
fetcherVersion = 1;
hash = "sha256-DYhPe59qfsSjyMIN31RL0mrHfmE6/I1SF+XutettkO8=";
};
cargoHash = "sha256-tWQRYD6hMU7cvtelGryLdpfoEnUKYt7yYNwHTFZ4pLw=";
}))So let’s try compiling like that!
error: builder for '/nix/store/1j31hb1c8fbq6qh9cpg2y88810hn9a68-gale-1.10.0.drv' failed with exit code 1;
last 23 log lines:
> Running phase: unpackPhase
> unpacking source archive /nix/store/3px0g9dfjg6vyr26jsj29dmbgj9jv5xf-source
> source root is source
> Executing cargoSetupPostUnpackHook
> Finished cargoSetupPostUnpackHook
> Running phase: patchPhase
> Executing cargoSetupPostPatchHook
> Validating consistency between /build/source/src-tauri/Cargo.lock and /build/gale-1.9.6-vendor/Cargo.lock
> 1845c1845
> < version = "1.10.0"
> ---
> > version = "1.9.6"
>
> ERROR: cargoHash or cargoSha256 is out of date
>
> Cargo.lock is not the same in /build/gale-1.9.6-vendor
>
> To fix the issue:
> 1. Set cargoHash/cargoSha256 to an empty string: `cargoHash = "";`
> 2. Build the derivation and wait for it to fail with a hash mismatch
> 3. Copy the "got: sha256-..." value back into the cargoHash field
> You should have: cargoHash = "sha256-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=";
>
For full logs, run 'nix log /nix/store/1j31hb1c8fbq6qh9cpg2y88810hn9a68-gale-1.10.0.drv'.
Ah, well, shit!
This error message is sort of doubly disappointing, because it sent
me down the wrong path. The cargoHash seems to be incorrect
(even though i updated the hash?), and there seems to be a
version mismatch even though i changed the version (i can see there are
plenty of references to 1.10.0 all over the code!). Trying
to figure out what i did wrong with the cargoHash (did i
maybe use the old hash and it pulls in an already built derivation from
somewhere?) took me a good hour or so, despite being the completely
wrong direction. The key lies in the lines that i omitted earlier in the
post (and originally skimmed over when i first looked at which
attributes to override).
rustPlatform.buildRustPackage (finalAttrs: {
pname = "gale";
version = "1.10.0";
src = fetchFromGitHub {
owner = "Kesomannen";
repo = "gale";
tag = finalAttrs.version;
hash = "sha256-SnPYuMYdoY69CWMztuDxw0ohRDU2uECNhBs46hLg+eA=";
};Ignoring the other couple of dependencies, the important bit is that
the overrideAttrs mechanism is provided by
stdenv.mkDerivation and this package is built using
rustPlatform.buildRustPackage! Now for most attributes that
is not really a problem. Even if you have a custom builder instead of
the standard mkDerivation, the attributes can still be
overriden using overrideAttrs as part of
lib.extendMkDerivation (overriding src for
example works just fine). The buildRustPackage derivation
however does a little preprocessing on its arguments meaning that some
attributes end up named differently once they are passed down to
mkDerivation:
cargoDeps =
if cargoVendorDir != null then
null
else if cargoDeps != null then
cargoDeps
else if cargoLock != null then
importCargoLock cargoLock
else if args.cargoHash or null == null then
throw "cargoHash, cargoVendorDir, cargoDeps, or cargoLock must be set"
else
fetchCargoVendor (
{
inherit
src
srcs
sourceRoot
cargoRoot
preUnpack
unpackPhase
postUnpack
;
name = cargoDepsName;
patches = cargoPatches;
hash = args.cargoHash;
}
// depsExtraArgs
);The declared cargoHash gets transformed into a
cargoDeps property using fetchCargoVendor.
This means if i (or you) want to override the cargoHash (as
one typically needs to do when changing any of the dependencies of a
rust package), i (and you) will need to instead manually apply this
transformation and prepare our own cargo vendor directory. If you are
more observant than me, you may have noticed that the mismatched version
appears in a directory called /build/gale-1.9.6-vendor/ in
our earlier error log.
Conclusion
In the end, this is a relatively easy fix for this transformation:
(gale.overrideAttrs (oldAttrs: rec {
version = "1.10.0";
name = "${oldAttrs.pname}-${version}";
src = fetchFromGitHub {
owner = "Kesomannen";
repo = "gale";
tag = version;
hash = "sha256-SnPYuMYdoY69CWMztuDxw0ohRDU2uECNhBs46hLg+eA=";
};
# This pnpmDeps is specific to gale, most rust programs dont have a pnpmDeps section. I am just including it here since gale specifically needs it.
pnpmDeps = pnpm_10.fetchDeps {
inherit version src;
inherit (oldAttrs) pname;
fetcherVersion = 1;
hash = "sha256-DYhPe59qfsSjyMIN31RL0mrHfmE6/I1SF+XutettkO8=";
};
# This is the important bit: declaring cargoDeps instead of cargoHash!
cargoDeps = rustPlatform.fetchCargoVendor {
inherit src;
hash = "sha256-tWQRYD6hMU7cvtelGryLdpfoEnUKYt7yYNwHTFZ4pLw=";
cargoRoot = "src-tauri";
};
}))I guess the take away here is to inspect the exact builder that is being used for the derivation and how it handles its arguments.
And as a small little bonus, here is a little fucked up way to still
directly specify cargoHash:
(gale.override {
rustPlatform.buildRustPackage = (
oldAttrsGen:
rustPlatform.buildRustPackage (
finalAttrs:
let
oldAttrs = (oldAttrsGen finalAttrs);
in
oldAttrs
// rec {
version = "1.10.0";
name = "${oldAttrs.pname}-${version}";
src = fetchFromGitHub {
owner = "Kesomannen";
repo = "gale";
tag = version;
hash = "sha256-SnPYuMYdoY69CWMztuDxw0ohRDU2uECNhBs46hLg+eA=";
};
pnpmDeps = pnpm_10.fetchDeps {
inherit version src;
inherit (oldAttrs) pname;
fetcherVersion = 1;
hash = "sha256-DYhPe59qfsSjyMIN31RL0mrHfmE6/I1SF+XutettkO8=";
};
cargoHash = "sha256-tWQRYD6hMU7cvtelGryLdpfoEnUKYt7yYNwHTFZ4pLw=";
}
)
);
})override overrides the arguments to the
gale expression itself (a/k/a the import section of the
gale package file). That way we can switch out the rust builder function
that is being imported and intercept the arguments passed to the builder
directly. That way of overriding arguments is discouraged by NixOS in
general, and is a lot more complicated and verbose to set up. But, if
the builder that is being used has a lot of functionality you don’t want
to replicate, this might be something worth giving a shot.