Dart SDK Discovery and Subprocess Spawning in AOT & JIT
Guidance on locating the Dart SDK and spawning Dart child processes across JIT (dart run, pub global activate) and standalone AOT (dart compile exe, dart install) execution modes.
1. The AOT SDK Discovery Trap
When writing CLI developer tools that spawn dart child processes (e.g. running build_runner, dart format, dart test, or code analyzers), developers frequently write:
// ❌ WRONG: Breaks when compiled to AOT
final dart = Platform.resolvedExecutable;
final sdkDir = path.dirname(path.dirname(dart));Why This Fails in Standalone AOT:
- JIT VM (
dart run,pub global activate):Platform.resolvedExecutablepoints directly to<dart-sdk>/bin/dart. Callingdirname(dirname(...))resolves to the valid SDK root directory. - AOT Binary (
dart compile exe,dart install):Platform.resolvedExecutablepoints to the compiled application binary (e.g.~/.dart/install/app-bundles/my_cli/.../bin/my_cli).
Consequences of Naive Resolution:
- Recursive Subprocess Loop: If the application executes
Platform.resolvedExecutableexpecting thedartVM, it spawns itself recursively. - Flag Rejection Crash: If the child process passes VM flags (such as
--observe,--enable-vm-service) or tool subcommands (likerun,format, ortest), the compiled binary fails immediately with unknown option errors. - Broken SDK Root: Traversing parent directories from
Platform.resolvedExecutableto locate SDK resources (such aslibraries.json) fails because the binary resides in an application bundle directory rather than a Dart SDK installation.
2. The Solution: package:cli_util (^0.6.0)
Do not write bespoke SDK discovery probes. Depend on package:cli_util (version 0.6.0 or higher), which provides memoized, nullable getters (dartExecutable and sdkPath) that locate the Dart SDK across both JIT and AOT environments:
import 'dart:io' as io;
import 'package:cli_util/cli_util.dart' as cli_util;
Future<void> runSubprocess() async {
// Resolves the dart executable across both JIT and AOT environments
final dartExe = cli_util.dartExecutable;
if (dartExe == null) {
io.stderr.writeln('Error: Could not locate the Dart SDK on PATH.');
io.exitCode = 1;
return;
}
final result = await io.Process.run(dartExe, ['format', '.']);
io.stdout.write(result.stdout);
io.stderr.write(result.stderr);
}Potential Dart SDK Locations:
A valid Dart SDK and dart executable may reside in several environmental locations across different developer setups:
- Running VM (
Platform.resolvedExecutable): When running on the JIT VM (dart run),resolvedExecutablepoints directly to<dart-sdk>/bin/dart. - Explicit Environment (
DART_SDK): Defined when a developer explicitly pointsDART_SDKto an SDK installation directory. - System
PATH: Resolved via systemPATHentries (dart,dart.exe, ordart.bat), including dereferencing symlinks and checkingbin/cache/dart-sdkfor Flutter installations. - Flutter Root (
FLUTTER_ROOT): Bundled underFLUTTER_ROOT/bin/cache/dart-sdk.
The exact search order and SDK directory validation logic should be delegated to package:cli_util rather than re-implemented in application code.
3. Subprocess Spawning Invariants
When executing child subprocesses from a Dart CLI:
- Always use
cli_util.dartExecutable: Never passPlatform.executableorPlatform.resolvedExecutable. - Fallback to
'dart'onPATH: Ifpackage:cli_utilis not an option, execute the literal string'dart'directly viaProcess.start('dart', [...], runInShell: Platform.isWindows). - Windows Batch File Handling: On Windows, Flutter installs
dart.batinflutter/bin. Invoking batch files directly viaProcess.startrequiresrunInShell: trueunless pointing to the resolved binarydart.exe.