C#, Mono, Nuget, Nunit, T4, CI, Oh My!
Published on:Table of Contents
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 theDesignTime
attribute from the generated source file and remove theGenerator
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
If you'd like to leave a comment, please email [email protected]