LibMan runs before dependent project targets
Problem ProjectB generates files in a Target
<Target Name="RunLBeforeBuildStepgen" BeforeTargets="BeforeBuild" >
<Message Text="=== Generating localizations ===" Importance="high" />
<WriteLinesToFile File="Localizations.js" Lines="Localization data generated before build." />
</Target>
ProjectA have this in its libman.json
{
"version": "1.0",
"defaultProvider": "filesystem",
"libraries": [
{
"library": "../../ProjectB/",
"files": [ "Localizations.js" ],
"destination": "wwwroot/assets/l10n/"
}
]
}
dotnet build .\ProjectA\ProjectA\ProjectA.csproj fails:
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
ProjectA net8.0 failed with 2 error(s) (0.1s)
D:\Nuget\microsoft.web.librarymanager.build\3.0.71\build\Microsoft.Web.LibraryManager.Build.targets(35,9): error
System.AggregateException: One or more errors occurred. (The "../../ProjectB/" library could not be resolved by th
e "filesystem" provider)
---> Microsoft.Web.LibraryManager.Contracts.InvalidLibraryException: The "../../ProjectB/" library could not be r
esolved by the "filesystem" provider
at Microsoft.Web.LibraryManager.Providers.FileSystem.FileSystemProvider.GetStreamAsync(ILibraryInstallationStat
e state, String file, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at Microsoft.Web.LibraryManager.Providers.FileSystem.FileSystemProvider.<>c__DisplayClass9_1.<InstallAsync>b__0
()
at Microsoft.Web.LibraryManager.Build.Contracts.HostInteraction.WriteFileAsync(String path, Func`1 content, ILi
braryInstallationState state, CancellationToken cancellationToken)
at Microsoft.Web.LibraryManager.Providers.FileSystem.FileSystemProvider.InstallAsync(ILibraryInstallationState
desiredState, CancellationToken cancellationToken)
libman.json : error LIB000: An unknown exception occurred
https://github.com/aspnet/LibraryManager/issues/642 describes a similar problem. But in my case its a target in a referenced project. And yes, also tried BeforeTargets="LibraryManagerRestore"
Created a little repo https://github.com/AlexanderRydberg/LibraryManagerTargetIssue
Expected behavior Targets in referenced project being run before libman.
Is this behavior by design?
Any suggestions on how I could solve this without:
manually building ProjectB first and then ProjectA
or
Copy Target from ProjectB to ProjectA's csproj and have BeforeTargets="LibraryManagerRestore"
@jimmylewis do you have any suggestions on how to work around this 🙏 ? Or should I clearify anything in the issue?
I'm still investigating. From my understanding, MSBuild should recognize that ProjectA having a ProjectReference to ProjectB requires B to build first. But in the clean case, it's clearly running the LibMan target in ProjectA before ProjectB has built. I need to see if there's a better way to schedule the target after ProjectA's dependencies have built.
However, when I build the slnx in Visual Studio, it correctly identifies the build order and I can't reproduce the problem. So this might be a bug in dotnet/msbuild CLI. The build order is also correct in the Project Build Dependencies wizard (right-click menu on the solution) and can't be changed.
As a side note, I did notice that the error message in 3.0.71 is bad, but I think this was fixed in #794. Error message from 3.0.71:
System.AggregateException: One or more errors occurred. (The "../../ProjectB/" library could not be resolved by
the "filesystem" provider)
Note that this is "library not resolved" which is weird. There's also a LIB000 unknown failure.
Error message from tip (3.0.106):
ProjectA net8.0 failed with 1 error(s) (0.0s)
libman.json : error LIB018:
"../../ProjectB/" does not contain the following: Localizations.js
Valid files are Program.cs, ProjectB.csproj
Of course, I had an inspiration right after posting that. If I use a SLN file instead of SLNX, it also works correctly, building B before A even from clean. So this is a bug in SLNX build on the CLI.
I played around with the slnx a little more. Explicitly setting the build dependency seems to be a workaround:
<Solution>
<Project Path="../ProjectB/ProjectB.csproj" />
<Project Path="ProjectA/ProjectA.csproj">
<BuildDependency Project="../ProjectB/ProjectB.csproj" />
</Project>
</Solution>
First of all, I really appreciate you taking the time to check on this. Isnt this an interesting issue 😄 !?
Yeah you are correct that everything works well in Visual Studio. Imagine my frustration when it didnt work on our buildserver 😅
Im testing a bit more now. No changes on the repo.
Msbuild version
MSBuild version 18.0.5+e22287bf1 for .NET Framework
18.0.5.56406
Building / Rebuilding slnx with msbuild works:
$msbuildPath = "C:\Program Files\Microsoft Visual Studio\18\Professional\MSBuild\Current\Bin\MSBuild.exe"
# Make sure we always start from a clean state
&"$msbuildPath" .\ProjectA\ProjectA.slnx /r '/t:Clean' /m /p:Configuration=Release
&"$msbuildPath" .\ProjectA\ProjectA.slnx /r '/t:Restore;Rebuild' /m /p:Configuration=Release
&"$msbuildPath" .\ProjectA\ProjectA.slnx /r '/t:Clean' /m /p:Configuration=Release
&"$msbuildPath" .\ProjectA\ProjectA.slnx /r '/t:Build' /m /p:Configuration=Release
$msbuildPath = "C:\Program Files\Microsoft Visual Studio\18\Professional\MSBuild\Current\Bin\MSBuild.exe"
&"$msbuildPath" .\ProjectA\ProjectA\ProjectA.csproj '/t:Clean' /m /p:Configuration=Release
&"$msbuildPath" .\ProjectB\ProjectB.csproj /r '/t:Restore;Build' /m /p:Configuration=Release
cat ProjectB\Localizations.js
pause
&"$msbuildPath" .\ProjectA\ProjectA\ProjectA.csproj /r '/t:Restore;Build' /m /p:Configuration=Release
cat ProjectB\Localizations.js
ProjectB\Localizations.js contains two rows:
Localization data generated before build.
Localization data generated before build.
We here know that ProjectA is triggering a build of ProjectB but its too slow. This is also what we see if we build the slnx with dotnet build.
dotnet build .\ProjectA\ProjectA.slnx
Restore complete (0.4s)
ProjectA net8.0 failed with 2 error(s) (0.0s)
D:\Nuget\microsoft.web.librarymanager.build\3.0.71\build\Microsoft.Web.LibraryManager.Build.targets(35,9): error
System.AggregateException: One or more errors occurred. (The "../../ProjectB/" library could not be resolved by the "filesystem" provider)
---> Microsoft.Web.LibraryManager.Contracts.InvalidLibraryException: The "../../ProjectB/" library could not be resolved by the "filesystem" provider
at Microsoft.Web.LibraryManager.Providers.FileSystem.FileSystemProvider.GetStreamAsync(ILibraryInstallationState state, String file, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at Microsoft.Web.LibraryManager.Providers.FileSystem.FileSystemProvider.<>c__DisplayClass9_1.<InstallAsync>b__0()
at Microsoft.Web.LibraryManager.Build.Contracts.HostInteraction.WriteFileAsync(String path, Func`1 content, ILibraryInstallationState state, CancellationToken cancellationToken)
at Microsoft.Web.LibraryManager.Providers.FileSystem.FileSystemProvider.InstallAsync(ILibraryInstallationState desiredState, CancellationToken cancellationToken)
libman.json : error LIB000: An unknown exception occurred
ProjectB net8.0 succeeded (0.3s) → ProjectB\bin\Debug\net8.0\ProjectB.dll
The second build will now succeed.
Can confirm that the issue doesnt exist on sln. This works as expected:
dotnet new sln -n Project
dotnet sln Project.sln add .\ProjectA\ProjectA\ProjectA.csproj
dotnet sln Project.sln add .\ProjectB\ProjectB.csproj
dotnet build .\Project.sln
And also works with msbuild:
$msbuildPath = "C:\Program Files\Microsoft Visual Studio\18\Professional\MSBuild\Current\Bin\MSBuild.exe"
&"$msbuildPath" .\Project.sln '/t:Clean' /m /p:Configuration=Release
&"$msbuildPath" .\Project.sln /r '/t:Restore;Rebuild' /m /p:Configuration=Release
&"$msbuildPath" .\Project.sln '/t:Clean' /m /p:Configuration=Release
&"$msbuildPath" .\Project.sln /r '/t:Build' /m /p:Configuration=Release
Maybe this is a bug within msbuild like you suggested. I will open a issue on their repo as well.