#!/usr/bin/env bash # Portable installer for this C++ / Sublime Text setup. # # ./setup.sh export copy this machine's Sublime config into ./sublime/ # ./setup.sh install install ./sublime/ onto this machine # ./setup.sh check verify the toolchain and report, change nothing # # Design notes: # - Configs live in ./sublime/ so they are version-controlled and travel with # the repo. The script never embeds copies that could drift. # - The .sublime-workspace is NEVER copied. It holds absolute paths and # Sublime's global recent-file history, which is machine-specific and # frequently personal. Only the pane layout is portable, so install # regenerates a clean workspace from sublime/layout.json. set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PAYLOAD="$REPO/sublime" STAMP="$(date +%Y%m%d-%H%M%S)" c_ok() { printf ' \033[32mok\033[0m %s\n' "$*"; } c_warn() { printf ' \033[33mwarn\033[0m %s\n' "$*"; } c_err() { printf ' \033[31mFAIL\033[0m %s\n' "$*"; } head_() { printf '\n\033[1m%s\033[0m\n' "$*"; } # ---------------------------------------------------------------- locate ST -- sublime_dir() { case "$(uname -s)" in Darwin) echo "$HOME/Library/Application Support/Sublime Text" ;; *) echo "$HOME/.config/sublime-text" ;; esac } # Keymaps are named per platform. keymap_name() { case "$(uname -s)" in Darwin) echo "Default (OSX).sublime-keymap" ;; *) echo "Default (Linux).sublime-keymap" ;; esac } # ---------------------------------------------------------------- export ----- do_export() { local user_dir; user_dir="$(sublime_dir)/Packages/User" head_ "Exporting Sublime config from $user_dir" [ -d "$user_dir" ] || { c_err "not found: $user_dir"; exit 1; } mkdir -p "$PAYLOAD" local f for f in CP.sublime-build compete_CPP.sublime-build \ LSP-clangd.sublime-settings; do if [ -f "$user_dir/$f" ]; then # Replace this machine's project path with a placeholder so the # payload is portable and contains no absolute paths. sed "s|$REPO|__PROJECT_ROOT__|g" "$user_dir/$f" > "$PAYLOAD/$f" c_ok "$f" else c_warn "missing, skipped: $f"; fi done # Keymap is stored platform-neutral; install renames it per OS. if [ -f "$user_dir/$(keymap_name)" ]; then cp "$user_dir/$(keymap_name)" "$PAYLOAD/keymap.json"; c_ok "keymap.json" fi # Package list, for Package Control to auto-install on the new machine. if [ -f "$user_dir/Package Control.sublime-settings" ]; then python3 - "$user_dir/Package Control.sublime-settings" "$PAYLOAD/packages.json" <<'PY' import json,re,sys s=open(sys.argv[1]).read(); s=re.sub(r'^\s*//.*$','',s,flags=re.M) s=re.sub(r',(\s*[\]}])',r'\1',s) # tolerate trailing commas pkgs=json.load(open(sys.argv[1]).name and __import__('io').StringIO(s)).get('installed_packages',[]) json.dump(sorted(pkgs), open(sys.argv[2],'w'), indent=2) print(" ok packages.json (%d packages)" % len(pkgs)) PY fi # Layout only -- extracted from the workspace, stripped of everything else. local ws; ws="$(ls "$REPO"/*.sublime-workspace 2>/dev/null | head -1 || true)" if [ -n "$ws" ]; then python3 - "$ws" "$PAYLOAD/layout.json" <<'PY' import json,sys d=json.load(open(sys.argv[1])) json.dump(d.get("layout",{}), open(sys.argv[2],"w"), indent=2) print(" ok layout.json (pane layout only; history discarded)") PY fi head_ "Exported to $PAYLOAD" ls -1 "$PAYLOAD" } # ---------------------------------------------------------------- check ------ do_check() { head_ "Toolchain" local missing=0 t for t in clang++ clangd make git; do if command -v "$t" >/dev/null 2>&1; then c_ok "$t $("$t" --version 2>&1 | head -1)" else c_err "$t not found"; missing=1; fi done command -v cmake >/dev/null 2>&1 && c_ok "cmake (optional)" || c_warn "cmake not found (optional)" head_ "Sublime Text" local sd; sd="$(sublime_dir)" [ -d "$sd" ] && c_ok "config dir: $sd" || c_warn "config dir absent: $sd (start Sublime once)" command -v subl >/dev/null 2>&1 && c_ok "subl on PATH" || c_warn "subl not on PATH (optional)" if [ "$missing" -ne 0 ]; then head_ "Install the missing tools" if command -v pacman >/dev/null 2>&1; then echo " sudo pacman -S --needed clang make git" elif command -v apt-get >/dev/null 2>&1; then echo " sudo apt-get install -y clang clangd make git" elif command -v dnf >/dev/null 2>&1; then echo " sudo dnf install -y clang clang-tools-extra make git" elif command -v brew >/dev/null 2>&1; then echo " brew install llvm make git" fi return 1 fi return 0 } # ---------------------------------------------------------------- install ---- # Render a payload file for this machine (placeholder -> real project path), # then install it only if the result differs from what is already there. # This makes re-running install a no-op instead of piling up backups. install_file() { local src="$1" dst="$2" tmp tmp="$(mktemp)" sed "s|__PROJECT_ROOT__|$REPO|g" "$src" > "$tmp" if [ -f "$dst" ] && cmp -s "$tmp" "$dst"; then c_ok "$(basename "$dst") (unchanged)"; rm -f "$tmp"; return 0 fi if [ -f "$dst" ]; then cp "$dst" "$dst.bak-$STAMP" c_warn "existing file backed up: $(basename "$dst").bak-$STAMP" fi mv "$tmp" "$dst"; chmod 644 "$dst" c_ok "$(basename "$dst")" } do_install() { [ -d "$PAYLOAD" ] || { c_err "no payload at $PAYLOAD -- run './setup.sh export' on the source machine first"; exit 1; } do_check || { c_err "toolchain incomplete; install the tools above and re-run"; exit 1; } local user_dir; user_dir="$(sublime_dir)/Packages/User" head_ "Installing Sublime config to $user_dir" mkdir -p "$user_dir" local f for f in CP.sublime-build compete_CPP.sublime-build LSP-clangd.sublime-settings; do [ -f "$PAYLOAD/$f" ] || continue install_file "$PAYLOAD/$f" "$user_dir/$f" done [ -f "$PAYLOAD/keymap.json" ] && install_file "$PAYLOAD/keymap.json" "$user_dir/$(keymap_name)" # Merge the package list rather than clobbering the user's own. if [ -f "$PAYLOAD/packages.json" ]; then python3 - "$PAYLOAD/packages.json" "$user_dir/Package Control.sublime-settings" <<'PY' import json,re,os,sys,io want=json.load(open(sys.argv[1])); dst=sys.argv[2] have=[] if os.path.exists(dst): s=re.sub(r'^\s*//.*$','',open(dst).read(),flags=re.M) s=re.sub(r',(\s*[\]}])',r'\1',s) try: have=json.load(io.StringIO(s)).get('installed_packages',[]) except Exception: have=[] merged=sorted(set(have)|set(want)) json.dump({"bootstrapped":True,"installed_packages":merged}, open(dst,'w'), indent=2) added=sorted(set(want)-set(have)) print(" ok Package Control list merged (%d total%s)" % (len(merged), (", added: "+", ".join(added)) if added else ", nothing new")) PY fi # Fresh workspace with the saved layout and no inherited history. if [ -f "$PAYLOAD/layout.json" ]; then python3 - "$PAYLOAD/layout.json" "$REPO/compete.sublime-workspace" "$REPO" <<'PY' import json,os,sys layout=json.load(open(sys.argv[1])); out,root=sys.argv[2],sys.argv[3] if os.path.exists(out): print(" warn workspace already exists, left alone: %s" % os.path.basename(out)) else: json.dump({"layout":layout,"buffers":[],"groups":[{"sheets":[]} for _ in layout.get("cells",[[]])], "folders":[{"path":root}],"file_history":[], "build_system":"Packages/User/CP.sublime-build"}, open(out,'w'), indent=2) print(" ok clean workspace created (layout only, no file history)") PY fi head_ "Building the project" if make -C "$REPO" --no-print-directory; then c_ok "make succeeded"; else c_err "make failed"; exit 1; fi head_ "Done" cat < Build System > CP (or compete_CPP) Sublime will prompt to install missing packages via Package Control on first start. Restart it once after that. EOF } case "${1:-}" in export) do_export ;; install) do_install ;; check) do_check ;; *) sed -n '2,9p' "${BASH_SOURCE[0]}"; exit 1 ;; esac