# C Development Ubuntu Setup Adding C development on top of an Ubuntu machine already configured per [[General_Development_Ubuntu_Setup]]. Most of the stack carries over — Ghostty, Starship, Git, GitHub CLI, VS Code, Claude Code, and the bash setup all work identically. This guide only covers the C-specific additions, and everything installs the same way on `amd64` and `arm64`. It's the Linux counterpart to [[C_Development_Mac_Tahoe_Setup]]. > [!tip] AI assistance is optional > The reference to Claude Code above assumes you completed Part 7 of the General guide. If you skipped it or use a different AI tool, 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 `~/.cargo/bin`, Go under `~/go/`, Rust under `~/.rustup/` and `~/.cargo/`, C tooling under apt. No conflicts. --- ## What you already have If you ran the prerequisites in the General guide, you installed `build-essential`, which already gives you: | Tool | What it is | Path | |------|------------|------| | `gcc` | The GNU C compiler (native for your arch) | `/usr/bin/gcc` | | `g++` | The GNU C++ compiler | `/usr/bin/g++` | | `make` | Build automation (GNU Make) | `/usr/bin/make` | | `ar`, `ld`, `as` | Binary utilities (binutils) | `/usr/bin/` | Verify: ```bash gcc --version make --version ``` > [!info] gcc is the native Linux compiler > On macOS the default compiler is Apple Clang. On Linux it's **GCC**, and it's a first-class, fully native compiler on both `amd64` and `arm64`. CMake will pick `gcc` as `cc` automatically. We'll also install Clang/LLVM below for the editor tooling (`clangd`, `clang-format`, `clang-tidy`) and as an alternative compiler. --- ## What to add ```bash sudo apt install -y cmake ninja-build clang clangd clang-format clang-tidy lldb gdb valgrind ``` | Tool | Why | |------|-----| | `cmake` | De-facto standard build system configurator; most open-source C projects use it | | `ninja-build` | Fast build tool that CMake targets instead of `make` (much faster on large projects) | | `clang` | The LLVM C/C++ compiler — useful as an alternative to gcc and for its sanitizers | | `clangd` | The language server that powers modern editor intelligence | | `clang-format` | Code formatter | | `clang-tidy` | Static-analysis linter | | `gdb` | The GNU debugger — the native, no-friction debugger on Linux | | `lldb` | LLVM's debugger (optional alternative to gdb) | | `valgrind` | Memory error and leak detector | > [!info] No PATH gymnastics needed > On macOS, Homebrew's LLVM tools have to be added to PATH manually so they don't shadow Apple's. On Ubuntu, `clangd`, `clang-format`, and `clang-tidy` are independent apt packages installed straight onto your PATH (`/usr/bin/`). Nothing to configure. ### Debugger choice: gdb (the easy default on Linux) On Linux, **use `gdb`**. It's the native debugger, requires zero code-signing or entitlement dances (unlike `lldb` on macOS), and the Microsoft C/C++ VS Code extension drives it out of the box via the `cppdbg` adapter. `lldb` is installed above too if you prefer it, but for most workflows gdb is the path of least resistance. > [!tip] Valgrind actually works here > On Apple Silicon macOS, Valgrind isn't available, so the Mac guide falls back to AddressSanitizer. On Linux, **Valgrind runs natively on both `amd64` and `arm64`** — so you get both Valgrind *and* the sanitizers. See [Optional: static analysis and memory checking](#optional-static-analysis-and-memory-checking) below. --- ## VS Code extensions for C ```bash # Microsoft's C/C++ extension (IntelliSense, cppdbg/gdb 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 # 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 and includes the `cppdbg` debugger we use below. --- ## VS Code settings Append to your existing `settings.json` (`~/.config/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 mkdir -p src tests ``` ### Project layout ``` calc-c/ ├── CMakeLists.txt ├── .clang-format ├── .gitignore ├── src/ │ ├── calc_lib.h │ ├── calc_lib.c │ └── main.c └── tests/ ├── unity.h ├── unity.c └── test_calc.c ``` ### `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 *.so ``` ### 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`. On Linux we use the `cppdbg` adapter with gdb: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug calc", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/calc", "args": ["6", "+", "7"], "cwd": "${workspaceFolder}", "stopAtEntry": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } ``` Set a breakpoint on the `calc_add` function, hit **F5**, and you'll step through native code with full variable inspection. > [!info] Prefer lldb? > If you installed `lldb` and the `lldb-dap` adapter (`code --install-extension llvm-vs-code-extensions.lldb-dap`), change `"type"` to `"lldb-dap"` and drop the `MIMode`/`setupCommands` lines. Both work; gdb is the lower-friction default on Linux. ### Publish 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 gcc (clang also available) - 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` - Memory check: `valgrind --leak-check=full ./build/calc 6 + 7` - 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, apt-installable graphics library that runs natively on both `amd64` and `arm64`. ### Why Raylib? | Option | Pros | Cons | |--------|------|------| | **Raylib** | Single dependency, apt-installable, modern, native, simple immediate-mode API | Custom widget look (not native GTK controls) | | **GTK** | Full native widget toolkit | Heavier dependency chain, more boilerplate | | **SDL2** | Ubiquitous for games/media | Lower-level than Raylib — you build your own buttons | | **Dear ImGui** | Excellent for debug UIs and tools | Bindings are C++ (cimgui wraps to C) | For a pedagogical calculator in C, Raylib is the right default. ### Install Raylib Raylib and its windowing/GL/audio dependencies are all in apt: ```bash sudo apt install -y libraylib-dev \ libgl1-mesa-dev libxi-dev libxcursor-dev libxrandr-dev \ libxinerama-dev libwayland-dev libxkbcommon-dev libasound2-dev ``` > [!info] If `libraylib-dev` is missing or too old > The version in apt is usually fine. For the newest raylib, the community PPA `ppa:texus/raylib` provides current `libraylib5-dev`/`libraylib6-dev` builds for both architectures. The system-library deps above are still required either way. ### `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 apt) 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: sudo apt install libraylib-dev") endif() ``` > [!info] If `find_package(raylib)` fails > Some apt builds of raylib don't ship CMake config files. If CMake can't find it, link manually instead: replace the `find_package`/`target_link_libraries` lines with `target_link_libraries(calc_gui PRIVATE calc_lib raylib m dl pthread GL)`. The PPA build includes the CMake config. ### 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. --- ## 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 (the general config already enables it). --- ## Optional: static analysis and memory checking This is where Linux pulls ahead of Apple Silicon — you get the full toolkit: - **`clang-tidy`** — runs automatically when enabled via clangd's `--clang-tidy` flag (already set in the VS Code settings above). - **Valgrind** — the gold-standard memory checker, available on both `amd64` and `arm64`: ```bash valgrind --leak-check=full --show-leak-kinds=all ./build/calc 6 + 7 ``` - **AddressSanitizer** — built into gcc and clang. Catches buffer overflows, use-after-free, leaks: ```bash cmake -B build-asan -G Ninja -DCMAKE_C_FLAGS="-fsanitize=address -g -O1" cmake --build build-asan ./build-asan/calc 6 + 7 ``` - **UndefinedBehaviorSanitizer** — `-fsanitize=undefined`. Catches signed overflow, null derefs, etc. > [!tip] Valgrind vs sanitizers > Use **sanitizers** during development (fast, compiled into the binary) and **Valgrind** for a thorough pass (slower, no recompile needed, catches some things sanitizers miss). Don't combine ASan and Valgrind in the same run — they conflict. --- ## direnv — auto-activate this project's environment With direnv hooked into your shell (see [[General_Development_Ubuntu_Setup]]), an `.envrc` in the project root loads when you `cd` in. For this CMake project it's handy for running built binaries from `build/` by name and pinning the compiler CMake configures with. ```bash # from the project root cat > .envrc <<'EOF' # run build/calc as just `calc` after building PATH_add build # optional: pin the compiler for CMake's first configure export CC=clang export CXX=clang++ EOF direnv allow ``` > [!info] `.envrc` doesn't replace your build directory > CMake still configures and builds into `build/` as usual; `PATH_add build` only makes the resulting executables callable by name. Swap `CC`/`CXX` to `gcc`/`g++` if you prefer — they're read by CMake's first `configure`, so set them before `build/` exists (or delete and re-create it). --- ## 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 > Install build basics: `sudo apt install -y build-essential`. Confirm with `gcc --version`. > [!warning] raylib window fails to open in a VM > Raylib needs OpenGL. Enable 3D acceleration in your hypervisor, or use a virtio-gpu display. The CLI and tests don't need a display — only `calc_gui` does. > [!warning] gdb won't attach: "ptrace: Operation not permitted" > Some hardened/VM setups restrict ptrace. As a quick fix for a dev box: `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` (resets on reboot; make permanent in `/etc/sysctl.d/`). --- ## Summary — the one-shot C addition > [!warning] This is a checklist, not a script > Copy and paste one block at a time. After running these, also **append the C/C++ settings block** above to `~/.config/Code/User/settings.json`. ```bash # Compilers, build system, debugger, analysis tools sudo apt install -y cmake ninja-build clang clangd clang-format clang-tidy lldb gdb valgrind # GUI library + its system dependencies (optional, for calc_gui demo) sudo apt install -y libraylib-dev libgl1-mesa-dev libxi-dev libxcursor-dev \ libxrandr-dev libxinerama-dev libwayland-dev libxkbcommon-dev libasound2-dev # 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.vscode-clangd # Verify gcc --version && clang --version && cmake --version && ninja --version && gdb --version && valgrind --version ``` About five minutes of work on top of the general setup. --- ## Related notes - [[General_Development_Ubuntu_Setup]] - [[C_Development_Mac_Tahoe_Setup]] — the macOS counterpart - [[Python_Development_Ubuntu_Setup]] - [[Ruby_Development_Ubuntu_Setup]] - [[Go_Development_Ubuntu_Setup]] - [[Rust_Development_Ubuntu_Setup]] - [CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html) - [GDB Quick Reference](https://sourceware.org/gdb/current/onlinedocs/gdb/) - [Valgrind Quick Start](https://valgrind.org/docs/manual/quick-start.html)