This page installs Aether, runs a first program, and starts a project. The language itself is taught by the path through the examples, with the tutorial beside it; Learn Aether at the end says how they fit together.
git clone https://github.com/aether-lang-dev/aether.git
cd aether
./install.shThis builds the compiler, CLI tool, and standard library, then installs everything to ~/.aether. After restarting your terminal (or running source ~/.bashrc, ~/.zshrc, or ~/.bash_profile), the ae command is available globally.
To also build the native contrib modules (sqlite, embedded Python/Lua/…) whose dev libraries your machine has:
./install.sh --contrib-tooTo install to a custom location:
./install.sh /usr/local # System-wide install (needs sudo)The installer checks for these automatically and provides platform-specific install commands if something is missing. Here's what you need:
macOS:
xcode-select --install
# Or with Homebrew: brew install gcc makeLinux (Debian/Ubuntu/Pop!_OS/Mint):
sudo apt-get install build-essentialLinux (Fedora):
sudo dnf install gcc makeLinux (RHEL/CentOS/Rocky/AlmaLinux):
sudo yum install gcc makeLinux (Arch/Manjaro):
sudo pacman -S base-develLinux (openSUSE):
sudo zypper install gcc makeLinux (Alpine):
apk add build-baseWindows:
The easiest way is to download the pre-built release binary, no MSYS2, no manual toolchain required:
- Download
aether-*-windows-x86_64.zipfrom GitHub Releases - Extract to any folder, e.g.
C:\aether - Add
C:\aether\binto your PATH (System Settings → Environment Variables → Path) - Restart your terminal (so PATH takes effect)
- Open any terminal (PowerShell or CMD):
ae version
ae init hello
cd hello
ae runGCC is downloaded automatically on the first ae run (~80 MB, one-time). No MSYS2, no separate installer needed.
Windows Defender tip: The first build may trigger a scan. Adding
C:\aetherto Windows Security exclusions speeds things up.
Building from source / contributors: Install MSYS2, open "MSYS2 MinGW 64-bit", then
pacman -S mingw-w64-x86_64-gcc make gitandmake ae.
If you prefer to build without installing to your system:
make ae
./build/ae versionThis builds the ae CLI tool in the build/ directory. You'll need to use ./build/ae instead of just ae.
For syntax highlighting and a better development experience:
VS Code / Cursor:
cd editor/vscode
./install.shFeatures included:
- Syntax highlighting with TextMate grammar
- Custom "Aether Erlang" dark theme optimized for Aether code
.aefile icons- Basic language configuration (comments, brackets, etc.)
After installation, open any .ae file and VS Code will automatically apply syntax highlighting. Select the "Aether Erlang" theme via Preferences > Color Theme for the best experience.
Create hello.ae:
main() {
name = "Aether"
println("Hello, ${name}!")
}
Run it:
ae run hello.aeae run compiles the file to C, builds it and runs it, keeping the build
in a cache so the next run of an unchanged file starts at once. The other
everyday commands:
ae build hello.ae -o hello # an executable
./hello
ae check hello.ae # type-check only, no build
ae fmt hello.ae # format in place (--check to only check)
ae build --target wasm hello.ae # WebAssembly; needs Emscripten (emcc) on PATH
node hello.jsae init myproject
cd myproject
ae run
ae testae init writes aether.toml, src/main.ae and tests/. ae run and
ae build in the directory build the program aether.toml names, and
ae test runs the tests under tests/ (Testing). The
configuration ae init writes:
[package]
name = "myproject"
version = "0.1.0"
description = "A new Aether project"
license = "MIT"
# modules = "mylib, mylib/internal" # Importable modules, if this is a library
# modules = "." # Export the package ROOT (for a core/*.ae package whose modules import each other as import core.*)
[[bin]]
name = "myproject"
path = "src/main.ae"
[dependencies]
[build]
target = "native"
# link_flags = "-lsqlite3 -lcurl" # Add extra linker flagsmodules is for a package other projects import. link_flags links C
libraries a program calls through extern
(C interoperability). A project's own modules live in
lib/<name>/module.ae; the modules example
shows one, and --lib adds
more directories to search.
ae add github.com/user/repo # the default branch
ae add github.com/user/repo@v1.0.0 # a version
ae add github.com/user/repo@v1.1.0 --update # move a pinned dependencyGitHub, GitLab, Bitbucket, Codeberg and self-hosted forges work the same
way. ae add needs no git: it takes the archive the forge serves, pins the
hash of its tree in aether.toml, and refuses a later fetch that is not
that tree. With a version, it takes the package's release artifact for your
platform when the package publishes one. Package fetching
has the rest: mirrors, release artifacts, and publishing binary packages.
ae help <script.ae> turns the compiler's terse findings into
suggestions: a misspelt name, a missing import, a YAML-shaped line inside a
builder block. --fix applies the safe rewrites after showing the diff,
and --json is for CI. It makes no network call and runs nothing
(Config-IS-Code Diagnostics,
an example).
ae repl evaluates as you type. Assignments and constants persist,
multi-line blocks continue until their braces close, :show prints the
session, :reset clears it and :quit leaves.
ae> x = 5
ae> println(x * 2)
10
ae> :quit
Three documents, each with one job:
- The path: the examples in order, from the first program to actors, the standard library, C, the sandbox, the tools and whole programs. Each is one file that teaches one idea and says how to run it. CI builds every one, and runs those that run unattended to check they still print the output written at their end.
- The tutorial: the path told as a story. A chapter per part, saying what each step shows and why it comes there; the first two chapters add the mistakes newcomers make and exercises with answers, and a quick reference of the syntax closes it.
- The language reference: everything, to look up. The glossary defines the words the others use.
Start with the first chapter of the tutorial and its examples, run each one, and change it.
Install, upgrade, and switch between Aether releases without reinstalling:
ae upgrade # Install the latest release and switch to it
ae install v0.830.0 # Install a specific release (latest if omitted)
ae use v0.830.0 # Switch to an installed version
ae version # Show current version
ae version list # List all available releases (marks installed/active)
ae version installed # List only what is installed locallyae install / ae use are the short forms of ae version install /
ae version use, which still work. ae upgrade (alias ae update) is a
no-op when you are already on the latest release.
Versions are stored in ~/.aether/versions/. The active version is symlinked to ~/.aether/current (Linux/macOS) or copied to ~/.aether/bin/ (Windows).
ae version tells you when a newer release exists, naming it and pointing at
ae upgrade. It matters more than it sounds: ae run resolves the standard
library from the install rather than from a checkout, so on a toolchain that has
aged the stdlib answers too, and a function added since resolves as undefined.
The same line appears when a build fails, which is where that symptom shows up.
The check asks GitHub at most once a day and caches the answer in
~/.aether/cache, so ae version still answers offline, and it says nothing at
all when the check cannot be made. Nothing updates itself; the decision is
yours.
ae version list asks GitHub what exists and marks the releases it also finds
locally, so it answers "what could I install?". ae version installed reads
only the disk, which is the question to ask when your installs have aged out of
the release window (they will show no Status in list) or when you are offline.
Each release occupies its full size, so a store of several releases gets large. Two commands prune it:
ae version remove v0.829.0 # Remove one or more installed releases
ae version gc --dry-run # Show what gc would remove
ae version gc --keep 3 # Keep the newest 3, remove the rest
ae version gc --keep 3 --dedupe # ...and share identical files afterwards
ae version dedupe # Share identical files, remove nothingBoth remove and gc refuse to delete the release you are using, whether
that is the one current points at or the one ~/.aether/active_version
pins, so the worst case is that they decline. Versions are ordered numerically,
not alphabetically, so --keep 2 on a store containing v0.9.0 and v0.10.0 keeps
v0.10.0.
dedupe reclaims space without removing anything, by pointing identical files
at the same storage. Roughly 70% of a multi-version store is byte-identical, and
about 20% of a single version is duplicated between include/aether/ and
share/aether/, so it is worth running even with one release installed. It
prefers copy-on-write reflinks (btrfs, XFS with reflink=1, APFS) and falls
back to hardlinks; where the filesystem supports neither (FAT, some network
mounts, a Windows account without Developer Mode), it reports that and changes
nothing. It never alters file contents, and it is safe to re-run: a second pass
finds nothing left to share.
One reporting quirk worth knowing: after a reflink dedupe, du still reports
the old size, because it counts every reflinked file at full size even though
the space really has come back. df shows the truth. After a hardlink dedupe
du drops, because it counts a shared inode once.
Aether supports cross-compilation to platforms without pthreads or POSIX APIs via the PLATFORM Makefile variable:
# Build for WebAssembly (requires Emscripten SDK)
make stdlib PLATFORM=wasm
# Or force cooperative mode on native for testing:
make stdlib EXTRA_CFLAGS="-DAETHER_NO_THREADING"
# Build for embedded ARM (syntax-check only, requires arm-none-eabi-gcc)
make ci-embedded
# Docker-based cross-compilation (no local toolchain needed):
make docker-ci-wasm # Emscripten + Node.js execution
make docker-ci-embedded # ARM Cortex-M4 syntax-checkOn threadless platforms, the cooperative scheduler (aether_scheduler_coop.c) replaces the multi-core scheduler. All actors run on a single thread via aether_scheduler_poll(). Multi-actor programs work correctly, messages are processed cooperatively during wait_for_idle().
Stdlib modules that depend on filesystem or networking return errors gracefully (NULL, 0, -1). Console I/O (print, println) always works.
The generated C builds as an ESP-IDF component for the ESP32 (the chip on boards such as the ESP32-2432S028R "cheap yellow display"): ESP-IDF's xtensa-esp32-elf-gcc compiles it with the runtime, using the cooperative scheduler, and the generated C supplies the app_main() entry point ESP-IDF calls. tests/esp32/project/ is a working project; its main/CMakeLists.txt lists the runtime sources and the settings an ESP32 needs, notably smaller scheduler tables (MAX_CORES=1 QUEUE_SIZE=64 SCHEDULER_DRAIN_BATCH=32 MAX_ACTORS_PER_CORE=256; at the defaults they need about 10 MB, against roughly 320 KB of DRAM).
make ci-esp32 # needs podman or docker; pulls espressif/idfbuilds a few programs (hello-world, a stateful actor, an actor pipeline, ping-pong) into firmware and boots each in Espressif's QEMU, checking the console output. A program is about 150 KB of firmware. Not yet covered: threads (FreeRTOS has pthreads, so the multicore scheduler is a candidate), the filesystem and networking, and real hardware.
"Aether compiler not found" (Windows)
- Make sure you restarted your terminal after adding
C:\aether\binto PATH - Verify:
where aeshould showC:\aether\bin\ae.exe - Verify:
where aethercshould showC:\aether\bin\aetherc.exe - If both exist but it still fails, set
AETHER_HOME:set AETHER_HOME=C:\aether
"gcc: command not found" (Windows)
- This should not happen with the pre-built binary,
aeauto-downloads GCC (~80 MB) on first run - If you built from source via MSYS2, open the "MSYS2 MinGW 64-bit" shell, not plain PowerShell
- Verify:
gcc --versionshould show MinGW-w64 GCC
"pthread.h: No such file or directory"
- Linux: the C library's development headers are missing:
build-essentialbrings them on Debian and Ubuntu (libc6-dev), and the other distributions' packages listed under Prerequisites do too - Windows: The Aether runtime uses Win32 threads natively, no pthread library needed
A function from the standard library is undefined
ae runreads the standard library of the installed toolchain: runae version, andae upgradewhen it names a newer release.
Test failures (building from source)
- Run
make testfor unit tests,make test-aefor integration tests - Check for port conflicts if network tests fail (port 8080)
- Building from source and anything looks stale: rebuild with
make clean && make. - On Windows, launch
aefrom a directory where your user can write (the first run downloads GCC into~\.aether\). - Inside an actor body,
stateis a reserved keyword, pick a different local-variable name; outside actor bodies it has no special meaning.
macOS:
- May need
xcode-select --installfor command line tools - Homebrew GCC recommended:
brew install gcc - Apple Silicon (M1/M2/M3): Runtime auto-detects P-cores for consistent performance
- Thread affinity is advisory on macOS; occasional benchmark variance is normal
Linux:
- Kernel 4.14+ recommended for full NUMA support
- AddressSanitizer may require
gcc-multilibon some distributions - Each scheduler thread is bound to one of the CPUs the process may use (its
tasksetor cpuset mask), and a container's CPU quota limits how many threads start
Windows:
- Pre-built binaries work in any terminal (PowerShell, CMD, Windows Terminal)
- GCC is auto-downloaded on first
ae runno MSYS2 or manual setup required - The runtime uses Win32 threads natively, no pthreads library required
- Each scheduler thread gets an ideal processor (
SetThreadIdealProcessor), a hint: Windows keeps it there and moves it when another program is using that CPU - Building from source: Use MSYS2 MinGW 64-bit shell with
make ae