nunit/nunit-framework — pull request #105 by simoneb (2014-02-25 02:09:48+00:00)
Title: Switch to MSBuild

Hello, here's the branch I used for experimenting and replacing NAnt with MSBuild. The initial aim was to switch from building using NAnt+csc to building using NAnt+MSBuild/xbuild. An initial spike showed that this is indeed possible so I proceeded to removing NAnt entirely and carry out the build using MSBuild/xbuild entirely. It still remains to be decided if we can live with MSBuild/xbuild alone or use a scripting system like Rake, with its known pros and cons, but this branch tries to achieve everything using MSBuild and minimal shell scripting only.

For building this branch I created a parallel [project](http://teamcity.jetbrains.com/project.html?projectId=NUnit_NUnit3_FrameworkMSBuild&tab=projectOverview&branch_NUnit_NUnit3_FrameworkMSBuild=%3Cdefault%3E) in TeamCity, check it out!

#### Overview ####

There are three parts to our build scripting system:

1. The entry point to the build script
2. Building and testing one framework
3. Building and packaging multiple frameworks

Feature parity between MSBuild and NAnt is not achievable for the very nature of the different approaches so I made several choices along the way, most of which I'll mention here.

#### The entry point to the build script ####

**Before** the entry point did not exist, one would simply invoke NAnt passing the path to the build script.

**Now** there is an entry point, actually multiple for convenience. They are in charge of invoking the right executable, either `MSBuild.exe` for .NET or `xbuild` for mono.

For building there is `runbuild.cmd`, for Windows, and `runbuild`, for other OSes.

The former can build for both .NET and mono, and it decides to choose `MSBuild.exe` or `xbuild` based on the target framework that was passed on the command line, more on this later. Once decided which one to use `MSBuild.exe` is searched in sensible locations, with the highest version number as first priority, whereas `xbuild` is expected to be on the `PATH`.

The latter simply invokes `xbuild` because on OSes other than Windows we can build only mono right now.

There are also two additional, symmetric scripts for packaging, they are simply wrappers around the respective ones mentioned earlier for packaging purposes.

#### Building and testing one framework ####

I replaced the main plus project-specific NAnt scripts with a single MSBuild script called `NUnitFramework.msbuild`. I tried to port functionality bit by bit, but there is a considerable feature mismatch between the two, although I believe we could live without some of the functionality.

###### Feature mismatches ######

- MSBuild cannot test for the availability of a certain framework therefore all the detection cannot be done.
- Loops cannot be done in MSBuild and there seems to be no way to reproduce what we were doing with NAnt in order to build multiple frameworks in one go.

These are the main drawbacks, I thought that the former was not a big deal because who builds will need to target a framework which is available on his machine, and in any case the detection carried out by NAnt was very brittle anyway.
For the second the idea would be to wrap the execution of the script with minimal shell scripts.

###### How to build ######

The build script sets a default framework to build, which is `net-4.5` on Windows and `mono-4.0` otherwise. Targets are no longer used to configure the build, but rather MSBuild properties should be used. There is a single custom property which is used to set the framework, it's called `Framework` and can have the same values as it had in the NAnt script. The configuration is set using the MSBuild built-in `Configuration` property.
The default target is `Build`, additional targets can be specified using the MSBuild syntax.

For example:

`runbuild /p:Framework=net-3.5 /p:Configuration=Release /t:DumpSettings;Build;Test`

I tried to stick as much as possible with the MSBuild idioms for simplicity, so for example all properties are now *PascalCase* without dots in their names and there are no custom targets to set configuration options, everything happens internally based on the few properties supplied to the script, or default values.

#### Building and packaging multiple frameworks ####

As I mentioned there is no way to execute loops in MSBuild, or at least none which allows to change property values and is compatible with xbuild. Therefore there is no way to build/test/package multiple frameworks with a single invocation of the build script.

The idea is to wrap such functionality in a minimal shell script which invokes the build script multiple times.

The build script contains packaging targets which work similarly to NAnt, but rather than building the binaries to package it expects that the binaries are already there. Therefore in order to build a full package it is first necessary to invoke the script multiple times to build/test the supported frameworks and then invoke it one more time to package the whole thing. Currently this is not yet implemented in the scripts, it will be the next step.

Note that the packaging is already happening on TeamCity for the frameworks we can build there. The individual framework builds build, test and create binaries, then there is a packaging build which collects all of them and invokes the packaging target of the build script, basically what will happen locally once we have implemented this functionality in the shell scripts.

Packaging in the MSBuild script is done without relying on project-specific scripts. The source is now packaged using `git archive`, which basically puts into the zip everything that is in the tree. This is much easier and much less error prone than maintaining several scripts, one for each project. After all if we want to package the source I guess we want all the source to go in there. I think it is possible to do some exclusions in case there is a need to do so.

Note that merging this branch is safe because any change I did to existing files (besides the solution file where I included the new files I added as solution items) have been reverted. There is a single change to a C# source file because I noticed that the mono C# 5 compiler could not infer the type of an async lambda expression. We're not targeting mono-4.5 yet but we could and I tried it on my machine, hence why I figured we had such a problem.