Msbuild: Referencing global MSBuildExtensionsPath32 but allow overriding Import/@Project

Created on 9 Mar 2017  路  5Comments  路  Source: dotnet/msbuild

We use a pattern like the following. At the top of the project is a property and at the bottom the Import/@Project uses that property. Simplified:

<PropertyGroup>
  <PathToTargetsFile Condition=" '$(PathToTargetsFile)'==''">
    $(MSBuildExtensionsPath32)Our\stuff.targets
  </PathToTargetsFile>
<PropertyGroup>

...
<Import Project="$(PathToTargetsFile)" />

This pattern allows our users to set the PathToTargetsFile Property and redirect their build to a private install location for our tools.

With MSBuild15 this creates problems because the $(MSBuildExtensionsPath32) will only point to the private location with MSBuild15 and not fallback to the global location where the tools are installed by default.

We've done the work to create a VS2017 extension to install a "trampoline" stuff.targets inside the private MSBuild15 location out to the global install location. This works inside VS but on a build machine the VS2017 extension may not be installed or the MSBuild Build Tools SKU may be used and extensions are not allowed there.

The best idea we've come up with to address this is to change our template projects file to do the following:

<Import Project="$(PathToTargetsFile)"
        Condition=" '$(PathToTargetsFile)'!=' "  />
<Import Project="$(MSBuildExtensionsPath32)Our\stuff.targets"
        Condition=" '$(PathToTargetsFile)'==''"  />

The problem is that change will break all of our existing customers and force them to change all of their project files. :sob:

Is there any other option to better handle the breaking change introduced in MSBuild15?

Most helpful comment

After spending the better part of the week looking for solutions that do not break all of our users (and finding none, my proposed solution won't help our users with existing projects... aka: pretty much all of them), I wonder if it would have been better to _not_ break the $(MSBuildExtensionsPath) behavior and instead create a new variable to opt-in to this "app-local" behavior.

Note: as the "build platform for Microsoft" making MSBuild "app-local" was a pretty painful decision for those of us that integrate with the platform deeply. SxS platforms, in general, are pretty dismal to integrate with. Just ask the .NET Framework team why they quit doing it after two attempts (everyone hated it). IMHO, Visual Studio's decision to go extreme SxS should not have affected MSBuild's role as a build platform.

However, at this point, none of that matters. We're just going to have to deal with the support burden this MSBuild breaking change causes. I apologize if my frustration shows through here. This sort of breakage goes completely against the way we've worked for years.

All I can ask now is that before you remove the ability for our users to reference installed build tools and SDKs (aka: not depend on NuGet as the sole way to extend the Microsoft's build platform), there is a solution for all project types. For example, building native code still requires installed software and integrating with it (which we do) requires complexity.

Maybe SDKs are the solution but as you note it isn't clear how to support it or when it will be available.

At this point, I'm not sure what you want to do with this issue. I'm eager to get to a better place but feel like we took a step backwards in MSBuild15 and have some debt to pay to get our users whole again. Help on that front would be much appreciated.

All 5 comments

@AndyGerlicher @rainersigwald Can you answer this?

A duplicate of #1735

Yep. Duplicate with no comments from MSBuild team. 馃槩

There really isn't a good solution here. In VS2017 there was a huge push to make things "app-local". You may have noticed that you can install multiple versions (say Enterprise and Community) as well as potentially a preview release of the next update. This is a huge step forward in terms of Visual Studio's impact to your machine and dogfooding future releases. To support this we had to move everything under Visual Studio's install root (and of course not GAC anything). This is why MSBuildExtensionsPath does not point to C:\Program Files (x86)\MSBuild by default. To mitigate some of the pain of this during the transition we added fallback to the project import logic to look in the global path since so many content files reference $(MSBuildExtensionsPath) and hadn't yet or couldn't update the drop location.

I looked into what you're asking pretty early on, and found it was not easy. At the time we do a project import, we do not have the ability to transitively know that property X derives from property Y. To know this information we would have to do something pretty extensive such as tagging expansions that involve certain "fallback" properties (kind of hacky), or implement the concept of property fallback as an engine feature. Both of these are pretty extensive changes for something we ultimately want to get rid of. Ideally logic to build should come from a package that can be acquired (e.g. NuGet package) and not from something installed. The installed scenario just makes predictability (and CI machines) harder.

As a side note, I also tried implementing fallback based on the value of $(MSBuildExtensionsPath) rather than the property name. So we would expand whatever import was specified and if it contained the value of $(MSBuildExtensionsPath) then replace that part of the string with the fallback. That worked OK for very basic cases but was a horrible mess for anything remotely complicated. I don't remember all the details but did abandon it pretty quickly once I started writing tests for it.

Going forward we hope that the MSBuildExtensionsPath becomes less and less relevant. Ideally we'd like to remove the fallback, but that's realistically not going to happen anytime soon. Instead we'd like to de-emphasize usage of it and point people towards package references or our new "SDK" feature. So instead of the above example of importing a path you would <Import Project="stuff.targets" SDK="Whatever.PackageName.Our.Stuff">. You can see #1493 for details on that, but it's not done yet so I realize that's not entirely helpful.

After spending the better part of the week looking for solutions that do not break all of our users (and finding none, my proposed solution won't help our users with existing projects... aka: pretty much all of them), I wonder if it would have been better to _not_ break the $(MSBuildExtensionsPath) behavior and instead create a new variable to opt-in to this "app-local" behavior.

Note: as the "build platform for Microsoft" making MSBuild "app-local" was a pretty painful decision for those of us that integrate with the platform deeply. SxS platforms, in general, are pretty dismal to integrate with. Just ask the .NET Framework team why they quit doing it after two attempts (everyone hated it). IMHO, Visual Studio's decision to go extreme SxS should not have affected MSBuild's role as a build platform.

However, at this point, none of that matters. We're just going to have to deal with the support burden this MSBuild breaking change causes. I apologize if my frustration shows through here. This sort of breakage goes completely against the way we've worked for years.

All I can ask now is that before you remove the ability for our users to reference installed build tools and SDKs (aka: not depend on NuGet as the sole way to extend the Microsoft's build platform), there is a solution for all project types. For example, building native code still requires installed software and integrating with it (which we do) requires complexity.

Maybe SDKs are the solution but as you note it isn't clear how to support it or when it will be available.

At this point, I'm not sure what you want to do with this issue. I'm eager to get to a better place but feel like we took a step backwards in MSBuild15 and have some debt to pay to get our users whole again. Help on that front would be much appreciated.

Was this page helpful?
0 / 5 - 0 ratings