sublime-cpp/README.md
PeterChrz 40c23f9c6b
Sublime Text + clang C++ setup with portable installer
Build systems, Makefile, and a setup script that reproduces this
environment on another machine.

- CP.sublime-build: incremental make-based build, clang++, flags shared
  with clangd via compile_flags.txt
- compete_CPP.sublime-build: competitive-programming workflow, runs
  against inputf.in and diffs the result against expectedf.out
- Makefile: builds every .cpp in the tree into a binary beside its source
- setup.sh: export/install/check, path-neutral payload, idempotent
- README: setup notes, the Sublime $-expansion trap, and the -MMD -MP
  header-dependency limitation to fix later

The .sublime-workspace is deliberately excluded: it holds Sublime's
global recent-file history and absolute paths. Only the pane layout is
portable, extracted to sublime/layout.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCtkkFDkSGi868JWuinwcF
2026-09-07 10:46:56 -04:00

290 lines
9.7 KiB
Markdown

# Mocpp — C++ learning exercises
Chapter exercises, one binary per `.cpp`. Built with **clang++**, edited in
Sublime Text with **clangd** for diagnostics and completion.
## Layout
```
Mocpp/
├── Makefile # builds every .cpp in the tree
├── compile_flags.txt # fallback flags (used if a chapter dir has none)
├── README.md
├── source.cpp
└── ch1/
├── compile_flags.txt # flags for this chapter — clangd reads this too
├── 1-1.cpp → 1-1 # each .cpp builds to a binary beside it
└── 1-4.cpp → 1-4
```
## Building
From Sublime:
| Key | Action |
|-----|--------|
| `Ctrl+B` | build everything (incremental — silent if nothing changed) |
| `Ctrl+Shift+B`*Build all + Run this file* | build, then run the focused file |
| `Ctrl+Shift+B`*Build all + Run this file (with stdin)* | same, for `std::cin` exercises |
| `Ctrl+Shift+B`*Run this file only* | compile just the focused file, skip make |
| `Ctrl+Shift+B`*Clean* | delete all binaries, force a full rebuild |
From a terminal — identical result, `make` is the single source of truth:
```bash
make # build what changed
make clean # remove all binaries
```
The Sublime build file lives at
`~/.config/sublime-text/Packages/User/CP.sublime-build`.
## Flags: one file, two consumers
`compile_flags.txt` is read by **both** clangd (editor squiggles) and the
Makefile (the actual compile). That is deliberate — it is why the warnings you
see while typing are the warnings you get when building. Edit flags there and
nowhere else.
Current flags: `-std=c++20 -Wall -Wextra -Wconversion`
### Starting a new chapter
```bash
mkdir ch2
cp ch1/compile_flags.txt ch2/
```
Without its own copy a directory falls back to the root `compile_flags.txt`, so
it still gets warnings — but keeping a copy per chapter means you can vary the
standard per chapter later.
---
## TODO: add `-MMD -MP` when you start writing your own headers
**This is the one known limitation of the current Makefile.** Read this before
you create your first `.h` file.
The build tracks only `.cpp` timestamps. It has no idea headers exist. So if you
edit a header, **make does nothing and leaves you running a stale binary** — with
no error and no warning. Verified behaviour, not theory:
```
$ ./ch1/1-1
hello v1
$ sed -i 's/hello v1/hello v2/' ch1/greet.h # edit ONLY the header
$ make
# ← silence. nothing rebuilt.
$ ./ch1/1-1
hello v1 # ← WRONG. still the old code.
```
This will cost you an hour of debugging code that is already correct.
### The fix (tested, works)
`-MMD` makes clang emit a `.d` file listing every header the source included.
`-MP` adds a dummy target for each header, so a *deleted* header does not break
the build. `-include $(DEPS)` feeds those back to make, which then knows the
binary depends on the headers too.
Three changes to the `Makefile`:
```make
BINS := $(basename $(SRCS))
DEPS := $(addsuffix .d,$(BINS)) # 1. add this line
%: %.cpp
@flags=$$(cat $(dir $<)compile_flags.txt 2>/dev/null || cat $(ROOT)compile_flags.txt 2>/dev/null); \
echo "compiling $(notdir $<)"; \
$(CXX) $$flags -MMD -MP $< -o $@ # 2. add -MMD -MP here
-include $(DEPS) # 3. add this line
clean:
@rm -f $(BINS) $(DEPS) # 4. also remove .d files
@echo "cleaned"
```
After that, editing a header rebuilds what depends on it:
```
$ sed -i 's/hello v1/hello v2/' ch1/greet.h
$ make
compiling 1-1.cpp
$ ./ch1/1-1
hello v2
```
The generated `ch1/1-1.d` is just a make rule:
```
ch1/1-1: ch1/1-1.cpp ch1/greet.h
ch1/greet.h:
```
The leading `-` in `-include` matters: it stops make erroring on the first build,
when no `.d` files exist yet.
---
## `compete_CPP` — the contest workflow
A second build system, for problems that read input from stdin (Codeforces,
LeetCode, or any book exercise using `std::cin`). Select it with
**Tools → Build System → compete_CPP**.
It compiles the focused `.cpp`, feeds it `inputf.in` on stdin, writes stdout to
`outputf.out`, and prints both — so you never retype a sample input.
| Variant | Does |
|---|---|
| `Ctrl+B` | compile, run with `inputf.in`, show input and output |
| *Run + check against expectedf.out* | same, then `diff` the answer — prints PASS or FAIL |
| *Run (type input live)* | no redirect; type input into the build panel |
| *Setup: create inputf.in / expectedf.out here* | creates the three files in the current folder |
| *DIAGNOSTIC* | prints Sublime's variables |
The three files live **next to the source**, not at the repo root. That was the
bug in the original tutorial build: it looked for `inputf.in` in the source's
folder while the files sat one level up, so they were never read.
Typical loop: open a problem, run *Setup*, paste the sample input into
`inputf.in` and the expected answer into `expectedf.out`, then hit
*Run + check* until it says PASS.
```
--- outputf.out ---
6
--- diff (expected vs actual) ---
PASS
```
A wrong answer shows the diff and exits non-zero:
```
-99
+6
FAIL
```
A compile error stops before running, so you never diff a stale binary.
Note this build compiles **only the focused file** — it does not use the
Makefile. That is intentional: contest problems are self-contained single files.
Use the `CP` build system for the chapter exercises.
## Gotcha: `$` in the Sublime build file
Sublime expands **both** `${name}` and bare `$name` in `cmd`, `shell_cmd`, and
`working_dir`. Any name it does not recognise becomes an empty string *before*
bash runs.
So a shell variable like `$SRC` is silently deleted, and a guard such as
`if [ -z "$SRC" ]` becomes `if [ -z "" ]` — always true. The symptom is a build
that insists there is no active file no matter what you do.
Rules for editing `CP.sublime-build`:
- Prefer Sublime's own `${file}`, `${file_path}`, `${file_base_name}` plus
`$(...)`, and use no bash variables at all.
- Any `$` meant for bash must be escaped `\$` (written `\\$` in the JSON).
- Test a variant with the *DIAGNOSTIC* build, which prints what Sublime actually
substituted.
Reference: https://www.sublimetext.com/docs/build_systems.html
## Saving the window layout
Three mechanisms, easy to confuse:
| What | Where | Saves |
|---|---|---|
| **Project** | `Mocpp.sublime-project` | folders, settings, excludes — commit this |
| **Workspace** | `Mocpp.sublime-workspace` (auto) | open tabs, pane layout, cursors — do NOT commit |
| **Hot exit** | automatic | last session, even with no project |
Open it once with **Project → Open Project → `Mocpp.sublime-project`**. From then
on Sublime writes a `.sublime-workspace` beside it holding your exact pane
arrangement and open files, and restores them next launch.
Keybindings for the contest layout (code left, input top-right, output
bottom-right), in `Packages/User/Default (Linux).sublime-keymap`:
| Key | Layout |
|---|---|
| `Ctrl+Alt+Shift+C` | three-pane contest layout |
| `Ctrl+Alt+Shift+1` | back to a single pane |
Sublime's built-in `Alt+Shift+1..5` (columns) and `Alt+Shift+8/9` (grids) still
work; the bindings above just add a split the menu does not offer.
If you commit this directory to git:
```
*.sublime-workspace
```
## Installing this setup on another machine
`setup.sh` moves the whole environment. Clone the repo, run install:
```bash
git clone <this repo> ~/git/Mocpp
cd ~/git/Mocpp
./setup.sh install
```
| Command | Does |
|---|---|
| `./setup.sh check` | verify the toolchain, change nothing |
| `./setup.sh install` | install `sublime/` onto this machine, then build |
| `./setup.sh export` | re-capture this machine's Sublime config into `sublime/` |
**Run `export` after changing a build system**, so the payload in `sublime/`
stays current, then commit. The script never embeds copies of the config — it
installs from `sublime/`, so there is nothing to drift out of sync.
What install does:
- checks for `clang++`, `clangd`, `make`, `git`, and prints the right
package-manager command for your distro if any are missing
- finds Sublime's config dir (differs on Linux and macOS) and names the keymap
per platform
- substitutes `__PROJECT_ROOT__` in the build files with wherever you cloned to,
so the repo need not live at the same path
- merges the Package Control list rather than overwriting yours, so Sublime
offers to install the missing packages on next start
- runs `make` as a smoke test and fails loudly if the toolchain is broken
Re-running is a no-op: files are compared after substitution, so nothing is
rewritten and no `.bak` files accumulate. Anything it would genuinely overwrite
is backed up as `<name>.bak-<timestamp>` first.
### Why the workspace is not copied
`compete.sublime-workspace` is deliberately **not** part of the payload and is
gitignored. Sublime stores your global recent-file history in it — on this
machine 17 of its 21 entries were unrelated personal paths, including documents
and TLS certificates. It also hardcodes absolute paths.
Only the pane layout is portable, so `export` extracts just that into
`sublime/layout.json`, and `install` builds a clean workspace from it. The same
layout is also bound to `Ctrl+Alt+Shift+C`, so it survives even without a
workspace file.
## When this setup runs out
It is built for single-file exercises. Move to CMake generating
`compile_commands.json` once you have multiple source files linking into one
program, external libraries, or more than one build configuration:
```bash
cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -sf build/compile_commands.json .
```
Then delete `compile_flags.txt` — if both exist in a directory,
`compile_commands.json` wins and the other becomes a silently ignored second
source of truth. Point the Sublime build at `ninja -C build`.