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?
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.
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>
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".
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With