# Build Options

### Build Options

A build option is a value passed from the command line into `build.zig`.

Build options let one project support several build configurations without editing source code every time. You can use them for feature flags, version strings, debug behavior, optional integrations, platform choices, and other settings.

A build option is usually passed with `-D`:

```bash
zig build -Denable-logging=true
zig build -Dapp-version=0.1.0
zig build -Dmax-clients=128
```

The `-D` means “define this build option.”

#### Standard Options

You have already seen two standard options:

```zig
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
```

These enable common command-line options.

For example:

```bash
zig build -Dtarget=x86_64-linux
zig build -Doptimize=ReleaseFast
```

You did not manually define `target` or `optimize`. Zig provides helper functions for these because almost every project needs them.

The target option controls where the program will run.

The optimize option controls how the program is compiled.

Common optimization modes are:

```text
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
```

A debug build is best while developing. It keeps safety checks and is easier to debug.

A release build is for final output. Depending on the mode, it may prefer speed, size, or safety.

#### Custom Build Options

You can define your own option with `b.option`.

For example:

```zig
const enable_logging = b.option(
    bool,
    "enable-logging",
    "Enable logging output",
) orelse false;
```

This creates a boolean build option named:

```text
enable-logging
```

The type is:

```zig
bool
```

The default value is:

```zig
false
```

Now the user can run:

```bash
zig build -Denable-logging=true
```

Inside `build.zig`, `enable_logging` will be `true`.

Without that command-line option, it will be `false`.

#### The Shape of `b.option`

The basic shape is:

```zig
const value = b.option(T, "name", "help text") orelse default_value;
```

The first argument is the type.

The second argument is the command-line name.

The third argument is the help text.

The expression returns an optional value. That is why we use `orelse`.

```zig
orelse false
```

means: if the user did not provide the option, use `false`.

For a string option:

```zig
const version = b.option(
    []const u8,
    "version",
    "Application version string",
) orelse "dev";
```

Now this works:

```bash
zig build -Dversion=1.2.3
```

For an integer option:

```zig
const max_clients = b.option(
    u32,
    "max-clients",
    "Maximum number of clients",
) orelse 64;
```

Now this works:

```bash
zig build -Dmax-clients=128
```

#### Passing Options to Source Code

Defining an option in `build.zig` does not automatically make it visible in `src/main.zig`.

To pass build options into your program, create an options module.

```zig
const options = b.addOptions();

options.addOption(bool, "enable_logging", enable_logging);
options.addOption([]const u8, "version", version);
options.addOption(u32, "max_clients", max_clients);
```

Then attach it to the executable:

```zig
exe.root_module.addOptions("build_options", options);
```

Now your Zig source code can import it:

```zig
const build_options = @import("build_options");
```

And use the values:

```zig
if (build_options.enable_logging) {
    std.debug.print("logging enabled\n", .{});
}

std.debug.print("version: {s}\n", .{build_options.version});
std.debug.print("max clients: {}\n", .{build_options.max_clients});
```

This is a common Zig pattern.

The build file decides the configuration. The source code imports that configuration as a normal module.

#### Full Example

Here is a complete `build.zig` using custom build options:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const enable_logging = b.option(
        bool,
        "enable-logging",
        "Enable logging output",
    ) orelse false;

    const version = b.option(
        []const u8,
        "version",
        "Application version string",
    ) orelse "dev";

    const max_clients = b.option(
        u32,
        "max-clients",
        "Maximum number of clients",
    ) orelse 64;

    const options = b.addOptions();
    options.addOption(bool, "enable_logging", enable_logging);
    options.addOption([]const u8, "version", version);
    options.addOption(u32, "max_clients", max_clients);

    const exe = b.addExecutable(.{
        .name = "server",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    exe.root_module.addOptions("build_options", options);

    b.installArtifact(exe);
}
```

And here is `src/main.zig`:

```zig
const std = @import("std");
const build_options = @import("build_options");

pub fn main() void {
    std.debug.print("version: {s}\n", .{build_options.version});
    std.debug.print("max clients: {}\n", .{build_options.max_clients});

    if (build_options.enable_logging) {
        std.debug.print("logging enabled\n", .{});
    } else {
        std.debug.print("logging disabled\n", .{});
    }
}
```

Run it normally:

```bash
zig build run
```

Then run it with options:

```bash
zig build run -Dversion=1.0.0 -Dmax-clients=128 -Denable-logging=true
```

The exact command depends on whether your `build.zig` has a `run` step. If it only installs the executable, use:

```bash
zig build -Dversion=1.0.0 -Dmax-clients=128 -Denable-logging=true
```

#### Build Options Are Compile-Time Values

Values from an options module are known at compile time.

That matters.

This means the compiler can remove unused branches when a feature is disabled.

For example:

```zig
if (build_options.enable_logging) {
    std.debug.print("debug log\n", .{});
}
```

If `enable_logging` is `false` at compile time, the compiler can remove that branch from the final program.

This is useful for feature flags.

You can build one binary with logging enabled and another binary with logging disabled, from the same source code.

#### Option Names

Command-line option names often use hyphens:

```text
enable-logging
max-clients
```

Zig field names use underscores:

```zig
enable_logging
max_clients
```

So this is normal:

```zig
const enable_logging = b.option(bool, "enable-logging", "Enable logging") orelse false;
options.addOption(bool, "enable_logging", enable_logging);
```

The command-line name is for users.

The source-code name is for Zig code.

#### Showing Options in Help

The help text appears when the user runs:

```bash
zig build --help
```

For example:

```zig
const version = b.option(
    []const u8,
    "version",
    "Application version string",
) orelse "dev";
```

This gives the user a discoverable option:

```text
-Dversion=[string]    Application version string
```

Good build options should have clear help text. A future reader should understand what the option does without reading the whole build file.

#### Options for Conditional Dependencies

Build options can control whether a dependency is used.

For example:

```zig
const use_tls = b.option(
    bool,
    "tls",
    "Enable TLS support",
) orelse false;
```

Then:

```zig
if (use_tls) {
    const tls_dep = b.dependency("tls_library", .{
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("tls_library", tls_dep.module("tls_library"));
}
```

Now TLS support is optional:

```bash
zig build -Dtls=true
```

This pattern is useful when a dependency is large, platform-specific, or only needed for some builds.

#### Options for Conditional Compilation

Because options are compile-time values, source code can use them to include or exclude code.

```zig
const build_options = @import("build_options");

pub fn connect() void {
    if (build_options.tls) {
        connectTls();
    } else {
        connectPlain();
    }
}
```

This gives you a simple configuration system without preprocessor macros.

In C, you might write:

```c
#ifdef ENABLE_TLS
```

In Zig, you usually pass a typed build option and use a normal `if`.

That is easier to read because it stays inside the language.

#### Avoid Too Many Build Options

Build options are useful, but too many options make a project harder to test.

Every boolean option doubles the number of possible configurations.

With one boolean option, there are two configurations.

With two boolean options, there are four.

With five boolean options, there are thirty-two.

So use build options for real build-time differences, not for ordinary runtime settings.

Good build options:

```text
enable TLS support
embed version string
choose release behavior
enable expensive debug checks
select optional backend
```

Poor build options:

```text
username
port number for normal local use
theme color
every small runtime setting
```

Those usually belong in a configuration file, command-line argument, or environment variable.

#### Build-Time vs Runtime Configuration

A build-time option changes the compiled program.

A runtime option changes how the program behaves when it runs.

Build-time example:

```bash
zig build -Dsqlite=true
```

This might include SQLite support in the binary.

Runtime example:

```bash
./server --database app.db
```

This chooses which database file to open when the program runs.

Use build-time options when you need to change the binary itself.

Use runtime options when you only need to change behavior for one run.

#### Common Mistakes

A common mistake is defining an option but never passing it to source code.

This works only inside `build.zig`:

```zig
const enable_logging = b.option(bool, "enable-logging", "Enable logging") orelse false;
```

To use it in `src/main.zig`, you still need:

```zig
const options = b.addOptions();
options.addOption(bool, "enable_logging", enable_logging);
exe.root_module.addOptions("build_options", options);
```

Another common mistake is using the wrong option type.

This expects a boolean:

```zig
b.option(bool, "debug-ui", "Enable debug UI")
```

So use:

```bash
zig build -Ddebug-ui=true
```

not:

```bash
zig build -Ddebug-ui=yes
```

Another common mistake is putting secrets into build options.

Do not pass API keys, passwords, or private tokens as build options. They may become embedded in the binary or visible in build logs.

#### The Important Idea

Build options let your `build.zig` file receive typed values from the command line.

The basic pattern is:

```zig
const value = b.option(T, "name", "help text") orelse default_value;
```

To make the value available to source code, create an options module:

```zig
const options = b.addOptions();
options.addOption(T, "field_name", value);
exe.root_module.addOptions("build_options", options);
```

Then import it:

```zig
const build_options = @import("build_options");
```

Use build options when the compiled program itself should change. Use runtime options when only one execution should change.

