Alright, let's cut to the chase and get a blank project going.
Install
- Install Nim if you haven't already, and make sure that it is accessible from the command line (PATH or something).
- Download SDCC, either the snapshots or the GBDK-2020 distribution. Extract it somewhere.
Setup
We're going to use Atlas, the workspace manager that is bundled with Nim starting from version 2.0. It doesn't have the most conventional way of managing things—but it works I guess.
For the following, a Unix-like shell will be assumed; adjust the commands as needed.
mkdir gbnim-workspace cd gbnim-workspace atlas init
This will initialize an Atlas workspace, a common environment in which every Jibby project would reside.
mkdir Blank cd Blank atlas use 'https://github.com/zoomten/jibby#head'
This initializes a project within the workspace that uses the Jibby library. As the package is currently "unpublished", the command to "use" this package currently points to the source repo.
And yes, the folder name is capitalized. This is because, in the workspace folder just up above, the jibby library will be present alongside the project, instead of being in a "dependencies" folder or something like that. I told you it's not very conventional, though this may change… This naming is not a requirement, although it may help you differentiate projects you made from dependencies pulled in from somewhere else.
Either way, two files will be generated:
- Blank.nimble: the traditional Nimble package file, which is used to tell Atlas what the project needs.
- nim.cfg: The file which contains what command line parameters to be automatically added when the Nim compiler is invoked.
Compiler tasks
However, we are not going to be invoking the Nim compiler in the usual way. Instead we are going to create tasks that we can invoke with the compiler instead. Create a new file, config.nims (and yes, it must be named that way), which will contain:
when defined(nimsuggest): import system/nimscript from std/os import `/`, absolutePath from std/strutils import join const srcPath = "src" romName = "blank" import jibby/helper/scriptConfig if projectPath().absolutePath() == thisDir() / srcPath / romName: precompileTools() setupToolchain() patchCompiler() switch "listCmd" task build, "Build the ROM": selfExec( ( @["compile"] & makeArgs() & @["-o:" & romName & ".gb"] & @[srcPath / romName] ).join(" ") ) task clean, "Clean build artifacts": for i in [".gb", ".ihx", ".map", ".noi", ".sym"]: rmFile(romName & i) rmDir(".tools")
Aight, let's break this down.
when defined(nimsuggest): import system/nimscript
So this bit is for the IDEs to not freak out when given this file. When this file is executed by the Nim compiler, it will automatically import this. Hopefully this issue will be fixed some time.
const srcPath = "src" romName = "blank"
Here we define two constants: our source folder relative to this Blank folder, and the name of the source file (and corresponding ROM) within that folder.
import jibby/helper/scriptConfig if projectPath().absolutePath() == thisDir() / srcPath / romName: precompileTools() setupToolchain() patchCompiler() switch "listCmd"
Here we set the additional stuff to do only when the compiler is given the file src/blank.nim. We do this to ensure that when the compiler processes files that get imported from that main file, we don't do these again.
- precompileTools: See scriptConfig: precompileTools. As the name implies, it will precompile the compile and link wrappers needed to correctly compile the ROM, see the former's link for why this is needed. These wrappers will then be compiled under a hidden .tools directory.
- setupToolchain: See scriptConfig: setupToolchain. This will configure the Nim compiler to set what I think are "optimal" settings for the Game Boy platform.
- patchCompiler: See scriptConfig: patchCompiler. This will redirect one of Nim's system files to a custom one provided by Jibby. At the time of writing, this will be the copyMem and related functions.
- switch "listCmd": This one is optional, but it will show exactly what commands are being called by the Nim compiler.
task build, "Build the ROM": selfExec( ( @["compile"] & makeArgs() & @["-o:" & romName & ".gb"] & @[srcPath / romName] ).join(" ") )
Here we define a build command. This is really just a quick shortcut to invoke nim compile -o:blank.gb src/blank.nim. Of course, blank.gb and src/blank.nim originating from the romName and srcPath variables we defined earlier. makeArgs() here attempts to pass whatever relevant defines you have set to the compiler process spawned here, in order for the tools to process them. See scriptConfig: makeArgs for more details.
task clean, "Clean build artifacts": for i in [".gb", ".ihx", ".map", ".noi", ".sym"]: rmFile(romName & i) rmDir(".tools")
The clean command here does pretty much what it says on the tin.
Nim compiler configuration
You can add this to the nim.cfg file, so you don't need to specify GBDK_ROOT manually in your shell:
--putEnv:GBDK_ROOT:"/root/gbdk"
Obviously, you need to point this to the absolute path where you extracted SDCC or GBDK to.
There is an additional rule here, namely directly beneath whatever you set GBDK_ROOT there must exist a bin folder containing all the SDCC programs, and an include folder containing the SDCC .h files. If you have downloaded the snapshots, the includes will be found in share/include, and you need to move this folder up.
Sources
So that's the configuration. But we can't build anything if we don't have a source file, can we? Fortunately, since this is a blank program, we can do this easily.
Create a folder called src, and then inside that folder create blank.nim:
import jibby/runtime/init import jibby/runtime/vblank
Finally, in the same folder, create panicoverride.nim:
proc panic(s: string) = discard proc rawoutput(s: string) = discard
The reason why this extra file is needed is because setupToolchain() earlier set the compiler's OS target to standalone. As a result, the compiler wants to include a specific file called panicoverride.nim located near the file it's compiling, to provide those two procs that it needs for this target.
Build
Alright, let's try building:
nim build
If everything went right, you should end up with a lot of files:
- blank.gb: The ROM!
- blank.sym: A plain-text symbol file that can be processed by emulators like BGB, SameBoy, or Emulicious to make the resulting assembly code a little easier to parse.
- blank.map: A plain-text file that tells you exactly where every section is, how big are they, and what object files were linked into the ROM.
- blank.ihx: An Intel Hex representation of the ROM, automatically generated by the SDLD linker, from whence the final .gb file came.
- blank.noi: The NoICE symbols automatically generated by the SDLD linker, from whence the .sym file came.
- .tools/: A folder containing the wrappers that were needed to compile the ROM.
All of those files can be cleaned automatically by doing:
nim clean
Because the build and clean commands are something that was defined earlier, you can change it to fit your specific needs.
But for now, go ahead and inspect the ROM in an emulator!