Running a target before the CoreBuild?

2019-07-05 20:57发布

I'm adding a custom .tt template generation target to my project to run before CoreBuild, and there appear to be 2 ways of doing it:

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

and

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

If my target should run before my project is built, as the project relies on it, would it be better for me to use the latter? I've seen the former used to do things like generate text templates, but it seems like an unreliable way to do it because it might get run after CoreBuild, which is too late. Or is there some reason why AfterTargets="BeforeBuild" is still guaranteed to run before the core build?

I've also seen BeforeTargets="BeforeBuild" which will build even earlier. Is this a better place to put a `.tt text generation target?

3条回答
淡お忘
2楼-- · 2019-07-05 21:32

Update 2

Based on the documentation from Microsoft:

This is the Target Build Order

  1. InitialTargets targets are run.
  2. Targets specified on the command line by the /target switch are run. If you specify no targets on the command line, then the DefaultTargets targets are run. If neither is present, then the first target encountered is run.
  3. The Condition attribute of the target is evaluated. If the Condition attribute is present and evaluates to false, the target isn't executed and has no further effect on the build.
  4. Before a target is executed, its DependsOnTargets targets are run.
  5. Before a target is executed, any target that lists it in a BeforeTargets attribute is run.
  6. Before a target is executed, its Inputs attribute and Outputs attribute are compared. If MSBuild determines that any output files are out of date with respect to the corresponding input file or files, then MSBuild executes the target. Otherwise, MSBuild skips the target.
  7. After a target is executed or skipped, any target that lists it in an AfterTargets attribute is run.

These are the answers to your questions:

If my target should run before my project is built, as the project relies on it, would it be better for me to use the latter?

  • No, because all the dependencies of CoreBuild will be executed before your template generation target and that will be too late.

Or is there some reason why AfterTargets="BeforeBuild" is still guaranteed to run before the core build?

  • AfterTargets="BeforeBuild" guarantees that your target will be executed on time because it will be executed before all the CoreBuild dependencies.

I've also seen BeforeTargets="BeforeBuild" which will build even earlier. Is this a better place to put a `.tt text generation target?

  • In both cases AfterTargets="BeforeBuild" or BeforeTargets="BeforeBuild" your target will be executed before all the CoreBuild dependencies, however in both cases you still have the risk to affect the results of your template generation target depending on what you have to execute in BeforeBuild. If you have this under control, you can use any of these options safely.

Running a target before the CoreBuild?

> there appear to be 2 ways of doing it.

    There are more options to achieve this. Please review below.

You should use the specific built in targets (BeforeBuild or AfterBuild) for this purpose. Thisis the mechanism provided by Microsoft to safely extend the build process when using projects that depends on Microsoft.Common.targets

If you have only one target to run before CoreBuild, you can do this:

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

If you have more than one target to run before CoreBuild, you can define a property with all the targets that needs to be called in the required order of execution:

<PropertyGroup>
    <BeforeBuildDependsOn>
      CustomTarget1;
      CustomTarget2;
      CustomTarget3
    </BeforeBuildDependsOn>
</PropertyGroup>

<Target Name="BeforeBuild" DependsOnTargets="$(BeforeBuildDependsOn)"/>

UPDATE:

Based on the fragment provided by @stijn:

AfterTargets="BeforeBuild" will insert/execute the custom target like this: (Depends on BeforeBuild

<BuildDependsOn>
  BeforeBuild;
       |-> Custom Target


  CoreBuild;
  AfterBuild
</BuildDependsOn>

BeforeTargets="CoreBuild" will insert/execute the custom like this (Depends on CoreBuild):

<BuildDependsOn>
  BeforeBuild;


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

So the "template generation target" will be executed in the same place (Between BeforeBuild and CoreBuild, but depending on different targets, that's why the apropriate Target to be used should be BeforeBuild inline or with dependencies.

Now regarding the 3rd party issue comment, the BeforeBuild/AfterBuild targets are intended for final users, the third party providers should implement their scripts without affect the basic workflow. These are some of the options a 3rd party should use to avoid breaking the regular flow:

Considering this as the base:

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

<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>

Option 1: This will inject your custom 3rd party script before BeforeBuild without affect the default BuildDependsOn sequence, that still allows the final user to use the BeforeBuild target without.

<PropertyGroup>
    <BuildDependsOn>
      MyCustomThirdParty;
      $(BuildDependsOn);
    </BuildDependsOn>
</PropertyGroup>

<PropertyGroup>
    <MyCustomThirdPartyDependsOn>
      BeforeMyCustomThirdParty;
      CustomStep1;
      CustomStep2;
      CustomStep1;
      AfterMyCustomThirdParty
    </MyCustomThirdPartyDependsOn>
</PropertyGroup>

<Target Name="MyCustomThirdParty" DependsOnTargets="$(MyCustomThirdPartyDependsOn)"/>

Option 2: If the 3rd party script needs to be executed after the BeforeBuild target, this can be done like this:

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

NOTE: In order for this to work properly, you must add the PropertyGroup and targets AFTER the Microsoft.CSharp.targets import.

This way you will be able to use multiple 3rd party scripts respecting the general workflow.

You can obviously use a combination of these options depending on the situation. But you should follow these general rules:

  1. Respect the default workflow (BeforeBuild, CoreBuild, AfterBuild).
  2. Include Before/After targets for your 3rd party script to allow the final user to inject anything before or after the 3rd party script execution.

These should be considered when you are using the default visual studio generated build scripts (Projects like .csproj, .vbproj, etc). If you are implementing your own scripts for other languajes or purposes, you can use BeforeTargets and AfterTargets wherever you want, but why don't you follow the good practices based on the existing scripts?

查看更多
太酷不给撩
3楼-- · 2019-07-05 21:36

From Microsoft.Common.CurrentVersion.targets, the Build target is basically:

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

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

So using BeforeTargets="CoreBuild" will run before CoreBuild indeed, but that is after all it's dependent targets ran, so after all actual build steps. That's usually not what you want, instead if you want to run something before compilation etc, use BeforeTargets="PrepareForBuild" or indeed AfterTargets="BeforeBuild" or even BeforeTargets="BeforeBuild".

查看更多
Fickle 薄情
4楼-- · 2019-07-05 21:51

Building on @stjin's answer, a good solution seems to be using

BeforeTargets="CoreCompile" DependsOnTargets="PrepareForBuild"

which is what the .net sdk (new-style csproj for .net core / standard projects) is doing for automatic AssemblyInfo.cs generation.

It uses the following comment to explain why:

Note that this must run before every invocation of CoreCompile to ensure that all compiler runs see the generated assembly info. There is at least one scenario involving Xaml where CoreCompile is invoked without other potential hooks such as Compile or CoreBuild, etc., so we hook directly on to CoreCompile. Furthermore, we must run after PrepareForBuild to ensure that the intermediate directory has been created.

Note that the "intermediate directory" (obj/[TargetFramework] in this case) is where the output .cs file is placed in this case which may also be what you might want to do.

查看更多
登录 后发表回答