Run Nim Code Anywhere with Cosmopolitan

A few years back a somewhat crazy fellow made [Cosmopolitan](https://github.com/jart/cosmopolitan)\:

Cosmopolitan Libc makes C/C++ a build-once run-anywhere language, like Java, except it doesn't need an interpreter or virtual machine. Instead, it reconfigures stock GCC and Clang to output a POSIX-approved polyglot format that runs natively on Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the best possible performance and the tiniest footprint imaginable.

It’s built on some crazy hacks. Yet surprisingly it works way better than it should. It went from a [crazy idea](https://justine.lol/ape.html) to a stable project. Rumor has it that companies actually use it!

For a long time I’ve wanted to try using it with Nim to make a truly portable executable. I get annoyed having to do the whole shell script dance to find the right OS and architecture for a binary:

#!/bin/bash
# Detect OS - but wait, is it "linux" or "Linux"? "darwin" or "macos"?
case "$(uname -s)" in
    Linux*)  OS="linux" ;;
    Darwin*) OS="darwin" ;;  # or is it "macos"? "osx"?
    *)       OS="windows" ;;
esac

# Detect arch - amd64? x86_64? x64? 64-bit?
case "$(uname -m)" in
    x86_64) ARCH="amd64" ;;  # or is it "x86_64"? "x64"?
    arm64)  ARCH="arm64" ;;  # or "aarch64"?
    *)      ARCH="386" ;;
esac

...

Compiling Nim Code with Cosmopolitan

The instructions for Cosmopolitan are pretty simple:

mkdir -p cosmocc
cd cosmocc
wget https://cosmo.zip/pub/cosmocc/cosmocc.zip
unzip cosmocc.zip
cosmocc -o hello hello.c
./hello

Plugging it into Nim took a bit of experimenting however. The C compiler configurations for Nim have always been a bit murky to me. Mostly I never have to bother with them. In this case we need to adjust the target OS for Nim, along with the target arch and C libraries.

After a bit of tinkering I found a simple way to setup the compiler by setting a simple -d:cosmopolatin flag:

# mycode.nim.cfg
@if cosmopolitan:
  # ARM64/aarch64 e.g. Raspberry Pi 3: gcc-aarch64-linux-gnu package on Debian/Ubuntu
  os = "linux"
  cc = "gcc"
  arm64.linux.gcc.exe = "cosmocc"
  arm64.linux.gcc.linkerexe = "cosmocc"
  amd64.linux.gcc.exe = "cosmocc"
  amd64.linux.gcc.linkerexe = "cosmocc"
  gcc.options.always = "-static -fno-pie -no-pie "
@else:
  --define:ssl
@end

I tried this with [Atlas](https://github.com/nim-lang/atlas) because I like the idea of just downloading a universal Atlas and have it ready to run on IoT devices, CIs, or whatnot. First I added the above nim.cfg to atlas.nim.cfg then ran:

-> % nim c -d:release --verbosity:3 -d:cosmopolitan -o:bin/atlas-cosmo src/atlas.nim
...
Hint: mm: orc; threads: on; opt: speed; options: -d:release
133181 lines; 2.017s; 341.906MiB peakmem; proj: /Volumes/projects/nims/atlas/src/atlas.nim; out: /Volumes/projects/nims/atlas/bin/atlas-cosmo [SuccessX]
/Users/elcritch/.local/share/grabnim/nim-2.2.4/compiler/msgs.nim(716, 13) compiler msg initiated here [MsgOrigin]
[GC] total memory: 358514688
[GC] occupied memory: 316239632
-> % ls -lh bin/                                                                     
total 30712
-rwxr-xr-x  1 elcritch  staff   4.5M Aug 11 01:28 atlas-cosmo
-rwx--x--x  1 elcritch  staff   4.4M Aug 11 01:28 atlas-cosmo.aarch64.elf
-rwx--x--x  1 elcritch  staff   5.3M Aug 11 01:28 atlas-cosmo.com.dbg

Boom a 4.5 mb binary that runs seamlessly on my ARM64 Macbook running MacOS or on an x86_64 IoT device I’m working on! It does also run on Windows, but the path handling would need to be tweaked to handle it since Atlas relies on compile time switching for windows paths.

Final Thoughts

Am I going to use this? I don’t know.

It’s a very impressive that it works at all but there’s some dark magic going on to do so. However, it’s at release v4 now and been around for years! It’s also impressive how simple it was to setup once I got the right compiler settings for Nim. Much easier than 99% of cross compilation I’ve done before.

So perhaps it’ll help in some of those cases instead of making a multi-platform docker image and that set of crap, you can make a universal Nim binary instead. I’ve wasted far too many hours of my life wanting to get the right set of commands to get a simple binary to cross compile and wading through Github CI issues or Docker OCI container rabbit holes.

0
Subscribe to my newsletter

Read articles from Jaremy Creechley directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jaremy Creechley
Jaremy Creechley