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

9.7 KiB

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+BBuild all + Run this file build, then run the focused file
Ctrl+Shift+BBuild all + Run this file (with stdin) same, for std::cin exercises
Ctrl+Shift+BRun this file only compile just the focused file, skip make
Ctrl+Shift+BClean delete all binaries, force a full rebuild

From a terminal — identical result, make is the single source of truth:

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

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:

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:

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:

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.