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.
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:
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.
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: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
Conditionattribute.