A Brine project is a folder with a brine.toml at its root and its main file at src/main.br. Commands run inside the folder (or any folder below it) work on the project without arguments. A single .br file works too: every command takes a file instead, and its build goes to a cache folder in your temp directory.
brine new and brine init
Section titled “brine new and brine init”brine new hello Created project `hello`
cd hello brine runhello/├── .git/├── .gitignore├── AGENTS.md├── README.md├── brine.toml└── src/ └── main.br| File | Contents |
|---|---|
brine.toml |
the project’s name and version |
src/main.br |
a starter program with a par comprehension and a test |
AGENTS.md |
instructions for coding agents: the commands to run and the rules that most often trip people up |
README.md |
a title and the two commands to build and test |
.gitignore |
/target |
brine new <name> makes the folder; brine init uses the current one, names the project after it, and keeps any of these files that already exist. Both run git init unless the folder is already inside a Git repository (and skip it quietly if git isn’t installed).
The starter program:
# Welcome to brine! Run this program with `brine run`.
fn greet(name: str) -> str: return f"Hello, {name}!"
fn main(): print(greet("brine"))
# parallel by default: this runs across all your cores let squares = par [i * i for i in 0..10] print(squares)
test "greeting": assert greet("you") == "Hello, you!"cd hellobrine run Built hello v0.1.0 [dev] in 2.05s Running target/dev/hello.exeHello, brine![0, 1, 4, 9, 16, 25, 36, 49, 64, 81]brine.toml
Section titled “brine.toml”The file brine new writes:
[package]name = "hello"version = "0.1.0"These are all the keys Brine reads:
| Section | Key | Default | Meaning |
|---|---|---|---|
[package] |
name |
(required) | the project’s name: the executable is target/<profile>/<name>, and it appears in status lines |
[package] |
version |
"0.1.0" |
shown in status lines (Built hello v0.1.0) |
[build] |
main |
"src/main.br" |
the main file, relative to brine.toml |
[check] |
typed |
false |
true requires a written type on every let, var and for variable |
A project that uses all of them:
[package]name = "tool"version = "1.2.0"
[build]main = "app/cli.br"
[check]typed = truepackage
Section titled “package”name is required:
error: brine.toml: missing `name` in [package]version is free text. Brine doesn’t compare versions yet (there is no package manager; brine add is on the roadmap).
main names the file that holds fn main. Modules load relative to the main file’s folder: with main = "app/cli.br", import util loads app/util.br.
typed = true makes every variable need a written type, for teams and agents that want types spelled out. It applies to check, build, run and test; the --typed flag does the same for one command, or for a single file. A missing type is E0337:
error[E0337]: `s` has no written type --> sum.br:2:9 |2 | var s = 0 | ^ add `: int` | = note: this project asks for a type on every variable (`typed = true` in brine.toml, or `--typed`) = run `brine explain E0337` for more about this errorThe value must be true or false:
error: brine.toml: `typed` in [check] must be true or false, not `yes`Syntax
Section titled “Syntax”Brine reads a small subset of TOML: [section] headers and key = "string" or key = value lines, with # comments. Keys it doesn’t know are ignored, so a newer brine.toml still works with an older Brine.
Finding the project
Section titled “Finding the project”Without a file argument, Brine looks for brine.toml in the current directory, then in each parent. So brine run works from src/ or any subfolder. A file or directory argument overrides this:
| Command | Builds |
|---|---|
brine run |
the project around the current directory |
brine run tools/gen.br |
that one file, as its own program |
brine run ../other |
the project in ../other (it has a brine.toml) |
brine run scripts |
scripts/main.br, if scripts/ has a main.br and no brine.toml |
The target folder
Section titled “The target folder”Project builds go to target/, one folder per profile:
target/├── cache/ objects and the compiled runtime│ ├── obj-0548db582edbf74a/│ ├── rt-2fdf516b1c19bd8c/│ └── ...├── debug/│ ├── hello.brine-hash│ └── hello.exe├── dev/│ ├── hello.brine-hash│ └── hello.exe└── release/ ├── hello.brine-hash └── hello.exe| Folder | Made by |
|---|---|
target/dev/ |
brine run, brine build (the default profile) |
target/release/ |
--release |
target/debug/ |
--debug, brine debug |
target/dev-c/, target/release-c/, target/debug-c/ |
a build with BRINE_BACKEND=c |
target/dev-native/, target/release-native/, target/debug-native/ |
a build with BRINE_BACKEND=native |
target/cache/ |
intermediate files for every profile |
Test builds sit next to the program as <name>-tests (target/dev/hello-tests.exe). On Linux and macOS executables have no .exe.
A build with BRINE_BACKEND set always gets its own folder, even when it picks the backend the profile would use anyway, so it can never replace an executable a default build is running.
The cache
Section titled “The cache”target/cache/ holds:
rt-<hash>/: the runtime (brine_rt.c,brine_rt.h) and its object file, compiled once per runtime version, C compiler and flags. This is why the first build in a new project takes about two seconds and later ones a fraction of one.obj-<hash>/: the object files of one output, each named by a hash of what it was made from. An edit recompiles only the objects whose input changed. Old objects are pruned once there are many more than the build needs.
Next to each executable, <name>.brine-hash records a hash of the objects and runtime it was linked from. When a build produces the same hash and the executable exists, Brine skips the link and prints Fresh instead of Built.
Deleting target/ is always safe: the next build makes it again.
Single files
Section titled “Single files”Every command takes a .br file instead of a project. Imports load next to that file.
brine run panic.brbrine check sum.brbrine test t.brbrine build tool.br # writes ./tool.exe (./tool on Linux and macOS)brine build writes a single file’s executable in the current directory, the way a C compiler does. brine run, brine test and brine debug put theirs in a cache folder under your temp directory:
<temp>/brine-cache/├── 465ef71a47ea2199/ one folder per source file (a hash of its absolute path)│ ├── dev/argv.exe│ └── debug/argv.exe├── obj-.../ object files, shared by all single-file builds└── rt-.../ the compiled runtime<temp> is %TEMP% on Windows (GetTempPath2W) and $TMPDIR (else /tmp) on Linux and macOS. Nothing removes old folders from brine-cache; delete it whenever you like.
| Project | Single file | |
|---|---|---|
| main file | src/main.br or [build] main |
the file you pass |
| executable name | [package] name |
the file name without .br |
brine build output |
target/<profile>/<name> |
./<name> |
brine run output |
target/<profile>/<name> |
<temp>/brine-cache/<hash>/<profile>/<name> |
| cache | target/cache/ |
<temp>/brine-cache/ |
[check] typed |
from brine.toml |
only with --typed |
brine fmt without arguments |
every .br file in the project |
every .br file under the current directory |