Update – 15/07/14
While the below worked, I ended up abandoning the branch in the NUnit project. There were over a thousand warnings in the Visual Studio Error List from loading the project (not from building) and I could not fix them. In Xamarin Studio, there were just as many, but different warnings and the .NET 2.0 projects failed to build with odd errors.
When I get some time, I am going to take another stab at this, maybe with Update 3 of Visual Studio to see if things have improved at all.
With Visual Studio 2013 Update 2, Microsoft released a new feature called Universal Apps. They are designed to share code between Phone and Windows applications. Before this, if you had code that was common between projects and weren’t using Portable assemblies, you would include the same source file in each project and use defines for platform specific code.
Universal Projects just automate that process. A Universal Project consists of two parts, the first is the SHPROJ project file. This file just imports the Code Sharing MSBuild extensions and imports the second PROJITEMS file. The PROJITEMS file is just an MSBuild file that looks very much like a CSPROJ file. This PROJITEMS file is then included in your platform specific projects and this is where the magic happens. The Universal Project appears under the references node of the project and when the project is built, all of the files in the Universal Project will get pulled in and built into the platform specific projects.
What most people don’t realize is that this can be used outside of sharing code between phone and desktop apps. For example, many libraries like NUnit or JSON.NET compile against multiple versions of the .NET Framework. They do this by having multiple projects for the library, each targeting a different framework, but including the same files. This becomes a maintenance nightmare on larger projects. For example, if I want to add a new class to the NUnit Framework, I need to add it to four nunit.framework projects and five nunitlite projects. Because of this, it is quite common for a feature to be missed on one platform when a developer missed adding a class file.
It is also causes problems developing. For example, if you open a file from a .NET 4.5 project and try to go to definition, but the target file was opened from the .NET 4.0 project, it fails with the error that the file is already open in another project.
Universal Projects to the Rescue
With Update 2 of Visual Studio 2013, the tooling around Universal Projects isn’t quite there, but with a bit of manual editing to get you started, you can quickly convert a solution with shared code. I will walk through how I did this for the NUnit solution.
First install the Shared Project Reference Manager extension for Visual Studio. This will allow you to add references to Universal Projects.
Next, with your favourite text editor, create the SHPROJ and PROJITEMS files based on the following templates. For NUnit, I just dropped them in the nunit.framework source directory with the platform specific projects.
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Label="Globals"> <ProjectGuid>c9209134-aea3-4c7f-ad74-00d578a6f208</ProjectGuid> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" /> <PropertyGroup /> <Import Project="nunit.framework.projitems" Label="Shared" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" /> </Project>
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> <HasSharedItems>true</HasSharedItems> <SharedGUID>c9209134-aea3-4c7f-ad74-00d578a6f208</SharedGUID> </PropertyGroup> <PropertyGroup Label="Configuration"> <Import_RootNamespace>NUnit.Framework</Import_RootNamespace> </PropertyGroup> </Project>
Generate a new GUID and use it in both files. Rename the Import Project in the SHPROJ file to whatever name you use and change the Import_RootNamespace in the PROJITEMS file to your namespace.
Next, right-click on your solution and create a new Solution Folder called something like Universal or Shared. Next, right click on that folder and Add Existing Project to add your new Universal Project.
Now you need to add the source files to the shared project that you want to remove from the platform specific projects. I find it easiest to click on your Universal Project and click on the Show All Files button in the Solution Explorer. Because I placed the Universal Project in with the source files, I can just select all of the files that I want to move over and Include In Project.
Now open up your platform specific project and Exclude From Project all of the files that you added to the Universal Project. Do not Delete the files from your project as it will delete them from disk.
Finally, right click on the References for your project and Add Shared Project Reference. This menu option was added by the Shared Project Reference Manager extension that was installed earlier.
To finish, repeat with all of the platform specific projects until all of the shared code is moved out. Notice in the image above that there is now no code in the nunit.framework-4.5 project, it is all now in the Universal Project.
When working with the code, if you want to switch which platform you are looking at, you select it from the dropdown at the top-left of the editor window. Other than that, there is no difference to the development process.
If you want to see more, I also added a long write-up to the NUnit pull request on GitHub.
If anyone knows where I can get a Universal App project template that just creates the shared project, please let me know in the comments. The templates that ship with Visual Studio only create desktop/phone projects with a shared project between them.