-
Notifications
You must be signed in to change notification settings - Fork 266
Description
Invoking the "Restore" target on projects in parallel leads to a race condition in committing the restore result to disk. This appears most often as the result of developers writing their own MSBuild targets and using the <MSBuild> task to invoke Restore.
The only workaround I could find was to avoid this calling pattern completely. Instead, you can call the the 'restore' target in NuGet.targets and pass in the list of projects.
cref dotnet/arcade#1567
cref https://github.com/aspnet/BuildTools/blob/0872134136bf3d181e3ca3fa700b7ff392c055b3/files/KoreBuild/modules/projectbuild/module.targets#L21-L70
Ideas
I don't see an obvious solution. Here are some ideas
- Use
IBuildEngine4.GetRegisteredTaskObjectwithinRestoreTaskto avoid double-restores during the same build. - Use
IBuildEngine4.GetRegisteredTaskObjectwithinRestoreTaskto detect a double-restore was invoked, and provide a warning message. - Include which file is the problem in the error message.
- Instead of failing immediately when you can't write the restore result, retry.
Details about Problem
NuGet product used: dotnet.exe or MSBuild.exe
dotnet.exe --version: 2.2.100
VS version: 15.9
OS version: Any
Worked before?: no
Detailed repro steps so we can see the same problem
- Create a set of 3 simple .NET standard class library projects (see attached repro). Two projects should both have ProjectReference to the third project.
Lib A --> LibC
Lib B --> LibC
- Create an MSBuild targets file like this. We'll call it "ci.proj".
<!-- ci.proj -->
<Project>
<ItemGroup>
<Projects Include="LibA/LibA.csproj" />
<Projects Include="LibB/LibB.csproj" />
<Projects Include="LibC/LibC.csproj" />
</ItemGroup>
<Target Name="Restore">
<MSbuild Projects="@(Projects)"
Targets="Restore"
BuildInParallel="true" />
</Target>
</Project>- Run restore by invoking
msbuild -t:ci.proj -t:restore
Result
NuGet fails in LibC with an error that says "Cannot create a file when that file already exists". The error message does not indicate which file was the problem.
Restoring packages for C:\tmp\restoreRepro\LibC\LibC.csproj...
Restoring packages for C:\tmp\restoreRepro\LibC\LibC.csproj...
Restoring packages for C:\tmp\restoreRepro\LibB\LibB.csproj...
Generating MSBuild file C:\tmp\restoreRepro\LibC\obj\LibC.csproj.nuget.g.props.
Generating MSBuild file C:\tmp\restoreRepro\LibC\obj\LibC.csproj.nuget.g.targets.
Generating MSBuild file C:\tmp\restoreRepro\LibC\obj\LibC.csproj.nuget.g.targets.
Generating MSBuild file C:\tmp\restoreRepro\LibB\obj\LibB.csproj.nuget.g.props.
Generating MSBuild file C:\tmp\restoreRepro\LibB\obj\LibB.csproj.nuget.g.targets.
Restore completed in 12.8 ms for C:\tmp\restoreRepro\LibC\LibC.csproj.
Restore completed in 14.61 ms for C:\tmp\restoreRepro\LibB\LibB.csproj.
Restoring packages for C:\tmp\restoreRepro\LibA\LibA.csproj...
Restoring packages for C:\tmp\restoreRepro\LibC\LibC.csproj...
C:\Users\namc\.dotnet\x64\sdk\2.2.100\NuGet.targets(114,5): error : Cannot create a file when that file already exists [C:\tmp\restoreRepro\LibB\LibB.csproj]
Generating MSBuild file C:\tmp\restoreRepro\LibA\obj\LibA.csproj.nuget.g.props.
Generating MSBuild file C:\tmp\restoreRepro\LibA\obj\LibA.csproj.nuget.g.targets.
Restore completed in 128.42 ms for C:\tmp\restoreRepro\LibC\LibC.csproj.
Restore completed in 147.55 ms for C:\tmp\restoreRepro\LibA\LibA.csproj.
Other suggested things
This is a race condition, so it may not appear right away. But it is consistent. I get consistent repros like this:
for($i = 0; $i -lt 100; $i+=1) {
gci -re */obj/**/* | % {rm $_ } # clean the last restore result
dotnet msbuild build.proj -bl -t:Restore
if ($LASTEXITCODE -ne 0){ break }
}