09 January 2012
Ruby, Python, Perl, Node, and Clojure all have, to varying degrees, a unified story for defining a project, translating its source code into runnable code, running a test suite, building distributable packages, and resolving and installing a project or package’s dependencies. These stories just work across all supported platforms.
Setting up a cross-platform open source .NET project is non-trivial.
.NET open source developers jump through crazy hoops to try to get their projects to a point where anyone can check out the code from source control, build, and test the software on both .NET and Mono. What follows is a description of how I approached this task. It was painful to devise this solution and it continues to be painful to maintain.
Step 1: Build
We want to build our source code into .DLLs. We’ve got MSBuild, and Mono maintains it’s own quirky clone of MSBuild, called xbuild. They both read in
.sln files, which are totally opaque and cannot be generated by hand, and
.csproj files, which are XML, but hey they’re hand-editable. Okay. Ignore for a second that MonoDevelop and Visual Studio will fight over the formatting of those files, stomp on each other changes, and add garbage that’s meaningless to the other. A VS- or MD-generated .csproj will be default import some target definitions called Microsoft.CSharp.targets. Uh oh, what’s that? Where does that live on my system? What does it do? What package do I have to install to get that?
Now we’re in IDE-vs-IDE-vs-source-control hell, just so we can get a build running with two separate implementations of an XML-based project specification. But it’s okay, it’s cool. Let’s assume we’ve got that figured out and we’ve got a cross-platform source-code-on-disk to DLLs-on-disk transformation. With the caveat that we’re using the
msbuild command on Windows and
xbuild everywhere else.
Step 2: Test
Now I’m ready to run tests. How am I supposed to do that again? NUnit? XUnit? Something else? What’s the difference between all of those? Does Microsoft have their own test framework/runner? Is there a Mono clone?
Okay, I picked a test framework and runner. It doesn’t matter which one because they’re all the same. Now my code has a dependency on the DLL that defines the test framework. Well, I don’t want to include that DLL in source control because checking binaries into source control is a no-no. I guess I’ll just tell my contributors that they need to have TestFrameworkX-versionY.Z installed in the GAC to hack, and I’ll just bite the bullet and field the inevitable build problems and complaints about a GAC dependency on the mailing list.
Okay so I’m building my tests (and all my contributors are too). Now I want to run them. Hopefully I picked something that’s portable between .NET and Mono and I can easily run those tests on both platforms. Hopefully myself and my contributors can have a great test debugging experience whether we’re using MonoDevelop or Visual Studio. Hopefully.
Step 3: Repeat
I can build my code and I can run tests. Sweet! Now I want to set up continuous integration so I can have nightly builds and maybe some benchmarks to track the performance of my codebase over time. I’d like the system to email my devs if they check in broken code. It want to do automated builds on both .NET and Mono to ensure that my codebase works on both.
So I set about writing a build script that my CI system can run. Hm. I guess I could write an MSBuild file, but do I really want to maintain an XML build file? Will it be able to everything I need? Will I run into platform-dependent hiccups?
Oh hey, Rake is pretty nice, it’s well-supported on both Windows and Unix, and someone put together a nice suite of .NET-specific Rake tasks. Okay, I write a Ruby script to build my .NET projects. Pretty nice! I have separate commands for
xbuild to build the
.csproj files that my IDE understands, but hey, just an implementation detail, right?
I also want to test that the Mono-compiled DLLs work on .NET and vice-versa. Gonna have to add some plumbing for that…
Step 4: Ship
I’ve got nightly builds that are tested on both .NET and Mono, so I have a pretty solid codebase! Okay, let’s get it into the hands of users as easily as possible. How are users getting my code these days? Should I tell them to check out and build the source with my Ruby script? What if they build from their IDE, I guess I should make sure that story works too. But ideally I should also ship binaries so users don’t have to compile the project themselves
Oh hey, NuGet sounds like pretty hot way to distribute binaries! Let’s investigate it since it’s all new and fancy. Maybe I can use this to resolve my dependency on the test framework DLL while I’m at it. Okay, now I’ve changed my Ruby build script to use NuGet to go download the test framework package before it builds my tests. Ah crap, NuGet blows up on Mono. Well, guess that test framework dependency stays in the GAC, and I must change my Rake script so it only uses NuGet to create the NuGet package for my project if it’s running on Windows.
Ah dang, you know, this also means that only Windows user are going to be able to use my project’s NuGet package. I’ll have to make my Ruby build script generate a zip file containing my project’s DLLs. Doing it in Ruby is a heck of a lot easier than using the ZIP task in MSBuild and I don’t have to worry about cross-platform issues.
So, my .NET users can use Visual Studio and NuGet to take a dependency on my project, and my Mono users can have the ZIP file.
Step 5: Go forth and innovate!
Not so fast. Nancy, FubuMVC, Kayak, and Gate all use Rake to perform their builds, and as far as I recall those Rake scripts all branch on whether the current platform is Mono or .NET to accomplish that task. We are all using slightly different set-ups, and there’s a ton of duplication of effort.
If any of our users want to create an open source project that depends on our open source project, they’re going to have to figure out and reimplement everything described above. The tooling to allow developers to rapidly create, distribute, and collaborate on portable modules doesn’t exist in the .NET ecosystem.
Open source does not beget more open source in .NET, because the barriers to standing on each other shoulders are so high.
These are not unsolvable problems, but as far as I can tell, no one in the community has both resources and incentive to solve them.
Tweet Follow @bvanderveen