MSBuild 目标,BeforeCompile,每当加载项目文件时触发

MSBuild target, BeforeCompile, triggering whenever project file is loaded

这是一个与我的整体问题相关的小问题。这就是我想要完成的。

我创建了一个任务来修改我的源文件。每当我编译和构建时,我都想用修改后的 cs 文件进行编译。我试图制作一个从预构建事件调用的控制台应用程序,但我发现只有一些修改后的文件被编译。即使我没有在编辑器中打开它们。然后我尝试在我的 csproj 文件中使用与以下 xml 一起使用的自定义任务:

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

我也用这个得到了同样的结果:

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

所以这就是我想做的,但我发现每当我加载项目文件时,它都会触发我的目标。我假设这只会 运行 当我的代码被编译时。很明显,它正在编译或编译相关目标在项目文件加载时被触发。

理想情况下,我希望此目标仅在我明确构建项目时触发(即手动构建或开始调试时)。我可以做些什么来实现这一目标?

根据文档,这些目标始终作为 Visual Studio 加载的一部分执行。这也将包括这些目标具有的任何依赖项。

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

比较 运行 msbuild /v:diag /t:compilemsbuild /v:diag /t:build 时执行的目标时,您会看到 ResGen 和其他一些目标被跳过。尝试搭载其中之一可能对您有用。

还要记住这些关于 Visual Studio 托管进程及其对动态更改文件的影响的事情:

Visual Studio 编译器可能正在使用其缓存版本的文件。这会导致问题,或者在 obj 文件夹下显式创建文件并将它们动态包含在 MsBuild 中,这样 Visual Studio 就不会使用它在内存中的文件实例。通过从 ItemGroup 中删除源文件并添加您自己生成的副本来执行此操作,您需要从自定义目标执行此操作:

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

除了硬编码,您还可以使用转换表达式:

<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 HostCompiler 添加

<UseHostCompilerIfAvailable>FALSE</UseHostCompilerIfAvailable>

到第一个属性组(不应该有条件)使 Visual Studio 始终使用磁盘版本(会稍微减慢 Visual Studio 构建)。

让你的任务 Visual Studio 意识到

或者让你的构建任务知道 IVsMSBuildTaskFileManager 并在你更新文件时告诉它。您需要注册 your build task in the registry of Visual Studio to flag it as "safe to load".

编辑: 这就是我所描述的。虽然它似乎并没有在编译过程中使用修改后的文件。如果有人需要 运行 在整个构建过程的早期执行任务但不希望在加载项目文件时执行这些任务,这仍然是一个很好的参考。如果我找到针对我的特定问题的解决方案,我会回来编辑(这仍然与这个问题相关!)


原答案:

所以我在玩弄 jessehouwing 的回答中的信息时找到了答案。这是我使用的代码,我将在后面进行解释。

<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>

我这里有两个目标。最下面的是我需要运行的任务。第二个是将自定义 属性、SetFirstTimeLoading 设置为 false.

RunCustomTask 将在编译前 运行 因为它列在 CompileDependsOn.

SetFirstTimeLoading 将 运行 在 Compile 之后但也在 ResolveAssemblyReferences 之前,因为它列在 ResolveAssemblyReferencesDependsOn.

因此,我可以在我的 RunCustomTask 目标中设置一个条件,以查看 FirstTimeLoading 属性 是否已设置为 false。这是在第一次加载项目文件时设置的。因此,我的任何后续任务都是 运行(每当我构建 visual studio 时),RunCustomTask 的条件将始终评估为 true 并将 运行 MyCustomTask.