MSBuild target, BeforeCompile, triggering whenever project file is loaded

2.9k views Asked by At

This is a smaller question related to my overall problem. This is what I am trying to accomplish.

Ive created a task that modifies my source files. Whenever I compile and build, I want to compile with the modified cs files. Ive attempted to make a console application that I call from the prebuild event but I found that only some of the modified files were compiled. Even when I did not have them opened in the editor. Then I attempted to use a custom task which works with the following xml in my csproj file:

<Target Name="BeforeCompile">
    <MyCustomTask />
</Target>

And I also used this to get the same result:

<PropertyGroup>
    <CompileDependsOn>
        $(CompileDependsOn);
        MyCustomTarget;
    </CompileDependsOn>
</PropertyGroup>
<Target Name="MyCustomTarget">
    <MyCustomTask />
</Target>

So this does what I want to do, but I found that whenever I load the project file, it triggers my target. I would assume that this would only run when my code is being compiled. So apparently, it is compiling or compile related targets are being triggered when the project file loads.

Ideally, I want this target to trigger only when I explicitly build my project (i.e. manually building or when I start debugging). What can I do differently to achieve this?

2

There are 2 answers

3
jessehouwing On BEST ANSWER

From the documentation, these targets are always executed as part of the loading of Visual Studio. That will also include any dependencies these targets have.

Design-Time Target Execution

Visual Studio attempts to execute targets with certain names when it loads a project. These targets include Compile, ResolveAssemblyReferences, ResolveCOMReferences, GetFrameworkPaths, and CopyRunEnvironmentFiles. Visual Studio runs these targets so that the compiler can be initialized to provide IntelliSense, the debugger can be initialized, and references displayed in Solution Explorer can be resolved. If these targets are not present, the project will load and build correctly but the design-time experience in Visual Studio will not be fully functional.

Source

When comparing which targets are executed when running msbuild /v:diag /t:compile vs msbuild /v:diag /t:build you'll see that ResGen and a few other targets are skipped. Trying to piggyback on one of these may do the trick for you.

Also keep in mind these things about the Visual Studio Hosting process and the effect it has on dynamically changed files:

The Visual Studio compiler may be using it's cached version of the files. Which will cause issues, either explicitly create the files under the obj folder and include them in MsBuild dynamically, that way Visual Studio will not use it's in-memory instance of the file. Do this by removing the source file from the ItemGroup and adding your own generated copy instead, you'll need to do this from your custom target:

<ItemGroup>
   <Compile Remove="ThefileYourWantGone.cs" />
   <Compile Include="$(BaseIntermediateOutputPath)\ThefileYourWantGone.g.cs/>
</ItemGroup>

Instead of hard coding it, you could also use a transformation expression:

<ItemGroup>
   <Compile Include="ThefileYourWantGone.cs">
      <IsRegenerated>true</IsRegenerated>
   </Compile>
</ItemGroup>

<ItemGroup>
   <RegeneratedCompile
        Include="@(Compile)"
        Condition="'%(Compile.IsRegenerated)' == 'true'"
   />
</ItemGroup>

<YourCustomtaskThatOutputs Inputs="@(RegeneratedCompile)" Outputs="@(RegeneratedCompile-> '$(BaseIntermediateOutputPath)\%(relativedir)%(filename).g.%(extension)')" />

<ItemGroup>
   <Compile Remove="@(RegeneratedCompile)" />
   <Compile Include="@(RegeneratedCompile-> '$(BaseIntermediateOutputPath)\%(relativedir)%(filename).g.%(extension)')" />
</ItemGroup>

Alternatives

Disable the host compiler

Or disable the HostCompiler by adding

<UseHostCompilerIfAvailable>FALSE</UseHostCompilerIfAvailable>

to the first propertygroup (that should not have a condition on it) to make Visual Studio always use the disk version (will slow down Visual Studio builds slightly).

Make your task Visual Studio aware

Or make your build task aware of the IVsMSBuildTaskFileManager and tell it when you update the files. You will need to register your build task in the registry of Visual Studio to flag it as "safe to load".

0
xadeka On

Edit: This does what I describe it to do. Although it doesn't seem that it uses the modified files in the compile process. This is still good reference if someone needs to run a task early on in the entire build process but doesn't want them executing when the project file is loaded. I will come back and edit if I find a solution for my specific problem (which is still relevant to this one!)


Original Answer:

So I found the answer while playing around with information from jessehouwing's answer. Here is the code I used and I will follow with an explanation.

<PropertyGroup>
    <ResolveAssemblyReferencesDependsOn>
        SetFirstTimeLoading;
        $(ResolveAssemblyReferencesDependsOn);
    </ResolveAssemblyReferencesDependsOn>
    <CompileDependsOn>
        RunCustomTask;
        $(CompileDependsOn);
    </CompileDependsOn>
</PropertyGroup>

<Target Name="SetFirstTimeLoading">
    <PropertyGroup>
        <FirstTimeLoading Condition=" '$(FirstTimeLoading)' == '' ">false</FirstTimeLoading>
    </PropertyGroup>
</Target>

<UsingTask TaskName="MyCustomTask" AssemblyFile="path\to\task" />
<Target Name="RunCustomTask" Condition=" '$(FirstTimeLoading)' == 'false' ">
    <MyCustomTask/>
</Target>

I have two targets here. The bottom one is the task I need to run. The second is setting a custom property, SetFirstTimeLoading, to false.

RunCustomTask will run before compiling because it is listed in the CompileDependsOn.

SetFirstTimeLoading will run after Compile but also before ResolveAssemblyReferences because it is listed in the ResolveAssemblyReferencesDependsOn.

So because of this, I can have a conditional in my RunCustomTask target that will see if the FirstTimeLoading property has been set to false. This is set the first time the project file is loaded. So any subsequent time the my tasks are run (whenever I build in visual studio), RunCustomTask's conditional will always evaluate to true and will run MyCustomTask.