Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds


This is a short post that addresses an issue I ran into today when converting a project to .NET Core 2.0. I've been upgrading a host of my existing tools to .NET Standard/Core 2.0 and most of these projects have existing .NET 4.5 (or later) targets that I want to continue pulling forward. The new SDK project type makes it relatively easy to create libraries that do multi-targeting in your SDK style .csproj file:

<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>

Assuming you can get your code to build on both platforms, this simple directive will build assemblies and NuGet packages (if you turn the option on) for both platforms which is very cool.

It'll work, but...

Cross Platform

There's a problem however when you do this multi-targeting. It works just fine on my local Windows machine where the specified target platform (.NET 4.5 SDK in this case) is installed.

However, if I now try to build on a Mac which doesn't have a net45 SDK I get:

/usr/local/share/dotnet/sdk/2.0.0/Microsoft.Common.CurrentVersion.targets(1122,5): error MSB3644:

The reference assemblies for framework ".NETFramework,Version=v4.5" were not found.

To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will beused in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend.

The problem is of course that when you're trying to build on a non-Windows platform, or even on Windows when the net45 build targeting is not installed, the build fails.

Why not just target .NET Standard?

Initially I thought I'd get away with just targeting .NET Standard and use that from full framework applications. That works as long as you can get all the features you need from NetStandard.

In my case however I'm porting very old code and there are number of dependencies on things that are not in .NET Standard and either require a separate set of libraries or simply can't run. So targeting native net45 (or whatever) is a good way to provide existing functionality while moving forward and also supporting .NET Standard/Core with a slightly diminished or altered feature set.

But how do you deal with that on multiple platforms?

You can change this:

<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>

manually to:

<TargetFramework>netstandard2.0</TargetFrameworks>

and that works. The project builds, but only for the single target. It works but it's a nasty hack and I know if I do this manually, it will be forgotten at one point. There's a better way though...

Conditional TargetFrameworks

I asked if there's an easy way to deal with detectecting targets, and while the immediate answer is, "not directly", I kindly got a response from @DamienEdwards that offered a conditional approach:

After a bit of experimenting with the right MSBuild invokations - and a little help from @andrewlocknet and @jeremylikness - I ended up with some conditional MSBuild blocks that work to do the right thing on Windows and in this case the Mac to build the project:

Old code

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- *** THIS *** -->
    <TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
    
    <Version>3.0.0-preview1-0</Version>
    <Authors>Rick Strahl</Authors>
    ...
</Project>

New Code

<Project Sdk="Microsoft.NET.Sdk">

    <!-- *** THIS *** -->
    <PropertyGroup Condition=" '$(OS)' != 'Windows_NT' ">
        <TargetFramework>netstandard2.0</TargetFramework>
    </PropertyGroup>
    <PropertyGroup Condition=" '$(OS)' == 'Windows_NT' "> 
        <TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
    </PropertyGroup>
    <!-- *** THIS *** -->
  
  <PropertyGroup>
    <!-- <TargetFrameworks>netstandard2.0;net45</TargetFrameworks> -->
    <Version>3.0.0-preview1-0</Version>
    <Authors>Rick Strahl</Authors>
    ...
</Project>

Notice that I use <TargetFramework /> for the single NetStandard reference and <TargetFrameworks /> for the 2 target NetStandard and Net45 build. It's an easy thing to miss!

Said and Done!

So now when I build on Windows, I get this output:

Figure 1 - With conditional flags, both NetStandard and Net45 projects are built on Windows

On the Mac:

Figure 2 - On OSX only the NetStandard package is built

This works as expected and is a reasonable solution for any project that requires building to multiple platform targets and still needs to build on multiple platforms where the target is not available.

this post created and published with Markdown Monster

I'll be at DevIntersection in Vegas this fall giving sessions on ASP.NET Core with Angular and Localization. Thinking of coming? Use discount code STRAHL and save a few bucks. If you do be sure to stop by and say hello!

ASP.NET DevIntersection 2017. Rick Strahl Coupon Code

Posted in .NET   ASP.NET Core  

The Voices of Reason


 

Evgeny
September 19, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

Hi Rick it is also possible to build a specific platform with --framework flag: e.g. on Windows 'dotnet build --framework "net451"' and on Mac 'dotnet build --framework "netcoreapp2.0"'


Chet Husk
September 19, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

If you've got mono installed on your osx machine there is a workaround you can do to tell dotnet build where the full-framework targeting shims are. Instructions can be found at https://github.com/dotnet/netcorecli-fsc/wiki/.NET-Core-SDK-1.0#using-net-framework-as-targets-framework-the-osxunix-build-fails

Many F# OSS projects are using this to build projects cross-platform successfully right now!


Muhammad Rehan Saeed
September 20, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

This is a great workaround! What do you do if you have one project only targetting net452?


Rick Strahl
September 20, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

@evgeny - thanks for the heads up - good to know, but not so useful if you're not building from the command line. Currently most of the tooling doesn't have support for this option. Thanks!


Lex Li
September 26, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

Visual Studio does not like the conditions, so though the trick works fine for command line, it breaks VS experience.

I blogged about that (and more) in my blog post, https://blog.lextudio.com/tips-for-net-nuget-package-authors-august-2017-48f07604e4a0

The workaround I use is to "sed" away the net*** TFM on non-Windows platforms in a shell script.


BB
September 26, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

How can you multi target .net standard and portable class library profile44(xamarin forms)?


Rick Strahl
September 27, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

@Lex - it works for me in Visual Studio 2017.3.5. VS happily works with it, but you can't change the TFM in the dialogs. You have to modify the .csproj directly. But it works fine. VS is just using the command line tools to build after all so no reason why this shouldn't work.


Rick Strahl
September 27, 2017

# re: Conditional TargetFrameworks for Multi-Targeted .NET SDK Projects on Cross-Platform Builds

@BB - I think the process is the same for portable libraries. You just need to find the target framework moniker for this. https://docs.microsoft.com/en-us/nuget/schema/target-frameworks (ie. portable-net40+win8+sl4+wp7)

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017