Identifying a build as being due to a dependent project reference

Viewed 619

In MSBuild is there a property, or some other mechanism, that indicates that the current project is being built because it was a referenced by another project?

3 Answers

In addition to @stijn's answer, I've discovered that you can also prevent a property from being passed to dependent projects.

For example, you can prevent Web Project dependencies from building with the top level project by updating their <ProjectReference> to include <GlobalPropertiesToRemove>DeployOnBuild</GlobalPropertiesToRemove>. Or, to do it automatically based on another property:

<PropertyGroup Condition="'$(DisableProjectReferenceDeployOnBuild)'=='true'">
  <BeforeResolveReferences>
    $(BeforeResolveReferences);
    DisableProjectReferenceDeployOnBuild
  </BeforeResolveReferences>
</PropertyGroup>

<Target Name="DisableProjectReferenceDeployOnBuild">
  <ItemGroup>
    <_ProjectReferencesTmp Include="@(ProjectReferences)" />
    <ProjectReferences Remove="@(ProjectReferences)" />
    <ProjectReferences Include="@(_ProjectReferencesTmp)">
      <GlobalPropertiesToRemove>%(GlobalPropertiesToRemove);DeployOnBuild</GlobalPropertiesToRemove>
    </ProjectReferences>
  </ItemGroup>
</Target>

(I won't mark this as the answer since it doesn't directly answer the question I asked)

After looking around a bit this does not seem possible using built-in functionaility. From one point of view this makes sense: why would a project have to know whether it's built directly by the user vs being built as a dependency? Possibly the MSBuild team followed that logic as well: there are quite a lot of extensions points in MSBuild but not for doing this.

Two problems: the code for building the dependent projects is just using the MSBuild Task and does not provide a way to pass properties. But even if it did, it would only work when building from the command line, not in VS, so it's not a 'complete' solution. Here's a snippet taken from the ResolveProjectReferences which builds the dependent projects:

<!--
    Build referenced projects when building from the command line.
    -->
<MSBuild
    Projects="@(_MSBuildProjectReferenceExistent)"
    Targets="%(_MSBuildProjectReferenceExistent.Targets)"
    BuildInParallel="$(BuildInParallel)"
    Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
    Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''"
    ContinueOnError="$(ContinueOnError)"
    RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)">
  ...
</MSBuild>

So, no way to add properties here. Though as you figured you can remove properties by setting a ProjectReference's GlobalPropertiesToRemove; depending on what you're after this could be valueable.

For the rest there aren't many options left; you can specify the target used: _MSBuildProjectReferenceExistent.Targets gets set to $(ProjectReferenceBuildTargets) so you can override the target called but then you'd need all your projects which could possibly be dependent projects to declare a custom target (which would in turn call the Build target as well in order not to break things). Doable, but not nice, and not a direct answer to the question. Same goes for other solutions: you could just override the whole ResolveProjectReferences target (for any project which can have dependent projects) by copying it and adding a property in the snippet shown above.

But as said (and as shown in the Condition in the above snippet): none of these possible solutions would apply when building in VS. I don't know exactly why or how that works, but if A depends on B and you build A in VS and it sees B is out of date it just fires up a build for it before even building A and I don't know of any standard way to interact with that.

With modern versions of visualstudio/.net SDK, you can do this in your Directory.Build.targets to apply this to all (as this requires participation from the project which is depending on other projects):

<?xml version="1.0"?>
<Project>
  <PropertyGroup>
    <IsBuildDueToProjectReference Condition=" '$(IsBuildDueToProjectReference)' == '' ">false</IsBuildDueToProjectReference>
  </PropertyGroup>
  <Target AfterTargets="AssignProjectConfiguration" Name="SetIsBuildDueToProjectReferenceOnProjectReferences">
    <ItemGroup>
      <ProjectReferenceWithConfiguration>
        <AdditionalProperties>%(ProjectReferenceWithConfiguration.AdditionalProperties);IsBuildDueToProjectReference=true</AdditionalProperties>
      </ProjectReferenceWithConfiguration>
    </ItemGroup>
  </Target>
</Project>

This works because targets build items from ProjectReferenceWithConfiguration. So you can treat that item as if it is passed as the Projects parameter of the MSBuild Task because its metadata will be carried along.

To see the effect, you can put something like the following in each of your project files:

<Target AfterTargets="Build" Name="PrintInfo">
  <Warning Text="IsBuildDueToProjectReference=$(IsBuildDueToProjectReference)"/>
</Target>

For example, if I build ConsoleApp1.csproj which has a dependency on ClassLibrary1.csproj, I get:

C:\Users\ohnob\source\repos\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj(10,5): warning : IsBuildDueToProjectReference=true
C:\Users\ohnob\source\repos\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj(15,5): warning : IsBuildDueToProjectReference=false

And if I build ClassLibrary.csproj direct, I get:

C:\Users\ohnob\source\repos\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj(10,5): warning : IsBuildDueToProjectReference=false
Related