# 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 ~/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 `.bak-` 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`.