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
root
contains 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 interpretdescriptor
as[<package>][:<command>]
.- If
<package>
is empty: default to the package atroot
. - If
<command>
is empty, resolve it asbin/<package>.dart
orbin/main.dart
to the first that exists.
- If
For example:
foo
will resolve tofoo:bin/foo.dart
orfoo:bin/main.dart
.:foo
will resolve to<current>:bin/foo.dart
.- `` and
:
both resolves to<current>:bin/<current>.dart
orbin/<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,
);
}
}