Build Configuration

#config reads defines: named values handed to a build, such as a feature switch, a vendor name or a limit. They come from the manifest and the command line, are fixed for the whole build, and are read while compiling, so a when can keep only the code a configuration needs.

import Core::{ #config };
QueryTypeResult
#config.Has("Name")boolwhether the build defines Name, whatever its value
#config.Get("Name")char8[..]the value of Name, or an empty slice when it is not defined

Get alone cannot tell "not defined" from "defined as empty"; ask Has when the difference matters. Names are case-sensitive.

Supplying defines

In the manifest. A [Build.Defines] table in Rux.toml gives the package's defaults. A value may be a TOML string, boolean or integer, and is always read as text:

[Build.Defines]
Name = "Grace"
Retries = 3
Fast = true

Here #config.Get("Retries") is "3" and #config.Get("Fast") is "true". The table holds at most 128 defines; see the manifest reference.

On the command line. --define NAME=VALUE sets or overrides one define for one build of rux build, rux run, rux check or rux test, and may be repeated. --define NAME without a value defines it as "true"; --define NAME= defines it as empty.

rux run --define Name=Ada --define Verbose
flowchart LR
    m["Rux.toml<br/>[Build.Defines]"] --> d{"the build's defines"}
    c["--define NAME[=VALUE]"] -- "overrides" --> d
    d --> has["#config.Has"]
    d --> get["#config.Get"]

Reading defines

The argument to Has and Get must be a string literal written at the call, because the lookup happens while compiling. Even a constant holding the name is refused:

error: the argument to 'Has' must be a string literal written in the source
  help: the value is looked up while compiling, so it cannot come from a variable

Both queries work in a when condition — Get compares with == and != against a string — and in ordinary expressions, where they compile to constants:

import Core::{ #config };
import Io::PrintLine;

when #config.Has("Verbose") {
    const Verbose: bool = true;
} else {
    const Verbose: bool = false;
}

func Main() -> int {
    when #config.Has("Name") {
        let name = #config.Get("Name");
    } else {
        let name = "World";
    }
    PrintLine("Hello, {}!", name);

    when #config.Get("Name") == "Ada" {
        PrintLine("(a special greeting)");
    }
    PrintLine("verbose: {}", Verbose);
    return 0;
}
Started withOutput
rux runHello, World! · verbose: false
rux run --define Name=Ada --define VerboseHello, Ada! · (a special greeting) · verbose: true

A define is folded in while compiling: changing one means rebuilding. An executable built earlier keeps the defines it was built with, and the code for a configuration it was not built with is not in it at all.

See also