Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
kzu opened this issue Mar 22, 2017 · 8 comments
Closed
Labels

Comments

@kzu
Copy link
Contributor

kzu commented Mar 22, 2017

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.

@kzu
Copy link
Contributor Author

kzu commented Mar 22, 2017

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!

@kzu kzu closed this as completed Mar 22, 2017
@dasMulli
Copy link
Contributor

dasMulli commented Apr 3, 2017

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.

@kzu
Copy link
Contributor Author

kzu commented May 12, 2017

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

@rainersigwald
Copy link
Member

@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.

@kzu
Copy link
Contributor Author

kzu commented May 12, 2017

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.

@rainersigwald
Copy link
Member

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

@AraHaan
Copy link
Member

AraHaan commented Jun 27, 2021

I need something like this but for projects referenced to the given project (as I got a package with a build task that should only execute once for every project that references) no mater if they target multiple TFM's or not.

I tried everything to have it disable the version of the same target (which executes the task that is based off of BeforeBuild being completed first) as the one that gets based off of DispatchToInnerBuilds before it is completed). In that case somehow both of them get executed even if I do not explicitly import the build\<package name>.props file inside of the buildMultiTargeting\<package name>.props file making this a major pain.

As such I think there really should be a change on either: the msbuild side of things, or the .NET SDK side of things.

Alternatively I guess there probably is an .NET SDK way to fix this however (maybe by having it check TargetFrameworks to see if it is set to some value and if it contains any ;'s inside (but makes sure it does not end with ;).

So possibly something like this basically:

  • <IsCrossTargeted Condition="'$(TargetFrameworks)' != '' AND $(TargetFrameworks.Contains(';'))' AND !$(TargetFrameworks.EndsWith(';'))">true</IsCrossTargeted>

Edit: Verified that IsCrossTargeted actully works like above when placed in build\<package name>.props on a package side of things to trap tasks that should only execute one time even if they are referenced in other projects.

See https://github.com/Elskom/GitBuildInfo.SourceGenerator/pull/46/files for more details on what all I did to test this out.

@RyanThomas73
Copy link

RyanThomas73 commented Oct 10, 2021

I ended up going with a slight alteration to the solution provided by @AraHaan
A cross targeted project can end with a ; (e.g. <TargetFrameworks>net5.0;net472;</TargetFrameworks>) and will still build.
To account for this I ended up going with this approach:

<IsMultiTargeted Condition="'$(TargetFrameworks)' != '' AND $(TargetFrameworks.IndexOf(';')) &gt; -1 AND $(TargetFrameworks.IndexOf(';')) != $([MSBuild]::Add($(TargetFrameworks.Length), 1))">true</IsMultiTargeted>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants