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.