Roads Less Taken

A blend of programming, boats and life.

Bootstrapping Nim(rod)

| Comments

Technically Nim is still called Nimrod up to and including upcoming bugfix version 0.9.6. But then with the next version 0.10.0 it will be just Nim. Currently Nimrod is at 0.9.4. And oh, the current development version is 0.9.5 which will become 0.9.6 when released - the “odd and even” versioning scheme.

Nim can be a bit funky to get started with, typically due to a slight lack of documentation in certain areas that may be obvious to Nimmers (or Nimsters? Knights who say Nim?). This article tries to fill a few mental holes in the first steps.

Also, Nimsters like to do it in Nim - and often the reasons for this are very good, like maintaining portability or minimizing dependencies. Just don’t expect the classic autoconf dance here. As a Smalltalker I am fully versed in the NIH syndrome - which of course doesn’t always have to be a bad thing. Thankfully Nimmers also seem to balance it with a strong tradition of standing on the shoulders of giants.

So building Nim isn’t hard, but it’s also not obvious what is going on. There are of course binary installers available too, but hey, we want to hack!

Building from source

Here is the official Unix mantra to build Nim from source:

Building from source
1
2
3
4
5
6
7
git clone -b master git://github.com/Araq/Nimrod.git
cd Nimrod
git clone -b master --depth 1 git://github.com/nimrod-code/csources
cd csources && sh build.sh
cd ..
bin/nimrod c koch
./koch boot -d:release

1. Get the source

First we clone from github. In Nim country the master is meant to be “stable”, although still newer than the latest tagged release. The devel branch can also be used, and then we are on the bleeding edge.

2. Get the C source

Then we clone out csources. The Nim compiler is written in Nim, so there is an obvious bootstrapping issue here. The approach is that someone (typically Araq - the project leader) who has an old Nim compiler compiles the latest Nim compiler (written in Nim) into C, and then distribute those C files separately in its own github repo, so that everyone can get a first Nim compiler working by simply relying on the existence of a C compiler on the target machine.

NOTE: The readme in csources says that the branch of csources to use, should always match the branch of the main Nim repository. This is why I use -b master to make sure I take the master branch from both repositories. You can use another branch like devel but make sure you use the same on both. I was confused first because if you use --depth 1 then git will not be aware of the other branches and git branch -a will not list them.

3. Compile first Nim compiler

Then we bootstrap a Nim compiler from the C sources using build.sh, its a generated script but its readable. It basically figures out OS and CPU and then has a big switch over the OS/CPU combinations and compiles/links the corresponding C files in the c_code directory. So the c_code/1_2 directory has the source for the 1st OS (Windows) and the 2nd CPU type (amd64), and so on. We can count 8 OSes, and Linux (OS=2) has sources for 7 different CPUs.

When this is done we will have a binary nimrod compiler somewhere. If there is a koch.nim in the parent directory build.sh seems to decide to put the nimrod binary in ../bin/nimrod - fine!

4. Compile the koch tool

Then we back up again, and use this first nimrod compiler to compile the koch.nim tool. “Koch” is the german word for “cook” and if we peek at the source this actually looks like logic you would often write as a shell script, but here its written in Nim and acts like a typical Makefile would do in other open source projects. This is the tool we want to use in order to perform various tasks, like the rest of the bootstrap for example.

Compiling with nimrod is very simple, we just do nimrod c myfile.nim (c = compile, do nimrod -h for more info) so compiling koch.nim is just that simple. And it will do all the messy work for us that C/C++ people tend to hide in a Makefile - it will pull in all needed imports, compile everything needed, link it all together and spit out an executable called koch based on the name of the .nim file. Very nice!

5. Run koch boot command

Now we have a koch tool written in Nim compiled with a slightly older Nim compiler written in Nim that we compiled from its C sources that was generated by someone else using an even older Nim compiler. Muaahaaha! But it gets even more funny, just wait. Now its time to let koch compile a shiny new Nim compiler from our sources, again using the “slightly older from C sources”-Nim compiler. Bootstrapping is fun.

So we run koch with the boot command, and we throw -d:release into the mix. This is an option for the compiler, and its a good option to be aware of if you are doing some test benchmarks learning Nim - because if you leave it out Nim will be MUCH slower, since the default is to include debugging stuff in the executable. Now, what does the boot command do?

Insanity: doing the same thing over and over again and expecting different results.

Albert Einstein

Koch is just 300 lines of code and its a fairly interesting example of a tool written in Nim doing “shell stuff”. But let’s look at the Heart Of Gold at line 155 here:

Heart Of Gold
1
2
3
4
5
6
7
8
9
10
11
  copyExe(findStartNimrod(), 0.thVersion)
  for i in 0..2:
    echo "iteration: ", i+1
    exec i.thVersion & " c $# $# compiler" / "nimrod.nim" % [bootOptions, args]
    if sameFileContent(output, i.thVersion):
      copyExe(output, finalDest)
      echo "executables are equal: SUCCESS!"
      return
    copyExe(output, (i+1).thVersion)
  copyExe(output, finalDest)
  when not defined(windows): echo "[Warning] executables are still not equal"

This is bootstrapping at its finest - I can just imagine the feeling Andreas had when this little thing finally said SUCCESS!

Let’s take a look at the code above, first we find the “slightly older compiler from C”-compiler and copy it to 0.thVersion. What? Ah… so its 0.thVersion() and that’s in Nim equal to thVersion(0), funky syntax and takes a while to get used to from other languages! It’s a procedure that produces a path like compiler/nimrod0.

Then we loop 0, 1, 2 and for each iteration we compile the compiler using the compiler produced previously.

First iteration we use nimrod0 (the slightly older one from C) to create a new compiler. If the resulting executable is binary equal to nimrod0 we copy the resulting compiler to the final destination, declare SUCCESS and return.

Otherwise we copy the resulting compiler to nimrod1 and try again using this new one instead of the “slightly older one from C”. So now we are compiling it with “itself”, but “itself” was still built by the old compiler so… who knows if it will work?! If that also fails the binary comparison test - then we try one last time. This time we are using nimrod2 built by nimrod1 that was built by nimrod0 (you guessed it, the old one from C).

If it still fails we decide that, ok… whatever! …and copy it to the final destination anyway, and we warn the user. Unless we are on Windows of course - those guys aren’t that picky! Just kidding, there is probably some reason for Windows being treated differently.

6. Tada!

We got ourselves a Nim compiler - about 2Mb executable on my Linux box. Coffee. Strong.

Installation

Its sitting there in bin/nimrod, and we can use it and all, but perhaps we would like to install it into say /usr/local/bin or something. Again, koch to the rescue:

sudo ./koch install /usr/local/bin

The install command first compiles and runs the niminst tool (see tools/niminst/niminst.nim) that in turn creates an install.sh and deinstall.sh script (for Unix/Linux that is) and then runs the install.sh script to actually copy the files to the proper places.

If we peek in install.sh we note that it typically puts some config files in /etc/nimrod, the binary compiler in /usr/local/bin (if that is what you chose), the standard library files in /usr/local/lib/nimrod and doc & data in /usr/local/share/nimrod. Worth noting is that library files are installed in source form - yay! Personally I like that.

Finally it should be said that most core Nim developers don’t install Nim at all, they just point their PATH to the bin directory in the github clone. Nothing more needed, and that way its easier to juggle multiple Nims, each in its own directory. Simple and neat, we like it!

And thus ends this little article, happy Nimming!

Comments