【问题标题】:Running a target before the CoreBuild?在 CoreBuild 之前运行目标?
【发布时间】:2017-05-18 09:32:00
【问题描述】:

我正在向我的项目中添加一个自定义的.tt 模板生成目标,以便在CoreBuild 之前运行,并且似乎有两种方法:

<Project...>
    <Target Name="TransformOnBuild" AfterTargets="BeforeBuild">
</Project>

<Project...>
    <Target Name="TransformOnBuild" BeforeTargets="CoreBuild">
</Project>

如果我的目标应该在我的项目构建之前运行,因为项目依赖它,我使用后者会更好吗?我见过前者用来做诸如生成文本模板之类的事情,但这似乎是一种不可靠的方法,因为它可能会在CoreBuild 之后运行,这为时已晚。还是有什么原因可以保证AfterTargets="BeforeBuild" 在核心构建之前运行?

我还看到了BeforeTargets="BeforeBuild",它将更早地构建。这是放置 `.tt 文本生成目标的更好位置吗?

【问题讨论】:

    标签: visual-studio msbuild


    【解决方案1】:

    基于@stjin 的回答,一个好的解决方案似乎是使用

    BeforeTargets="CoreCompile" DependsOnTargets="PrepareForBuild"
    

    这就是 .net sdk(用于 .net 核心/标准项目的新型 csproj)is doing for automatic AssemblyInfo.cs generation

    它使用以下注释来解释原因:

    请注意,这必须在每次调用 CoreCompile 之前运行 确保所有编译器运行看到生成的程序集信息。那里 至少是一种涉及 Xaml 的场景,其中调用了 CoreCompile 没有其他潜在的钩子,例如 Compile 或 CoreBuild 等,所以 我们直接连接到 CoreCompile。此外,我们必须在 之后运行 PrepareForBuild 确保中间目录已经 已创建。

    请注意,“中间目录”(在本例中为 obj/[TargetFramework])是在本例中放置输出 .cs 文件的位置,这也可能是您可能想要做的。

    【讨论】:

      【解决方案2】:

      更新 2

      基于documentation from Microsoft

      这是目标构建顺序

      1. InitialTargets 目标已运行。
      2. 由 /target 开关在命令行上指定的目标运行。如果您在命令行上未指定任何目标,则运行 DefaultTargets 目标。如果两者都不存在,则运行遇到的第一个目标。
      3. 评估目标的条件属性。如果 Condition 属性存在且计算结果为 false,则不会执行目标,并且不会对构建产生进一步影响。
      4. 在执行目标之前,会运行其 DependsOnTargets 目标。
      5. 在执行目标之前,将运行在 BeforeTargets 属性中列出它的任何目标。
      6. 在执行目标之前,会比较其输入属性和输出属性。如果 MSBuild 确定任何输出文件相对于相应的输入文件已过期,则 MSBuild 将执行目标。否则,MSBuild 会跳过目标。
      7. 执行或跳过目标后,将运行在 AfterTargets 属性中列出该目标的任何目标。

      这些是您问题的答案:

      如果我的目标应该在我的项目构建之前运行,因为项目依赖它,我使用后者会更好吗?

      • 不,因为CoreBuild 的所有依赖项都会在你的模板生成目标 之前执行,那为时已晚。

      或者有什么原因为什么 AfterTargets="BeforeBuild" 仍然保证在核心构建之前运行?

      • AfterTargets="BeforeBuild" 保证您的目标将按时执行,因为它将在所有 CoreBuild 依赖项之前执行。

      我还看到了 BeforeTargets="BeforeBuild",它会更早地构建。这是放置`.tt文本生成目标的更好地方吗?

      • AfterTargets="BeforeBuild"BeforeTargets="BeforeBuild" 这两种情况下,您的目标都将在所有 CoreBuild 依赖项之前执行,但是在这两种情况下,您仍然有可能影响 模板生成目标 的结果,具体取决于您在 BeforeBuild 中必须执行的操作。如果您对此有所控制,则可以安全地使用这些选项中的任何一个。

      在 CoreBuild 之前运行目标?

      > there appear to be 2 ways of doing it.
      
          There are more options to achieve this. Please review below.
      

      您应该为此目的使用特定的内置目标(BeforeBuild 或 AfterBuild)。这是微软在使用依赖于Microsoft.Common.targets的项目时提供给safely extend the build process的机制

      如果您在 CoreBuild 之前只有一个目标要运行,您可以这样做:

      <Target Name="BeforeBuild">
          <!-- add your tasks here -->
      </Target>
      

      如果您在 CoreBuild 之前有多个目标要运行,您可以定义一个属性,其中包含所有需要按所需执行顺序调用的目标:

      <PropertyGroup>
          <BeforeBuildDependsOn>
            CustomTarget1;
            CustomTarget2;
            CustomTarget3
          </BeforeBuildDependsOn>
      </PropertyGroup>
      
      <Target Name="BeforeBuild" DependsOnTargets="$(BeforeBuildDependsOn)"/>
      

      更新:

      基于@stijn 提供的片段:

      AfterTargets="BeforeBuild" 将像这样插入/执行自定义目标:(取决于 BeforeBuild

      <BuildDependsOn>
        BeforeBuild;
             |-> Custom Target
      
      
        CoreBuild;
        AfterBuild
      </BuildDependsOn>
      

      BeforeTargets="CoreBuild" 将像这样插入/执行自定义(取决于 CoreBuild):

      <BuildDependsOn>
        BeforeBuild;
      
      
             |-> Custom Target
        CoreBuild;
        AfterBuild
      </BuildDependsOn>
      

      所以“模板生成目标”将在同一个地方执行(在BeforeBuildCoreBuild之间,但是根据不同的目标,这就是为什么要选择合适的目标used 应该是 BeforeBuild 内联或带有依赖项。

      现在关于第 3 方问题评论,BeforeBuild/AfterBuild 目标适用于最终用户,第三方提供商应在不影响基本工作流程的情况下实施其脚本。这些是第 3 方应使用的一些选项以避免破坏常规流程:

      以此为基础:

      <PropertyGroup>
          <BuildDependsOn>
            BeforeBuild;
            CoreBuild;
            AfterBuild
          </BuildDependsOn>
      </PropertyGroup>
      
      <Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>
      

      选项 1:这将在 BeforeBuild 之前注入您的自定义 3rd 方脚本,而不影响默认的 BuildDependsOn 序列,这仍然允许最终用户在没有的情况下使用 BeforeBuild 目标。

      <PropertyGroup>
          <BuildDependsOn>
            MyCustomThirdParty;
            $(BuildDependsOn);
          </BuildDependsOn>
      </PropertyGroup>
      
      <PropertyGroup>
          <MyCustomThirdPartyDependsOn>
            BeforeMyCustomThirdParty;
            CustomStep1;
            CustomStep2;
            CustomStep1;
            AfterMyCustomThirdParty
          </MyCustomThirdPartyDependsOn>
      </PropertyGroup>
      
      <Target Name="MyCustomThirdParty" DependsOnTargets="$(MyCustomThirdPartyDependsOn)"/>
      

      选项2:如果需要在BeforeBuild目标之后执行第3方脚本,可以这样做:

      <PropertyGroup>
          <BuildDependsOn>
            BeforeBuild;
            MyCustomThirdParty;
            CoreBuild;
            AfterBuild
          </BuildDependsOn>
      </PropertyGroup>
      

      注意:为了使其正常工作,您必须在 Microsoft.CSharp.targets 导入之后添加 PropertyGroup 和目标。

      这样您就可以使用多个符合一般工作流程的第 3 方脚本。

      您显然可以根据情况使用这些选项的组合。但是您应该遵循以下一般规则:

      1. 尊重默认工作流程(BeforeBuild、CoreBuild、AfterBuild)。
      2. 为您的 3rd 方脚本包括 Before/After 目标,以允许最终用户在 3rd 方脚本执行之前或之后注入任何内容。

      当您使用默认的 Visual Studio 生成的构建脚本(如 .csproj、.vbproj 等项目)时,应考虑这些。如果您正在为其他语言或目的实现自己的脚本,您可以在任何需要的地方使用 BeforeTargets 和 AfterTargets,但为什么不遵循基于现有脚本的良好实践呢?

      【讨论】:

      • @Jez 我读了这个问题,我给了你适当的答案,你在这里描述的是一个常见的场景,你想在实际构建过程之前执行一个或多个目标,这就是为什么您有可用的 BeforeBuild 和 AfterBuild 目标。您需要了解 Microsoft MSBuild 脚本的设计方式和推荐做法,让我尝试解释一下……BeforeBuild 和 AfterBuild 是两个“空”目标,当您需要在实际构建过程之前或之后执行某些操作时,它们将被覆盖防止破坏构建顺序...
      • 您可以覆盖这些目标内联或使用我的示例中提到的属性组,您使用 AfterTargets="BeforeBuild" 或 BeforeTargets="CoreBuild" 提出的建议将起作用,两者之间没有真正的区别除非您在 BeforeBuild 目标中有一些逻辑,否则这不是适当的做法,它会使您的脚本难以理解和维护。如果您想说服自己,可以从控制台运行 msbuild 生成详细的 MSBuild 执行日志,并在命令末尾添加 /v:diag。
      • @Rolo 它们之间没有真正的区别有一个主要区别:前者将在所有依赖Corebuild步骤(编译等)之前运行,后者将在它之后运行(至少在我尝试过的 2 个 VS 版本中).. 另外我建议不要使用 BeforeBuild/AfterBuild:它们可以被任何人覆盖,因此只提供有限的单个扩展点:如果你覆盖它们并且然后导入再次执行此操作的第 3 方代码,反之亦然,并且只会调用其中一个版本。使用 Before/After/DependsOn 属性就没有这个问题了。
      • @stijn 请查看我的更新,我想它会澄清我的答案。
      • 首先:BeforeBuild 和 AfterBuild 在项目文件中的位置很重要:它必须在例如Microsoft.CSharp.targets 导入。您的答案中缺少这个相当重要的信息。第二:它适用于 C# 项目,但并不总是适用于其他项目类型,例如在某一时刻,MS 放弃了 BeforeBuild for C++。所以,总而言之,考虑到这些明显的缺点,我不确定 AfterBuild 和 BeforeBuild 是否真的是一个好习惯,据我所知,当替代方案工作得很好而且没有更少的问题时。
      【解决方案3】:

      从 Microsoft.Common.CurrentVersion.targets 来看,Build 目标基本上是:

      <BuildDependsOn>
        BeforeBuild;
        CoreBuild;
        AfterBuild
      </BuildDependsOn>
      <Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>
      
      <PropertyGroup>
        <CoreBuildDependsOn>
          PrepareForBuild;
          PreBuildEvent;
          ...
          Compile;
          ...
          PostBuildEvent
        </CoreBuildDependsOn>
      </PropertyGroup>
      <Target Name="CoreBuild" DependsOnTargets="$(CoreBuildDependsOn)">
      

      所以使用BeforeTargets="CoreBuild" 确实会在CoreBuild 之前运行,但那是所有它的依赖目标运行之后,所以所有实际的构建步骤之后。这通常不是您想要的,相反,如果您想在编译等之前运行某些东西,请使用 BeforeTargets="PrepareForBuild" 或实际上是 AfterTargets="BeforeBuild" 甚至 BeforeTargets="BeforeBuild"

      【讨论】:

      • 如果您使用AfterTargets="BeforeBuild",是否有执行风险为:BeforeBuild -&gt; CoreBuild -&gt; MyTarget
      • 我不这么认为:据我所知,“AfterTargets”的意思是“紧接在目标之后”。再说一次,msdn.microsoft.com/en-us/library/… 对此也不是很清楚。不过,使用 Martin 的答案,您可以强制执行这种“在 X 和 Y 之间运行”的行为。
      • @Jez 在 BeforeBuild 中使用 AfterTargets、BeforeTargets 或在 BeforeBuild 内部工作对您来说是安全的。请查看我对答案所做的更新,它解释了目标的执行顺序以及为什么使用 BeforeBuild 而不是 CoreBuild 不会出现问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-16
      • 2017-04-23
      • 1970-01-01
      • 1970-01-01
      • 2011-09-12
      • 1970-01-01
      相关资源
      最近更新 更多