Build options
Some code should be optional at build time: linking a library such as
libcrypto or zlib when it is there, tracing that only a debug build wants,
a backend chosen when the program is built. Build options set that on
the command line with -D, as in C compilers, and the code tests them with
BuildOption.
pep-build main.pep -D USE_LIBCRYPTO -D LOG_LEVEL=2 -D BACKEND=zlib -o app
-D NAMEdefines an option. It counts asNAME=1, as in C.-D NAME=VALUEdefines it with a value, which may be any text.- Repeat
-Dfor more options. When two name the same option, the later one wins.
pep-build --test defines TEST, which keeps the [Test] functions and
can mark code only tests use (see Unit tests).
A name is a letter or _, then letters, digits and _. Any other name is
an error:
error: invalid build option name '2FAST' in '-D 2FAST'; a name is a letter or '_' followed by letters, digits and '_'
# Build options: compile with `pep-build main.pep -D FAST_MATH -D LOG_LEVEL=2`
# to pick the other versions.
# `[BuildOption("NAME")]` keeps a declaration when `-D NAME` is given, and
# `!BuildOption(NAME)` inside a `Target` keeps its fallback otherwise.
[BuildOption("FAST_MATH")]
scale(x: int): int {
return x << 1;
}
[Target(!BuildOption(FAST_MATH))]
scale(x: int): int {
return x * 2;
}
# A `static` block reads an option's value while compiling, with a default
# for a build that does not set it.
log_level(): int {
var level = 1;
static (level) {
if (reflect.build_option("LOG_LEVEL", "1") == "2") {
level = 2;
}
}
return level;
}
main(): int {
if (scale(21) != 42) {
return 1;
}
var level = log_level();
if (level < 1 || level > 2) {
return 2;
}
return 0;
}
Selecting code
[BuildOption("NAME")] keeps a declaration or a block only when the build
defines NAME. It is shorthand for [Target(BuildOption(NAME))], and it
goes everywhere [Target(...)] goes: functions, externs, structs,
methods, module-scope variables, imports, re-exports and { } blocks in
a function (see Platform selection).
Inside Target, BuildOption(NAME) combines with OS(...), Arch(...),
!, &&, || and parentheses. Used alone, it is true when NAME is
defined, whatever its value, and false when it is not, with no warning:
[BuildOption("USE_LIBCRYPTO")]
[Link("crypto")]
extern int SHA256_Init(__ptr<void> context);
[Target(!BuildOption(USE_LIBCRYPTO))]
_sha256_compress(...) { ... } # the portable fallback
[Target(BuildOption(TRACE) && OS(Linux))]
import "linux_trace";
Several attributes on one item combine with “and”, as [Target] does.
Options the runtime reads
| Option | Effect |
|---|---|
USE_LIBCRYPTO | digest’s SHA-256 and SHA-1 use libcrypto (CNG on Windows); see Cryptographic hashes |
USE_ZLIB | compress uses zlib; see Compression |
TEST | defined by pep-build --test; see Unit tests |
Comparing values
BuildOption(NAME) may be compared with a literal using ==, !=, <,
<=, > and >=, on either side. The literal’s type says how the value
is read:
| Literal | The value is read as | Example |
|---|---|---|
| integer | a signed 64-bit integer, decimal or 0x hex | BuildOption(LOG_LEVEL) >= 2 |
| float | a double | BuildOption(SCALE) > 1.5 |
true / false | true/false, 1/0, on/off or yes/no, in any case (== and != only) | BuildOption(TRACE) == true |
| string | text, compared byte for byte | BuildOption(BACKEND) == "zlib" |
[Target(BuildOption(LOG_LEVEL) >= 2 && OS(Linux))]
_trace(message: String) { ... }
[Target(BuildOption(BACKEND) == "zlib")]
import "zlib_backend";
A value the literal’s type cannot read is an error at the comparison:
main.pep:1:28: error: build option 'LEVEL' is 'high', which is not an integer
1 | [Target(BuildOption(LEVEL) >= 2)]
| ^~~~
Comparing two build options, or a build option with anything but a literal, is an error too.
A comparison with an option the build does not define is false, whatever the operator, and the compiler warns at it, since a missing option there is usually a mistake:
main.pep:3:13: warning: build option 'LOG_LEVEL' is not defined; the comparison is false
3 | [Target(BuildOption(LOG_LEVEL) >= 2)] {
| ^~~~~~~~~~~~~~~~~~~~~~
note: define it with '-D LOG_LEVEL=<value>'
Reading values in static blocks
A static block reads build options while compiling:
reflect.has_build_option("NAME")is whether the build definesNAME.reflect.build_option("NAME")is its value, as aString. It is an error when the build does not defineNAME.reflect.build_option("NAME", "default")is its value, ordefaultwhen the build does not define it.
The build cache and the editor
The options and their values are part of the build cache’s key, so building with other options never reuses objects built with different ones.
The language server analyzes with the options in the VS Code setting
pepper.buildOptions, a list written as -D takes them
(["USE_ZLIB", "LOG_LEVEL=2"]). It has none by default, so code that needs
an option shows dimmed, and comparisons with options warn until the
setting names them.
Not yet
- A
pep.tomltable declaring the options a package takes, with their types and defaults. - Arithmetic on options, and comparing two options.