# C Development Mac Tahoe Setup Adding C development on top of an Apple Silicon Mac already configured per [[General_Development_Mac_Tahoe_Setup]]. Most of the stack carries over — Ghostty, Starship, Git, GitHub CLI, VS Code, Claude Code, and the zsh setup all work identically. This guide only covers the C-specific additions. > [!tip] AI assistance is optional > The reference to Claude Code above assumes you completed Part 7 of the General guide. If you skipped it (which is fine — your dev environment works without any AI) or use a different AI tool (Copilot, Cursor, Continue, etc.), this guide's instructions still work — the language toolchain doesn't depend on any AI being present. > [!info] Coexistence > C, Python, Ruby, Go, and Rust can all live on the same system without interfering. Each language's toolchain installs to its own prefix: `uv` under `~/.local/share/uv/`, `rv` under `~/.rv/`, Go under `~/go/`, Rust under `~/.rustup/` and `~/.cargo/`, C/CMake under Homebrew. No conflicts. --- ## What you already have If you followed the Python setup guide, Xcode Command Line Tools came with your initial setup, which means you already have: | Tool | What it is | Path | |------|------------|------| | `clang` | C compiler (Apple's LLVM build, native arm64) | `/usr/bin/clang` | | `clang++` | C++ compiler | `/usr/bin/clang++` | | `lldb` | Debugger (LLVM) | `/usr/bin/lldb` | | `make` | Build automation (GNU Make 3.81) | `/usr/bin/make` | | `ar`, `ld`, `as` | Binary utilities | `/usr/bin/` | Verify: ```bash clang --version lldb --version make --version xcode-select -p ``` If anything's missing, run `xcode-select --install`. --- ## What to add ```bash brew install cmake ninja llvm ``` | Tool | Why | |------|-----| | `cmake` | De-facto standard build system configurator; most open-source C projects use it | | `ninja` | Fast build tool that CMake targets instead of `make` (much faster on large projects) | | `llvm` | Homebrew's newer LLVM — provides `clangd` (language server), `clang-format`, `clang-tidy`, newer `clang`, and `lldb-dap` | ### Why Homebrew LLVM *and* Apple Clang? Apple Clang compiles and debugs fine. But it doesn't ship `clangd`, `clang-format`, or `clang-tidy` — the tools that power modern editor intelligence and code style enforcement. Homebrew's `llvm` gives you those without replacing Apple Clang. The Homebrew LLVM binaries are **not on your PATH by default** because they'd shadow Apple's. To make them discoverable: ```bash # Add to ~/.zshrc (optional — needed for clangd, clang-format, clang-tidy) export PATH="/opt/homebrew/opt/llvm/bin:$PATH" ``` Reload with `source ~/.zshrc`. ### Debugger choice: lldb, not gdb On Apple Silicon, **use `lldb`**. `gdb` via Homebrew requires manual code-signing to attach to processes and isn't worth the hassle for standard workflows. `lldb` is already installed, natively arm64, actively maintained by Apple, and supported by VS Code via the modern `lldb-dap` adapter. The only reason to install `gdb` on macOS in 2026 is cross-compiling for embedded Linux targets — and even then, you'd want `gdb-multiarch` or a target-specific toolchain rather than plain `brew install gdb`. --- ## VS Code extensions for C ```bash # Microsoft's C/C++ extension (IntelliSense, cppdbg debugger) code --install-extension ms-vscode.cpptools code --install-extension ms-vscode.cpptools-extension-pack # CMake integration — configure, build, and run from the command palette code --install-extension ms-vscode.cmake-tools code --install-extension twxs.cmake # Modern LLDB debug adapter (better than cppdbg on Apple Silicon) code --install-extension llvm-vs-code-extensions.lldb-dap # clangd — language server alternative to Microsoft's IntelliSense code --install-extension llvm-vs-code-extensions.vscode-clangd ``` > [!warning] clangd vs Microsoft IntelliSense — pick one > Running both causes dueling diagnostics. Convention: if you install `vscode-clangd`, disable Microsoft's IntelliSense with `"C_Cpp.intelliSenseEngine": "disabled"`. If you prefer Microsoft's, skip the clangd extension. clangd is faster and more standards-correct; Microsoft's is more beginner-friendly. --- ## VS Code settings Append to your existing `settings.json` (`~/Library/Application Support/Code/User/settings.json`): ```json { // ── C/C++ ───────────────────────────────────────────────── "[c]": { "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", "editor.tabSize": 4, "editor.insertSpaces": true, "editor.formatOnSave": true }, "[cpp]": { "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", "editor.tabSize": 4, "editor.formatOnSave": true }, // Disable Microsoft IntelliSense if using clangd (pick one) "C_Cpp.intelliSenseEngine": "disabled", // clangd settings "clangd.arguments": [ "--background-index", "--clang-tidy", "--header-insertion=iwyu", "--completion-style=detailed" ], // CMake Tools "cmake.configureOnOpen": true, "cmake.buildDirectory": "${workspaceFolder}/build", "cmake.generator": "Ninja" } ``` --- ## Full demo: A calculator program This walks through building, running, debugging, and publishing a simple four-function calculator from scratch. ### Create the project ```bash cd ~/projects mkdir calc-c && cd calc-c ``` ### Project layout Refactor into a library + CLI so the operations can be unit-tested: ``` calc-c/ ├── CMakeLists.txt ├── .clang-format ├── .gitignore ├── src/ │ ├── calc_lib.h │ ├── calc_lib.c │ └── main.c └── tests/ ├── unity.h ├── unity.c └── test_calc.c ``` Create the directories: ```bash mkdir -p src tests ``` ### `src/calc_lib.h` — the public API ```c #ifndef CALC_LIB_H #define CALC_LIB_H /* Result type: carries either a value or an error */ typedef enum { CALC_OK = 0, CALC_ERR_DIVIDE_BY_ZERO, CALC_ERR_UNKNOWN_OP, } calc_status_t; typedef struct { calc_status_t status; double value; } calc_result_t; calc_result_t calc_add(double a, double b); calc_result_t calc_sub(double a, double b); calc_result_t calc_mul(double a, double b); calc_result_t calc_div(double a, double b); /* Dispatch by operator string: "+", "-", "*", "x", "/" */ calc_result_t calc_calculate(double a, const char *op, double b); #endif /* CALC_LIB_H */ ``` ### `src/calc_lib.c` — implementation ```c #include "calc_lib.h" #include <string.h> calc_result_t calc_add(double a, double b) { return (calc_result_t){CALC_OK, a + b}; } calc_result_t calc_sub(double a, double b) { return (calc_result_t){CALC_OK, a - b}; } calc_result_t calc_mul(double a, double b) { return (calc_result_t){CALC_OK, a * b}; } calc_result_t calc_div(double a, double b) { if (b == 0.0) { return (calc_result_t){CALC_ERR_DIVIDE_BY_ZERO, 0.0}; } return (calc_result_t){CALC_OK, a / b}; } calc_result_t calc_calculate(double a, const char *op, double b) { if (strcmp(op, "+") == 0) return calc_add(a, b); if (strcmp(op, "-") == 0) return calc_sub(a, b); if (strcmp(op, "*") == 0 || strcmp(op, "x") == 0) return calc_mul(a, b); if (strcmp(op, "/") == 0) return calc_div(a, b); return (calc_result_t){CALC_ERR_UNKNOWN_OP, 0.0}; } ``` ### `src/main.c` — the CLI ```c #include <stdio.h> #include <stdlib.h> #include "calc_lib.h" static void print_usage(const char *prog) { fprintf(stderr, "Usage: %s <number> <op> <number>\n", prog); fprintf(stderr, " op: + - * /\n"); fprintf(stderr, "Example: %s 6 + 7\n", prog); } int main(int argc, char *argv[]) { if (argc != 4) { print_usage(argv[0]); return 1; } double a = strtod(argv[1], NULL); double b = strtod(argv[3], NULL); calc_result_t result = calc_calculate(a, argv[2], b); switch (result.status) { case CALC_OK: printf("%g\n", result.value); return 0; case CALC_ERR_DIVIDE_BY_ZERO: fprintf(stderr, "Error: division by zero\n"); return 1; case CALC_ERR_UNKNOWN_OP: fprintf(stderr, "Error: unknown operator '%s'\n", argv[2]); print_usage(argv[0]); return 1; } return 1; } ``` ### Add Unity for unit testing [Unity](https://github.com/ThrowTheSwitch/Unity) is a minimal C test framework — three files total. Download it: ```bash curl -LO https://raw.githubusercontent.com/ThrowTheSwitch/Unity/master/src/unity.h curl -LO https://raw.githubusercontent.com/ThrowTheSwitch/Unity/master/src/unity.c curl -LO https://raw.githubusercontent.com/ThrowTheSwitch/Unity/master/src/unity_internals.h mv unity.h unity.c unity_internals.h tests/ ``` ### `tests/test_calc.c` — unit tests ```c #include "unity.h" #include "calc_lib.h" void setUp(void) {} void tearDown(void) {} void test_add(void) { calc_result_t r = calc_calculate(6, "+", 7); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(13.0, r.value); } void test_sub(void) { calc_result_t r = calc_calculate(10, "-", 3); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(7.0, r.value); } void test_mul_star(void) { calc_result_t r = calc_calculate(4, "*", 5); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(20.0, r.value); } void test_mul_x(void) { calc_result_t r = calc_calculate(4, "x", 5); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(20.0, r.value); } void test_div(void) { calc_result_t r = calc_calculate(20, "/", 4); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(5.0, r.value); } void test_div_by_zero(void) { calc_result_t r = calc_calculate(5, "/", 0); TEST_ASSERT_EQUAL_INT(CALC_ERR_DIVIDE_BY_ZERO, r.status); } void test_unknown_operator(void) { calc_result_t r = calc_calculate(1, "?", 2); TEST_ASSERT_EQUAL_INT(CALC_ERR_UNKNOWN_OP, r.status); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_add); RUN_TEST(test_sub); RUN_TEST(test_mul_star); RUN_TEST(test_mul_x); RUN_TEST(test_div); RUN_TEST(test_div_by_zero); RUN_TEST(test_unknown_operator); return UNITY_END(); } ``` ### `CMakeLists.txt` — the build configuration ```cmake cmake_minimum_required(VERSION 3.20) project(calc C) set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # The calculator library (reusable by both CLI and tests) add_library(calc_lib STATIC src/calc_lib.c) target_include_directories(calc_lib PUBLIC src) target_compile_options(calc_lib PRIVATE -Wall -Wextra -Wpedantic) # The CLI executable add_executable(calc src/main.c) target_link_libraries(calc PRIVATE calc_lib) target_compile_options(calc PRIVATE -Wall -Wextra -Wpedantic) # Tests (only built if testing is enabled) enable_testing() add_executable(test_calc tests/test_calc.c tests/unity.c) target_link_libraries(test_calc PRIVATE calc_lib) target_include_directories(test_calc PRIVATE tests) add_test(NAME unit_tests COMMAND test_calc) ``` The `CMAKE_EXPORT_COMPILE_COMMANDS ON` line is important — it generates `build/compile_commands.json`, which `clangd` reads to understand include paths and flags. Without it, clangd shows spurious errors. ### `.clang-format` — code style (optional but recommended) ```yaml BasedOnStyle: LLVM IndentWidth: 4 ColumnLimit: 100 AllowShortFunctionsOnASingleLine: Inline ``` ### `.gitignore` ``` build/ *.o *.a *.dylib *.dSYM/ ``` ### Build and run ```bash # Configure (generates build/ with Ninja build files) cmake -B build -G Ninja # Build cmake --build build # Run the CLI ./build/calc 6 + 7 ./build/calc 10 - 3 ./build/calc 4 x 5 ./build/calc 20 / 4 # Run unit tests ./build/test_calc # Or via CTest: cd build && ctest --output-on-failure && cd .. ``` ### Debug in VS Code Create `.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug calc", "type": "lldb-dap", "request": "launch", "program": "${workspaceFolder}/build/calc", "args": ["6", "+", "7"], "cwd": "${workspaceFolder}", "stopOnEntry": false } ] } ``` Set a breakpoint on the `add` function, hit **F5**, and you'll step through native arm64 code with full variable inspection. ### Publish to GitHub Same workflow as any project (see [[General_Development_Mac_Tahoe_Setup#Publishing a repo to GitHub]]): ```bash git init git add . git commit -m "Initial commit: four-function calculator" gh repo create calc-c --private --source=. --remote=origin --push ``` ### `CLAUDE.md` for this project ```markdown # Project: calc-c ## Purpose Four-function command-line calculator in C — pedagogical example. ## Conventions - C17 standard, compiled with Apple clang - Library (src/calc_lib.{c,h}) + CLI (src/main.c) + tests (tests/test_calc.c) - Build: CMake + Ninja (build dir: ./build/) - Format: clang-format (LLVM base, 4-space indent, 100-col limit) - Warnings: -Wall -Wextra -Wpedantic - Tests with Unity (bundled in tests/) ## Commands - Configure: `cmake -B build -G Ninja` - Build: `cmake --build build` - Run CLI: `./build/calc <a> <op> <b>` - Run tests: `./build/test_calc` or `cd build && ctest --output-on-failure` - Format: `clang-format -i src/*.c src/*.h tests/test_calc.c` - Clean: `rm -rf build/` ## Style - snake_case for functions and variables - SCREAMING_SNAKE_CASE for macros and enum values - Static linkage for internal helpers - Return calc_result_t (status + value) instead of errno-style out-params ``` --- ## Full demo: A calculator GUI Same calculator, wrapped in a graphical interface. This uses **Raylib**, a simple, modern, Homebrew-installable graphics library that runs natively on Apple Silicon. ### Why Raylib? | Option | Pros | Cons | |--------|------|------| | **Raylib** | Single dependency, Homebrew-installable, modern, native arm64, simple immediate-mode API | Custom widget look (not native macOS controls) | | **GTK** | Full widget toolkit, cross-platform | Heavy dependency chain, dated macOS integration | | **SDL2** | Ubiquitous for games/media | Lower-level than Raylib — you build your own buttons | | **Dear ImGui** | Excellent for debug UIs and tools | Binds are C++ (cimgui wraps to C) | For a pedagogical calculator in C, Raylib is the right default. It's one dependency, one header include, and the API is designed to be learnable in an afternoon. ### Install Raylib ```bash brew install raylib ``` This installs the Raylib headers and library to `/opt/homebrew/`. ### `src/calc_gui.c` — the GUI ```c #include <stdio.h> #include <string.h> #include <stdlib.h> #include "raylib.h" #include "calc_lib.h" #define SCREEN_WIDTH 300 #define SCREEN_HEIGHT 400 #define BUTTON_WIDTH 65 #define BUTTON_HEIGHT 60 #define PADDING 10 #define DISPLAY_HEIGHT 70 typedef struct { const char *label; int row; int col; } button_t; static const button_t BUTTONS[] = { {"7", 0, 0}, {"8", 0, 1}, {"9", 0, 2}, {"/", 0, 3}, {"4", 1, 0}, {"5", 1, 1}, {"6", 1, 2}, {"*", 1, 3}, {"1", 2, 0}, {"2", 2, 1}, {"3", 2, 2}, {"-", 2, 3}, {"0", 3, 0}, {".", 3, 1}, {"=", 3, 2}, {"+", 3, 3}, }; static const int NUM_BUTTONS = sizeof(BUTTONS) / sizeof(BUTTONS[0]); typedef struct { char display[64]; char current[32]; double stored; char pending_op[4]; int has_stored; } calc_state_t; static void state_reset(calc_state_t *s) { s->current[0] = '\0'; s->stored = 0.0; s->pending_op[0] = '\0'; s->has_stored = 0; strcpy(s->display, "0"); } static void state_append(calc_state_t *s, const char *ch) { size_t len = strlen(s->current); if (len + strlen(ch) < sizeof(s->current) - 1) { strcat(s->current, ch); strcpy(s->display, s->current); } } static void state_apply_pending(calc_state_t *s) { if (s->current[0] == '\0') return; double value = strtod(s->current, NULL); if (!s->has_stored || s->pending_op[0] == '\0') { s->stored = value; s->has_stored = 1; } else { calc_result_t r = calc_calculate(s->stored, s->pending_op, value); if (r.status != CALC_OK) { strcpy(s->display, "Error"); state_reset(s); strcpy(s->display, "Error"); return; } s->stored = r.value; } /* Format: integer if whole, otherwise %g */ if (s->stored == (long long)s->stored) { snprintf(s->display, sizeof(s->display), "%lld", (long long)s->stored); } else { snprintf(s->display, sizeof(s->display), "%g", s->stored); } s->current[0] = '\0'; } static void state_handle_press(calc_state_t *s, const char *label) { if ((label[0] >= '0' && label[0] <= '9') || label[0] == '.') { state_append(s, label); } else if (strcmp(label, "+") == 0 || strcmp(label, "-") == 0 || strcmp(label, "*") == 0 || strcmp(label, "/") == 0) { state_apply_pending(s); strncpy(s->pending_op, label, sizeof(s->pending_op) - 1); } else if (strcmp(label, "=") == 0) { state_apply_pending(s); s->pending_op[0] = '\0'; } } int main(void) { InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Calculator"); SetTargetFPS(60); calc_state_t state; state_reset(&state); const int grid_start_x = PADDING; const int grid_start_y = PADDING + DISPLAY_HEIGHT + PADDING; while (!WindowShouldClose()) { /* Keyboard input */ int key = GetCharPressed(); while (key > 0) { char ch[2] = {(char)key, '\0'}; if ((key >= '0' && key <= '9') || key == '.' || key == '+' || key == '-' || key == '*' || key == '/' || key == '=') { state_handle_press(&state, ch); } key = GetCharPressed(); } if (IsKeyPressed(KEY_ENTER)) state_handle_press(&state, "="); if (IsKeyPressed(KEY_ESCAPE)) state_reset(&state); if (IsKeyPressed(KEY_BACKSPACE)) { size_t len = strlen(state.current); if (len > 0) { state.current[len - 1] = '\0'; if (state.current[0] == '\0') { strcpy(state.display, "0"); } else { strcpy(state.display, state.current); } } } /* Draw */ BeginDrawing(); ClearBackground((Color){30, 30, 35, 255}); /* Display */ Rectangle display_rect = {PADDING, PADDING, SCREEN_WIDTH - 2 * PADDING, DISPLAY_HEIGHT}; DrawRectangleRec(display_rect, (Color){50, 50, 55, 255}); DrawRectangleLinesEx(display_rect, 1, GRAY); int display_width = MeasureText(state.display, 28); DrawText(state.display, display_rect.x + display_rect.width - display_width - 10, display_rect.y + 20, 28, RAYWHITE); /* Buttons */ Vector2 mouse = GetMousePosition(); int mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); for (int i = 0; i < NUM_BUTTONS; i++) { button_t btn = BUTTONS[i]; Rectangle r = { grid_start_x + btn.col * (BUTTON_WIDTH + 5), grid_start_y + btn.row * (BUTTON_HEIGHT + 5), BUTTON_WIDTH, BUTTON_HEIGHT }; int hovered = CheckCollisionPointRec(mouse, r); int is_op = (strchr("+-*/=", btn.label[0]) != NULL); Color fill = is_op ? (Color){200, 120, 40, 255} : (Color){70, 70, 75, 255}; if (hovered) fill = (Color){fill.r + 30, fill.g + 30, fill.b + 30, 255}; DrawRectangleRec(r, fill); int tw = MeasureText(btn.label, 20); DrawText(btn.label, r.x + (r.width - tw) / 2, r.y + 20, 20, RAYWHITE); if (hovered && mouse_clicked) { state_handle_press(&state, btn.label); } } EndDrawing(); } CloseWindow(); return 0; } ``` ### Update `CMakeLists.txt` to build the GUI Append to `CMakeLists.txt`: ```cmake # GUI executable (requires raylib from Homebrew) find_package(raylib QUIET) if(raylib_FOUND) add_executable(calc_gui src/calc_gui.c) target_link_libraries(calc_gui PRIVATE calc_lib raylib) target_compile_options(calc_gui PRIVATE -Wall -Wextra -Wpedantic) else() message(STATUS "raylib not found — skipping calc_gui target. Install with: brew install raylib") endif() ``` On Apple Silicon, raylib from Homebrew is at `/opt/homebrew/`, which CMake picks up automatically. ### Tests for the GUI state machine Since the state machine (`state_reset`, `state_handle_press`, `state_apply_pending`) is separate from Raylib's drawing, it can be unit-tested. Add to `tests/test_calc.c`: ```c /* State machine tests — put these after the existing tests, before main() */ /* Copy the state struct & helpers from calc_gui.c into a header, */ /* or extract them into calc_gui_state.{c,h} for a cleaner split. */ /* This example assumes you've factored them into calc_gui_state.h. */ #include "calc_gui_state.h" void test_state_initial(void) { calc_state_t s; state_reset(&s); TEST_ASSERT_EQUAL_STRING("0", s.display); } void test_state_simple_addition(void) { calc_state_t s; state_reset(&s); state_handle_press(&s, "6"); state_handle_press(&s, "+"); state_handle_press(&s, "7"); state_handle_press(&s, "="); TEST_ASSERT_EQUAL_STRING("13", s.display); } void test_state_division_by_zero(void) { calc_state_t s; state_reset(&s); state_handle_press(&s, "5"); state_handle_press(&s, "/"); state_handle_press(&s, "0"); state_handle_press(&s, "="); TEST_ASSERT_EQUAL_STRING("Error", s.display); } /* ... and register them with RUN_TEST(test_state_...) in main() */ ``` > [!tip] Factoring for testability > To actually run these tests, extract the `calc_state_t` struct and its helper functions from `src/calc_gui.c` into `src/calc_gui_state.h` + `src/calc_gui_state.c`. Link both `calc_lib` and `calc_gui_state` into the test executable. The Raylib drawing stays in `src/calc_gui.c` and doesn't need to be tested directly. ### Run the GUI ```bash cmake -B build -G Ninja cmake --build build ./build/calc_gui ``` A 300×400 window opens with the calculator UI. Click buttons or use the keyboard — digits, `+`, `-`, `*`, `/`, `Enter` (for `=`), `Esc` (to clear), and `Backspace` (to delete the last digit). --- ## Starship prompt — C auto-detection Starship's built-in `[c]` module shows the compiler and standard when a `.c` file or `CMakeLists.txt` exists in the directory. No config needed, but if you want to customize, append to `~/.config/starship.toml`: ```toml [c] format = "[$symbol($version(-$name) )]($style)" symbol = " " style = "149 bold" disabled = false ``` --- ## Optional: static analysis and memory checking - **`clang-tidy`** (from `brew install llvm`) — runs automatically when enabled via clangd's `--clang-tidy` flag - **AddressSanitizer** — built into Apple Clang. Enable per-project: add `-fsanitize=address -g` to `target_compile_options` and linker flags. Catches buffer overflows, use-after-free, leaks. - **UndefinedBehaviorSanitizer** — `-fsanitize=undefined`. Catches signed overflow, null derefs, etc. - **Valgrind** — **not available on Apple Silicon macOS**. Use AddressSanitizer instead; it catches most of what Valgrind would. To build with sanitizers: ```bash cmake -B build-asan -G Ninja -DCMAKE_C_FLAGS="-fsanitize=address -g -O1" cmake --build build-asan ./build-asan/calc 6 + 7 ``` --- ## Troubleshooting > [!warning] clangd shows "file not found" for standard headers > Build the project at least once so `compile_commands.json` is generated. Ensure `CMAKE_EXPORT_COMPILE_COMMANDS ON` is in your CMakeLists.txt. Restart VS Code after the first build. > [!warning] `cmake` can't find a compiler > Run `xcode-select --install` to install/reinstall Command Line Tools. Then: `sudo xcode-select --reset`. > [!warning] lldb-dap extension says "command not found" > The `lldb-dap` binary ships with `brew install llvm`. If PATH isn't exporting Homebrew LLVM, set the path explicitly in VS Code settings: `"lldb-dap.executable-path": "/opt/homebrew/opt/llvm/bin/lldb-dap"`. > [!warning] `brew install gdb` — should I bother? > No, for Apple Silicon. Use `lldb`. If you truly need gdb for an embedded target, use a cross-compilation toolchain that includes `gdb-multiarch` rather than the standalone Homebrew formula. --- ## Summary — the one-shot C addition > [!warning] This is a checklist, not a script > The block below is **not a shell script you can save and run.** It's a sequence of commands to **copy and paste into your terminal one block at a time**, in order. After running these, you also need to **append the recommended C/C++ settings block** from earlier in this guide to `~/Library/Application Support/Code/User/settings.json` — without that, VS Code's clangd and CMake integration won't be configured. ```bash # Build system + modern LLVM brew install cmake ninja llvm # GUI library (optional, for calc_gui demo) brew install raylib # VS Code extensions code --install-extension ms-vscode.cpptools code --install-extension ms-vscode.cpptools-extension-pack code --install-extension ms-vscode.cmake-tools code --install-extension twxs.cmake code --install-extension llvm-vs-code-extensions.lldb-dap code --install-extension llvm-vs-code-extensions.vscode-clangd # Optional: Homebrew LLVM on PATH for clangd/clang-format/clang-tidy echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc source ~/.zshrc # Verify clang --version && lldb --version && cmake --version && ninja --version ``` About five minutes of work on top of the general setup. --- ## Related notes - [[General_Development_Mac_Tahoe_Setup]] - [[Python_Development_Mac_Tahoe_Setup]] - [[Ruby_Development_Mac_Tahoe_Setup]] - [[Go_Development_Mac_Tahoe_Setup]] - [[Rust_Development_Mac_Tahoe_Setup]] - [CMake Cheat Sheet](https://cmake.org/cmake/help/latest/guide/tutorial/index.html) - [LLDB Command Reference](https://lldb.llvm.org/use/map.html)