I begun to port Nim to a custom operating system (here mentioned as "myOS"). The initial goal was to be able to compile a small hello world program and run it on the custom OS. In order make this happen I had to do the following steps.
In the file **compiler/platform.nim** I had to add an entry for the OS. type TSystemOS* = enum # Also add OS in initialization section and alias # conditionals to condsyms (end of module). osNone, osDos, osWindows, osOs2, osLinux, osMorphos, osSkyos, osSolaris, osIrix, osNetbsd, osFreebsd, osOpenbsd, osDragonfly, osCrossos, osAix, osPalmos, osQnx, osAmiga, osAtari, osNetware, osMacos, osMacosx, osIos, osHaiku, osAndroid, osVxWorks osGenode, osJS, osNimVM, osStandalone, osNintendoSwitch, osFreeRTOS, osZephyr, **osMyOS**, osAny ... const OS*: array[succ(low(TSystemOS))..high(TSystemOS), TInfoOS] = [ ... (name: "Zephyr", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/", scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".", props: {ospPosix}), #added entry (name: "myOS", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/", scriptExt: ".sh", curDir: ".", exeExt: ".exe", extSep: ".", props: {}), #----------- (name: "Any", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/", scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".", props: {}), ] Run This solution is really nice, that many of the OS dependent settings are in one place. Imagine if this would be scattered around in several files, you wouldn't be able to find it. What would perhaps be even more convenient is to have a configuration file that the compiler parses, but this is good enough. One question I have is if this information is for the host compiler only or is this also used for the actual binary target? In **lib/system/platforms.nim** you also need to add an entry for the OS. OsPlatform* {.pure.} = enum ## the OS this program will run on. none, dos, windows, os2, linux, morphos, skyos, solaris, irix, netbsd, freebsd, openbsd, aix, palmos, qnx, amiga, atari, netware, macos, macosx, haiku, android, js, standalone, nintendoswitch, **myos** ... const targetOS* = ... elif defined(nintendoswitch): OsPlatform.nintendoswitch elif defined(myos): OsPlatform.myos else: OsPlatform.none ## the OS this program will run on. Run A small change in **lib/system/dyncalls.nim** was needed. We can wait with the dynamic library support for now. elif defined(nintendoswitch) or defined(freertos) or defined(zephyr) or defined(myos): proc nimUnloadLibrary(lib: LibHandle) = cstderr.rawWrite("nimUnLoadLibrary not implemented") cstderr.rawWrite("\n") rawQuit(1) Run To support memory allocation changes in **lib/system/osalloc.nim** was needed proc osDeallocPages(p: pointer, size: int) {.inline.} = if bumpPointer-size == cast[int](p): dec bumpPointer, size elif defined(myos) and not defined(StandaloneHeapSize): include myos/alloc # osAllocPages, osTryAllocPages, osDeallocPages else: {.error: "Port memory manager to your platform".} Run First the people who develop Genode really showed how it should be done. Instead of inserting everything in the same file, just include another implementation file which is much cleaner. I decided to copy this way of doing it. The implementation of osAllocPages, osTryAllocPages and osDeallocPages was very easy by calling the custom OS interface and it doesn't need to be more complicated than that. This is a very low bar to reach to enable the memory management. The file **config/nim.cfg** was changed in order to give custom parameters for the C compiler. ... arm.myos.clang.exe = "clang" ... @if myos: clang.options.always = "--target=arm-none-eabi ..............." clang.cpp.options.always = "--target=arm-none-eabi ............" @end Run I didn't specify any linker because I use cmake to link the binary files. This was because I have already an infrastructure to link to different types of targets as well it opens up for mixed Nim,C,C++ projects. Nim is a bit weird that it outputs several C and object files with for humans unpredictable file names. Also build systems don't like this because they want to work with files that they know about before hand. In order overcome this I set Nim to compile a static library with a specific name. Now cmake has a file name it can trigger on. This was basically all that was needed to produce a basic binary. Good thing that nim uses standard file write with stdout when you call echo so that worked right out of the box since I have a C library that partially works. Compared to other languages I have worked with this porting was very quick and in this case Nim didn't drag in anything from standard library so that I had to port everything in one go. Often other languages use a runtime where you have to port (or stub) _everything_ in order to get it to compile. Now this is where it starts to become difficult, even with Nim. I decided to try out passing command line parameters. By doing so you need to "import os". This imports a complete ball of files. Many of these files are like oscommon.nim, ossymlink.nim which contains code for file system functionality. Do you need the file system in order to read command line parameters? Here it would be more beneficial if the standard library had a more pay as you go approach. You don't include things you don't need which should be possible with Nim as the standard library is a bunch of source files. Operating systems have a wide variety what they support beyond basic memory management. They might have a file system but do not implement any access rights management. They implement basic file system functionality but don't have things like moving, copying files implemented because they don't care about that. Network support is not always there etc. To have a more pay as you go approach makes it easier to port as you can do it gradually.