Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions integrationtests/Paket.IntegrationTests/RestoreSpecs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,49 @@ let ``#3527 BaseIntermediateOutputPath``() =
defaultObjDir.GetFiles() |> shouldBeEmpty
customObjDir.GetFiles().Length |> shouldBeGreaterThan 0

[<Test>]
let ``#4333 paket.lock content change invalidates per-project restore skip``() =
// Step 2a's "skip per-project restore" optimisation only invalidates on
// paket.references content change. When a merge bumps a transitive
// package version, paket.lock/paket.resolved change but paket.references
// does not, and (with BaseIntermediateOutputPath redirected) the
// platform-specific obj/<rid>/<proj>.fsproj.paket.props is left stale —
// NuGet then resolves to the previous version forever. This test pins
// the lock-content invalidation that fixes that case.
let project = "project"
let scenario = "i4333-lock-content-invalidates-project-props"
use __ = prepareSdk scenario

let wd = (scenarioTempPath scenario) @@ project

// Set up steady state with an initial dotnet restore. This drives
// MSBuild's PaketRestore target which writes platform-specific
// obj/custom/<proj>.fsproj.<tfm>.paket.resolved (paket-CLI on its own
// doesn't know about BaseIntermediateOutputPath).
directDotnet true (sprintf "restore %s.fsproj" project) wd
|> ignore

// Sanity: in steady state, dotnet restore does not invoke per-project
// paket restore. PAKET_ERROR_ON_MSBUILD_EXEC=true would error if it did.
// (Mirrors the assertion in #2684 fast-restore.)
directDotnetEx [ "PAKET_ERROR_ON_MSBUILD_EXEC", "true" ] true (sprintf "restore %s.fsproj" project) wd
|> ignore

// Mutate paket.lock content (simulates a merge that bumps a transitive
// version while paket.references stays unchanged — the case Step 2a's
// hash-based skip misses).
let lockPath = (scenarioTempPath scenario) @@ "paket.lock"
File.AppendAllText(lockPath, Environment.NewLine + "// trivial content change to invalidate lock hash" + Environment.NewLine)

// The next dotnet restore should now detect the lock-content change and
// request a per-project restore. PAKET_ERROR_ON_MSBUILD_EXEC=true causes
// that to fail with a message identifying the reason.
let failure = Assert.Throws<ProcessFailedWithExitCode>(fun () ->
directDotnetEx [ "PAKET_ERROR_ON_MSBUILD_EXEC", "true" ] true (sprintf "restore %s.fsproj" project) wd
|> ignore)
let message = failure.ToString()
Assert.IsTrue(message.Contains "paket.lock content changed", sprintf "expected the restore to fail with a 'paket.lock content changed' reason, got:\n%s" message)

[<Test>]
let ``#3000-a dotnet restore``() =
let scenario = "i003000-netcoreapp2"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project>

<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);$(MSBuildProjectDirectory)/obj/**/*</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);$(MSBuildProjectDirectory)/bin/**/*</DefaultItemExcludes>

<PaketDisableGlobalRestore>true</PaketDisableGlobalRestore>
</PropertyGroup>

<PropertyGroup>
<BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/obj/custom/</BaseIntermediateOutputPath>
<BaseOutputPath>$(MSBuildProjectDirectory)/bin/custom/</BaseOutputPath>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source https://api.nuget.org/v3/index.json

nuget FSharp.Core

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Learn more about F# at http://fsharp.org

open System

[<EntryPoint>]
let main argv =
printfn "Hello World from F#!"
0 // return an integer exit code
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FSharp.Core
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>
37 changes: 37 additions & 0 deletions src/Paket.Core/embedded/Paket.Restore.targets
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,38 @@
<PaketRestoreRequired Condition=" '$(PaketRestoreReferencesFileHash)' == '$(PaketRestoreCachedHash)' ">false</PaketRestoreRequired>
</PropertyGroup>

