Msbuild: Allow inner builds to tell if they are being built from a cross-targeting build

Created on 22 Mar 2017  路  6Comments  路  Source: dotnet/msbuild

Currently, it's possible for MSBuild props/targets (provided by nugets or otherwise) to know if a cross-targeting build is taking place by checking for the IsCrossTargetingBuild=true property, which trigges the outer/inner build behavior.

The inner built projects, however, have no way of knowing if they are being built as part of the outer/inner dispatching, or straight as a single TargetFramework chosen by the user from one of the TargetFrameworks configured for the project. This is relevant as soon as the Target Framework selector ships, apparently by 15.3 according to that issue's Milestone.

What would be needed for a project to determine if its being "single targeted" or "cross-targeted" for build is a different IsCrossTargeted=true property passed down when the DispatchToInnerBuilds is dispatching the inner builds, likely as part of the AdditionalProperties.

One concrete use case I'm needing this for:
The VSSDK BuildTools nuget package provides targets and tasks for building VSIXes. But only one version of its tasks can ever be loaded as part of a build. Therefore, I want to guarantee that if IsCrossTargeted=true, I always build with the latest VSSDK. But if I'm not being cross-targeted, I want to build instead with the VSSDK (conditional PackageReference) I choose (i.e. if TF=net461, target VSSDK 14.x == VS2015 instead of latest/VS0217).

This would allow me to use the cross-targeting functionality in VS2017.3 to easily author a VSIX project and target from within the IDE all supported versions (i.e. by saying TF=net46 == VS2013, TF=net461 == VS2015 and TF=net462 == VS2017), which would in that case (non cross-targeted build, since TF will have a value, via the linked issue/feature and persisted in the .user) also deploy to the right VS Experimental, since the VSSDK targets for the matching VS version know how to do that.

In turn, when I build the VSIX from CI, the cross-targeting build will take place, which will pass down to the inner build that it's being cross-targeted and the effect will be in this case that the latest & greatest VSSDK build tools is always chosen, regardless of the TF, since we're in Highlander mode in that case.

Hopefully this makes sense :). Oh, and it would be a blast if this hits simultaneously with the framework selector!

My test project (after adding the IsCrossTargetedBuild additional property for inner build dispatch manually to my common targets:

<Project Sdk="Microsoft.NET.Sdk" InitialTargets="Test">
    <PropertyGroup>
        <TargetFrameworks>net462;net461</TargetFrameworks>
    </PropertyGroup>

    <PropertyGroup Condition="'$(Dev)' != ''">
        <TargetFramework Condition="'$(Dev)' == '14'">net461</TargetFramework>
        <TargetFramework Condition="'$(Dev)' == '15'">net462</TargetFramework>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Dev)' == ''">
        <Dev Condition="'$(TargetFramework)' == 'net461'">14</Dev>
        <Dev Condition="'$(TargetFramework)' == 'net462'">15</Dev>
    </PropertyGroup>

    <PropertyGroup>
        <BuildToolsVersion Condition="'$(IsCrossTargetedBuild)' == 'true'">15</BuildToolsVersion>
        <BuildToolsVersion Condition="'$(IsCrossTargetedBuild)' != 'true'">$(Dev)</BuildToolsVersion>
    </PropertyGroup>

    <Target Name="Test">
        <Message Importance="high" Text="
IsCrossTargetingBuild=$(IsCrossTargetingBuild)
IsCrossTargetedBuild=$(IsCrossTargetedBuild)
Dev=$(Dev)
BuildToolsVersion=$(BuildToolsVersion)
TargetFramework=$(TargetFramework)" />
    </Target>
</Project>

When running msbuild:

>msbuild /nologo

  IsCrossTargetingBuild=true
  IsCrossTargetedBuild=
  Dev=
  BuildToolsVersion=
  TargetFramework=

  IsCrossTargetingBuild=
  IsCrossTargetedBuild=true
  Dev=15
  BuildToolsVersion=15
  TargetFramework=net462
  MultiVsix -> C:\Code\Personal\MultiVsix\Test\bin\Debug\net462\MultiVsix.dll

  IsCrossTargetingBuild=
  IsCrossTargetedBuild=true
  Dev=14
  BuildToolsVersion=15
  TargetFramework=net461
  MultiVsix -> C:\Code\Personal\MultiVsix\Test\bin\Debug\net461\MultiVsix.dll

Since this is Highlander mode, BuildToolsVersion is 15 in both inner builds.

When running msbuild /t:TargetFramework=net461 or msbuild /t:Dev=14:

 IsCrossTargetingBuild=
  IsCrossTargetedBuild=
  Dev=14
  BuildToolsVersion=14
  TargetFramework=net461
  MultiVsix -> C:\Code\Personal\MultiVsix\Test\bin\Debug\net461\MultiVsix.dll

Since we know we can have the specific VSSDK build tools loaded, it now matches the target Dev/TF.

Most helpful comment

I still think that there should be a way to determine if a project is built as an inner build.
Suppose you want to ship an extension NuGet to with a build task as AfterTargets="Build" that has to run:

  • after all inner builds have been completed when multitargeting
  • after the build has been completed when not multitargeting

While adding cross targeting props to the NuGet is certainly an option, i do believe there should be an easier way to do this with one Condition attribute.

All 6 comments

After discussing this with @rainersigwald on Slack, I think there are alternative ways I can use that wouldn't risk introducing a race condition in sln builds. I could set a global property in a cross-targeting-only imported .props and then check for that in the inner builds, or run after _ComputeTargetFrameworkItems target and augment the _InnerBuildProjects.

Either way, the fact that my scenario involves a top-level project (the "app" or VS extension in my case), which would never be referenced by other projects, therefore mitigating the risk of a race condition, means I can probably work around this without changes to MSBuild.

Thanks!

I still think that there should be a way to determine if a project is built as an inner build.
Suppose you want to ship an extension NuGet to with a build task as AfterTargets="Build" that has to run:

  • after all inner builds have been completed when multitargeting
  • after the build has been completed when not multitargeting

While adding cross targeting props to the NuGet is certainly an option, i do believe there should be an easier way to do this with one Condition attribute.

Here's the mechanism in 15.3 to detect if inner builds are being cross-targeted. Just place the following in your project (or an imported target)

    <ItemDefinitionGroup>
        <_InnerBuildProjects>
            <Properties>IsCrossTargetedBuild=$(IsCrossTargetingBuild)</Properties>
        </_InnerBuildProjects>
    </ItemDefinitionGroup>

Now you can use $(IsCrossTargetedBuild)='true' to detect a cross-targeting build

@kzu that will create races between inner builds invoked via solution->outer build->inner build and those invoked via solution->other project->inner build. I don't recommend it.

Keep in mind that this is only used (in my case at least) on the top-level entry point project (in my case, the VSIX project). Nothing references this project.

As long as nothing has a ProjectReference to the given project, this should be fine.

Was this page helpful?
0 / 5 - 0 ratings