# Chapter 24: Building and distributing

This chapter covers getting a native Loup program from a project directory onto someone else's machine: the project manifest, the delivery targets `loup build` can produce, the native build options and backends, which platforms can build what, testing, and what to ship. Everything here is the 1.25.1 command line; where the specification describes more, the text says so. The normative references are the Language Reference §3-2.1 to §3-2.8 and §1-13.3 (the manifest).

## Projects and the manifest

`loup build` always works on a project: a directory with a manifest named `<name>.lppkg` (or `package.lppkg`). `loup-bootstrap init` creates one:

```sh
loup-bootstrap init inventory
cd inventory
```

This writes `inventory.lppkg`, `src/main.loup` (a `host.dual` "hello" program) and an empty `tests/` directory. The manifest is TOML. The part that matters for a native program:

```toml
name = "inventory"
version = "0.1.0"
loup = "1.20.0"
authors = []

[build]
entry = "src/main.lphc"
delivery = "native"
profile = "release"

[dependencies]

[modes]
```

*Listing 24-1: The manifest of a native project*

| Key | Meaning |
|---|---|
| `name`, `version` | the package identity (required) |
| `loup` | the minimum toolchain version |
| `description`, `authors`, `license` | metadata |
| `[build] entry` | the file that holds `main()` |
| `[build] delivery` | what `loup build` produces by default (below); `target` is accepted as the older spelling |
| `[build] profile` | `release` or `debug` |
| `[build] mode` | the mode for files that do not declare one (every file should, ruling MODE-2) |
| `[build] arch` | the default `--arch` |
| `[dependencies]`, `[dev_dependencies]` | other packages, by local path (chapter 13) |
| `[modes]` | per-file mode overrides |
| `[options]` | the package's own build options, set with `--option NAME=VALUE` |
| `[exports] modules` | the modules other packages may import |

The scaffold writes a few more tables (`[test]`, `[stdlib]`, `[linker]`, `[freestanding]`, `[const_eval]`); leave them as they are. `loup.toml` and `loup.pkg` are not read.

To switch the scaffold to `host.native`, rename `src/main.loup` to `src/main.lphc`, change its first line to `@mode(host.native)`, and set `entry` and `delivery` as in Listing 24-1 (chapter 1 walks through it).

## Delivery targets

`delivery` in the manifest, or `--target` on the command line (also spelled `--delivery`), chooses what a build produces:

