Skip to content

Setting up on macOS (primary platform)

This is the main supported setup: an Apple Silicon Mac (M1 or newer) with Homebrew. Intel Macs work too, because Homebrew lives in /usr/local there and the build finds it automatically.

You will install:

What Why From
LLVM 23 (llvm) headers, libraries, CMake config, opt/llc/lli/FileCheck, and the clang we compile with Homebrew
uv manages the course's Python environment (lit, PyYAML, the website) from uv.lock Homebrew
googletest the unit-test framework Homebrew
ninja, cmake the build tools Homebrew
Python ≥ 3.11 the ./course CLI, lit, the course website uv installs one if needed

1. Install the tools

You need the Xcode Command Line Tools for the macOS SDK and linker. You do not need full Xcode.

xcode-select --install        # skip this if it says the tools are already installed
brew update
brew install llvm uv googletest ninja cmake

Check that Homebrew's LLVM is version 23:

$(brew --prefix llvm)/bin/llvm-config --version     # must print 23.x.y

If it prints an older version, run brew upgrade llvm. If Homebrew has moved on to LLVM 24 or later, install the versioned formula instead (brew install llvm@23) and pass -DPEBBLE_LLVM_PREFIX=$(brew --prefix llvm@23) in step 3.

2. Why Homebrew clang and not Apple clang

macOS ships its own clang (Apple clang, in /usr/bin). Don't use it for this course.

  • Apple clang is a fork with its own versioning ("Apple clang 17") and its own libc++. It does not match LLVM 23 exactly.
  • Your pass plugins are loaded into Homebrew's opt. If a plugin is built with one toolchain and loaded into a tool built with another, the C++ ABI and symbol details can differ. That leads to crashes at load time, or "Symbol not found" errors.
  • The course needs C++23 (std::expected, std::print), and Homebrew clang 23 has complete support.

The macos CMake preset handles this for you. It uses cmake/toolchains/homebrew-llvm.cmake, which finds $(brew --prefix llvm) and sets CMAKE_CXX_COMPILER to Homebrew's clang++. If Apple clang is ever picked up, configuration fails on purpose with instructions to fix it.

You do not need to change your PATH. Adding $(brew --prefix llvm)/bin to PATH is optional. It's convenient when you want to run opt or FileCheck by hand, but it isn't required.

3. Configure, build, test

From the repository root:

uv sync                        # .venv with the pinned lit, PyYAML and website tools (uv.lock)
cmake --preset macos            # configure into build/macos
cmake --build --preset macos    # compile
ctest --preset macos            # run all tests (skeleton: most chapters fail with TODO)

uv sync creates .venv/ from pyproject.toml + uv.lock (exact versions, including lit 23.1.2), and CMake prefers .venv when it exists. cmake --build build/macos --target setup-venv runs the same uv sync. You never need to activate the environment: ./course runs itself through uv run, and uv run <cmd> runs any other tool in it.

Configuration prints a summary, and also writes it to build/macos/pebble-env.txt:

-- Pebble: compiler Clang 23.1.x (/opt/homebrew/opt/llvm/bin/clang++)
-- Pebble: LLVM 23.1.x from /opt/homebrew/opt/llvm/lib/cmake/llvm
-- Pebble: FileCheck: /opt/homebrew/opt/llvm/bin/FileCheck
-- Pebble: GoogleTest installed 1.17.0 (...)

4. Verify the installation

ctest --preset macos -L infra

This runs the infrastructure self-test (tests/infra/). infra.lit and infra.TodoTest.* must pass. They prove that the pass plugin loads into opt, and that lit, FileCheck and GoogleTest work.

The infra.InfraDemoTest.* tests fail with TODO(infra): .... That is intended: it's what an unimplemented exercise looks like. To see them pass, build with the solution:

cmake --preset macos -DPEBBLE_USE_SOLUTION=infra-demo && cmake --build --preset macos
ctest --preset macos -L infra                       # now all pass
cmake --preset macos -DPEBBLE_USE_SOLUTION=         # back to your own code

Finally, run ./course doctor. It reads build/<preset>/pebble-env.txt and checks everything.

Everyday commands

Task Command
Run one chapter's tests cmake --build build/macos --target check-ch15 or ctest --preset macos -L '^ch15$'
Unit tests only / lit only ctest --preset macos -L unit / -L lit
Run a single lit test build/macos/bin/pebble-lit -v tests/ch15/lit/some-test.ll
Use the reference solution for some components cmake --preset macos -DPEBBLE_USE_SOLUTION=lexer,parser
Debug build cmake --preset macos-debug (then use macos-debug for build/test too)

If something goes wrong, see troubleshooting.md.