An Odin build system as a library. Write your build scripts in Odin with type-safe compiler flag configuration.
Inspired by nob.h but designed Odin-first — the primary abstraction is a Build_Config struct that maps 1:1 to Odin compiler flags, not a generic shell command runner.
Copy or symlink the bld/ directory into your project:
my_project/
src/
main.odin
bld/ ← this package
build/
build.odin ← your build script
// build/build.odin
package main
import bld "../bld"
main :: proc() {
bld.mkdir_if_not_exists("out")
if !bld.build({
package_path = "src",
out = "out/myapp",
opt = .Speed,
}) {
bld.log_error("Build failed!")
}
}odin build build -out:build_it
./build_itEvery Odin compiler flag has a typed field. Zero values mean "don't emit that flag" (use compiler defaults).
bld.build({
package_path = "src",
out = "out/myapp",
opt = .Speed,
debug = true,
vet = {.Shadowing, .Unused_Imports, .Style},
warnings_as_errors = true,
defines = {{name = "VERSION", value = "1.0.0"}},
collections = {{name = "shared", path = "libs/shared"}},
})| Field | Type | Compiler Flag |
|---|---|---|
package_path |
string |
Positional arg (required) |
out |
string |
-out: |
opt |
Opt_Level |
-o:none|minimal|size|speed|aggressive |
build_mode |
Build_Mode |
-build-mode:exe|dll|lib|obj|asm|llvm-ir |
debug |
bool |
-debug |
file_mode |
bool |
-file |
target |
string |
-target: |
microarch |
string |
-microarch: |
collections |
[]Collection |
-collection:name=path |
defines |
[]Define |
-define:name=value |
vet |
Vet_Flags |
-vet-shadowing, -vet-unused, etc. |
sanitize |
Sanitize_Flags |
-sanitize:address|memory|thread |
thread_count |
int |
-thread-count: |
extra_linker_flags |
string |
-extra-linker-flags: |
extra_assembler_flags |
string |
-extra-assembler-flags: |
show_timings |
bool |
-show-timings |
show_more_timings |
bool |
-show-more-timings |
warnings_as_errors |
bool |
-warnings-as-errors |
terse_errors |
bool |
-terse-errors |
disable_assert |
bool |
-disable-assert |
default_to_nil_allocator |
bool |
-default-to-nil-allocator |
default_to_panic_allocator |
bool |
-default-to-panic-allocator |
keep_temp_files |
bool |
-keep-temp-files |
strict_style |
bool |
-strict-style |
custom_attributes |
[]string |
-custom-attribute: |
vet_packages |
[]string |
-vet-packages: |
ignore_unused_defineables |
bool |
-ignore-unused-defineables |
extra_flags |
[]string |
Raw flags passed verbatim |
bld.build(config) // odin build
bld.run(config, "arg1", "arg2") // odin run ... -- arg1 arg2
bld.test(config) // odin test
bld.check(config) // odin check (type-check only)All return bool — true on success.
config := bld.release_config("src", "out/myapp") // opt = .Speed
config := bld.debug_config("src", "out/myapp") // debug = true
// Then customize:
config.vet = {.All}
config.warnings_as_errors = true
bld.build(config)A build script with type-check gate, tests, debug build, and release build:
package main
import bld "../bld"
main :: proc() {
bld.go_rebuild_urself("build") // Auto-rebuild when this file changes
start := bld.timer_start()
bld.mkdir_if_not_exists("out")
// Fast type-check gate.
if !bld.check({package_path = "src"}) {
bld.log_error("Type check failed")
return
}
// Run tests.
if !bld.test({package_path = "src", vet = {.All}}) {
bld.log_error("Tests failed")
return
}
// Debug build.
if !bld.build({
package_path = "src",
out = "out/myapp-debug",
debug = true,
vet = {.Shadowing, .Unused_Imports},
}) {
bld.log_error("Debug build failed")
return
}
// Release build.
if !bld.build({
package_path = "src",
out = "out/myapp",
opt = .Speed,
disable_assert = true,
warnings_as_errors = true,
}) {
bld.log_error("Release build failed")
return
}
bld.log_info("All done in %.2fs", bld.timer_elapsed(start))
}bld.mkdir_if_not_exists("dist")
bld.mkdir_all("dist/linux/amd64")
bld.copy_file("out/myapp", "dist/myapp")
bld.copy_directory_recursively("assets", "dist/assets")
bld.write_entire_file_string("out/version.txt", "1.0.0")
data, ok := bld.read_entire_file("config.json", context.temp_allocator)
bld.delete_file("out/temp.bin")
bld.rename_file("out/old", "out/new")
bld.file_exists("out/myapp")Check if output is older than inputs (like make dependency tracking):
rebuild, ok := bld.needs_rebuild1("out/myapp", "src/main.odin")
if !ok {
bld.log_error("Error checking")
} else if rebuild {
bld.log_info("Rebuild needed")
} else {
bld.log_info("Up to date")
}
// Multiple inputs:
rebuild, ok = bld.needs_rebuild("out/myapp", {"src/main.odin", "src/game.odin", "src/render.odin"})Put this at the top of your build script's main(). If the build script source is newer than the running binary, it rebuilds and re-executes itself:
main :: proc() {
bld.go_rebuild_urself("build")
// ... rest of build logic ...
}For anything outside Odin compilation:
cmd := bld.cmd_create(context.temp_allocator)
bld.cmd_append(&cmd, "strip", "out/myapp")
bld.cmd_run(&cmd)procs := bld.procs_create()
cmd1 := bld.cmd_create(context.temp_allocator)
bld.cmd_append(&cmd1, "echo", "task 1")
bld.cmd_run(&cmd1, {async = &procs})
cmd2 := bld.cmd_create(context.temp_allocator)
bld.cmd_append(&cmd2, "echo", "task 2")
bld.cmd_run(&cmd2, {async = &procs})
bld.procs_flush(&procs) // Wait for all
bld.procs_destroy(&procs) // Clean upchain: bld.Chain
if !bld.chain_begin(&chain) do return
cmd1 := bld.cmd_create(context.temp_allocator)
bld.cmd_append(&cmd1, "cat", "input.txt")
bld.chain_cmd(&chain, &cmd1)
cmd2 := bld.cmd_create(context.temp_allocator)
bld.cmd_append(&cmd2, "grep", "pattern")
bld.chain_cmd(&chain, &cmd2)
bld.chain_end(&chain)
bld.chain_destroy(&chain)bld.walk_dir("src", proc(entry: bld.Walk_Entry, _: rawptr) -> bld.Walk_Action {
if entry.type == .Regular {
bld.log_info(" %s", entry.path)
}
return .Continue
})start := bld.timer_start()
// ... work ...
bld.log_info("Took %.2fs", bld.timer_elapsed(start))bld.log_info("Building %s", name)
bld.log_warn("Deprecated flag used")
bld.log_error("Build failed!")
bld.minimal_log_level = .Warning // Suppress info messages
bld.echo_actions = false // Suppress CMD/PIPE echoSee example/ for a complete working example:
example/src/main.odin— a small Odin app withaddandgreetprocsexample/src/main_test.odin— tests for the appexample/build.odin— comprehensive build script that exercises every bld feature (97 tests)
Run it:
odin build example/build.odin -file -out:target/test_build
./target/test_build| File | Purpose |
|---|---|
odin.odin |
Core — Build_Config struct, build/run/test/check |
cmd.odin |
Command builder and execution |
procs.odin |
Async process management, nprocs() |
fs.odin |
File system operations |
path.odin |
Path utilities |
walk.odin |
Directory tree walker |
chain.odin |
Command pipes |
rebuild.odin |
Go Rebuild Urself, needs_rebuild |
time.odin |
Timing utilities |
log.odin |
Logging |
Public domain / Unlicense — do whatever you want.