sublime-cpp/setup.sh

207 lines
7.9 KiB
Bash
Raw Permalink Normal View History

#!/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 <<EOF
Open the project: subl --project "$REPO/Mocpp.sublime-project"
Build: Ctrl+B
Contest layout: Ctrl+Alt+Shift+C
Select build: Tools > 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