Description
Paket.Restore.targets' Step 2a invalidates the per-project restore skip only when the content hash of paket.references changes. When paket.lock is updated by a transitive change — e.g. a merge that bumps a transitive package version — paket.references content is unchanged, Step 2a's skip kicks in, and the per-project obj/<rid>/<proj>.fsproj.paket.props stays at the pre-merge content. Subsequent dotnet restore reads it via $(BaseIntermediateOutputPath) and project.assets.json continues resolving the package to the previous version indefinitely. Neither dotnet paket restore, dotnet paket restore --force, nor dotnet restore --force-evaluate fixes the project's resolved view.
The bug shows up most visibly when BaseIntermediateOutputPath is redirected (e.g. obj/$(_BuildPlatform)/) because the platform-agnostic obj/<proj>.fsproj.paket.props does get rewritten by the CLI's full restore but the platform-specific copy at obj/<rid>/<proj>.fsproj.paket.props (which is what MSBuild actually reads) does not.
Repro steps
- Small SDK-style F# project that pulls in a single package transitively.
Directory.Build.props redirects BaseIntermediateOutputPath to obj/custom/ (mirrors the existing i003527 scenario layout).
dotnet restore project.fsproj — establishes steady state. Per-project files in obj/custom/ reflect the lock.
- Mutate
paket.lock content (e.g. append a comment line to simulate a transitive bump). Do not touch paket.references.
dotnet restore project.fsproj again, with PAKET_ERROR_ON_MSBUILD_EXEC=true to assert whether a per-project paket restore fires.
Expected behavior
The per-project skip should treat a paket.lock content change as an invalidation event and re-run the per-project restore, just as it does on a paket.references content change.
Actual behavior
PAKET_ERROR_ON_MSBUILD_EXEC=true does not fire — Step 2a sets PaketRestoreRequired=false because paket.references hash matches the cached hash, and Step 2b sees the resolved files exist. obj/<rid>/<proj>.fsproj.paket.props stays at the pre-mutation content; project.assets.json continues resolving the affected package to the old version.
Known workarounds
- Delete
obj/<rid>/ for the affected projects to force DoAllResolvedFilesExist=false (Step 2b then fires). Heavy hammer; loses build outputs.
dotnet paket install --force (re-resolves and rewrites all per-project state). Heavy hammer; touches the lock.
- Set
PAKET_SKIP_RESTORE_TARGETS=true and patch Paket.Restore.targets locally to add the missing check. Workable but requires every developer / CI / IDE to set the env var.
I have a candidate fix and an integration test (red/green) that follows the same shape as i002684-fast-restore and i003527; PR to follow this issue.
Description
Paket.Restore.targets' Step 2a invalidates the per-project restore skip only when the content hash ofpaket.referenceschanges. Whenpaket.lockis updated by a transitive change — e.g. a merge that bumps a transitive package version —paket.referencescontent is unchanged, Step 2a's skip kicks in, and the per-projectobj/<rid>/<proj>.fsproj.paket.propsstays at the pre-merge content. Subsequentdotnet restorereads it via$(BaseIntermediateOutputPath)andproject.assets.jsoncontinues resolving the package to the previous version indefinitely. Neitherdotnet paket restore,dotnet paket restore --force, nordotnet restore --force-evaluatefixes the project's resolved view.The bug shows up most visibly when
BaseIntermediateOutputPathis redirected (e.g.obj/$(_BuildPlatform)/) because the platform-agnosticobj/<proj>.fsproj.paket.propsdoes get rewritten by the CLI's full restore but the platform-specific copy atobj/<rid>/<proj>.fsproj.paket.props(which is what MSBuild actually reads) does not.Repro steps
Directory.Build.propsredirectsBaseIntermediateOutputPathtoobj/custom/(mirrors the existingi003527scenario layout).dotnet restore project.fsproj— establishes steady state. Per-project files inobj/custom/reflect the lock.paket.lockcontent (e.g. append a comment line to simulate a transitive bump). Do not touchpaket.references.dotnet restore project.fsprojagain, withPAKET_ERROR_ON_MSBUILD_EXEC=trueto assert whether a per-project paket restore fires.Expected behavior
The per-project skip should treat a
paket.lockcontent change as an invalidation event and re-run the per-project restore, just as it does on apaket.referencescontent change.Actual behavior
PAKET_ERROR_ON_MSBUILD_EXEC=truedoes not fire — Step 2a setsPaketRestoreRequired=falsebecausepaket.referenceshash matches the cached hash, and Step 2b sees the resolved files exist.obj/<rid>/<proj>.fsproj.paket.propsstays at the pre-mutation content;project.assets.jsoncontinues resolving the affected package to the old version.Known workarounds
obj/<rid>/for the affected projects to forceDoAllResolvedFilesExist=false(Step 2b then fires). Heavy hammer; loses build outputs.dotnet paket install --force(re-resolves and rewrites all per-project state). Heavy hammer; touches the lock.PAKET_SKIP_RESTORE_TARGETS=trueand patchPaket.Restore.targetslocally to add the missing check. Workable but requires every developer / CI / IDE to set the env var.I have a candidate fix and an integration test (red/green) that follows the same shape as
i002684-fast-restoreandi003527; PR to follow this issue.