207 lines
7.9 KiB
Bash
207 lines
7.9 KiB
Bash
|
|
#!/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
|