<!-- Step 2 a-bis Detect changes in paket.lock since this project's
last per-project restore. Step 2a alone misses the case where
paket.references content is unchanged but paket.lock has been
updated (e.g. a merge that bumps a transitive package version).
Without this, the per-project paket.props under a redirected
BaseIntermediateOutputPath stays stale and NuGet keeps resolving
to the old version. The cache file recording the lock hash is
written after a successful per-project restore, see Step 3. -->
<PropertyGroup>
<PaketProjectLockHashCacheFilePath>$(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.lock.hash</PaketProjectLockHashCacheFilePath>
</PropertyGroup>
<!-- Compute current paket.lock hash here even if Step 1 was disabled
(e.g. PaketDisableGlobalRestore=true) so the per-project check
still works. -->
<GetFileHash Condition=" '$(MSBuildSupportsHashing)' == 'true' AND Exists('$(PaketLockFilePath)') AND '$(PaketProjectLockHashCurrent)' == '' " Files="$(PaketLockFilePath)" Algorithm="SHA256" HashEncoding="hex">
<Output TaskParameter="Hash" PropertyName="PaketProjectLockHashCurrent" />
</GetFileHash>
<PropertyGroup Condition=" '$(MSBuildSupportsHashing)' == 'true' AND Exists('$(PaketLockFilePath)') AND Exists('$(PaketProjectLockHashCacheFilePath)') ">
<PaketProjectLockHashCached>$([System.IO.File]::ReadAllText('$(PaketProjectLockHashCacheFilePath)').Trim())</PaketProjectLockHashCached>
<PaketRestoreRequired Condition=" '$(PaketProjectLockHashCurrent)' != '' AND '$(PaketProjectLockHashCached)' != '$(PaketProjectLockHashCurrent)' ">true</PaketRestoreRequired>
<PaketRestoreRequiredReason Condition=" '$(PaketProjectLockHashCurrent)' != '' AND '$(PaketProjectLockHashCached)' != '$(PaketProjectLockHashCurrent)' ">paket.lock content changed since last per-project restore</PaketRestoreRequiredReason>
</PropertyGroup>
<!-- First encounter: hash file does not exist but paket.lock does.
This catches checkouts that have legacy per-project state from
before this check was introduced (so Step 2b may not fire) but
where paket.lock has since been bumped. After the per-project
restore runs, Step 3 a-bis writes the cache file. -->
<PropertyGroup Condition=" '$(MSBuildSupportsHashing)' == 'true' AND Exists('$(PaketLockFilePath)') AND !Exists('$(PaketProjectLockHashCacheFilePath)') ">
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequiredReason>per-project paket.lock hash cache missing</PaketRestoreRequiredReason>
</PropertyGroup>

<PropertyGroup Condition="!Exists('$(PaketOriginalReferencesFilePath)') AND !Exists('$(PaketReferencesCachedFilePath)') ">
<!-- If both don't exist there is nothing to do. -->
<PaketRestoreRequired>false</PaketRestoreRequired>
Expand All @@ -218,6 +250,11 @@
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" --output-path "$(PaketIntermediateOutputPath)" --target-framework "$(TargetFrameworks)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' == '' " ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" --output-path "$(PaketIntermediateOutputPath)" --target-framework "$(TargetFramework)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' != '' " ContinueOnError="false" />

<!-- Step 3 a-bis After a successful per-project restore, stamp the
paket.lock hash next to the per-project paket files. Step 2 a-bis
reads this on the next call to detect lock-content changes. -->
<WriteLinesToFile Condition=" '$(MSBuildSupportsHashing)' == 'true' AND '$(PaketRestoreRequired)' == 'true' AND '$(PaketProjectLockHashCurrent)' != '' " File="$(PaketProjectLockHashCacheFilePath)" Lines="$(PaketProjectLockHashCurrent)" Overwrite="true" />

<!-- This shouldn't actually happen, but just to be sure. -->
<PropertyGroup>
<DoAllResolvedFilesExist>false</DoAllResolvedFilesExist>
Expand Down