Build Settings
Settings in the source, values per build
A program’s build-time configuration (which server a release talks to, a development build’s extra panels, a feature switch) is declared in its source with @setting, and each build picks the values with --set:
import io from "io";
pub enum Build : u8 { Dev = 1; Beta = 2; Release = 3; }
@setting pub const BUILD: Build = Build.Dev;
@setting const SERVER: []const u8 = "local";
@setting const VERBOSE_SYNC: bool = false;
fn show_debug_panel() {
io.println("debug panel");
}
fn main() -> i32 {
if (BUILD == Build.Dev) { show_debug_panel(); } // not in a Release build
io.println("connecting to {SERVER}");
return 0;
}tape run app.tape # Dev, "local"
tape build app.tape -o sulle.exe --set BUILD=Release --set SERVER=chat.example.comThe source says which settings exist, their types and their defaults; the build only picks values. Without --set a setting is its default, so tape run, tape test, tape check and the editor see the first variant of an enum like Build: what a developer runs.
Declaring a setting
@setting goes on a module-level const, pub or not, in any module of the program (libraries and the standard library included). Its type is written and is one of:
| Type | --set value |
|---|---|
bool | true or false |
an integer type (i8 … u64) | a decimal integer in the type’s range: PORT=8080, DELTA=-1 |
[]const u8 | any bytes: SERVER=chat.example.com |
an enum (this module’s, or an imported one’s m.Kind) | a variant’s name exactly as declared: BUILD=Release |
The initializer is the default and is a compile-time constant. A @setting anywhere else (a local, a var, a struct field, a function) or of another type is a compile error.
Otherwise a setting is an ordinary constant. It is folded at compile time, the same in the VM and natively, and a branch its value decides is left out of the program: in a Release build show_debug_panel above is not reached, so it is not in the executable. It works in @tape code:
@tape {
if (BUILD != Build.Dev) {
@emit fn edition() -> []const u8 { return "release"; }
} else {
@emit fn edition() -> []const u8 { return "development"; }
}
}and in the #resource region’s conditions, such as a release with no console window (@windowed):
#resource
@windowed(BUILD != Build.Dev)--set
--set NAME=VALUE is an option of tape run, tape build, tape test (passed on to each file’s VM and native runs) and tape check, as many times as there are settings to set.
NAMEis a setting of the root file’s module by its name as declared:--set BUILD=Release.- Another module’s setting is
module.NAME, the module named as its file is, the wayimportnames it:--set brand.SERVER=prod,--set netstack.TRACE=true.
Every --set is checked before anything is compiled, so a typo is never a silent default:
error: --set BUILD=Relase: Build has no variant 'Relase'; its variants are Dev, Beta, Release
error: --set VERBOSE_SYNC=yes: VERBOSE_SYNC is a bool: true or false
error: --set SRVER=x: the root file's module (app) declares no @setting SRVER; its settings are BUILD, SERVER, VERBOSE_SYNC
error: --set brnd.SERVER=prod: no module brnd in this build (a module is named as its file is, the way `import` names it)
error: --set BUILD is given twice (Beta and Release): one value per settingWhat a build can see
Only @target and @program describe the build to its code, and settings are the program’s own. How a build is produced (-O0, --map, --no-dce, …) is not readable, so it never changes what the program does. Settings are inputs of the build like its sources and @embeded files: the same sources, files, flags and --set values give the same program. A value from outside, such as a CI build number or a commit hash, comes in through --set, or through a file the build writes and the program @embeds; compile-time code reads no environment variables.
A build script
A build script run with tape run build.tape starts the same compiler through the TAPE environment variable and passes the settings on:
import os from "os";
import process from "process";
fn main() -> i32 {
var c: process.Command = process.command(os.env("TAPE")!);
c.args(["build", "app.tape", "-o", "out/sulle.exe", "--set", "BUILD=Release"]);
let st: process.Status = c.run() or { return 1; };
if (!st.success()) { return 1; }
return 0;
}Last modified: