getExecutableForCommand function
Returns the dart program/snapshot to invoke for running descriptor
resolved according to the package configuration of the package at root
(defaulting to the current working directory). Using the pub-cache at
pubCacheDir (defaulting to the default pub cache).
The returned path will be relative to root.
Resolution:
descriptor is resolved as follows:
-
If
<descriptor>is an existing file (resolved relative to root, either as a path or a file uri): return that (without snapshotting). -
Otherwise if
rootcontains nopubspec.yaml, throws a CommandResolutionFailedException. -
Otherwise if the current package resolution is outdated do an implicit
pub get, if that fails, throw a CommandResolutionFailedException. -
Otherwise let
<current>be the name of the package atroot, and interpretdescriptoras[<package>][:<command>].- If
<package>is empty: default to the package atroot. - If
<command>is empty, resolve it asbin/<package>.dartorbin/main.dartto the first that exists.
- If
For example:
foowill resolve tofoo:bin/foo.dartorfoo:bin/main.dart.:foowill resolve to<current>:bin/foo.dart.- `` and
:both resolves to<current>:bin/<current>.dartorbin/<current>:main.dart.
If that doesn't resolve as an existing file, throw an exception.
Snapshotting
The returned executable will be a snapshot if allowSnapshot is true and
the package is an immutable (non-path) dependency of root.
If returning the path to a snapshot that doesn't already exist, the script Will be built. And a message will be printed only if a terminal is attached to stdout.
Throws an CommandResolutionFailedException if the command is not found or
if the entrypoint is not up to date (requires pub get) and a pub get.
The additionalSources, if provided, instructs the compiler to include
additional source files into compilation even if they are not referenced
from the main library that descriptor resolves to.
The nativeAssets, if provided, instructs the compiler to include
the native-assets mapping for @Native external functions.
Implementation
Future<DartExecutableWithPackageConfig> getExecutableForCommand(
String descriptor, {
bool allowSnapshot = true,
String? root,
String? pubCacheDir,
List<String> additionalSources = const [],
String? nativeAssets,
}) async {
final rootOrCurrent = root ?? p.current;
var asPath = descriptor;
try {
asPath = Uri.parse(descriptor).toFilePath();
} catch (_) {
// Consume input path will either be a valid path or a file uri
// (e.g /directory/file.dart or file:///directory/file.dart). We will try
// parsing it as a Uri, but if parsing failed for any reason (likely
// because path is not a file Uri), `path` will be passed without
// modification to the VM.
}
final asDirectFile = p.join(rootOrCurrent, asPath);
if (fileExists(asDirectFile)) {
return DartExecutableWithPackageConfig(
executable: p.relative(asDirectFile, from: rootOrCurrent),
packageConfig: null,
);
}
if (!fileExists(p.join(rootOrCurrent, 'pubspec.yaml'))) {
throw CommandResolutionFailedException._(
'Could not find file `$descriptor`',
CommandResolutionIssue.fileNotFound,
);
}
final PackageConfig packageConfig;
try {
packageConfig = await Entrypoint.ensureUpToDate(
rootOrCurrent,
cache: SystemCache(rootDir: pubCacheDir),
);
} on ApplicationException catch (e) {
throw CommandResolutionFailedException._(
e.toString(),
CommandResolutionIssue.pubGetFailed,
);
}
// TODO(https://github.com/dart-lang/pub/issues/4127): for workspaces: close
// the nearest enclosing package. That is the "current package" the one to
// default to.
late final rootPackageName = packageConfig.packages
.firstWhereOrNull(
(package) => p.equals(
p.join(rootOrCurrent, '.dart_tool', p.fromUri(package.rootUri)),
rootOrCurrent,
),
)
?.name;
if (rootPackageName == null) {
throw CommandResolutionFailedException._(
'.dart_tool/package_config did not contain the root package',
CommandResolutionIssue.fileNotFound,
);
}
late final String command;
String package;
if (descriptor.contains(':')) {
final parts = descriptor.split(':');
if (parts.length > 2) {
throw CommandResolutionFailedException._(
'[<package>[:command]] cannot contain multiple ":"',
CommandResolutionIssue.parseError,
);
}
package = parts[0];
if (package.isEmpty) package = rootPackageName;
command = parts[1];
} else {
package = descriptor;
if (package.isEmpty) package = rootPackageName;
command = package;
}
if (!packageConfig.packages.any((p) => p.name == package)) {
throw CommandResolutionFailedException._(
'Could not find package `$package` or file `$descriptor`',
CommandResolutionIssue.packageNotFound,
);
}
final executable = Executable(package, p.join('bin', '$command.dart'));
final packageConfigPath = p.relative(
p.join(rootOrCurrent, '.dart_tool', 'package_config.json'),
from: rootOrCurrent,
);
final path = executable.resolve(packageConfig, packageConfigPath);
if (!fileExists(p.join(rootOrCurrent, path))) {
throw CommandResolutionFailedException._(
'Could not find `bin${p.separator}$command.dart` in package `$package`.',
CommandResolutionIssue.noBinaryFound,
);
}
if (!allowSnapshot) {
return DartExecutableWithPackageConfig(
executable: p.relative(path, from: rootOrCurrent),
packageConfig: packageConfigPath,
);
} else {
// TODO(sigurdm): attempt to decide on package mutability without looking at
// PackageGraph, as it requires loading and reading all the pubspec.yaml
// files.
final entrypoint = Entrypoint(
rootOrCurrent,
SystemCache(rootDir: pubCacheDir),
);
final snapshotPath = entrypoint.pathOfSnapshot(executable);
final snapshotStat = tryStatFile(snapshotPath);
final packageConfigStat = tryStatFile(packageConfigPath);
if (snapshotStat == null ||
packageConfigStat == null ||
packageConfigStat.modified.isAfter(snapshotStat.modified) ||
(await entrypoint.packageGraph).isPackageMutable(package)) {
try {
await errorsOnlyUnlessTerminal(
() => entrypoint.precompileExecutable(
executable,
additionalSources: additionalSources,
nativeAssets: nativeAssets,
),
);
} on ApplicationException catch (e) {
throw CommandResolutionFailedException._(
e.toString(),
CommandResolutionIssue.compilationFailed,
);
}
}
return DartExecutableWithPackageConfig(
executable: p.relative(snapshotPath, from: rootOrCurrent),
packageConfig: packageConfigPath,
);
}
}