mirror of
https://github.com/MobAI-App/simslim.git
synced 2026-09-30 18:16:29 +02:00
simslim only ever talked to the default CoreSimulator device set, so
Xcode parallel-testing clones — which live in a separate set — were
invisible to `list` and any command against a clone UDID failed with
"Invalid device or device pair".
simctl cannot enumerate device sets, so simslim now probes the two
well-known locations automatically: the default set and Xcode's
parallel-testing clone set ("testing", ~/Library/Developer/XCTestDevices).
- `list` scans both sets and tags each row (and the JSON `set` field) so
clones are visible with no extra flags.
- Per-UDID commands resolve which set a UDID lives in via findDevice and
thread that set through every simctl call, so `on`/`boot`/etc. act on a
clone with no flag needed. The set is passed explicitly (Device.Set),
not held in a global.
- A global `--set <name|path>` (comma-separated) adds further sets to the
scan for a device set at a custom path.
- Disk commands take the on-disk data directory straight from the
dataPath simctl already reports, dropping the hand-rolled set-to-path
reconstruction.
Fixes #6
121 lines
3.9 KiB
Go
121 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// reporter receives human-readable progress lines. A nil reporter is a no-op,
|
|
// so non-interactive callers can ignore progress entirely.
|
|
type reporter func(string)
|
|
|
|
func (r reporter) report(msg string) {
|
|
if r != nil {
|
|
r(msg)
|
|
}
|
|
}
|
|
|
|
// ensure brings the device to exactly the desired disabled state and boots it.
|
|
// The disabled overrides persist in the device's launchd DB, so once set a slim
|
|
// device comes up slim in a single boot; a reboot only happens when the state
|
|
// actually changes. A running device is required to read/mutate launchctl, so
|
|
// the device is booted first regardless. Each slow phase reports progress so the
|
|
// caller can show the user that a multi-minute reconfigure is still working.
|
|
func ensure(ctx context.Context, set, udid string, desired map[string]bool, report reporter) (changed bool, err error) {
|
|
report.report("Booting the simulator (a first boot can take up to a minute)...")
|
|
if err := bootAndWait(ctx, set, udid); err != nil {
|
|
return false, err
|
|
}
|
|
current, err := readDisabled(ctx, set, udid)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
toDisable, toEnable := delta(current, desired, managedSet())
|
|
if len(toDisable) == 0 && len(toEnable) == 0 {
|
|
return false, nil
|
|
}
|
|
if len(toDisable) > 0 {
|
|
report.report(fmt.Sprintf("Disabling %d background services...", len(toDisable)))
|
|
}
|
|
if len(toEnable) > 0 {
|
|
report.report(fmt.Sprintf("Re-enabling %d background services...", len(toEnable)))
|
|
}
|
|
if err := applyDelta(ctx, set, udid, toDisable, toEnable, report); err != nil {
|
|
return true, err
|
|
}
|
|
report.report("Rebooting the simulator to apply the changes...")
|
|
if err := shutdown(ctx, set, udid); err != nil {
|
|
return true, fmt.Errorf("shutdown before reboot: %w", err)
|
|
}
|
|
if err := waitShutdown(ctx, set, udid, shutdownTimeout); err != nil {
|
|
return true, err
|
|
}
|
|
return true, bootAndWait(ctx, set, udid)
|
|
}
|
|
|
|
// enableSlim disables the profile's daemons and boots the device slim.
|
|
func enableSlim(ctx context.Context, set, udid string, p Profile, report reporter) (bool, error) {
|
|
return ensure(ctx, set, udid, p.desired(), report)
|
|
}
|
|
|
|
// disableSlim re-enables every managed daemon, returning the device to stock.
|
|
func disableSlim(ctx context.Context, set, udid string, report reporter) (bool, error) {
|
|
return ensure(ctx, set, udid, map[string]bool{}, report)
|
|
}
|
|
|
|
// Status describes how slim a device currently is.
|
|
type Status struct {
|
|
ManagedDisabled int `json:"managedDisabled"` // managed labels currently disabled
|
|
ManagedTotal int `json:"managedTotal"` // size of the managed universe
|
|
Booted bool `json:"booted"`
|
|
}
|
|
|
|
// status reports how slim a device is and returns the labels it currently has
|
|
// disabled (nil when the device is not booted).
|
|
func status(ctx context.Context, udid string) (Status, map[string]bool, error) {
|
|
d, err := findDevice(ctx, udid, "")
|
|
if err != nil {
|
|
return Status{}, nil, err
|
|
}
|
|
return statusForDevice(ctx, d)
|
|
}
|
|
|
|
func statusForDevice(ctx context.Context, d Device) (Status, map[string]bool, error) {
|
|
managed := slimmableSet()
|
|
st := Status{ManagedTotal: len(managed), Booted: d.State == "Booted"}
|
|
if !st.Booted {
|
|
return st, nil, fmt.Errorf("simulator must be booted to read its state (it is %s)", d.State)
|
|
}
|
|
disabled, err := readDisabled(ctx, d.Set, d.UDID)
|
|
if err != nil {
|
|
return st, nil, err
|
|
}
|
|
for l := range disabled {
|
|
if managed[l] {
|
|
st.ManagedDisabled++
|
|
}
|
|
}
|
|
return st, disabled, nil
|
|
}
|
|
|
|
// droppedCategories groups the disabled managed daemons by category, in category
|
|
// order, omitting categories with nothing disabled.
|
|
func droppedCategories(disabled map[string]bool) []DroppedCategory {
|
|
var out []DroppedCategory
|
|
for _, c := range Categories {
|
|
var labels []string
|
|
for _, l := range c.Labels {
|
|
if disabled[l] {
|
|
labels = append(labels, l)
|
|
}
|
|
}
|
|
if len(labels) == 0 {
|
|
continue
|
|
}
|
|
sort.Strings(labels)
|
|
out = append(out, DroppedCategory{ID: c.ID, Name: c.Name, Downside: c.Downside, Labels: labels})
|
|
}
|
|
return out
|
|
}
|