mirror of
https://github.com/tw93/Mole.git
synced 2026-02-14 23:25:08 +00:00
feat(clean): add interactive Xcode simulator runtime volumes cleanup (#444)
* feat(clean): add interactive Xcode simulator runtime volumes cleanup Scan CoreSimulator Volumes/Cryptex directories, detect IN_USE/UNUSED status via mount points, and let users interactively select items to clean. Also show size info for unavailable simulator cleanup. * refactor(clean): auto-clean Xcode simulator runtime volumes Remove interactive selector, now automatically cleans all UNUSED runtime volumes while skipping IN_USE and protected paths.
This commit is contained in:
@@ -81,6 +81,7 @@ mo analyze /Volumes # Analyze external drives only
|
||||
- **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`).
|
||||
- **Status Shortcuts**: In `mo status`, press `k` to toggle cat visibility and save preference, `q` to quit.
|
||||
- **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths.
|
||||
- **Simulator Runtime Volumes**: In `mo clean` -> Developer tools, Mole auto-detects and removes unused CoreSimulator `Volumes/Cryptex` entries (IN_USE items are safely skipped).
|
||||
|
||||
## Features in Detail
|
||||
|
||||
|
||||
234
lib/clean/dev.sh
234
lib/clean/dev.sh
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Developer Tools Cleanup Module
|
||||
set -euo pipefail
|
||||
|
||||
# Tool cache helper (respects DRY_RUN).
|
||||
clean_tool_cache() {
|
||||
local description="$1"
|
||||
@@ -221,22 +222,249 @@ clean_xcode_documentation_cache() {
|
||||
fi
|
||||
}
|
||||
|
||||
_sim_runtime_mount_points() {
|
||||
if [[ -n "${MOLE_XCODE_SIM_RUNTIME_MOUNT_POINTS:-}" ]]; then
|
||||
printf '%s\n' "$MOLE_XCODE_SIM_RUNTIME_MOUNT_POINTS"
|
||||
return 0
|
||||
fi
|
||||
mount 2> /dev/null | command awk '{print $3}' || true
|
||||
}
|
||||
|
||||
_sim_runtime_is_path_in_use() {
|
||||
local target_path="$1"
|
||||
shift || true
|
||||
local mount_path
|
||||
for mount_path in "$@"; do
|
||||
[[ -z "$mount_path" ]] && continue
|
||||
if [[ "$mount_path" == "$target_path" || "$mount_path" == "$target_path"/* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_sim_runtime_size_kb() {
|
||||
local target_path="$1"
|
||||
local size_kb=0
|
||||
if has_sudo_session; then
|
||||
size_kb=$(sudo du -skP "$target_path" 2> /dev/null | command awk 'NR==1 {print $1; exit}' || echo "0")
|
||||
else
|
||||
size_kb=$(du -skP "$target_path" 2> /dev/null | command awk 'NR==1 {print $1; exit}' || echo "0")
|
||||
fi
|
||||
|
||||
[[ "$size_kb" =~ ^[0-9]+$ ]] || size_kb=0
|
||||
echo "$size_kb"
|
||||
}
|
||||
|
||||
clean_xcode_simulator_runtime_volumes() {
|
||||
local volumes_root="${MOLE_XCODE_SIM_RUNTIME_VOLUMES_ROOT:-/Library/Developer/CoreSimulator/Volumes}"
|
||||
local cryptex_root="${MOLE_XCODE_SIM_RUNTIME_CRYPTEX_ROOT:-/Library/Developer/CoreSimulator/Cryptex}"
|
||||
|
||||
local -a candidates=()
|
||||
local candidate
|
||||
for candidate in "$volumes_root" "$cryptex_root"; do
|
||||
[[ -d "$candidate" ]] || continue
|
||||
while IFS= read -r -d '' entry; do
|
||||
candidates+=("$entry")
|
||||
done < <(command find "$candidate" -mindepth 1 -maxdepth 1 -type d -print0 2> /dev/null)
|
||||
done
|
||||
|
||||
if [[ ${#candidates[@]} -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -a mount_points=()
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && mount_points+=("$line")
|
||||
done < <(_sim_runtime_mount_points)
|
||||
|
||||
local -a size_values=()
|
||||
local -a entry_statuses=()
|
||||
local -a sorted_candidates=()
|
||||
local sorted
|
||||
while IFS= read -r sorted; do
|
||||
[[ -n "$sorted" ]] && sorted_candidates+=("$sorted")
|
||||
done < <(printf '%s\n' "${candidates[@]}" | LC_ALL=C sort)
|
||||
|
||||
local idx=0
|
||||
for candidate in "${sorted_candidates[@]}"; do
|
||||
local status="UNUSED"
|
||||
if _sim_runtime_is_path_in_use "$candidate" "${mount_points[@]}"; then
|
||||
status="IN_USE"
|
||||
fi
|
||||
|
||||
local size_kb
|
||||
size_kb=$(_sim_runtime_size_kb "$candidate")
|
||||
size_values+=("$size_kb")
|
||||
entry_statuses+=("$status")
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
local in_use_count=0
|
||||
local unused_count=0
|
||||
local in_use_kb=0
|
||||
local unused_kb=0
|
||||
local status
|
||||
local i=0
|
||||
for status in "${entry_statuses[@]}"; do
|
||||
local entry_size_kb="${size_values[$i]:-0}"
|
||||
if [[ "$status" == "IN_USE" ]]; then
|
||||
in_use_count=$((in_use_count + 1))
|
||||
in_use_kb=$((in_use_kb + entry_size_kb))
|
||||
else
|
||||
unused_count=$((unused_count + 1))
|
||||
unused_kb=$((unused_kb + entry_size_kb))
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Xcode runtime volumes · ${unused_count} unused, ${in_use_count} in use"
|
||||
local dryrun_total_kb=$((unused_kb + in_use_kb))
|
||||
local dryrun_total_human
|
||||
dryrun_total_human=$(bytes_to_human "$((dryrun_total_kb * 1024))")
|
||||
local dryrun_unused_human
|
||||
dryrun_unused_human=$(bytes_to_human "$((unused_kb * 1024))")
|
||||
local dryrun_in_use_human
|
||||
dryrun_in_use_human=$(bytes_to_human "$((in_use_kb * 1024))")
|
||||
echo -e " ${GRAY}${ICON_LIST}${NC} Runtime volumes total: ${dryrun_total_human} (unused ${dryrun_unused_human}, in-use ${dryrun_in_use_human})"
|
||||
|
||||
local dryrun_max_items="${MOLE_SIM_RUNTIME_DRYRUN_MAX_ITEMS:-20}"
|
||||
[[ "$dryrun_max_items" =~ ^[0-9]+$ ]] || dryrun_max_items=20
|
||||
if [[ "$dryrun_max_items" -le 0 ]]; then
|
||||
dryrun_max_items=20
|
||||
fi
|
||||
|
||||
local shown=0
|
||||
local line_size_kb line_status line_path
|
||||
while IFS=$'\t' read -r line_size_kb line_status line_path; do
|
||||
[[ -z "${line_path:-}" ]] && continue
|
||||
local line_human
|
||||
line_human=$(bytes_to_human "$((line_size_kb * 1024))")
|
||||
echo -e " ${GRAY}${line_status}${NC} ${line_human} · ${line_path}"
|
||||
shown=$((shown + 1))
|
||||
if [[ "$shown" -ge "$dryrun_max_items" ]]; then
|
||||
break
|
||||
fi
|
||||
done < <(
|
||||
local j=0
|
||||
while [[ $j -lt ${#sorted_candidates[@]} ]]; do
|
||||
printf '%s\t%s\t%s\n' "${size_values[$j]:-0}" "${entry_statuses[$j]:-UNUSED}" "${sorted_candidates[$j]}"
|
||||
j=$((j + 1))
|
||||
done | LC_ALL=C sort -nr -k1,1
|
||||
)
|
||||
|
||||
local total_entries="${#sorted_candidates[@]}"
|
||||
if [[ "$total_entries" -gt "$shown" ]]; then
|
||||
local remaining=$((total_entries - shown))
|
||||
echo -e " ${GRAY}${ICON_LIST}${NC} ... and ${remaining} more runtime volume entries"
|
||||
fi
|
||||
note_activity
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Auto-clean all UNUSED runtime volumes (no user selection)
|
||||
local -a selected_paths=()
|
||||
local -a selected_sizes_kb=()
|
||||
local selected_total_kb=0
|
||||
local skipped_protected=0
|
||||
local i=0
|
||||
for ((i = 0; i < ${#sorted_candidates[@]}; i++)); do
|
||||
local status="${entry_statuses[$i]:-UNUSED}"
|
||||
[[ "$status" == "IN_USE" ]] && continue
|
||||
|
||||
local candidate_path="${sorted_candidates[$i]}"
|
||||
if should_protect_path "$candidate_path" || is_path_whitelisted "$candidate_path"; then
|
||||
skipped_protected=$((skipped_protected + 1))
|
||||
continue
|
||||
fi
|
||||
selected_paths+=("$candidate_path")
|
||||
selected_sizes_kb+=("${size_values[$i]:-0}")
|
||||
selected_total_kb=$((selected_total_kb + ${size_values[$i]:-0}))
|
||||
done
|
||||
|
||||
if [[ ${#selected_paths[@]} -eq 0 ]]; then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode runtime volumes · nothing to clean"
|
||||
note_activity
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! has_sudo_session; then
|
||||
if ! ensure_sudo_session "Cleaning Xcode runtime volumes requires admin access"; then
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode runtime volumes cleanup skipped (sudo denied)"
|
||||
note_activity
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local selected_human
|
||||
selected_human=$(bytes_to_human "$((selected_total_kb * 1024))")
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode runtime volumes · cleaning ${#selected_paths[@]} unused, ${selected_human}"
|
||||
if [[ $skipped_protected -gt 0 ]]; then
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode runtime volumes · skipped ${skipped_protected} protected items"
|
||||
fi
|
||||
|
||||
local removed_count=0
|
||||
local removed_size_kb=0
|
||||
local i=0
|
||||
local selected_path
|
||||
for selected_path in "${selected_paths[@]}"; do
|
||||
local selected_size_kb="${selected_sizes_kb[$i]:-0}"
|
||||
if safe_sudo_remove "$selected_path"; then
|
||||
removed_count=$((removed_count + 1))
|
||||
removed_size_kb=$((removed_size_kb + selected_size_kb))
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [[ $removed_count -gt 0 ]]; then
|
||||
local removed_human
|
||||
removed_human=$(bytes_to_human "$((removed_size_kb * 1024))")
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode runtime volumes · removed ${removed_count}, ${removed_human}"
|
||||
note_activity
|
||||
else
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode runtime volumes · no items removed"
|
||||
note_activity
|
||||
fi
|
||||
}
|
||||
|
||||
clean_dev_mobile() {
|
||||
check_android_ndk
|
||||
clean_xcode_documentation_cache
|
||||
clean_xcode_simulator_runtime_volumes
|
||||
|
||||
if command -v xcrun > /dev/null 2>&1; then
|
||||
debug_log "Checking for unavailable Xcode simulators"
|
||||
local unavailable_before=0
|
||||
local unavailable_after=0
|
||||
local removed_unavailable=0
|
||||
local unavailable_size_kb=0
|
||||
local unavailable_size_human="0B"
|
||||
local -a unavailable_udids=()
|
||||
local unavailable_udid=""
|
||||
|
||||
unavailable_before=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
||||
[[ "$unavailable_before" =~ ^[0-9]+$ ]] || unavailable_before=0
|
||||
while IFS= read -r unavailable_udid; do
|
||||
[[ -n "$unavailable_udid" ]] && unavailable_udids+=("$unavailable_udid")
|
||||
done < <(
|
||||
xcrun simctl list devices unavailable 2> /dev/null |
|
||||
command sed -nE 's/.*\(([0-9A-Fa-f-]{36})\).*\(unavailable.*/\1/p' || true
|
||||
)
|
||||
if [[ ${#unavailable_udids[@]} -gt 0 ]]; then
|
||||
local udid
|
||||
for udid in "${unavailable_udids[@]}"; do
|
||||
local simulator_device_path="$HOME/Library/Developer/CoreSimulator/Devices/$udid"
|
||||
if [[ -d "$simulator_device_path" ]]; then
|
||||
unavailable_size_kb=$((unavailable_size_kb + $(get_path_size_kb "$simulator_device_path")))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
unavailable_size_human=$(bytes_to_human "$((unavailable_size_kb * 1024))")
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
if ((unavailable_before > 0)); then
|
||||
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Xcode unavailable simulators · would clean ${unavailable_before}"
|
||||
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Xcode unavailable simulators · would clean ${unavailable_before}, ${unavailable_size_human}"
|
||||
else
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||
fi
|
||||
@@ -255,9 +483,9 @@ clean_dev_mobile() {
|
||||
if ((unavailable_before == 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||
elif ((removed_unavailable > 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${removed_unavailable}"
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${removed_unavailable}, ${unavailable_size_human}"
|
||||
else
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · cleanup completed"
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · cleanup completed, ${unavailable_size_human}"
|
||||
fi
|
||||
else
|
||||
stop_section_spinner
|
||||
|
||||
Reference in New Issue
Block a user