In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s specified. In this post I’m going to show you how you can use the combination of a simple script and Solution Filters to optimise both the time it takes to load your solution in Visual Studio and the time it takes to compile your application.
Firstly, we’re going to adjust the Core project so that we can adjust which target frameworks are include based on a solution property called TargetsToBuild. If this is set to All, we’ll simply use the TargetFrameworks we defined in the previous post (i.e. .NET Standard, iOS, Android and Windows (UAP) when building on Windows). However, if TargetsToBuild is not equal to All, we’ll set the TargetFrameworks to be just the specific target framework that we’re interested in.
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetsToBuild)' != 'All' ">
<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Android' ">monoandroid10.0;</TargetFrameworks>
<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Windows' ">uap10.0.16299</TargetFrameworks>
<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'iOS' ">xamarinios10</TargetFrameworks>
</PropertyGroup>
Note: I quite often will include netstandard2.0 in all of the TargetFrameworks to make sure the .NET Standard 2.0 build doesn’t break as we’re changing the Core library. This is a personal choice and you’ll definitely get better performance out of not having it build every time.
Ok, so the question is, where does TargetsToBuild get set. For this, we’re going to include a file called Directory.build.props in the solution folder. You can think of this as a file where you can define properties and include items and references across the entire solution. In this case, we’re going to set the contents to the following.
<Project>
<PropertyGroup>
<TargetsToBuildDeveloperOverride>All</TargetsToBuildDeveloperOverride>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TargetsToBuild>$(TargetsToBuildDeveloperOverride)</TargetsToBuild>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
<TargetsToBuild>All</TargetsToBuild>
</PropertyGroup>
</Project>
There’s two parts to this:
- Firstly, if the Configuration is not set to Debug, the TargetsToBuild is always set to All. This is never modified, and is a fail safe to ensure all target frameworks are built when doing a Release build.
- Secondly, the TargetsToBuild is set to the value held by TargetsToBuildDeveloperOverride when the Configuration is Debug. This variable is currently set to All.
The property TargetsToBuildDeveloperOverride essentially has four possible values: All, Android, iOS and Windows. Changing this value will adjust the target frameworks for the Core project. I would recommend restarting Visual Studio as the support for dynamically switching target frameworks in any given session isn’t there yet and doesn’t work 100% reliably.
The next piece to the puzzle is to use Solution Filters to limit which projects are loaded. By limiting which projects are loaded, your solution will load quicker and will also build faster (depending on what your build configuration settings are). To create a Solution Filter, simply unload the projects you don’t want loaded, then right-click on the solution node in Solution Explorer and click Save as Solution Filter. I’ve created the following solution filters:
- MultiTargetSample.all.slnf: All projects are loaded
- MultiTargetSample.Android.slnf: Core and Android head project loaded
- MultiTargetSample.iOS.slnf: Core and iOS head project loaded
- MultiTargetSample.Windows.slnf: Core and Windows head project loaded
The solution filter file, SLNF, is just a JSON file that lists which projects should be loaded. For example the MultiTargetSample.Windows.slnf file is as follows:
{
"solution": {
"path": "MultiTargetSample.sln",
"projects": [
"MultiTarget.Core\\MultiTarget.Core.csproj",
"MultiTarget.Windows\\MultiTarget.Windows.csproj"
]
}
}
The last piece of the puzzle is to combine the solution filters with the TargetsToBuildDeveloperOverride property. For example, if we want to be working on the Android application, we want to set the TargetsToBuildDeveloperOverride property to Android and then load the MultiTargetSample.Android.slnf solution filter. This is easily done using a couple of simple batch script.
Firstly, a generic script (LaunchVS.bat) that takes a parameter (the platform) and adjusts the TargetsToBuildDeveloperOverride property and then launches the appropriate solution filter.
powershell -Command "(gc Directory.build.props) -replace '[a-zA-Z]*', '%~1 ' | Out-File -encoding ASCII Directory.build.props"
start MultiTargetSample.%~1.slnf
Then, we have a script for each platform (eg LaunchVS.Windows.bat) which simply has the following command
LaunchVS.bat Windows
When you double-click on LaunchVS.Windows.bat in File Explorer, Visual Studio is launched with only the Core and Windows projects loaded. The Core library will only build the UAP target framework. This significantly improves both the solution load time and the build time, making working with MultiTargeted projects much easier.
Important: You might think that this isn’t important when you’re first starting out but I would highly recommend adding this to your solution from the get-go. It will save you hours of time both now and when your solution grows. Visual Studio does not handle large solutions with projects that have many target frameworks. Applying these changes will ensure Visual Studio continues to operate properly