Loïc Blanchard

Loïc Blanchard

Magic Compiler and Nostrand

2021-02-01 - 2022-12-09 | Blog Article
Data Light Wallpaper
Clojure
C#
JVM
CLR
Compiler
Interop
NuGet
Unity

Rational

I worked on integrating the Magic Compiler and its tooling to Flybot's development workflow.

Magic is an open-source bootstrapped compiler written in Clojure that takes Clojure code as input and produces dotnet assemblies (.dll) as output. The dlls produced by Magic can be run in the Game engine Unity which Flybot uses for their card game UIs. The goal was to be able to compile our backend Clojure APIs to dlls so we can used that logic in Unity directly.

There are 4 main open-source libraries involved:

Working closely with the author of the Magic compiler Ramsey Nasser, I helped improving the tooling around the Magic compiler so it integrates well with our workflow Clojure Backend/Unity Frontend.

My contributions were to:

  • Fix some high level issues on the compiler that were preventing us from compiling our Clojure projects
  • Report compiling issues and performance issues to Ramsey Nasser so he can improve the compiler.
  • Improve the tooling around the Magic compiler to make it easier for our developers to compile/test/package Clojure libraries in Dotnet
  • Successfully port our Clojure projects to Unity
  • Improve the way a project and its dependencies are compiled to dlls
  • Make it easy to package the newly compiled dlls in NuGet packages
  • Allow developers to deploy these packages in online GitHub repos (public or private)
  • Package the dlls in a way that makes it easy to import them into Unity projects

Nostrand

Why Nostrand

Nostrand is for magic what tools.deps or leiningen are for a regular Clojure project. Magic has its own dependency manager and does not use tools.deps or len because it was implemented before these deps manager came out!

Private Gitlab support

Since we wanted to compile private gitlab projects with deps on both private gitlab repos and public repos, I added the Gitlab support and private repo supports using the Github/GitLab tokens.

NuGet pack and push

Adding a .csproj that refers to a .nuspec at the root of a Clojure repo allows me to pack and deploy the generated dlls to a NuGet package that will be store on the Remote git repo. For private repositories, a nuget.config can be added to specify the PAT token for GitHub or Deploy token for Gitlab. The package is then added to GitHub/GitLab Package Registry.

Example of a Clojure library ported to Magic

An example of a Clojure library that has been ported to Magic is skydread1/clr.test.check, a fork of clojure/clr.test.check.

If you have a look at the dotnet.clj namespace, you can see the different convenient function that can be called by nostrand with the command nos:

  • compile the clojure codebase to dotnet assemblies:
    nos dotnet/build
    
  • run all the clojure tests using the CLR:
    nos dotnet/run-tests
    
  • pack and push NuGet packages to the GitHub/GitLab Package Registries:
    nos dotnet/nuget-push
    

So it only 3 commands, a developer can compile a Clojure project and its deps to dotnet assemblies that can be run in Unity, test that all the tests are passing in the CLR and push a NuGet Package in a remote public or private repository.

Magic Unity

The goal of Magic.Unity is to provide a Clojure runtime in Unity.

Magic.Unity used to have a compilation UI and a runtime. However, there was a mismatch between the Magic dlls of Nostrand and Magic.Unity. Also the compilation UI was not easy to use and we wanted to use Nostrand directly. The compilation has since been removed and Magic.Unity is now only a runtime that can use the last magic dlls.

Finally, You can add Magic.Unity (runtime for magic inside Unity) in the manifest.json like so:

{
  "dependencies": {
	  ...,
    "sr.nas.magic.unity": "https://github.com/nasser/Magic.Unity.git"
	}
}

Magic compiler

What is the Magic Compiler

Magic is a bootstrapped compiler written in Clojure that takes Clojure code as input and produces dotnet assemblies (.dll) as output.

Compiler Bootstrapping is the technique for producing a self-compiling compiler that is written in the same language it intends to compile. In our case, MAGIC is a Clojure compiler that compiles Clojure code to .NET assemblies (.dll and .exe files).

It means we need the old dlls of MAGIC to generate the new dlls of the MAGIC compiler. We repeat this process until the compiler is good enough.

The very first magic dlls were generated with the clojure/clojure-clr project which is also a Clojure compiler to CLR but written in C# with limitations over the dlls generated (the problem MAGIC intends to solve).

Why the Magic Compiler

There is already a clojure->clr compiler clojure/clojure-clr. However, clojure-clr uses a technology called the DLR (dynamic language runtime) to optimize dynamic call sites but it emits self modifying code which make the assemblies not usable on mobile devices (IL2CPP in Unity). So we needed a way to have a compiler that emit assemblies that can target both Desktop and mobile (IL2CPP), hence the Magic compiler.

Documentations and Bug reports

I do not have the knowledge for such low level compiler implementation, so I did not fix any issues on the compiler myself. However, I could help Ramsey Nasser improving the documentation for both user and potential contributors and fix some high level small issues. I was also reporting all the bugs and creating the issues on the different related repos.

GitHub Action

I added a GitHub action to perform the bootstrapping at every push to automate the process and make the latest dlls available in the GitHub action artifact to anybody.

Importing the nuget packages to our frontend Unity projects

Since all the Clojure libraries and the Magic.Unity were packaged via nugget and pushed to the GitHub/GitLab repo, we can use a packages.config to list our packages and use the command nuget restore to import them. Same as to push packages, a nuget.config can be added with the credentials.

Learn more

You can learn more about Magic in my blog articles listed at the top of the page.

Contribute

Found any typo, errors or parts that need clarification? Feel free to raise a PR on the GitHub repo and become a contributor.