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 file with a null 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 containing root, and interpret descriptor as [<package>][:<command>].

    • If <package> is empty: default to the package at current.
    • 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.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),
    );
  }
}