Ability to normalize workspace member packages' versions for dependency derivation#974
Ability to normalize workspace member packages' versions for dependency derivation#974vypxl wants to merge 7 commits intoipetkov:masterfrom
Conversation
50c71f5 to
e2ed5c4
Compare
The source lockfile is still included if present and not overriden with either a supplied Cargo.toml or modified via doStripVersion
e2ed5c4 to
03d96f9
Compare
|
Thanks for the PR @vypxl! The problem I ran into the last time I looked into this is the fact that cargo will refuse to reuse artifacts from two different versions of the same crate, making the version cleanup moot. With published crates this obviously makes sense (two different versions most likely have different sources) but I suspect cargo handles even workspace-members in the same way. The real key here is that we add a |
|
I can try implementing such a test, but I'll need some more explanation. What should this test verify, specifically? |
|
Search around in the test suite for existing examples of Basically this checks that given a specific workspace and specific build flags, we can chain vendoring -> |
|
I've looked into this now. I added two tests, one that checks this for the 'simple' project, and one for the 'strip-version-test' project. The simple test passes, so there the byteorder dependency is cached properly. In the other one, there's a dev-dependency. The test fails here, because the check command that buildDepsOnly caches uses '--all-targets', while the What should we do here? P.S. In our production repo, I observed that the caching behavior with this change is.. inconsistent. It doesn't rebuild all dependencies, but a subset is rebuilt atm. Maybe a different configuration is needed there to match the cargo flags, similar to the test. |
|
I was able to modify our production flake to fix the weird partial caching, it was a mismatch of env variables and flags, together with missing feature unification. Now the dependencies are being reused fully, with the version stripping enabled. |
ipetkov
left a comment
There was a problem hiding this comment.
I was able to modify our production flake to fix the weird partial caching, it was a mismatch of env variables and flags, together with missing feature unification. Now the dependencies are being reused fully, with the version stripping enabled.
Glad you sorted it out, this is exactly what I was going to suggest. Generally you want the same build flags/features/env vars to match between buildDepsOnly and any other derivations chained on top, at the very least to the extent that it affects feature unification and other cache-busting aspects
| serde_json = "1.0" | ||
|
|
||
| [dev-dependencies] | ||
| tokio-test = "0.4" |
There was a problem hiding this comment.
This doesn't actually seem to be used, can we replace it with a "lighter" dependency? E.g. a lot of the test cases use crates like libc and byteorder since they have no other transitive deps. If we just need to check that dev-dependencies are still being cached it shouldn't matter what they are (though the smaller we keep these the ligher our test suite is to run. We're already hitting some disk limits in the github actions runners)
| cargoExtraArgs ? "--locked", | ||
| cargoTestCommand ? "cargoWithProfile test", | ||
| cargoTestExtraArgs ? "", | ||
| doStripVersion ? false, |
There was a problem hiding this comment.
In Nix adding a foo ? default binding doesn't actually "insert" it in the args binding, its only visible in the current scope. Meaning this doesn't actually do anything as currently defined since we don't access doStripVersion anywhere in this file
Though if this is being handled by other helpers that's totally fine, we can omit it here
| crateName = crateNameFromCargoToml argsMaybeDummySrcOverride; | ||
|
|
||
| # Use placeholder version when stripping versions to improve cache hit rates | ||
| versionPlaceholder = "0.0.0"; |
There was a problem hiding this comment.
Thoughts on making the placeholder string something explicit like "version-placeholder" or even just a blank string?
It might be confusing to folks seeing logs and derivations with version 0.0.0 instead of their actual version, but seeing something named like *placeholder* might be a better clue
As currently written this functionality is opt-in (so using 0.0.0 is reasonable!) but if we ever decided to make this on-by-default the placeholder may matter slightly more
| effectiveVersion = | ||
| if doStripVersion then versionPlaceholder else (args.version or crateName.version); |
There was a problem hiding this comment.
Instead of explicitly handling this here (and in all other callers which set version), thoughts about extending crateNameFromCargoToml to understand if args.doStripVersion is set and have it inject the placeholder? Might simplify things so callers don't need to juggle it themselves
| // { | ||
| CARGO_BUILD_TARGET = args.CARGO_BUILD_TARGET or "wasm32-unknown-unknown"; | ||
| doCheck = args.doCheck or false; | ||
| inherit doStripVersion; |
There was a problem hiding this comment.
We probably don't need to forward this here explicitly, we're already passing args along, so if doStripVersion is set, we'll observe it at the lower levels (i.e. we can probably revert all the changes in this file)
| { inherit cargoLockContents; } | ||
| { inherit cargoLockParsed; } |
There was a problem hiding this comment.
Both of these specific cases now fail to evaluate with error: cannot coerce null to a string: null
| { | ||
| cargoToml ? throw "either cargoToml or cargoTomlContents must be specified", | ||
| cargoTomlContents ? builtins.readFile cargoToml, | ||
| # ([String] -> Boolean) | ||
| cleanCargoTomlFilter ? filters.cargoTomlDefault, | ||
| doStripVersion ? false, | ||
| versionPlaceholder ? "0.0.0", | ||
| }: |
There was a problem hiding this comment.
Just curious, why did this get moved? It was intentionally kept lower down the file as everything up to the filterData declaration does not depend on the actual inputs to cleanCargoToml (i.e. Nix can optimize not needing to re-evaluate all that on each invocation)
if we need a new let ... in ... binding for cleaned we can easily add it in below!
| if doStripVersion && builtins.hasAttr "package" cleaned then | ||
| cleaned | ||
| // { | ||
| package = cleaned.package // { | ||
| version = versionPlaceholder; | ||
| }; | ||
| } | ||
| else | ||
| cleaned |
There was a problem hiding this comment.
This can probably be simplified with a mix of lib.recursiveUpdate and lib.optionalAttrs. Something like:
lib.recursiveUpdate cleaned (lib.optionalAttrs (doStripVersion && cleaned ? package) {
package.version = versionPlaceholder;
})| cleanedCargoLock = writeTOML "Cargo.lock" ( | ||
| if doStripVersion then cleanWorkspacePackageVersions cargoLockContents else cargoLockContents | ||
| ); |
There was a problem hiding this comment.
If doStripVersion is disabled this will still end up writing a Cargo.lock to the Nix store and copying from it. Can we optimize this to fall back to whatever the original value was
| copyCargoLock = if cargoLock == null then "" else "cp ${cargoLock} $out/Cargo.lock"; | ||
| copyCargoLock = | ||
| if cargoLock == null && !builtins.pathExists (origSrc + "/Cargo.lock") then | ||
| null |
There was a problem hiding this comment.
This is probably what's causing the evaluation failure above. If there's no cargoLock file we should be interpolating an empty string (i.e. nothing to do) rather than a null here.
In general you can run the test suite via nix flake check ./test# (tests are defined in a sub flake for reasons beyond this PR), or you can use ./test.sh which will run the full, comprehensive test suite
Motivation
Resolves #445
I've come across the same problem as described in the mentioned issue, and for us, it is causing many wasted CI build minutes. That's why I set out to fix it.
This adds a new flag 'doStripVersion', which can be used to set the versions of workspace crates to a placeholder value. Doing this in the Cargo.lock and Cargo.toml's means we can reuse the dependency cache when we bump versions of our workspace crates.
Checklist
docs/API.md(or general documentation) with changesCHANGELOG.md