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
290 lines
9.7 KiB
Markdown
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`.
|