Using NimScript for your build system
Welcome to this tutorial on using NimScript for your build system! In this tutorial, we'll explore how to use NimScript to define tasks and automate your build process. NimScript is a powerful and flexible scripting language that is a sub-dialect of the Nim programming language.
Benefits
NimScript is a versatile scripting language that can be used on any platform where Nim can run. With NimScript, you can enjoy the same syntax, style, and ecosystem as compiled Nim. This means that developers won't need to learn anything new or switch contexts. [source: https://nim-lang.github.io/Nim/nims.html#Benefits]
Getting Started
To use NimScript as your build system, you can use the
task
template. With this, you can write tasks that can be executed with a working Nim compiler.
You can write these tasks either in the .nimble file or in a separate .nims file, which
can be executed by calling nim taskname filename.nims
directly. Running nimble tasks
in
the package root will display a list of available tasks.
Alright, the first thing you gotta do is figure out how to run the tests you wrote for
your project, which are stored in the tests folder. By default, Nimble has a pre-defined
test
task that compiles and runs all files in the tests directory that begin with 't' in
their filename. However, you might want to customize this test task to do something like
generating coverage reports for each individual test file.
An issue that commonly arises when running tests is the need to import the associated
package. It's important to avoid accidentally using the package that's already installed
locally in your ~/.nimble
directory. To ensure that everything is resolved correctly,
you'll need to modify the path. Simply use the --path:"../src/"
argument and place it in
a nim.cfg
file in the tests/ directory to make it permanent. This should be sufficient to
prioritize it over any locally installed package.
You can control the behavior of your script by setting the global mode
variable to one
of three options: Silent
, Verbose
, or Whatif
. Setting the mode to
ScriptMode.Verbose
, for instance, will cause the script to output every command that is
being executed to your terminal.
Run tests with switch and setCommand
You can use the switch
procedure to convert command line switches, and then use the
setCommand
procedure to specify the Nim command to be executed after the completion of the
current Nimscript. Here's an example:
task test, "Run the tests":
# Lists of available options https://nim-lang.github.io/Nim/nimc.html
switch("define", "debug")
switch("run")
setCommand "c", "tests/all.nim" # runs nim
It's important to note that although the shorthand for the switch
template exists with
the --
prefix (e.g. --define:release
), it can be frustrating to discover that its
arguments are passed verbatim using
astToStr. This means that
stringifying variables, for example, will not work as expected.
A common approach is to use a all.nim
file that simply imports all the tests from the
test folder. However, as we will see later, there are other ways to accomplish the same
thing.
Run tests with exec
Using exec
in your NimScript allows for more flexibility in executing programs when running
tasks, including the Nim compiler. For instance, you can override the default test
task
like this:
import std/[os, strutils]
task test, "Run the tests":
for f in listFiles("tests"):
if f.startsWith("t") and f.endsWith(".nim"):
exec("nim c -r --hints:off -w:off " & quoteShell(f))
When calling the nim compiler from your Nimscript using the exec
command any switch
calls defined in the Nimscript will be ignored.
Bonus: Task that produces coverage reports
Congratulations on making it this far! Here's a task that can generate a coverage report for your tests. However, please note that the report is outputted in C, not Nim, so some experience with reading the Nim compiler's output may be required.
task cov, "Produce coverage reports":
mkDir "build"
let name = "all" # The name of the test file
let target = "myFunc" # Target a specific library function.
exec "nim c --cc:clang -d:danger -d:useMalloc -t:\"-fprofile-instr-generate -fcoverage-mapping\" -l:\"-fprofile-instr-generate -fcoverage-mapping\" -g -f --out:build/" & name & " tests/" & name & ".nim"
withDir("build/"):
exec "LLVM_PROFILE_FILE=\"" & name & ".profraw\" ./" & name
exec "llvm-profdata merge -sparse " & name & ".profraw -o " & name & ".profdata"
exec "llvm-cov show -instr-profile=" & name & ".profdata -name=" & target & " ./" & name
One way to learn more about how tasks are implemented in various popular packages is to search nimble.directory. You can find many interesting tasks there, such as generating APK files of Android applications or setting up build environments.
Comments
Post a Comment