Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Speed up development with Multi-Targeted Visual Studio Projects

One of the biggest changes that MvvmCross undertook at the beginning of the year was to switch over to using Multi-targeting in Visual Studio. Instead of having a lot of platform specific projects, multi-targeting made it possible to create a single MvvmCross project that had multiple target frameworks. MvvmCross leverages Oren’s MSBuild.Sdk.Extras nuget package which allows us to define the list of target frameworks using the TargetFrameworks element in the csproj file (note that this is different from the default singular TargetFramework element which usually exists).

<TargetFrameworks>netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid81;tizen40;netcoreapp2.0;uap10.0.16299</TargetFrameworks>

Now, instead of ~220 projects, the MvvmCross.sln now has 57 projects. However, this transition comes at a cost. Firstly, defining all the target frameworks like this will mean that it won’t compile on Mac because UWP doesn’t exist there. MvvmCross addressed this by including some conditional logic to detect whether the solution was being built on Windows or not.

<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' "> netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid81;tizen40;netcoreapp2.0;uap10.0.16299</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' "> netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid81;tizen40;netcoreapp2.0</TargetFrameworks>

The second problem is that building the solution became incredibly slow. Every change to the core MvvmCross project meant it had to be recompiled for each target framework. Luckily this doesn’t impact developers using MvvmCross, but it does mean that making changes to MvvmCross is a painful process. This is made worse by the weak support within Visual Studio for multi-targeting, often resulting in having to clean bin/obj folders, restarting VS etc.

Most of the time when working on a feature or a bug in MvvmCross we’ll be targeting one platform; at least until we have it working, and then we’ll test on the other platforms. It would be really nice if Visual Studio supported this workflow, allowing us to effectively disable compilation of the platforms we’re not interested in.

Recently we made a change to the MvvmCross projects to add further conditional logic:

<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
   <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid81;tizen40;netcoreapp2.0;uap10.0.16299</TargetFrameworks>
   <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid81;tizen40;netcoreapp2.0</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetsToBuild)' != 'All' ">
   <TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Android' ">netstandard2.0;MonoAndroid81;</TargetFrameworks>
   <TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Uap' ">netstandard2.0;uap10.0.16299</TargetFrameworks>
   <TargetFrameworks Condition=" '$(TargetsToBuild)' == 'iOS' ">netstandard2.0;Xamarin.iOS10</TargetFrameworks>
</PropertyGroup>

As you can see, this inspects the variable TargetsToBuild in order to determine what target platforms to build. This variable is defined in the Directory.build.props so that it can be referenced from any of the MvvmCross projects.

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
   <!-- <TargetsToBuild>All</TargetsToBuild>  -->
   <!--<TargetsToBuild>Android</TargetsToBuild>-->
   <TargetsToBuild>Uap</TargetsToBuild>
   <!--<TargetsToBuild>iOS</TargetsToBuild>-->
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
   <TargetsToBuild>All</TargetsToBuild>
</PropertyGroup>

To make sure that this option doesn’t affect Release builds (ie creation of nuget packages) if the Configuration isn’t set to Debug, TargetsToBuild is set to All. In the above code, the TargetsToBuild variable is set to Uap. This means that only the Uap (and netstandard) targets will be built – this is a massive performance gain when debugging.

Note: If you change the TargetsToBuild property you will most likely have to restart Visual Studio and/or force a rebuild of the entire solution but the performance gain you get out of using the TargetsToBuild variable is well worth it.

Comments are closed