diff --git a/.agents/skills/build-mudlet/SKILL.md b/.agents/skills/build-mudlet/SKILL.md new file mode 100644 index 000000000..57931ceee --- /dev/null +++ b/.agents/skills/build-mudlet/SKILL.md @@ -0,0 +1,124 @@ +--- +name: build-mudlet +description: >- + Read before running any cmake, ninja or make command in this repository, and when asked to + build, rebuild, compile, or run Mudlet locally. Gives the correct per-platform configure and + build invocation, and the parallelism pitfalls that make builds slow or thrash the machine. +license: GPL-2.0-or-later +--- + +## When to use + +Read this **before** typing a build command, not after one fails. The correct invocation differs +per platform, and the wrong one is not merely slower — see Pitfalls. + +## Use a preset — every platform, one command + +`CMakePresets.json` in the repository root encodes the generator, build type and sanitizer +settings, so no platform-specific flags need to be remembered or typed. Run +`cmake --list-presets` to see the ones available on the current machine. + +Presets need CMake 3.25.1 or newer, which is the same floor `CMakeLists.txt` already sets for +building Mudlet at all. An older CMake reports an unknown argument or an unsupported preset +version rather than anything informative, so check `cmake --version` if `--preset` is rejected. + +```bash +cmake --preset macos-debug # configure +cmake --build --preset macos-debug # build +ctest --preset macos-debug # run the test suite +``` + +| Preset | Platform | Notes | +| --- | --- | --- | +| `macos-debug` / `linux-debug` | macOS / Linux | Ninja, Debug, AddressSanitizer on | +| `windows-debug` | Windows | MSYS2 CLANG64, Ninja, Debug | +| `-debug-nosan` | macOS / Linux | No sanitizers — faster to build and to run | +| `-debug-tsan` | macOS / Linux | ThreadSanitizer instead of AddressSanitizer | +| `-debug-ubsan` | macOS / Linux | UndefinedBehaviorSanitizer | +| `-static-analysis` | macOS / Linux | Runs clang-tidy and cppcheck during compilation | +| `linux-lowspec` | Linux | No sanitizers, no updater, no 3D mapper, 2 jobs — Raspberry Pi and similar | + +Every configure preset has a matching build and test preset of the same name, and all three are +conditioned on the host system — so `cmake --list-presets` on macOS will not offer `linux-debug`, +and `ctest --preset X` always runs against the tree that `cmake --build --preset X` produced. + +The plain `-debug` presets build into `build/`. Every variant builds into +`build-/` instead, so an AddressSanitizer tree and a sanitizer-free tree can coexist +without forcing each other to rebuild. The `/build*` entry in `.gitignore` covers all of them. + +### Qt discovery + +The presets do not pin a Qt location. CMake installed via Homebrew or a distribution package +finds Qt on its default search path. If configuring fails to find Qt — likely with a CMake from +the Qt online installer — pass the prefix explicitly: + +```bash +cmake --preset macos-debug -DCMAKE_PREFIX_PATH="$(brew --prefix qt6)" +``` + +### Windows + +Builds run under MSYS2. Use the **CLANG64** environment: `CI/setup-windows-sdk.sh`, which installs +the dependencies, accepts only that one and exits on any other `MSYSTEM`. Open a CLANG64 shell, not +MINGW64, and make sure it is a real MSYS2 shell — Git for Windows' bash can carry an inherited +`MSYSTEM` that makes it look like one, in which case `MSYSTEM_PREFIX` is empty. + +The preset itself is not tied to a particular environment: it reads `MSYSTEM_PREFIX`, which MSYS2 +sets in each of its shells, so it follows whichever one is provisioned. On an ARM64 host the native +environment is `CLANGARM64`, which the setup script does not currently handle, so dependencies have +to come from a CLANG64 shell. + +Sanitizers are not enabled on Windows, so there is no `-nosan` variant. + +## Running the result + +```bash +# macOS +./build/src/mudlet.app/Contents/MacOS/mudlet + +# Linux +./build/src/mudlet +``` + +Mudlet is a graphical desktop application; launching it opens a window. Variant presets put the +binary under `build-/` instead. Allow up to 10 minutes for a full build. + +## Pitfalls + +**Never pass `--parallel` without a job count on a Makefiles build.** `cmake --build . --parallel` +with no number passes a bare `-j` to make, which imposes no limit on concurrent jobs: make starts +as many compilers as the dependency graph allows, exhausting RAM and swap. Ninja defaults to a +bounded job count, which is why the presets use it. In a pre-existing Makefiles tree, use +`make -j $(nproc)` on Linux or `make -j $(sysctl -n hw.ncpu)` on macOS. + +**ccache is wired in automatically** — `CMakeLists.txt` sets it as the compiler launcher whenever +ccache is installed. A full cache evicts objects continuously, so switching branches can trigger a +near-full rebuild. Run `ccache -s`; if `Cache size` has reached `Max cache size`, raise it with +`ccache -M G`. + +**Sanitizers are on by default** on every non-Windows build, regardless of build type +(`src/cmake/EnableSanitizers.cmake` defaults `USE_SANITIZER` to `address`). They cost both compile +time and runtime speed. Use a `-nosan` preset when not chasing a memory bug. + +For a combination the presets do not cover, pass a **CMake list — semicolon-separated, not +comma-separated**: `-DUSE_SANITIZER="Address;Undefined"`. A comma-separated value is treated as one +name, which silently skips the per-sanitizer options such as `-fno-omit-frame-pointer`. + +Usable names are `Address`, `Thread` and `Undefined` on macOS, plus `Memory` and `Leak` on Linux. +`MemoryWithOrigins` appears in the `USE_SANITIZER` cache docstring but has no mapping declared, so +it always fails. An unavailable or incompatible selection raises a `SEND_ERROR`: configure runs to +completion, but generation is blocked. + +**Static analysis** (`-static-analysis`) needs clang-tidy and cppcheck on `PATH`. They +are independent: whichever is present runs. A missing clang-tidy produces a CMake warning, but a +missing cppcheck only prints a `STATUS` line that is easy to miss — so check the configure output +rather than assuming both ran. On macOS, Homebrew's llvm is keg-only, so clang-tidy is not on +`PATH` by default. + +**Configuring a second generator in an existing build directory fails.** CMake cannot switch +generator in place; configure into a fresh directory instead. + +## Related + +- `docs/platform-builds.md` — platform detail and the compile-time debugging defines +- — full setup, including dependency installation diff --git a/.agents/skills/open-pr/SKILL.md b/.agents/skills/open-pr/SKILL.md new file mode 100644 index 000000000..b52ea9fce --- /dev/null +++ b/.agents/skills/open-pr/SKILL.md @@ -0,0 +1,130 @@ +--- +name: open-pr +description: >- + Publish the current branch and open a pull request against the upstream Mudlet repository, + using the project's PR template and the title prefix that Danger enforces. Use when asked to + open a PR, create a pull request, or push changes upstream. +license: GPL-2.0-or-later +argument-hint: Optional hint about what this PR does (e.g. "adds shimmer blink effect") +user-invocable: true +disable-model-invocation: true +--- + +## When to use + +Use when the user asks to open a pull request, create a PR, or push their changes upstream. This +publishes the current branch to their fork if needed, then opens a PR against upstream +`development`. + +## Procedure + +1. **Gather context.** Run `git status --porcelain` first. If anything is uncommitted, stop: ask + the user to commit what belongs in the PR, or to confirm explicitly that it should be left out. + A pull request is built from commits, so uncommitted work is silently absent from it. + + Then run `git log upstream/development..HEAD --oneline` for the commit messages, + `git diff upstream/development...HEAD` to see what actually changed, and + `git branch --show-current` for the branch name. An empty commit list means there is nothing to + open a pull request for. + + `git branch --show-current` prints nothing on a detached HEAD. If it is empty, stop and ask the + user to check out a named branch — every later step needs that name, and an empty one produces + a malformed push and an unusable `--head`. + +2. **Choose the title prefix.** `docs/CONTRIBUTING.md` states the rule and Danger enforces it on + every PR. Choosing between the four is the part that is not written down elsewhere: + + | Prefix | Use for | + | --- | --- | + | `Fix: ` | bug fixes visible to users | + | `Improve: ` | enhancements to existing user-facing functionality | + | `Add: ` | new user-facing features or capabilities | + | `Infra: ` | build system, CI, tooling, refactoring, and other non-player-visible changes | + + Distinguishing the last two: user-visible behavior change is `Improve`, everything internal is + `Infra`, however large the diff. + +3. **Write the title.** Use the exact form `Prefix: Summary` — capitalized prefix, colon, single + space, then a summary whose first word is also capitalized. Keep the summary short and + understandable to a non-technical reader; it becomes a line in the PTB changelog, which is why + the casing is worth being consistent about even though Danger's check is case-insensitive and + does not require the colon. Danger warns on overly long titles, so keep it brief. + + Write in American English, matching the rest of the project's user-facing text — "color" not + "colour", "standardize" not "standardise". + + ``` + Fix: Profiles named "." or ".." no longer delete every profile when removed + Improve: OSC 8 hyperlink handling, and a setting to turn it off + Add: Text-to-speech support for incoming game text + Infra: Tidy up how CI installs Lua + ``` + + Dependabot raises its own PRs as `Infrastructure: Bump ...`; that is generated upstream and is + not something to correct by hand. + +4. **Draft the body.** Read `.github/PULL_REQUEST_TEMPLATE.md` and fill in its headings — read it + rather than reproducing it here, so this skill cannot drift from the real template. Note that + passing `--body` to `gh` bypasses the template file, which is why it has to be read and filled + in explicitly. + + House style on top of the template: keep each section terse, 1-3 bullet points for the overview + and a single sentence of motivation. Add a `**Test case:**` line at the end giving brief steps + to verify the change — not part of the template, but reviewers expect it. No fluff. + +5. **Show the draft title and body to the user, and ask whether to open it as a draft PR or as + ready for review.** Wait for both answers before anything is published — never assume either. + Draft suits work that is still moving, wants early CI feedback, or needs discussion before + reviewers spend time on it; ready for review suits a change that is complete and tested. + +6. **Confirm the fork before pushing anything.** `origin` is not guaranteed to be the user's fork, + and pushing to the wrong remote is awkward to undo, so establish this before the push rather + than after. Derive the head explicitly too — a checkout commonly has several remotes, including + other people's forks, and a head inferred by `gh` can point at the wrong one. + + ```bash + BRANCH=$(git branch --show-current) + FORK_OWNER=$(printf '%s' "$(git remote get-url --push origin)" \ + | sed -E 's#\.git$##; s#^[a-zA-Z+]+://##; s#^[^@/]+@##; + s#^[^/:]+(:[0-9]+)?/##; s#^[^/:]+:##; s#/[^/]*$##') + ``` + + Use `--push`: a remote can carry a separate `pushurl`, and it is the push URL the branch + actually lands on, so the head must be derived from the same URL `git push` will use. + + This handles the `https://`, `ssh://` and `git@host:owner/repo` forms, with or without an + explicit port. Check the result before using it: if `$FORK_OWNER` is empty or still contains + `/`, `:` or `@`, the URL was not in a form this understands — stop and report it rather than + building a malformed `--head`. + + Show `$FORK_OWNER` to the user and confirm it is the fork the pull request should come from. + +7. **Publish the branch** with `git push -u origin "$BRANCH"`. Do this every time, not only when + the branch lacks an upstream — a branch that already tracks `origin` can still hold local + commits that have not been pushed, and those would be missing from the pull request. + + The push must succeed before continuing. If it fails, stop and report the error; do not open a + pull request against a branch whose commits are not on the fork. + +8. **Open the PR** against upstream, adding `--draft` if that is what the user chose: + + ```bash + gh pr create --repo Mudlet/Mudlet --base development \ + --head "${FORK_OWNER}:${BRANCH}" \ + --title "Fix: " \ + --body "$(cat <<'EOF' + + EOF + )" + ``` + + A draft can be marked ready later with `gh pr ready `, so choosing draft is the + reversible option. + +9. **Report the result** — the PR URL on success, the error output on failure. + +## Notes + +- Before a human signs off on AI-assisted work they must have built and manually tested it; see + the commit-trailer policy in `docs/ai-instructions.md`. Do not fabricate a `Signed-off-by`. +- Never force-push to a remote branch. diff --git a/.claude/commands/pr-mudlet.md b/.claude/commands/pr-mudlet.md deleted file mode 100644 index 2ecf9d73d..000000000 --- a/.claude/commands/pr-mudlet.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: Create a PR with Mudlet's template format -allowed-tools: Bash, Read, Glob, Grep ---- - -Create a pull request for the current branch. Follow these steps: - -1. Run `git log development..HEAD --oneline` and `git diff development...HEAD` to understand all changes -2. Determine the appropriate title prefix: - - `add:` for new user-facing features - - `improve:` for enhancements to existing user-facing features - - `fix:` for bug fixes visible to users - - `infrastructure:` for internal/non-player-visible changes (refactoring, build system, code cleanup, developer tooling) -3. Write a title that a non-technical user can understand (appears in changelogs) - -Create the PR using `gh pr create` with this exact template format: - -``` -gh pr create --title ": " --body "$(cat <<'EOF' -#### Brief overview of PR changes/additions -<1-3 terse bullet points describing what changed> - -#### Motivation for adding to Mudlet -<1 sentence explaining why this change matters> - -#### Other info (issues closed, discussion etc) - - -**Test case:** -EOF -)" -``` - -Keep all descriptions terse and to the point. No fluff. diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 000000000..2b7a412b8 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.github/skills/open-pr/SKILL.md b/.github/skills/open-pr/SKILL.md deleted file mode 100644 index efeaf54a6..000000000 --- a/.github/skills/open-pr/SKILL.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: open-pr -description: >- - Publish the current branch and open a pull request to the upstream Mudlet repository. - Generates a concise, non-technical PR title and description following the project template. -argument-hint: Optional hint about what this PR does (e.g. "adds shimmer blink effect") -user-invocable: true -disable-model-invocation: true ---- - -## When to Use - -Use this skill when the user asks to open a pull request, create a PR, or push their changes upstream. The skill publishes the current branch if needed and opens a PR against the upstream `development` branch. - -## Procedure - -1. **Gather context**: Run `git diff upstream/development...HEAD` to understand what changed. Also run `git log upstream/development..HEAD --oneline` for commit messages and `git branch --show-current` for the branch name. - -2. **Determine the PR title prefix** based on the nature of the changes: - - `Fix: ` — for bug fixes - - `Improve: ` — for enhancements to existing functionality - - `Add: ` — for new features or capabilities - - `Infrastructure: ` — for build system, CI, tooling, or project configuration changes - -3. **Generate the PR title**: After the prefix, write a short, non-technical summary that anyone can understand. This title appears in PTB changelogs. - -4. **Generate the PR body** using this exact template format: - - ``` - #### Brief overview of PR changes/additions - - <1-3 sentences summarizing what changed, written for a non-technical audience> - - #### Motivation for adding to Mudlet - - <1-2 sentences explaining why this change is valuable> - - #### Other info (issues closed, discussion etc) - - - ``` - -5. **Show the draft title and body to the user** and ask for confirmation before proceeding. - -6. **Publish the branch** if it has not been pushed to the user's fork yet: - - Run `git push -u origin ` to push and set the upstream tracking branch. - -7. **Open the pull request** using the `mcp_github_create_pull_request` tool: - - `owner`: `Mudlet` - - `repo`: `Mudlet` - - `base`: `development` - - `head`: `:` (e.g. `mpconley:feature/my-branch`) - - `title`: The generated PR title - - `body`: The generated PR body - - To determine ``, parse it from the `origin` remote URL. - -8. **Report the result**: Show the user the PR URL on success, or the error details on failure. diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..d4a857848 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,228 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 1 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + } + }, + { + "name": "variant", + "hidden": true, + "inherits": "base", + "binaryDir": "${sourceDir}/build-${presetName}" + }, + { + "name": "macos-debug", + "displayName": "macOS Debug (Ninja, AddressSanitizer)", + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "macos-debug-nosan", + "displayName": "macOS Debug, no sanitizers (fastest build and run)", + "inherits": ["variant", "macos-debug"], + "cacheVariables": { + "USE_SANITIZER": "" + } + }, + { + "name": "macos-debug-tsan", + "displayName": "macOS Debug, ThreadSanitizer", + "inherits": ["variant", "macos-debug"], + "cacheVariables": { + "USE_SANITIZER": "Thread" + } + }, + { + "name": "macos-debug-ubsan", + "displayName": "macOS Debug, UndefinedBehaviorSanitizer", + "inherits": ["variant", "macos-debug"], + "cacheVariables": { + "USE_SANITIZER": "Undefined" + } + }, + { + "name": "macos-static-analysis", + "displayName": "macOS Debug with clang-tidy and cppcheck", + "inherits": ["variant", "macos-debug"], + "cacheVariables": { + "ENABLE_STATIC_ANALYSIS": "ON" + } + }, + { + "name": "linux-debug", + "displayName": "Linux Debug (Ninja, AddressSanitizer)", + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "linux-debug-nosan", + "displayName": "Linux Debug, no sanitizers (fastest build and run)", + "inherits": ["variant", "linux-debug"], + "cacheVariables": { + "USE_SANITIZER": "" + } + }, + { + "name": "linux-debug-tsan", + "displayName": "Linux Debug, ThreadSanitizer", + "inherits": ["variant", "linux-debug"], + "cacheVariables": { + "USE_SANITIZER": "Thread" + } + }, + { + "name": "linux-debug-ubsan", + "displayName": "Linux Debug, UndefinedBehaviorSanitizer", + "inherits": ["variant", "linux-debug"], + "cacheVariables": { + "USE_SANITIZER": "Undefined" + } + }, + { + "name": "linux-static-analysis", + "displayName": "Linux Debug with clang-tidy and cppcheck", + "inherits": ["variant", "linux-debug"], + "cacheVariables": { + "ENABLE_STATIC_ANALYSIS": "ON" + } + }, + { + "name": "linux-lowspec", + "displayName": "Linux Debug for low-memory machines (Raspberry Pi and similar)", + "inherits": ["variant", "linux-debug"], + "cacheVariables": { + "USE_SANITIZER": "", + "USE_UPDATER": "OFF", + "USE_3DMAPPER": "OFF" + } + }, + { + "name": "windows-debug", + "displayName": "Windows Debug (MSYS2, Ninja)", + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "CMAKE_PREFIX_PATH": "$env{MSYSTEM_PREFIX}" + } + } + ], + "buildPresets": [ + { + "name": "on-macos", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "on-linux", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { "name": "macos-debug", "inherits": "on-macos", "configurePreset": "macos-debug" }, + { "name": "macos-debug-nosan", "inherits": "on-macos", "configurePreset": "macos-debug-nosan" }, + { "name": "macos-debug-tsan", "inherits": "on-macos", "configurePreset": "macos-debug-tsan" }, + { "name": "macos-debug-ubsan", "inherits": "on-macos", "configurePreset": "macos-debug-ubsan" }, + { "name": "macos-static-analysis", "inherits": "on-macos", "configurePreset": "macos-static-analysis" }, + { "name": "linux-debug", "inherits": "on-linux", "configurePreset": "linux-debug" }, + { "name": "linux-debug-nosan", "inherits": "on-linux", "configurePreset": "linux-debug-nosan" }, + { "name": "linux-debug-tsan", "inherits": "on-linux", "configurePreset": "linux-debug-tsan" }, + { "name": "linux-debug-ubsan", "inherits": "on-linux", "configurePreset": "linux-debug-ubsan" }, + { "name": "linux-static-analysis", "inherits": "on-linux", "configurePreset": "linux-static-analysis" }, + { + "name": "linux-lowspec", + "inherits": "on-linux", + "configurePreset": "linux-lowspec", + "jobs": 2 + }, + { + "name": "windows-debug", + "configurePreset": "windows-debug", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ], + "testPresets": [ + { + "name": "test-defaults", + "hidden": true, + "output": { + "outputOnFailure": true + } + }, + { + "name": "on-macos", + "hidden": true, + "inherits": "test-defaults", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "on-linux", + "hidden": true, + "inherits": "test-defaults", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { "name": "macos-debug", "inherits": "on-macos", "configurePreset": "macos-debug" }, + { "name": "macos-debug-nosan", "inherits": "on-macos", "configurePreset": "macos-debug-nosan" }, + { "name": "macos-debug-tsan", "inherits": "on-macos", "configurePreset": "macos-debug-tsan" }, + { "name": "macos-debug-ubsan", "inherits": "on-macos", "configurePreset": "macos-debug-ubsan" }, + { "name": "macos-static-analysis", "inherits": "on-macos", "configurePreset": "macos-static-analysis" }, + { "name": "linux-debug", "inherits": "on-linux", "configurePreset": "linux-debug" }, + { "name": "linux-debug-nosan", "inherits": "on-linux", "configurePreset": "linux-debug-nosan" }, + { "name": "linux-debug-tsan", "inherits": "on-linux", "configurePreset": "linux-debug-tsan" }, + { "name": "linux-debug-ubsan", "inherits": "on-linux", "configurePreset": "linux-debug-ubsan" }, + { "name": "linux-static-analysis", "inherits": "on-linux", "configurePreset": "linux-static-analysis" }, + { "name": "linux-lowspec", "inherits": "on-linux", "configurePreset": "linux-lowspec" }, + { + "name": "windows-debug", + "inherits": "test-defaults", + "configurePreset": "windows-debug", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ] +} diff --git a/docs/ai-instructions.md b/docs/ai-instructions.md index ab37b777f..847993467 100644 --- a/docs/ai-instructions.md +++ b/docs/ai-instructions.md @@ -4,6 +4,13 @@ Mudlet is a cross-platform MUD client built with Qt6 and C++20, providing scripting capabilities in Lua 5.1. The project emphasizes "powerful simplicity" - clean interface with deep customization options. +## Skills + +Task-specific instructions live in `.agents/skills//SKILL.md`, in the [Agent Skills](https://agentskills.io) format. Claude Code, GitHub Copilot and Cursor all read this directory (Claude Code via the `.claude/skills` symlink). Read the relevant one before starting that kind of task rather than working from memory: + +- `build-mudlet` - building, rebuilding or running Mudlet on any platform +- `open-pr` - publishing a branch and opening a pull request upstream + ## Coding standards All files should end with a newline character at the end of the file. @@ -131,10 +138,11 @@ To demonstrate a bug fix or UI change with a screen recording, follow the before ## Build system notes +- **Before running any `cmake`, `ninja` or `make` command, read the `build-mudlet` skill in `.agents/skills/build-mudlet/SKILL.md`.** It carries the per-platform invocation and the parallelism pitfalls; an unbounded build command can saturate the machine's memory. - **Build system**: CMake (handles platform-specific configurations). See https://wiki.mudlet.org/w/Compiling_Mudlet for instructions. - Check code quality with clang-tidy using `.clang-tidy` configuration file - Allow up to 10mins for a build - it can take a while -- Building on macOS or Windows, and compile-time debugging defines: see `docs/platform-builds.md` +- Platform specifics and compile-time debugging defines: see `docs/platform-builds.md` ### Code formatting @@ -170,21 +178,22 @@ Before the human signs off, they must have built and manually tested the change Both trailers go at the end of the commit message. Apply this to every AI-assisted commit, not just the first. See the "AI Coding Assistants" section in `docs/CONTRIBUTING.md` for the full policy. -### Building on Linux +### Building For complete setup instructions, see: https://wiki.mudlet.org/w/Compiling_Mudlet +Build through a preset from `CMakePresets.json`. Run `cmake --list-presets` to see the ones offered +on the current machine — the listing is filtered by host system, so only that platform's presets +appear. Read the `build-mudlet` skill for the per-platform detail: + ```bash -# cd to the right build directory -cd /path/to/Mudlet/build - -# configure (only needed the first time) -cmake ../ -G Ninja - -# Compile using this command and wait up to 10mins for a build. Cmake runs the build in parallel by default, no need to specify number of jobs: -cmake --build . +# from the repository root; wait up to 10mins for a full build +cmake --preset linux-debug +cmake --build --preset linux-debug # Run Mudlet - it's a visual, desktop application -cd /path/to/Mudlet/build -./src/mudlet +./build/src/mudlet # macOS: ./build/src/mudlet.app/Contents/MacOS/mudlet ``` + +Variant presets such as `linux-debug-nosan` build into `build-/`, so the binary is +under that directory rather than `build/`. diff --git a/docs/platform-builds.md b/docs/platform-builds.md index 239536c33..8d33f9eb4 100644 --- a/docs/platform-builds.md +++ b/docs/platform-builds.md @@ -7,20 +7,88 @@ For complete setup instructions, see: https://wiki.mudlet.org/w/Compiling_Mudlet **Essential build commands:** ```bash -# Build -cd /path/to/Mudlet/build -# wait up to 10mins for a build -cmake ../../Mudlet -DCMAKE_PREFIX_PATH=`brew --prefix qt6` -make -j `sysctl -n hw.ncpu` +cd /path/to/Mudlet +# wait up to 10mins for a full build +cmake --preset macos-debug +cmake --build --preset macos-debug -# Run Mudlet - use absolute path to avoid directory confusion -/path/to/Mudlet/build/src/mudlet.app/Contents/MacOS/mudlet +# Run Mudlet +./build/src/mudlet.app/Contents/MacOS/mudlet ``` +Run `cmake --list-presets` to see the presets available on your machine; alongside `macos-debug` +there are `-nosan`, `-tsan` and `-ubsan` variants and a `macos-static-analysis` preset. Variants +build into `build-/` rather than `build/`, so several configurations can coexist +without invalidating each other. + +The presets do not pin a Qt location, relying on CMake's default search path. If Qt is not found, +pass it explicitly: `cmake --preset macos-debug -DCMAKE_PREFIX_PATH="$(brew --prefix qt6)"`. + +**Do not use `cmake --build . --parallel` without a job count in a Makefiles build tree.** A bare +`--parallel` passes `-j` with no number to make, which imposes no limit on concurrent jobs; make +will start as many compilers as the dependency graph allows, exhausting RAM and swap and finishing +slower than a bounded build. Ninja, which the presets use, defaults to a bounded job count. In an +existing Makefiles tree, use `make -j $(sysctl -n hw.ncpu)`. + +ccache is enabled automatically whenever it is installed. A full cache evicts objects continuously, +so branch switches can trigger near-full rebuilds — run `ccache -s`, and if `Cache size` has +reached `Max cache size`, raise it with `ccache -M G`. + ## Building on Windows For complete setup instructions, see: https://wiki.mudlet.org/w/Compiling_Mudlet#Compiling_on_Windows +Builds run under MSYS2, in the **CLANG64** environment — open a CLANG64 shell, not MINGW64, and +check it is a real MSYS2 shell rather than Git for Windows' bash carrying an inherited `MSYSTEM` +(`MSYSTEM_PREFIX` is empty in the latter). `CI/setup-windows-sdk.sh` and +`CI/build-mudlet-for-windows.sh` exit with an error on any other `MSYSTEM`, including the +`CLANGARM64` environment native to ARM64 hosts. + +The `windows-debug` preset reads `MSYSTEM_PREFIX`, which MSYS2 sets in each of its shells, so the +preset follows whichever environment is provisioned: + +```bash +cmake --preset windows-debug +cmake --build --preset windows-debug +``` + +Sanitizers are not enabled on Windows (`src/CMakeLists.txt` guards them with `if(NOT WIN32)`), +so there is no `-nosan` variant. + +## Sanitizers and static analysis + +Sanitizers are enabled on every non-Windows build; `USE_SANITIZER` defaults to `address`. Use the +`-tsan` / `-ubsan` / `-nosan` presets to change that, or pass a CMake list — semicolon-separated, +not comma-separated — such as `-DUSE_SANITIZER="Address;Undefined"`. A comma-separated value is +read as a single name, which silently skips the per-sanitizer options such as +`-fno-omit-frame-pointer`. + +Usable names are `Address`, `Thread` and `Undefined` on macOS, plus `Memory` and `Leak` on Linux. +`MemoryWithOrigins` appears in the `USE_SANITIZER` cache docstring but has no mapping declared in +`src/cmake/EnableSanitizers.cmake`, so it always fails. An unavailable or incompatible selection +raises a `SEND_ERROR`: configure finishes, but generation is blocked. + +Static analysis (clang-tidy and cppcheck) runs during compilation with the +`-static-analysis` presets, which set `ENABLE_STATIC_ANALYSIS=ON`. The two tools are +independent — whichever is on `PATH` runs. A missing clang-tidy produces a CMake warning, but a +missing cppcheck only emits a `STATUS` line, so read the configure output rather than assuming both +are active. + +Because IDEs read `CMakePresets.json` natively, selecting one of these presets in CLion, VS Code or +Qt Creator is enough — no per-IDE sanitizer configuration is needed. + +## Optional feature modules + +Six feature modules are declared through `include_optional_module` in `CMakeLists.txt`: the updater, +fonts, 3D mapper, shader hot-reloading, memory tracking and the build-type splash screen. Each has a +`USE_*` option and a `WITH_*` name, and they are **not** interchangeable — `cmake/IncludeOptionalModule.cmake` +reads the `WITH_*` name from the **environment** only. So `-DWITH_UPDATER=NO` on the command line is +accepted by CMake and silently ignored; use `-DUSE_UPDATER=OFF`, or set `WITH_UPDATER=NO` in the +environment. Note that shader hot-reloading and memory tracking default to OFF, the rest to ON. + +This applies only to those six. Other `WITH_*` names are ordinary options: `WITH_SENTRY` and +`SENTRY_SEND_DEBUG` are declared with `option()` and are set on the command line as normal. + ## Debugging options `src/CMakeLists.txt` contains commented debugging defines for development (search "Debugging code inclusions"):