msbuildmsbuild-target

Unexpected AfterTargets="Build" execution order for dependent projects


I have two multi-target projects in visual studio 2017.

Project Dependency

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netcoreapp2.0;net45</TargetFrameworks>
  </PropertyGroup>

  <Target Name="DependencyAfterBuild" AfterTargets="Build">
    <Message Text="DependencyAfterBuild  IsCrossTargetingBuild=$(IsCrossTargetingBuild)" />
  </Target>

</Project>

Project Main

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>netcoreapp2.0;net45</TargetFrameworks>
  </PropertyGroup>

    <PropertyGroup>
        <TestProperty>Exe</TestProperty>
    </PropertyGroup>

  <Target Name="MainAfterBuild" AfterTargets="Build">
    <Message Text="MainAfterBuild IsCrossTargetingBuild=$(IsCrossTargetingBuild)" />
  </Target> 

  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\Dependency.csproj" />
  </ItemGroup>

</Project>

Project Main depends on Dependency. I expect that build output would be

DependencyAfterBuild  IsCrossTargetingBuild=
DependencyAfterBuild  IsCrossTargetingBuild=
DependencyAfterBuild  IsCrossTargetingBuild=true
MainAfterBuild IsCrossTargetingBuild=
MainAfterBuild IsCrossTargetingBuild=
MainAfterBuild IsCrossTargetingBuild=true

and when i build solution with visual studio 2017 i get that output

but when i run msbuild from cmd i get next output

DependencyAfterBuild  IsCrossTargetingBuild=
MainAfterBuild IsCrossTargetingBuild=
DependencyAfterBuild  IsCrossTargetingBuild=
MainAfterBuild IsCrossTargetingBuild=
MainAfterBuild IsCrossTargetingBuild=true
DependencyAfterBuild  IsCrossTargetingBuild=true

And that order somewhat unexpected (and break my custom logic in real project).

Why "DependencyAfterBuild IsCrossTargetingBuild=true" run later than "MainAfterBuild IsCrossTargetingBuild=true"?

How i need modify my projects for msbuild to run "DependencyAfterBuild IsCrossTargetingBuild=true" before "MainAfterBuild IsCrossTargetingBuild=true".


Solution

  • There are a few things at play here:

    First, Visual Studio builds a bit differently - it figures out project dependencies but builds of individual projects may or may not happen, in an order resolved by components inside of VS. A project system may decide that a project is "up-to-date" and skip calling MSBuild for that project entirely.

    Calling the command line MSBuild on a solution can therefore behave a bit differently, since all projects are built as-needed in not necessarily a particular order. If you changed the order the projects are defined inside the .sln file (the Project… EndProject part), the order would be the different.

    To enforce a special oder, you can use project dependencies inside the solution, which would answer your question of what you can do about it (caution: there have been NuGet bugs when using this feature).

    In your case, the Main project is chosen by msbuild to be built first.

    Since it is a multi-targeting project, it will spin off "inner builds" for each target framework.

    These inner builds reference the "DependencyAfterBuild" project, which is also a multi-targeting project. When a multi-targeting project is referenced, only one of the target frameworks is built as a reference - so msbuild selects the "nearest target framework" for the dependency. This is why you see the DependencyAfterBuild IsCrossTargetingBuild= line first (and before the similar lines for the main project).

    After the inner builds complete, your after build target of the main project runs.

    Since you are building a solution and not the main project alone, the "Build" target of the dependency project is called as well, simply because it is also in the solution you are building. Since the inner builds have been performed already, they are skipped and no additional after-build messages are displayed.

    If you manually added a project dependency or simply edited the .sln file to change the order the projects are defined, you'd see all of the "DependencyAfterBuild" targets to be run before all of the main project's targets.