Documented "Run a target exactly once" solution not working with Visual Studio
Run a target exactly once describes how to write a target that is only run once even if the project is multi-targetted.
Slightly shortened version:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<Target Name="MyBeforeBuildTarget">
<Warning Text="MyBeforeBuildTarget" />
</Target>
<Target Name="BuildMyBeforeBuildTargetBeforeOuterBuild"
DependsOnTargets="MyBeforeBuildTarget"
BeforeTargets="DispatchToInnerBuilds" />
<Target Name="BuildMyBeforeBuildTargetBeforeInnerBuild"
BeforeTargets="BeforeBuild">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="MyBeforeBuildTarget"
RemoveProperties="TargetFramework" />
</Target>
</Project>
This works as expected when using the command line, but building the csproj in Visual Studio will cause two warning : MyBeforeBuildTarget outputs to appear instead of the expected one:
1>------ Rebuild All started: Project: Test, Configuration: Debug Any CPU ------
Restored E:\temp\test\Test.csproj (in 19 ms).
1>E:\temp\test\Test.csproj(11,5): warning : MyBeforeBuildTarget
1>E:\temp\test\Test.csproj(11,5): warning : MyBeforeBuildTarget
1>Done building project "Test.csproj".
1>Test -> E:\temp\test\bin\Debug\net48\Test.dll
1>Test -> E:\temp\test\bin\Debug\net8.0\Test.dll
1>Done building project "Test.csproj".
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
This is probably due to the black magic Visual Studio is doing when building projects (i.e. not using msbuild/dotnet directly), so this might be the wrong place to report this, but given the prevalence of VS for development, the currently documented solution is not really practical currently.
Is there a solution that works with the CLI and the main IDEs?
Further technical details
details of dotnet --info
dotnet --info .NET SDK: Version: 9.0.305 Commit: 3fc74f3529 Workload version: 9.0.300-manifests.6fcb754b MSBuild version: 17.14.21+8929ca9e3
Runtime Environment: OS Name: Windows OS Version: 10.0.26100 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\9.0.305\
Visual Studio 2022 Professional 17.14.15.
Hi @danstur, I've reproduced this issue and found a simple workaround that works with both CLI and Visual Studio. The problem occurs because the documented solution calls the target from both outer and inner build contexts.
Solution: Use a custom property to prevent duplicate execution
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<Target Name="MyBeforeBuildTarget"
Condition="'$(CalledFromInnerBuild)' != 'true'">
<Warning Text="MyBeforeBuildTarget" />
</Target>
<Target Name="BuildMyBeforeBuildTargetBeforeOuterBuild"
DependsOnTargets="MyBeforeBuildTarget"
BeforeTargets="DispatchToInnerBuilds" />
<Target Name="BuildMyBeforeBuildTargetBeforeInnerBuild"
BeforeTargets="BeforeBuild">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="MyBeforeBuildTarget"
RemoveProperties="TargetFramework"
Properties="CalledFromInnerBuild=true" />
</Target>
</Project>
The key change is adding a condition to MyBeforeBuildTarget and passing a custom property CalledFromInnerBuild=true when called from inner builds. This way, the target content only executes during the outer build, ensuring you see exactly one warning instead of two.
Please try this workaround and let me know the result. Thank you so much!
@huulinhnguyen-dev Sorry for the late response, vacation and christmas time. Works fine for me in both Visual Studio and the CLI.
What would be the right approach to get the documentation updated so that others have an easier time finding this solution as well? I don't see any downside to using your approach compared to the documented one.