Install
Get the hale toolchain on your path. There are two ways to get hale: download a prebuilt binary (quickest), or build from source (for contributors, or a…
Get the
haletoolchain on your path.
There are two ways to get hale: download a prebuilt binary
(quickest), or build from source (for contributors, or a
platform without a prebuilt). Either way, read What you need to
run programs — hale is a
compiler that shells out to a C toolchain, so it has a couple of
runtime requirements no matter how you install it.
Quickest: prebuilt binary
Section titled “Quickest: prebuilt binary”Grab the tarball for your platform from the releases page:
| Platform | Asset |
|---|---|
| Linux x86_64 (glibc) | hale-<version>-x86_64-unknown-linux-gnu.tar.gz |
| Linux ARM64 (glibc) | hale-<version>-aarch64-unknown-linux-gnu.tar.gz |
| macOS Apple Silicon | hale-<version>-aarch64-apple-darwin.tar.gz |
The installer picks the right one for you, unpacks it into
~/.hale/bin (HALE_INSTALL moves it, HALE_VERSION=v<x.y.z>
pins a release) and tells you the PATH line to add:
curl -fsSL https://hale-lang.org/install.sh | shOr by hand:
mkdir -p ~/.hale/bintar -xzf hale-<version>-<triple>.tar.gz -C ~/.hale/bin# The archive holds `hale` AND `libhale_ts_shim.a` (and, on macOS,# the dylibs `hale` loads) — keep them in the SAME directory: the# compiler looks for the shim next to its own binary, and without# it refuses to build `std::ts` programs.export PATH="$HOME/.hale/bin:$PATH"hale --helpThe binary is self-contained with respect to LLVM — LLVM 18 is statically linked in, so you do not need to install LLVM to run the compiler. There is no prebuilt for Intel Macs: build from source there (the installer says so).
What you need to run programs
Section titled “What you need to run programs”Regardless of how you installed hale, compiling a program
(hale run / hale build) recompiles and links the runtime on
your machine, so you need a C toolchain present:
clangon yourPATH, under that name — used to compile the runtime’s C sources and link the emitted native code. (On Debian/Ubuntu,clang-18alone installs onlyclang-18; add theclangpackage or a symlink.)lldis optional: on Linux a native build usesld.lldwhen it is onPATH, for faster links, and it is required forLOTUS_LTO=1and forwasm32(wasm-ld).- OpenSSL and zlib development files (headers and
libraries:
libssl-devandzlib1g-devon Debian/Ubuntu,openssl@3from Homebrew on macOS) — the runtime’s TLS and compression sources are compiled against them and every program links them.
Installing clang pulls in libLLVM as clang’s own dependency —
that’s expected and harmless; hale itself doesn’t need it.
Build from source
Section titled “Build from source”Requirements:
- Rust 1.95 or newer (the compiler is written in Rust).
- LLVM 18 development libraries, with
llvm-config-18on yourPATH(orLLVM_SYS_180_PREFIXpointing at the install). LLVM 17, 19, and 20 will not link — the backend is pinned to 18. - clang (+ lld for LTO / wasm), OpenSSL headers, and git.
Debian / Ubuntu (LLVM 18 is in stock apt on 24.04+):
sudo apt install llvm-18-dev libpolly-18-dev libzstd-dev \ clang clang-18 libclang-18-dev lld-18 zlib1g-dev \ libssl-dev pkg-config gitFedora
sudo dnf install llvm18-devel clang18 lld openssl-devel gitmacOS (Homebrew)
brew install llvm@18 openssl@3 gitexport LLVM_SYS_180_PREFIX="$(brew --prefix llvm@18)"Then:
git clone https://github.com/hale-lang/halecd halecargo build --releaseThe hale binary lands at target/release/hale (and
libhale_ts_shim.a beside it). Put the binary on your path, or
invoke it through Cargo as shown below.
Reproducible / release build
Section titled “Reproducible / release build”release/docker-compose.yml builds a self-contained Linux tarball
in a pinned ubuntu:24.04 + LLVM 18 container, so you don’t have
to match the toolchain locally:
docker compose -f release/docker-compose.yml run --rm build# -> dist/hale-x86_64-unknown-linux-gnu.tar.gzPlatform support
Section titled “Platform support”Three different questions hide behind “does Hale support X”, and they have different answers. Keeping them apart:
1. Where a prebuilt compiler exists — Linux x86_64, Linux ARM64, and macOS on Apple Silicon, per the table above.
2. Where the compiler can be built from source — the three above,
plus Intel macOS. Needs LLVM 18 dev libraries and clang; see
building from source.
3. What a build can emit — hale targets (also spelled
hale --list-targets) is the authority. Native binaries for the platform hale itself runs on — a
Linux hale builds Linux programs, a macOS hale builds macOS ones;
wasm32 objects for the browser from either. A Linux triple from
any other host — --target x86_64-unknown-linux-gnu or
aarch64-unknown-linux-gnu on a Mac, the other architecture on
Linux, or a musl triple (x86_64-unknown-linux-musl,
aarch64-unknown-linux-musl: a static binary that runs on any Linux)
from anywhere — is cross-compiled and linked here; see
Cross-compiling for Linux below. A macOS
triple from anywhere else gets as far as a relocatable object for that
platform (app.o) and stops with a note — there is no Apple SDK to
link against off a Mac. x86_64-pc-windows-msvc and
aarch64-pc-windows-msvc are named and refused with a precise error,
because Windows codegen does not exist yet
(GH #445).
Cross-compiling for Linux
Section titled “Cross-compiling for Linux”The everyday case: develop on a Mac, deploy to Linux servers. A Hale
program always links the lotus C runtime, OpenSSL, zlib and (for
std::ts) a tree-sitter staticlib, so unlike a pure-Go binary it needs
a C toolchain and those libraries for the target. Two pieces supply
them (GH #970):
- zig, as the C compiler and linker.
zig cc -target x86_64-linux-gnu.2.31carries its own glibc headers and stubs for every Linux target, so nothing has to be installed for the target’s libc.brew install zig(or a release from ziglang.org);HALE_ZIGnames the binary if it is not onPATH. - A target sysroot: OpenSSL and zlib for the target, as static
archives, plus the tree-sitter shim.
scripts/target-sysroot.sh <triple>builds one into~/.cache/hale/sysroot/<triple>/— both libraries from pinned source tarballs, compiled with zig against the same glibc floor (a minute or two, once per target) — and cross-buildslibhale_ts_shim.awhen run from a checkout withrustup target add <triple>done. Needscurl,perlandmakebesides zig.HALE_TARGET_SYSROOTpoints at one kept elsewhere.
Then:
scripts/target-sysroot.sh x86_64-unknown-linux-gnu # oncehale build --target x86_64-unknown-linux-gnu app.hl # ELF x86-64, runs on any glibc ≥ 2.31The emitted binary depends on the target’s glibc and nothing else —
OpenSSL and zlib are linked in. For a binary that depends on nothing,
build for musl: hale build --target x86_64-unknown-linux-musl app.hl
(after scripts/target-sysroot.sh x86_64-unknown-linux-musl) is one
static file that runs on any Linux — Alpine, a scratch container, an
old glibc — with no libc to match. One carve-out: async_io pools are
refused for musl at check time (its libc has no ucontext, which the
coroutine backend needs). hale run refuses a foreign target
(nothing it builds runs here) and hale test has no --target;
LOTUS_ASAN and the other sanitizers are host-only. Without zig or the sysroot the build
fails before linking and says which one is missing. HALE_TARGET_GLIBC
picks a different glibc floor (the script and the compiler read the
same variable).
The rest of this section is about the host — where hale itself
runs, and what changes about a program compiled there.
| Platform | Status |
|---|---|
| Linux x86_64 (glibc) | First-class — hosts the compiler and runs compiled programs, all features. |
| Linux ARM64 (glibc) | Supported, prebuilt — the release matrix builds it on a native aarch64 runner (AWS Graviton, EKS arm64 nodes, Ampere). Same feature set as x86_64. |
| macOS (Apple Silicon) | Supported — hosts the compiler and targets itself, all pool backends included: async_io pools run on kqueue (Linux’s on epoll; same coroutines, same semantics). One carve-out. Cross-process unix(...) bindings use a framed byte-stream transport on macOS (Darwin has no SOCK_SEQPACKET) — same semantics, message boundaries preserved by a per-message header rather than the kernel; both ends of a socket must be Hale binaries on the same wire format (always true on one host). What hale emits does not need Homebrew on the machine that runs it: emitted binaries link OpenSSL statically (otool -L shows only libSystem and libz). The prebuilt hale launches without Homebrew too — the release tarball carries its libunwind beside it — but compiling a program needs OpenSSL’s headers, so the machine that runs hale needs brew install openssl@3. Building the compiler from source needs brew install llvm@18 openssl@3. Intel Macs have no prebuilt; build from source. |
| Windows | No native support yet — the runtime is POSIX. Use WSL2 (Ubuntu) and follow the Linux instructions. The compiler names x86_64-pc-windows-msvc (hale targets) and refuses it with a precise error rather than a link failure; the codegen and runtime work is tracked in GH #445. |
| wasm32 | hale build --target wasm32 for the browser. |
Verify
Section titled “Verify”hale --helpOr from a source checkout:
cargo build --release # the whole workspace./target/release/hale --helpTo run the compiler’s own test suite (in parallel — every test builds to its own path):
cargo nextest run --release --workspaceOn macOS, export LIBRARY_PATH="$(brew --prefix)/lib" first. LLVM 18
links against zstd, which Homebrew keeps outside the linker’s default
search path; hale’s own build script adds the directory, but the test
binaries have no such script and fail with ld: library 'zstd' not found without it.
The two ways to run a program
Section titled “The two ways to run a program”Both go through the same LLVM-native compiler — there’s no separate interpreter, so they never disagree:
hale run prog.hl— compiles and runs in one step (the binary is temporary). The fast inner loop while you write.hale build prog.hl— compiles to a native binary on disk via LLVM. This is the artifact you ship.
hale run prog.hl # compile + runhale build prog.hl # compile to ./prog./progThroughout this guide we write hale run / hale build as if
hale is on your path. From a source checkout without it
installed, build the workspace once with cargo build --release
and use ./target/release/hale. Build the whole workspace:
cargo build -p hale-cli skips the tree-sitter staticlib that
std::ts links against, and a hale built that way refuses
std::ts programs with a build error saying so.
Starting a project rather than a scratch file? hale init my-app
scaffolds the canonical minimal shape — hale.toml, a hello-world
seed, a first test — ready for hale run / hale test / hale verify out of the box.
Next: Your first run.