| Target | Produces | Runs with |
|---|---|---|
| `native` | a platform executable | itself |
| `cli` (the scaffold's default) | `build/<name>.wasm` | `loup-launcher --run-bare build/<name>.wasm` |
| `desktop` | an application directory: `bin/main` (the launcher), `lib/main.wasm`, `loup-package.json` | `./<dir>/bin/main` |
| `service` | a WebAssembly module for a long-running service | `loup-launcher` |
| `browser` | a bare `build/<name>.wasm`; no HTML page or JavaScript loader yet | — |
| `freestanding` | a bare-metal image (*The Loup Freestanding Book*) | a board or QEMU |

`native` needs a native toolchain (next section). The WebAssembly targets need nothing but the Loup archive, on every platform, and they accept `host.dual` programs. That makes `desktop` the practical way to hand a `host.dual` program to someone on Windows or Linux arm64 today:

```sh
loup-bootstrap build --target desktop -o inventory-app
./inventory-app/bin/main
```

```text
hello, inventory
```

The directory is self-contained: copy it anywhere and run `bin/main`. `loup-package.json` records the toolchain version, the build profile and a SHA-256 hash of each file. Despite the name, `desktop` does not mean a graphical program; the planned GUI library is chapter 22.

The WebAssembly targets are designed for `host.dual` programs. Stage0 1.25.1 will also build a `host.native` file to WebAssembly without complaint, even one that declares a `unique class` or an `extern native` function, although the specification makes `host.native` a native-only mode. Keep code that you deliver both ways in `host.dual`, where the compiler does refuse the native-only features (chapter 2).

## Native builds

```sh
loup build --target native -o inventory
./inventory
```

`loup build --help` lists every option. The ones for native builds:

| Option | Meaning |
|---|---|
| `-o PATH` | where to write the executable |
| `--release`, `--debug` | the release or debug profile (`--profile release\|debug`) |
| `--backend llvm\|cranelift\|llbe` | the code generator (below); `llvm` is the default |
| `--lto thin\|full` | link-time optimization (LLVM only) |
| `--arch ARCH`, `--os OS` | target architecture and OS (see "Platforms") |
| `--foundation libc\|libos` | the runtime the program links against; `libc` is the default, `libos` is the Helios kernel's runtime |
| `--error-traces`, `--no-error-traces` | record where `?` propagated an error (on by default with `--debug`) |
| `--option NAME=VALUE` | set one of the package's `[options]` |
| `--no-cache`, `--clean` | rebuild everything / delete the `.loup-cache` directory |
| `-v`, `-q` | more or less progress output |

The executable contains the Loup runtime (reference counting, strings, collections, exceptions, threads). It links dynamically against the system C library and nothing else, so it runs on any machine of the same OS and architecture with a compatible C library.

### Backends

- **`llvm`** (the default) writes LLVM IR and compiles and links it with `clang` at `-O2` with ThinLTO. It is the only backend for macOS, and the one to ship with.
- **`cranelift`** generates code in-process and is faster to compile. It writes ELF objects only, so it works on Linux and is refused elsewhere.
- **`llbe`** is Loup's own clang-free backend. It emits machine code and ELF files itself, so it needs no C toolchain at all. With the Stage0 compiler it builds freestanding images only (`E-BUILD-BACKEND: `--backend llbe` is only supported for freestanding targets`); the self-hosted toolchain on Linux x86-64 also uses it for hosted builds.

### Mistake: Cranelift on macOS

```sh
loup build --target native --backend cranelift -o inventory
```

```text
error[E-BUILD-NATIVE]: --backend cranelift writes ELF objects, which a macOS host cannot link; use the default LLVM backend (`--backend llvm`)
```

Drop the option. The other native-build refusal you will meet is a construct the native code generator does not lower yet. It names every offending declaration, for example:

```text
error[E-BUILD-NATIVE]: unsupported in managed native (libc foundation): async function `main` (native builds have no task scheduler: `async`/`await` run on the interpreters only); …
```

Chapter 2 lists these constructs (`async`, template fences, `for concurrent`, enums with tuple or `Result` payloads, and a few others). The build stops instead of producing a binary that behaves differently from `loup run`.

### Platforms

| Build machine | Native executables | Notes |
|---|---|---|
| Linux x86-64 | supported | needs `clang`; all three backends |
| macOS 11+ (Apple silicon, Intel) | preview | LLVM backend only; needs the Xcode Command Line Tools; `--arch arm64` or `--arch x86_64` selects either Apple architecture |
| Windows 10+ x86-64 | not available | build WebAssembly targets instead |
| Linux arm64 | not available | the Stage0 compiler still emits x86-64 code |

There is no cross-OS native build: the executable is always for the build machine's OS. `--os` is accepted but only names the output, and on macOS other architectures are refused (`--arch riscv64: a macOS host builds hosted native programs for arm64 and x86_64 only`). To ship Linux and macOS binaries, build on each. A macOS build is signed ad hoc by the linker, which is enough to run it locally; distributing it to other Macs requires your own `codesign` and notarization.

### Libraries

> ⚠ **Planned.** `--lib shared` and `--lib static` are accepted, but in 1.25.1 the file they write (`lib<name>.so`, `.dylib`, `.a`, …) carries the module's WebAssembly bytes, not native code, so it cannot be linked into a C or Rust program (§3-2.1). Calling Loup from other languages, the stable ABI of §3-2.6 (`stable_abi`, `abi_version`) and the `@cexport` annotation are not implemented yet; see chapter 19 for the header tools.

### Linking C libraries

An `extern native "name"` declaration (chapter 19) names the library its functions come from.

> ⚠ **Planned.** The specification also lets the manifest add libraries and search paths for the linker (`link`, `link_path`, `framework`, §3-2.8). In 1.25.1 these directives cannot be written: the specification's line form (`link sqlite3`) is not valid TOML, and the TOML form is rejected (`invalid library name '= ["sqlite3"]' in a 'link' directive in the native delivery section`).

## Testing

`loup test` finds the test files in a project (by default `tests/**/*_test.loup`, set by the manifest's `[test] pattern`) and runs every function named `test_…` that takes no arguments:

```loup
@mode(host.dual)

from debug.assert import (expectEq);

func checksum(data: string): int {
    var h = 0;
    for (i in 0..data.length()) {
        h = h + data.length();
    }
    return h;
}

func test_empty(): void {
    expectEq(checksum(""), 0, "empty input");
}

func test_three(): void {
    expectEq(checksum("abc"), 9, "three characters");
}
#~
#~ func main(): void {
#~     test_empty();
#~     test_three();
#~ }
```

*Listing 24-2: `tests/checksum_test.loup`*

```sh
loup test
```

```text
Found 2 tests in 1 files (0 compile failures)
  ✓ tests/checksum_test.loup::test_empty (18ms)
  ✓ tests/checksum_test.loup::test_three (18ms)

Tests: 2 total, 2 passed, 0 failed, 0 skipped (19ms)
```

By default tests run on the interpreter. `loup test --native` builds each test through the native pipeline and runs the binary, which is how to catch the differences between the two (overflow, exception output, finalization timing). `--filter`, `--jobs`, `--timeout` and `--format pretty|json|junit` do what their names say. On Linux x86-64, where `loup` is the self-hosted toolchain, the runner is `loup-test` and is invoked as `loup test --strict`.

## What is not in the command line

Earlier editions of this chapter described options that 1.25.1 does not have. Each of these is `invalid argument '…': unknown flag` or `unknown command`: `--strip`, `--opt-for-size`, `--crate-type`, `--emit-header`, `--emit-symbols`, `--dynamic-runtime`, `--reproducible`, `--lockfile`, `--sanitize`, `loup bundle`, `loup run --inspect`, and the `[[bin]]`/`[[lib]]` manifest tables. One project builds one entry point. There is no package registry yet, so dependencies are local paths (chapter 13).

## Summary

- A project is a directory with a TOML manifest, `<name>.lppkg`; `[build] entry` and `delivery` decide what `loup build` makes.
- `--target native` builds a platform executable that contains the Loup runtime; `cli`, `service` and `desktop` build WebAssembly that runs under `loup-launcher` on every platform, and `desktop` packages it as a self-contained directory.
- `llvm` is the default and the shipping backend; `cranelift` is Linux-only; `llbe` is clang-free and, with Stage0, freestanding-only.
- Native executables can be built on Linux x86-64 and (as a preview) on macOS, for the build machine's OS only. Windows and Linux arm64 cannot build them yet.
- `--lib` output, manifest link directives, a stable C ABI and the size/strip/reproducibility flags are planned or absent.
- `loup test` runs `test_…` functions on the interpreter; `loup test --native` runs them as native binaries.
- Chapter 25 brings the safety features of the previous chapters together.
