getExecutableForCommand function

Future<DartExecutableWithPackageConfig> getExecutableForCommand(
  1. String descriptor, {
  2. bool allowSnapshot = true,
  3. String? root,
  4. String? pubCacheDir,
  5. List<String> additionalSources = const [],
  6. String? nativeAssets,
})

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 no pubspec.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 at root, and interpret descriptor as [<package>][:<command>].

    • If <package> is empty: default to the package at root.
    • If <command> is empty, resolve it as bin/<package>.dart or bin/main.dart to the first that exists.

For example:

  • foo will resolve to foo:bin/foo.dart or foo:bin/main.dart.
  • :foo will resolve to <current>:bin/foo.dart.
  • `` and : both resolves to <current>:bin/<current>.dart or bin/<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,
    );
  }
}