Repository navigation
Conversation
…uilding
Changing a run node's folder used to stop the app and `flutter run` again
from the new checkout — a full native build for what is usually a Dart-only
difference between worktrees. When the running app is the one the new folder
would build (same device, same flavor, a Flutter run on both sides —
canHotSwitch), the toolbar now offers "Switch (keep app)" beside "Rebuild":
1. type `d` — flutter's own detach: the tool exits, the app keeps running;
2. `flutter attach` from the new folder to the same device;
3. once it has connected (its key help printed after its own launch line),
a hot RESTART by SIGUSR2. Not a reload: a freshly attached tool only
pushes files changed after it connected.
The app's state resets; nothing native is rebuilt. Every failure leaves the
app running and says so, with Rebuild one click away.
attachArgs keeps only the flags `flutter attach --help` lists (target,
dart-defines, app id, VM service URL, device options) and drops run-only
ones — attach has no --flavor and rejects unknown options. Flutter refuses a
hand-passed FLUTTER_APP_FLAVOR, so after an attach appFlavor is the pubspec
default; an app whose lib/ reads appFlavor is refused the switch with that
reason. Dart "request": "attach" configurations now run as flutter attach;
every other attach is still refused.
…vice URL
Live check (Flutter 3.47, iOS 27.1 simulator, two doc-flutter checkouts)
found the switch's attach step could never connect on a simulator:
`flutter attach` discovers an already-running app by mDNS, which the
simulator barely supports ("The Dart VM Service was not discovered after
30 seconds"), and the URL `flutter run` printed is its DDS proxy, which
closes with the detached tool.
simulatorVmServiceUrl reads the app's own "The Dart VM service is listening
on …" announcement from `simctl spawn <udid> log show` (about 3 s) and takes
the newest whose process is still alive; the switch passes it as
`--debug-url`. Not found ⇒ the switch is refused with Rebuild offered.
Measured with this in place, through the node's own startRun / launcher /
attachConnected / signalRun: attach connected after a 20.8 s file sync, the
SIGUSR2 hot restart took 2.4 s, the app (same pid, no rebuild) then ran the
second checkout's code — read back from the VM. A plain hot reload after
attaching reloaded 0 libraries and kept the old code, confirming restart
(not reload) is required.
Also: booting a simulator falls back to the active Xcode's Simulator.app
when `open -a Simulator` cannot find one (a machine where LaunchServices
has none runs its simulators headless).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pointing a running Flutter run node at another checkout (one worktree to the next) no longer means a full native rebuild. When the running app is the one the new folder would build — same device, same flavor, a Flutter run on both sides — the toolbar offers ⇄ Switch (keep app) beside Rebuild: it detaches the Flutter tool,
flutter attaches from the new folder to the app still running on the device, and hot restarts. Measured on an iOS simulator: about 25 s (mostly attach's file sync) instead of a full Xcode build, the app's state resets, and nothing native is rebuilt.Why
The run node (#1118) made two checkouts of one app easy to run side by side, but switching a node from one worktree to another meant stop →
flutter runagain: pods, Xcode build, install — minutes, for what between two worktrees of the same app is almost always a Dart-only difference. Flutter can already do better: a debug app keeps running when its tool detaches, and another tool can attach to it.What it does
d, flutter's own detach key: the tool exits, the app keeps running.flutter attachfrom the new folder to the same device. Only the arguments attach accepts survive (attachArgs, checked againstflutter attach --help): the target and dart-defines are kept;--flavor,--web-portand other run-only flags are dropped, because attach rejects unknown options.attachConnected: its key-command help printed after its own▶ flutter attachlaunch line, so an earlier run's help on screen does not count).--pid-file, the same signal path the node already uses).Every failure leaves the app running and says so, with Rebuild one click away.
canHotSwitchis the one rule for when the button appears.On an iOS simulator, attach is given
--debug-url. Attach finds an already-running app by mDNS, which the simulator barely supports, and the URLflutter runprinted belongs to its DDS proxy, which closes when that tool detaches.simulatorVmServiceUrlreads the app's own "The Dart VM service is listening on …" line fromxcrun simctl spawn <udid> log showand takes the newest whose process is still alive. If none is found, the switch is refused and Rebuild is offered.Also in this PR:
"request": "attach"configurations in launch.json now run asflutter attach(every other attach is still refused, as before).Simulator.appwhenopen -a Simulatorcannot find one; with neither, simulators run headless, as before.Why a restart and not a reload
A freshly attached tool only pushes files that change after it connected. Measured: after attaching from the other checkout, a hot reload printed "Reloaded 0 libraries" and the app kept running the old checkout's code. A restart pushes the whole program. The cost is the app's in-memory state.
Limits, stated in the UI
ios/, Info.plist) need Rebuild; the node cannot know that, which is why Rebuild is always beside Switch.appFlavor. Attach has no--flavor, and Flutter refuses a hand-passedFLUTTER_APP_FLAVORdefine (_ensureReservedDartDefineIsUnset), so after an attachappFlavoris the pubspec's default. An app whoselib/readsappFlavoris refused the switch with that reason.How it was checked
lib/config/environment_config.dartdiffer). The checkout the app was actually running was read back from the app itself over the Dart VM service (getScripts/getObject) at each step:flutter runfrom checkout A → the app runs A's code.d→ the tool exited in 2 s with code 0; the app kept running (same pid).flutter attach(no URL) → never connected ("not discovered after 30 seconds").startRun({attach})→ launcher →attachConnected→signalRun('restart'): attach (with the discovered--debug-url) connected after a 20.8 s file sync; the hot restart took 2.4 s; the app — same pid, nothing reinstalled — then ran B's code.attachArgs,flavorOf,canHotSwitch,attachConnected,parseVmServiceLog,isVmServiceUrl, the attach plans (including--debug-urlprecedence), Dart attach configurations, and on the host the attach launcher, the non-Flutter refusal and theappFlavorguard.npm run typecheckclean; renderer + shared + run-node suites: 10,532 passed.Not verified
--debug-urlis passed there; attach discovers apps on those normally).Surfaces
Desktop: full. Server Edition: the same core handlers; simulator discovery is macOS-only and simply finds nothing elsewhere. SSH / relay / Windows: unchanged from #1118 (not offered). Mobile: N/A.