Infra: Add CMake presets for every platform, and one home for AI assistant skills (#9742)

#### Brief overview of PR changes/additions

- Adds `CMakePresets.json` with configure, build and test presets for
macOS, Linux and Windows, including sanitizer and static-analysis
variants. Each is gated on the host system, so a listing only offers
what the current machine can build.
- Consolidates the AI assistant skills into `.agents/skills/`, which
Claude Code, GitHub Copilot and Cursor all read, replacing two copies
that had drifted into contradicting each other.
- Corrects the build documentation: the Windows toolchain is CLANG64
rather than MinGW64, and the previous "no need to specify number of
jobs" advice holds only for Ninja.

#### Motivation for adding to Mudlet

The instructions presented platform-specific build advice as though it
were universal, so following them on the wrong platform produced either
an unbounded parallel build or a toolchain the setup scripts refuse to
run.

#### Other info (issues closed, discussion etc)

`windows-debug` has been exercised on Windows: `CI/setup-windows-sdk.sh`
in an MSYS2 CLANG64 shell, then configure and build both to completion
against Qt 6.11.1 and Clang 22.1.7. `cmake --list-presets` correctly
offered only `windows-debug` there.

Note that `windows-debug` is a Debug configuration, whereas
`CI/build-mudlet-for-windows.sh` builds Release, so the two are not
equivalent.

`.claude/skills` is a symlink to `.agents/skills`, following the
existing pattern used by `CLAUDE.md`, `AGENTS.md` and `.cursorrules`. On
Windows checkouts without `core.symlinks` it lands as a plain file, in
which case Copilot and Cursor still read `.agents/skills` directly.

**Test case:** `cmake --preset macos-debug && cmake --build --preset
macos-debug`, then `ctest --preset macos-debug`. `cmake --list-presets`
should offer only the current platform's presets, and a variant such as
`macos-debug-nosan` should build into `build-macos-debug-nosan/` while
leaving `build/` untouched.

---------

Signed-off-by: Michael Conley <sousesider@gmail.com>
This commit is contained in:
Mike Conley 2026-08-12 15:28:13 -04:00 committed by GitHub
parent 6c2d2444b2
commit 6705cadd11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 579 additions and 111 deletions

View file

@ -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 |
| `<platform>-debug-nosan` | macOS / Linux | No sanitizers — faster to build and to run |
| `<platform>-debug-tsan` | macOS / Linux | ThreadSanitizer instead of AddressSanitizer |
| `<platform>-debug-ubsan` | macOS / Linux | UndefinedBehaviorSanitizer |
| `<platform>-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 `<platform>-debug` presets build into `build/`. Every variant builds into
`build-<preset-name>/` 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-<preset-name>/` 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 <n>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** (`<platform>-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
- <https://wiki.mudlet.org/w/Compiling_Mudlet> — full setup, including dependency installation

View file

@ -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: <short non-technical title>" \
--body "$(cat <<'EOF'
<the body from step 4>
EOF
)"
```
A draft can be marked ready later with `gh pr ready <number>`, 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.

View file

@ -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 "<prefix>: <short non-technical 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)
<Reference any issues with "Fixes #123" or "Closes #123" if applicable>
**Test case:** <Brief steps to verify the change works>
EOF
)"
```
Keep all descriptions terse and to the point. No fluff.

1
.claude/skills Symbolic link
View file

@ -0,0 +1 @@
../.agents/skills

View file

@ -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)
<Any related issues, discussions, or notes or "None" if not applicable>
```
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 <branch-name>` 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`: `<fork-owner>:<branch-name>` (e.g. `mpconley:feature/my-branch`)
- `title`: The generated PR title
- `body`: The generated PR body
To determine `<fork-owner>`, 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.

228
CMakePresets.json Normal file
View file

@ -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"
}
}
]
}

View file

@ -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/<name>/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-<preset-name>/`, so the binary is
under that directory rather than `build/`.

View file

@ -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-<preset-name>/` 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 <n>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
`<platform>-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"):