# Build flags

> Pass build flags to Elide with `--flag` or `-f` and read them in an elide.pkl manifest to parameterize a build, such as a release mode or version number.

A build flag is a name, with an optional value, that `elide.pkl` reads when it is evaluated.

## Pass flags

```bash
elide build run --args="Hello World 🌎" --flag=RELEASE --flag=VERSION=1.2.3
elide build run --args="Hello World 🌎" -fRELEASE -fVERSION=1.2.3
```

`--flag NAME` and `-f NAME` also work, in any position. Arguments after `--` are never flags. `--args` belongs to the
`run` task; see [Build Tasks](https://elide.help/docs/toolchain/build-tasks).

Every project-aware command takes flags:

```bash
elide run -fRELEASE -fVERSION=1.2.3 -- "Hello World 🌎"
elide install --flag=RELEASE
elide format -fNO_KOTLIN
elide build -fRELEASE
elide test -fFAST
elide project export -fRELEASE    # shows the evaluated result
```

- A flag without a value (`-fRELEASE`) is **bare**: `true` to `bool(...)`, an error to `string(...)`.
- The value is everything after the first `=`, verbatim: `-fEXPR=a=b` gives `a=b`.
- A repeated flag keeps every value; scalar accessors read the last ([Repeated flags](#repeated-flags)).
- Flags need no declaration; an unknown name is absent.
- Names cannot be empty or contain `,` or `=`.

## Read flags in elide.pkl

```pkl
amends "elide:project.pkl"

local isRelease = build.flags.bool("RELEASE")

name = if (isRelease) "demo-release" else "demo-debug"
version = build.flags.stringOr("VERSION", "0.0.0")
```

| Accessor | Returns |
| --- | --- |
| `has(name)` | Whether the flag was passed, with or without a value. |
| `count(name)` | Times passed; `0` when absent. |
| `string(name)` | The value; `null` when absent; throws when bare. |
| `stringOr(name, orElse)` | The value, or `orElse` when absent. |
| `bool(name)` | `false` absent, `true` bare; `true`/`false`, `yes`/`no`, `on`/`off`, `1`/`0` (any case); else throws. |
| `int(name)` | The value as `Int`; `null` when absent; throws if not an integer. |
| `strings(name)` | Every value in order; empty when absent; throws on a bare occurrence. |
| `stringsOr(name, orElse)` | Every value, or `orElse` when absent. |
| `ints(name)` | Every value as `Int`; empty when absent; throws if any is not an integer. |
| `bools(name)` | Every value as `Boolean`; empty when absent; bare reads `true`. |
| `raw` | `Mapping<String, String>` of effective values; bare maps to `""`. |
| `all` | `Mapping<String, List<String>>` of every value. |

Use `strings(...)` to accept one or more values. Typed accessors fail on bad input:

```text
–– Pkl Error ––
build flag `RELEASE` is not a boolean: `perhaps`
```

### Environment variables

`read("env:NAME")` sees only `PATH`, `HOME`, `USER`, `LANG`, `LC_*`, `TZ`, `CI`, `TERM`, `SHELL`, `OS`, `TMPDIR` and
`ELIDE_*`; any other variable reads as absent. Use a build flag, or allow more:

```shell
elide build --allow-env=GITHUB_SHA,JAVA_HOME
elide build --allow-env            # every variable
```

### Repeated flags

```bash
elide build -fARG=one -fARG=two -fARG='three, and a half'
```

```pkl
local args = build.flags.strings("ARG")  // List("one", "two", "three, and a half")
local first = build.flags.all["ARG"].first
local latest = build.flags.string("ARG") // "three, and a half" — scalars read the last value
```

Values keep commas, spaces, `=` and Unicode. List accessors check every element:

```text
–– Pkl Error ––
build flag `N` is not an integer: `x1`
```

### Flags inside nested blocks

Inside a nested object body, bare `build` does not resolve. Bind a module-level `local`, or use
`module.build.flags`:

```pkl
local wantsExtra = build.flags.bool("EXTRA")

dependencies {
  maven {
    packages {
      "com.google.guava:guava:33.0.0-jre"
      when (wantsExtra) {
        "com.example:extra:1.0.0"
      }
      when (module.build.flags.bool("MORE")) {
        "com.example:more:1.0.0"
      }
    }
  }
}
```
