C#, Mono, Nuget, Nunit, T4, CI, Oh My!

Somtimes combining technologies can be a distant goal. I can’t take credit for this photo

In all my experiences, I have had no need to run C# code on linux. This has recently changed as the demographics for my side project, Pdoxcl2Sharp has expanded to include linux. Being the ambitious developer that I am, I decided that not only can people use the code on linux, but also develop the code without any problems. Turns out there are a lot of problems. This is my story.

Pdoxcl2Sharp makes use of Nuget for dependencies, Nunit for tests, T4 for templates, and Travis-CI for continuous integration. Each one of these is a roadblock in development.

Mono

Make sure you have a recent version of Mono. If you’re on Ubuntu 14.04+ you are good to go just sudo apt-get install mono-devel mono-gmcs. Many aren’t on 14.04 yet, so you can add a PPA. I used directhex/monoxide (sudo add-apt-repository ppa:directhex/monoxide), but there others; Nancy uses badgerports.org. If this isn’t your style you can take the route that F# projects take and download the latest MDK.

All of these options will install xbuild, which is an msbuild replacement. Simply call xbuild <proj> to build. Before we can build successfully there are requirements we are missing: dependencies.

Nuget

Nuget is great for dependency management, but if you don’t have the latest version it can be annoying. I didn’t realize it but Nuget can only restore on linux recently, and I didn’t have a recent version. I kept getting build errors telling me I didn’t set Visual Studio to the correct options. I didn’t think to immediately download a new version of Nuget, as I thought dependency managers wouldn’t need to be updated! I was thrown off because I keep Nuget inside a .nuget folder inside my source control and it turns out when Visual Studio updates Nuget it doesn’t the version in the source control, only its internal version. The solution ended up being quite easy.

  • Overwrite .nuget/NuGet.exe with the newest version
  • Overwrite .nuget/NuGet.targets with the latest version in the source control
  • Edit the individual projects to make sure they refer to NuGet.exe and NuGet.targets instead nuget.exe and nuget.targets. Linux is case sensitive and windows is not!

Nunit

An important of any workflow are tests. Setting up Nunit is impossibly easy: sudo apt-get nunit-console. After the build succeeds, run the tests by invoking nunit-console <Proj.Test.dll>

Travis-CI

A build server will have a clean slate and in order for Nuget to restore packages it must access the outside word. Well, by default many machines don’t trust where Nuget gets it’s sources, so you have to add them explicitly.

sudo mozroots --import --machine --sync
yes | sudo certmgr -ssl -m https://go.microsoft.com
yes | sudo certmgr -ssl -m https://nugetgallery.blob.core.windows.net
yes | sudo certmgr -ssl -m https://nuget.org

The command yes is piped to the other commands because they normally wait for user input.

T4

I’ve saved the best (worst?) for last. I still don’t have the perfect solution, so I will take suggestions. The goal is have a cross platform way using xbuild, msbuild, and Visual Studios. If there is only one T4 and it is relatively small, you might just be better off committing the autogenerated code to source control. This way there are no added dependencies – you just git a weird feeling in your stomach.

If you have a much larger T4 like my project, EU4.Savegame, where committing the autogenerated content will be an extra 5000 lines to keep track of, it may be better off generating the content on each build. I will warn you that if your T4 template contains the import name="CustomProj.dll then this solution will result in Visual Studio doing no-op on save. To accomplish this, follow these steps:

  • wget -O monodev.tar.gz "https://github.com/mono/monodevelop/archive/monodevelop-5.2.0.384.tar.gz" && tar -xzf monodev.tar.gz: The guys over at Monodevelop reversed engineers Microsoft’s technology to create a cross platform tool for generating code.
  • xbuild main/src/addins/TextTemplating/TextTransform TextTransform.csproj
  • cp main/build/AddIns/MonoDevelop.TextTemplating/{Mono.TextTemplating.dll,TextTransform.exe} <wherever>: I stored the couple of files in a folder in my solution “dependencies/t4”
  • Edit your project file to transform the template before build:
<Target Name="BeforeBuild">
  <Exec 
    Condition=" '$(OS)' != 'Windows_NT'"
     WorkingDirectory="$(SolutionDir)\dependencies"
     Command="mono $(SolutionDir)dependencies\t4\TextTransform.exe \
              -o $(MSBuildProjectDirectory)\ParseTemplate.cs \
               $(MSBuildProjectDirectory)\ParseTemplate.tt" />
  <Exec 
    Condition=" '$(OS)' == 'Windows_NT'"
     WorkingDirectory="$(SolutionDir)\dependencies"
     Command="$(SolutionDir)dependencies\t4\TextTransform.exe \
              -o $(MSBuildProjectDirectory)\ParseTemplate.cs \
              -r $(FrameworkDir)$(FrameworkVersion)\System.Linq.dll \
              $(MSBuildProjectDirectory)\ParseTemplate.tt" />
</Target>
  • If you use <#@ assembly name="CustomProj.dll" #> we must disable Visual Studio from transforming the template, else it will result in cryptic errors. In the project file remove the DesignTime attribute from the generated source file and remove the Generator attribute from the text template. So now Visual Studio doesn’t render the template on save, is this a big deal? I don’t think so. We just won’t get intellisense until compile time.

Conclusion

Honestly the hardest thing about getting a workflow going on linux was finding the information. I hope this will provide enough info for others to start developing C# on linux!

Comments: