Search code examples
msbuildmsbuild-task

How to create a msbuild target to run after "Run" target (dotnet run)?


I'm trying to create a msbuild csproj where I can invoke dotnet run myproject and get bin/obj folders deleted after execution. However, the CustomAfterRun target (below) does not get executed after the Run target.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <EnableDefaultItems>false</EnableDefaultItems>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
  </ItemGroup>

  <Target Name="CustomAfterRun" AfterTargets="Run">
    <Message Importance="high" Text="[msbuild] Cleaning bin and obj..."/>
    <RemoveDir Directories="$(TargetDir)" />
    <RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" />
  </Target>
</Project>

If I create a target CustomAfterBuild (with AfterTargets="Build"), it works fine (it gets executed after the build when I run dotnet build myproj).

Is this a bug or am I doing something wrong?

And the crazy part: if inside CustomAfterBuild I invoke <CallTarget Targets="Run" /> then after the build my program is invoked, and in this case CustomAfterRun runs fine !! The downside of this method is that the program output is only visible if I enable detailed verbosity level, and also after CustomAfterRun runs (deleting bin/obj folders) it will also try to run the program AGAIN (meaning that the Run target is invoked TWICE?!) giving me an error: System.ComponentModel.Win32Exception (2): The system cannot find the file specified..

EDIT: As an alternative I also tried overriding Run target, but that didn't work at all:

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <EnableDefaultItems>false</EnableDefaultItems>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
  </ItemGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

  <Target Name="Run" >
    <Message Importance="high" Text="[msbuild] overriden..."/>
  </Target>
</Project>

Solution

  • Turns out I had a wrong assumption about dotnet run.

    • There's dotnet msbuild which is basically a facade to invoke msbuild. dotnet build is just a shortcut to dotnet msbuild -restore, so it will restore nuget packages and build (default target). dotnet clean also invokes msbuild (clean target).

    • In a similar sense I was expecting dotnet run to use the Run target from msbuild, however it does NOT. It invokes dotnet msbuild (if required), but it will run the output WITHOUT using msbuild.
      If I run msbuild -target:Run the post-run events work fine!

    So one possible solution would be to use plain msbuild.
    E.g.: dotnet msbuild /t:Restore;Build;Run;Clean MyProj.csproj),

    However, another behavior which was annoying me is that when the program is launched through msbuild I don't get colors (if any) in console output. This is probably because msbuild uses console-redirection to run my program. dotnet run fortunately doesn't have this behavior, so my final solution will be:

    dotnet run & dotnet clean

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <EnableDefaultItems>false</EnableDefaultItems>
      </PropertyGroup>
      <ItemGroup>
        <Compile Include="Program.cs" />
      </ItemGroup>
    
      <!-- Clean is not so perfect, clear leftovers -->
      <Target Name="CleanBinObj" AfterTargets="Clean">
        <RemoveDir Directories="$(ProjectDir)$(BaseOutputPath)" />
        <RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" />
      </Target>
    
      <!-- If someone uses msbuild to launch, force a clean.. -->
      <Target Name="CustomAfterRun" AfterTargets="Run">
        <CallTarget Targets="Clean" />
      </Target>
    </Project>
    

    Or for my specific case where I'll keep multiple individual cs programs in the same folder (each one with their own csproj) and I want to override bin/obj folders with an individual (temporary) folder for my file:

    <Project>
    
      <PropertyGroup>
        <TempDir>$(MSBuildProjectName).cs.tmp\</TempDir>
        <BaseOutputPath>$(TempDir)</BaseOutputPath>
        <BaseIntermediateOutputPath>$(TempDir)</BaseIntermediateOutputPath>
      </PropertyGroup>
    
      <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <EnableDefaultItems>false</EnableDefaultItems>
      </PropertyGroup>
    
      <ItemGroup>
        <Compile Include="$(MSBuildProjectName).cs" />
      </ItemGroup>
    
      <!-- Clean is not so perfect, clear leftovers -->
      <Target Name="CleanBinObj" AfterTargets="Clean">
        <Message Importance="high" Text="Cleaning $(TempDir) (bin/obj)..."/>
        <RemoveDir Directories="$(TempDir)" />
      </Target>
    
      <!-- If someone uses msbuild to launch, force a clean.. -->
      <Target Name="CustomAfterRun" AfterTargets="Run">
        <CallTarget Targets="Clean" />
      </Target>
      
      <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
    
    </Project>