Use `open -g` (background) and remove `osascript activate` calls across reload.sh, reloadp.sh, and reloads.sh so rebuilds no longer yank focus away from the active window.
351 lines
11 KiB
Bash
Executable file
351 lines
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
APP_NAME="cmux DEV"
|
|
BUNDLE_ID="com.cmuxterm.app.debug"
|
|
BASE_APP_NAME="cmux DEV"
|
|
DERIVED_DATA=""
|
|
NAME_SET=0
|
|
BUNDLE_SET=0
|
|
DERIVED_SET=0
|
|
TAG=""
|
|
CMUX_DEBUG_LOG=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./scripts/reload.sh --tag <name> [options]
|
|
|
|
Options:
|
|
--tag <name> Required. Short tag for parallel builds (e.g., feature-xyz-lol).
|
|
Sets app name, bundle id, and derived data path unless overridden.
|
|
--name <app name> Override app display/bundle name.
|
|
--bundle-id <id> Override bundle identifier.
|
|
--derived-data <path> Override derived data path.
|
|
-h, --help Show this help.
|
|
EOF
|
|
}
|
|
|
|
sanitize_bundle() {
|
|
local raw="$1"
|
|
local cleaned
|
|
cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/./g; s/^\\.+//; s/\\.+$//; s/\\.+/./g')"
|
|
if [[ -z "$cleaned" ]]; then
|
|
cleaned="agent"
|
|
fi
|
|
echo "$cleaned"
|
|
}
|
|
|
|
sanitize_path() {
|
|
local raw="$1"
|
|
local cleaned
|
|
cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-+/-/g')"
|
|
if [[ -z "$cleaned" ]]; then
|
|
cleaned="agent"
|
|
fi
|
|
echo "$cleaned"
|
|
}
|
|
|
|
print_tag_cleanup_reminder() {
|
|
local current_slug="$1"
|
|
local path=""
|
|
local tag=""
|
|
local seen=" "
|
|
local -a stale_tags=()
|
|
|
|
while IFS= read -r -d '' path; do
|
|
tag="${path#/tmp/cmux-}"
|
|
if [[ "$tag" == "$current_slug" ]]; then
|
|
continue
|
|
fi
|
|
# Only surface stale debug tag builds.
|
|
if [[ ! -d "$path/Build/Products/Debug" ]]; then
|
|
continue
|
|
fi
|
|
if [[ "$seen" == *" $tag "* ]]; then
|
|
continue
|
|
fi
|
|
seen="${seen}${tag} "
|
|
stale_tags+=("$tag")
|
|
done < <(find /tmp -maxdepth 1 -type d -name 'cmux-*' -print0 2>/dev/null)
|
|
|
|
echo
|
|
echo "Tag cleanup status:"
|
|
echo " current tag: ${current_slug} (keep this running until you verify)"
|
|
if [[ "${#stale_tags[@]}" -eq 0 ]]; then
|
|
echo " stale tags: none"
|
|
echo " stale cleanup: not needed"
|
|
else
|
|
echo " stale tags:"
|
|
for tag in "${stale_tags[@]}"; do
|
|
echo " - ${tag}"
|
|
done
|
|
echo "Cleanup stale tags only:"
|
|
for tag in "${stale_tags[@]}"; do
|
|
echo " pkill -f \"cmux DEV ${tag}.app/Contents/MacOS/cmux DEV\""
|
|
echo " rm -rf \"/tmp/cmux-${tag}\" \"/tmp/cmux-debug-${tag}.sock\""
|
|
echo " rm -f \"/tmp/cmux-debug-${tag}.log\""
|
|
echo " rm -f \"$HOME/Library/Application Support/cmux/cmuxd-dev-${tag}.sock\""
|
|
done
|
|
fi
|
|
echo "After you verify current tag, cleanup command:"
|
|
echo " pkill -f \"cmux DEV ${current_slug}.app/Contents/MacOS/cmux DEV\""
|
|
echo " rm -rf \"/tmp/cmux-${current_slug}\" \"/tmp/cmux-debug-${current_slug}.sock\""
|
|
echo " rm -f \"/tmp/cmux-debug-${current_slug}.log\""
|
|
echo " rm -f \"$HOME/Library/Application Support/cmux/cmuxd-dev-${current_slug}.sock\""
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--tag)
|
|
TAG="${2:-}"
|
|
if [[ -z "$TAG" ]]; then
|
|
echo "error: --tag requires a value" >&2
|
|
exit 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--name)
|
|
APP_NAME="${2:-}"
|
|
if [[ -z "$APP_NAME" ]]; then
|
|
echo "error: --name requires a value" >&2
|
|
exit 1
|
|
fi
|
|
NAME_SET=1
|
|
shift 2
|
|
;;
|
|
--bundle-id)
|
|
BUNDLE_ID="${2:-}"
|
|
if [[ -z "$BUNDLE_ID" ]]; then
|
|
echo "error: --bundle-id requires a value" >&2
|
|
exit 1
|
|
fi
|
|
BUNDLE_SET=1
|
|
shift 2
|
|
;;
|
|
--derived-data)
|
|
DERIVED_DATA="${2:-}"
|
|
if [[ -z "$DERIVED_DATA" ]]; then
|
|
echo "error: --derived-data requires a value" >&2
|
|
exit 1
|
|
fi
|
|
DERIVED_SET=1
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "error: unknown option $1" >&2
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$TAG" ]]; then
|
|
echo "error: --tag is required (example: ./scripts/reload.sh --tag fix-sidebar-theme)" >&2
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$TAG" ]]; then
|
|
TAG_ID="$(sanitize_bundle "$TAG")"
|
|
TAG_SLUG="$(sanitize_path "$TAG")"
|
|
if [[ "$NAME_SET" -eq 0 ]]; then
|
|
APP_NAME="cmux DEV ${TAG}"
|
|
fi
|
|
if [[ "$BUNDLE_SET" -eq 0 ]]; then
|
|
BUNDLE_ID="com.cmuxterm.app.debug.${TAG_ID}"
|
|
fi
|
|
if [[ "$DERIVED_SET" -eq 0 ]]; then
|
|
DERIVED_DATA="/tmp/cmux-${TAG_SLUG}"
|
|
fi
|
|
fi
|
|
|
|
XCODEBUILD_ARGS=(
|
|
-project GhosttyTabs.xcodeproj
|
|
-scheme cmux
|
|
-configuration Debug
|
|
-destination 'platform=macOS'
|
|
)
|
|
if [[ -n "$DERIVED_DATA" ]]; then
|
|
XCODEBUILD_ARGS+=(-derivedDataPath "$DERIVED_DATA")
|
|
fi
|
|
if [[ -z "$TAG" ]]; then
|
|
XCODEBUILD_ARGS+=(
|
|
INFOPLIST_KEY_CFBundleName="$APP_NAME"
|
|
INFOPLIST_KEY_CFBundleDisplayName="$APP_NAME"
|
|
PRODUCT_BUNDLE_IDENTIFIER="$BUNDLE_ID"
|
|
)
|
|
fi
|
|
XCODEBUILD_ARGS+=(build)
|
|
|
|
XCODE_LOG="/tmp/cmux-xcodebuild-${TAG_SLUG}.log"
|
|
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | tee "$XCODE_LOG" | grep -E '(warning:|error:|fatal:|BUILD FAILED|BUILD SUCCEEDED|\*\* BUILD)' || true
|
|
XCODE_EXIT="${PIPESTATUS[0]}"
|
|
echo "Full build log: $XCODE_LOG"
|
|
if [[ "$XCODE_EXIT" -ne 0 ]]; then
|
|
echo "error: xcodebuild failed with exit code $XCODE_EXIT" >&2
|
|
exit "$XCODE_EXIT"
|
|
fi
|
|
sleep 0.2
|
|
|
|
FALLBACK_APP_NAME="$BASE_APP_NAME"
|
|
SEARCH_APP_NAME="$APP_NAME"
|
|
if [[ -n "$TAG" ]]; then
|
|
SEARCH_APP_NAME="$BASE_APP_NAME"
|
|
fi
|
|
if [[ -n "$DERIVED_DATA" ]]; then
|
|
APP_PATH="${DERIVED_DATA}/Build/Products/Debug/${SEARCH_APP_NAME}.app"
|
|
if [[ ! -d "${APP_PATH}" && "$SEARCH_APP_NAME" != "$FALLBACK_APP_NAME" ]]; then
|
|
APP_PATH="${DERIVED_DATA}/Build/Products/Debug/${FALLBACK_APP_NAME}.app"
|
|
fi
|
|
else
|
|
APP_BINARY="$(
|
|
find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Debug/${SEARCH_APP_NAME}.app/Contents/MacOS/${SEARCH_APP_NAME}" -print0 \
|
|
| xargs -0 /usr/bin/stat -f "%m %N" 2>/dev/null \
|
|
| sort -nr \
|
|
| head -n 1 \
|
|
| cut -d' ' -f2-
|
|
)"
|
|
if [[ -n "${APP_BINARY}" ]]; then
|
|
APP_PATH="$(dirname "$(dirname "$(dirname "$APP_BINARY")")")"
|
|
fi
|
|
if [[ -z "${APP_PATH}" && "$SEARCH_APP_NAME" != "$FALLBACK_APP_NAME" ]]; then
|
|
APP_BINARY="$(
|
|
find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Debug/${FALLBACK_APP_NAME}.app/Contents/MacOS/${FALLBACK_APP_NAME}" -print0 \
|
|
| xargs -0 /usr/bin/stat -f "%m %N" 2>/dev/null \
|
|
| sort -nr \
|
|
| head -n 1 \
|
|
| cut -d' ' -f2-
|
|
)"
|
|
if [[ -n "${APP_BINARY}" ]]; then
|
|
APP_PATH="$(dirname "$(dirname "$(dirname "$APP_BINARY")")")"
|
|
fi
|
|
fi
|
|
fi
|
|
if [[ -z "${APP_PATH}" || ! -d "${APP_PATH}" ]]; then
|
|
echo "${APP_NAME}.app not found in DerivedData" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$TAG" && "$APP_NAME" != "$SEARCH_APP_NAME" ]]; then
|
|
TAG_APP_PATH="$(dirname "$APP_PATH")/${APP_NAME}.app"
|
|
rm -rf "$TAG_APP_PATH"
|
|
cp -R "$APP_PATH" "$TAG_APP_PATH"
|
|
INFO_PLIST="$TAG_APP_PATH/Contents/Info.plist"
|
|
if [[ -f "$INFO_PLIST" ]]; then
|
|
/usr/libexec/PlistBuddy -c "Set :CFBundleName $APP_NAME" "$INFO_PLIST" 2>/dev/null \
|
|
|| /usr/libexec/PlistBuddy -c "Add :CFBundleName string $APP_NAME" "$INFO_PLIST"
|
|
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME" "$INFO_PLIST" 2>/dev/null \
|
|
|| /usr/libexec/PlistBuddy -c "Add :CFBundleDisplayName string $APP_NAME" "$INFO_PLIST"
|
|
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$INFO_PLIST" 2>/dev/null \
|
|
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string $BUNDLE_ID" "$INFO_PLIST"
|
|
if [[ -n "${TAG_SLUG:-}" ]]; then
|
|
APP_SUPPORT_DIR="$HOME/Library/Application Support/cmux"
|
|
CMUXD_SOCKET="${APP_SUPPORT_DIR}/cmuxd-dev-${TAG_SLUG}.sock"
|
|
CMUX_SOCKET="/tmp/cmux-debug-${TAG_SLUG}.sock"
|
|
CMUX_DEBUG_LOG="/tmp/cmux-debug-${TAG_SLUG}.log"
|
|
echo "$CMUX_SOCKET" > /tmp/cmux-last-socket-path || true
|
|
echo "$CMUX_DEBUG_LOG" > /tmp/cmux-last-debug-log-path || true
|
|
/usr/libexec/PlistBuddy -c "Add :LSEnvironment dict" "$INFO_PLIST" 2>/dev/null || true
|
|
/usr/libexec/PlistBuddy -c "Set :LSEnvironment:CMUXD_UNIX_PATH \"${CMUXD_SOCKET}\"" "$INFO_PLIST" 2>/dev/null \
|
|
|| /usr/libexec/PlistBuddy -c "Add :LSEnvironment:CMUXD_UNIX_PATH string \"${CMUXD_SOCKET}\"" "$INFO_PLIST"
|
|
/usr/libexec/PlistBuddy -c "Set :LSEnvironment:CMUX_SOCKET_PATH \"${CMUX_SOCKET}\"" "$INFO_PLIST" 2>/dev/null \
|
|
|| /usr/libexec/PlistBuddy -c "Add :LSEnvironment:CMUX_SOCKET_PATH string \"${CMUX_SOCKET}\"" "$INFO_PLIST"
|
|
/usr/libexec/PlistBuddy -c "Set :LSEnvironment:CMUX_DEBUG_LOG \"${CMUX_DEBUG_LOG}\"" "$INFO_PLIST" 2>/dev/null \
|
|
|| /usr/libexec/PlistBuddy -c "Add :LSEnvironment:CMUX_DEBUG_LOG string \"${CMUX_DEBUG_LOG}\"" "$INFO_PLIST"
|
|
if [[ -S "$CMUXD_SOCKET" ]]; then
|
|
for PID in $(lsof -t "$CMUXD_SOCKET" 2>/dev/null); do
|
|
kill "$PID" 2>/dev/null || true
|
|
done
|
|
rm -f "$CMUXD_SOCKET"
|
|
fi
|
|
if [[ -S "$CMUX_SOCKET" ]]; then
|
|
rm -f "$CMUX_SOCKET"
|
|
fi
|
|
fi
|
|
/usr/bin/codesign --force --sign - --timestamp=none --generate-entitlement-der "$TAG_APP_PATH" >/dev/null 2>&1 || true
|
|
fi
|
|
APP_PATH="$TAG_APP_PATH"
|
|
fi
|
|
|
|
# Ensure any running instance is fully terminated, regardless of DerivedData path.
|
|
/usr/bin/osascript -e "tell application id \"${BUNDLE_ID}\" to quit" >/dev/null 2>&1 || true
|
|
sleep 0.3
|
|
if [[ -z "$TAG" ]]; then
|
|
# Non-tag mode: kill any running instance (across any DerivedData path) to avoid socket conflicts.
|
|
pkill -f "/${BASE_APP_NAME}.app/Contents/MacOS/${BASE_APP_NAME}" || true
|
|
else
|
|
# Tag mode: only kill the tagged instance; allow side-by-side with the main app.
|
|
pkill -f "${APP_NAME}.app/Contents/MacOS/${BASE_APP_NAME}" || true
|
|
fi
|
|
sleep 0.3
|
|
CMUXD_SRC="$PWD/cmuxd/zig-out/bin/cmuxd"
|
|
if [[ -d "$PWD/cmuxd" ]]; then
|
|
(cd "$PWD/cmuxd" && zig build -Doptimize=ReleaseFast)
|
|
fi
|
|
if [[ -x "$CMUXD_SRC" ]]; then
|
|
BIN_DIR="$APP_PATH/Contents/Resources/bin"
|
|
mkdir -p "$BIN_DIR"
|
|
cp "$CMUXD_SRC" "$BIN_DIR/cmuxd"
|
|
chmod +x "$BIN_DIR/cmuxd"
|
|
fi
|
|
# Avoid inheriting cmux/ghostty environment variables from the terminal that
|
|
# runs this script (often inside another cmux instance), which can cause
|
|
# socket and resource-path conflicts.
|
|
OPEN_CLEAN_ENV=(
|
|
env
|
|
-u CMUX_SOCKET_PATH
|
|
-u CMUX_TAB_ID
|
|
-u CMUX_PANEL_ID
|
|
-u CMUXD_UNIX_PATH
|
|
-u CMUX_TAG
|
|
-u CMUX_DEBUG_LOG
|
|
-u CMUX_BUNDLE_ID
|
|
-u CMUX_SHELL_INTEGRATION
|
|
-u GHOSTTY_BIN_DIR
|
|
-u GHOSTTY_RESOURCES_DIR
|
|
-u GHOSTTY_SHELL_FEATURES
|
|
# Dev shells (including CI/Codex) often force-disable paging by exporting these.
|
|
# Don't leak that into cmux, otherwise `git diff` won't page even with PAGER=less.
|
|
-u GIT_PAGER
|
|
-u GH_PAGER
|
|
-u TERMINFO
|
|
-u XDG_DATA_DIRS
|
|
)
|
|
|
|
if [[ -n "${TAG_SLUG:-}" && -n "${CMUX_SOCKET:-}" ]]; then
|
|
# Ensure tag-specific socket paths win even if the caller has CMUX_* overrides.
|
|
"${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" open -g "$APP_PATH"
|
|
elif [[ -n "${TAG_SLUG:-}" ]]; then
|
|
"${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" open -g "$APP_PATH"
|
|
else
|
|
echo "/tmp/cmux-debug.log" > /tmp/cmux-last-debug-log-path || true
|
|
"${OPEN_CLEAN_ENV[@]}" open -g "$APP_PATH"
|
|
fi
|
|
|
|
# Safety: ensure only one instance is running.
|
|
sleep 0.2
|
|
PIDS=($(pgrep -f "${APP_PATH}/Contents/MacOS/" || true))
|
|
if [[ "${#PIDS[@]}" -gt 1 ]]; then
|
|
NEWEST_PID=""
|
|
NEWEST_AGE=999999
|
|
for PID in "${PIDS[@]}"; do
|
|
AGE="$(ps -o etimes= -p "$PID" | tr -d ' ')"
|
|
if [[ -n "$AGE" && "$AGE" -lt "$NEWEST_AGE" ]]; then
|
|
NEWEST_AGE="$AGE"
|
|
NEWEST_PID="$PID"
|
|
fi
|
|
done
|
|
for PID in "${PIDS[@]}"; do
|
|
if [[ "$PID" != "$NEWEST_PID" ]]; then
|
|
kill "$PID" 2>/dev/null || true
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -n "${TAG_SLUG:-}" ]]; then
|
|
print_tag_cleanup_reminder "$TAG_SLUG"
|
|
fi
|