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 file with anull
packageConfig. -
Otherwise if it looks like a file name (ends with '.dart' or contains a '/' or a r'') throw a CommandResolutionFailedException. (This is for more clear error messages).
-
Otherwise call
Entrypoint.ensureUpToDate
in the current directory to obtain a package config. If that fails, return a CommandResolutionFailedException. -
Otherwise let
<current>
be the name of the innermost package containingroot
, and interpretdescriptor
as[<package>][:<command>]
.- If
<package>
is empty: default to the package atcurrent
. - 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.normalize(p.relative(asDirectFile, from: rootOrCurrent)),
packageConfig: null,
);
} else if (_looksLikeFile(asPath)) {
throw CommandResolutionFailedException._(
'Could not find file `$descriptor`',
CommandResolutionIssue.fileNotFound,
);
}
final PackageConfig packageConfig;
final String workspaceRootDir;
try {
final String workspaceRootRelativeToCwd;
(packageConfig: packageConfig, rootDir: workspaceRootRelativeToCwd) =
await Entrypoint.ensureUpToDate(
rootOrCurrent,
cache: SystemCache(rootDir: pubCacheDir),
);
workspaceRootDir = p.absolute(workspaceRootRelativeToCwd);
} on ApplicationException catch (e) {
throw CommandResolutionFailedException._(
e.toString(),
CommandResolutionIssue.pubGetFailed,
);
}
// Find the first directory from [rootOrCurrent] to [workspaceRootDir] (both
// inclusive) that contains a package from the package config.
final packageConfigDir =
p.join(workspaceRootDir, '.dart_tool', 'package_config.json');
final rootPackageName = maxBy<(String, String), int>(
packageConfig.packages.map((package) {
final packageRootDir =
p.canonicalize(package.resolvedRootDir(packageConfigDir));
if (p.equals(packageRootDir, rootOrCurrent) ||
p.isWithin(packageRootDir, rootOrCurrent)) {
return (package.name, packageRootDir);
} else {
return null;
}
}).nonNulls,
(tuple) => tuple.$2.length,
)?.$1;
if (rootPackageName == null) {
throw CommandResolutionFailedException._(
'${p.join(workspaceRootDir, '.dart_tool', 'package_config.json')} did not contain its own 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.normalize(
p.join(
rootOrCurrent,
workspaceRootDir,
'.dart_tool',
'package_config.json',
),
);
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.normalize(path),
packageConfig: p.relative(packageConfigPath, from: rootOrCurrent),
);
} 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.normalize(p.relative(snapshotPath, from: rootOrCurrent)),
packageConfig: p.relative(packageConfigPath, from: rootOrCurrent),
);
}
}