--enable-experiments for `pub run` (#2493)

* Fix global run

* Normalize executable paths on windows (use \)

* Document lack of snapshotting when experiments are enabled
diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index 221d05c..82d6783 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -7,6 +7,7 @@
 import 'package:path/path.dart' as p;
 
 import '../command.dart';
+import '../executable.dart';
 import '../io.dart';
 import '../log.dart' as log;
 import '../utils.dart';
@@ -27,6 +28,10 @@
   GlobalRunCommand() {
     argParser.addFlag('enable-asserts', help: 'Enable assert statements.');
     argParser.addFlag('checked', abbr: 'c', hide: true);
+    argParser.addMultiOption('enable-experiment',
+        help: 'Runs the executable in a VM with the given experiments enabled. '
+            '(Will disable snapshotting, resulting in slower startup)',
+        valueHelp: 'experiment');
     argParser.addOption('mode', help: 'Deprecated option', hide: true);
   }
 
@@ -57,8 +62,14 @@
       log.warning('The --mode flag is deprecated and has no effect.');
     }
 
-    var exitCode = await globals.runExecutable(package, executable, args,
-        enableAsserts: argResults['enable-asserts'] || argResults['checked']);
+    final experiments = argResults['enable-experiment'] as List;
+    final vmArgs = vmArgFromExperiments(experiments);
+    final globalEntrypoint = await globals.find(package);
+    final exitCode = await runExecutable(globalEntrypoint,
+        Executable.adaptProgramName(package, executable), args,
+        vmArgs: vmArgs,
+        enableAsserts: argResults['enable-asserts'] || argResults['checked'],
+        recompile: globalEntrypoint.precompileExecutable);
     await flushThenExit(exitCode);
   }
 }
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index 9e66cec..931e8ec 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -28,6 +28,11 @@
   RunCommand() {
     argParser.addFlag('enable-asserts', help: 'Enable assert statements.');
     argParser.addFlag('checked', abbr: 'c', hide: true);
+    argParser.addMultiOption('enable-experiment',
+        help:
+            'Runs the executable in a VM with the given experiments enabled.\n'
+            '(Will disable snapshotting, resulting in slower startup)',
+        valueHelp: 'experiment');
     argParser.addOption('mode', help: 'Deprecated option', hide: true);
     // mode exposed for `dartdev run` to use as subprocess.
     argParser.addFlag('dart-dev-run', hide: true);
@@ -67,28 +72,14 @@
       log.warning('The --mode flag is deprecated and has no effect.');
     }
 
-    // The user may pass in an executable without an extension, but the file
-    // to actually execute will always have one.
-    if (p.extension(executable) != '.dart') executable += '.dart';
+    final experiments = argResults['enable-experiment'] as List;
+    final vmArgs = vmArgFromExperiments(experiments);
 
-    var snapshotPath = p.join(
-        entrypoint.cachePath, 'bin', package, '$executable.snapshot.dart2');
-
-    // Don't ever compile snapshots for mutable packages, since their code may
-    // change later on.
-    var useSnapshot = fileExists(snapshotPath) ||
-        (package != entrypoint.root.name &&
-            !entrypoint.packageGraph.isPackageMutable(package));
-
-    var exitCode = await runExecutable(entrypoint, package, executable, args,
+    var exitCode = await runExecutable(
+        entrypoint, Executable.adaptProgramName(package, executable), args,
         enableAsserts: argResults['enable-asserts'] || argResults['checked'],
-        snapshotPath: useSnapshot ? snapshotPath : null, recompile: () {
-      final pkg = entrypoint.packageGraph.packages[package];
-      // The recompile function will only be called when [package] exists.
-      assert(pkg != null);
-      final executablePath = pkg.path(p.join('bin', executable));
-      return entrypoint.precompileExecutable(package, executablePath);
-    });
+        recompile: entrypoint.precompileExecutable,
+        vmArgs: vmArgs);
     await flushThenExit(exitCode);
   }
 
@@ -101,7 +92,8 @@
   ///
   /// Runs `bin/<command>.dart` from package `<package>`. If `<package>` is not
   /// mutable (local root package or path-dependency) a source snapshot will be
-  /// cached in `.dart_tool/pub/bin/<package>/<command>.dart.snapshot.dart2`.
+  /// cached in
+  /// `.dart_tool/pub/bin/<package>/<command>.dart-<sdkVersion>.snapshot`.
   Future _runFromDartDev() async {
     var package = entrypoint.root.name;
     var command = package;
@@ -126,41 +118,13 @@
       args = argResults.rest.skip(1).toList();
     }
 
-    String snapshotPath(String command) => p.join(
-          entrypoint.cachePath,
-          'bin',
-          package,
-          '$command.dart.snapshot.dart2',
-        );
-
-    // If snapshot exists, we strive to avoid using [entrypoint.packageGraph]
-    // because this will load additional files. Instead we just run with the
-    // snapshot. Note. that `pub get|upgrade` will purge snapshots.
-    var snapshotExists = fileExists(snapshotPath(command));
-
-    // Don't ever compile snapshots for mutable packages, since their code may
-    // change later on. Don't check if this the case if a snapshot already
-    // exists.
-    var useSnapshot = snapshotExists ||
-        (package != entrypoint.root.name &&
-            !entrypoint.packageGraph.isPackageMutable(package));
+    final experiments = argResults['enable-experiment'] as List;
+    final vmArgs = vmArgFromExperiments(experiments);
 
     return await flushThenExit(await runExecutable(
-      entrypoint,
-      package,
-      '$command.dart',
-      args,
-      enableAsserts: argResults['enable-asserts'] || argResults['checked'],
-      snapshotPath: useSnapshot ? snapshotPath(command) : null,
-      recompile: () {
-        final pkg = entrypoint.packageGraph.packages[package];
-        // The recompile function will only be called when [package] exists.
-        assert(pkg != null);
-        return entrypoint.precompileExecutable(
-          package,
-          pkg.path('bin', '$command.dart'),
-        );
-      },
-    ));
+        entrypoint, Executable(package, 'bin/$command.dart'), args,
+        vmArgs: vmArgs,
+        enableAsserts: argResults['enable-asserts'] || argResults['checked'],
+        recompile: entrypoint.precompileExecutable));
   }
 }
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index c6e1418..93a06d0 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -34,7 +34,7 @@
   });
 }
 
-/// Snapshots the Dart executable at [executableUrl] to a snapshot at
+/// Snapshots the Dart executable at [executablePath] to a snapshot at
 /// [snapshotPath].
 ///
 /// If [packagesFile] is passed, it's used to resolve `package:` URIs in the
@@ -43,11 +43,15 @@
 ///
 /// If [name] is passed, it is used to describe the executable in logs and error
 /// messages.
-Future snapshot(Uri executableUrl, String snapshotPath,
-    {Uri packagesFile, String name}) async {
-  name = log.bold(name ?? executableUrl.toString());
+Future snapshot(
+  String executablePath,
+  String snapshotPath, {
+  Uri packagesFile,
+  String name,
+}) async {
+  name = log.bold(name ?? executablePath.toString());
 
-  var args = ['--snapshot=$snapshotPath', executableUrl.toString()];
+  var args = ['--snapshot=$snapshotPath', p.toUri(executablePath).toString()];
 
   if (packagesFile != null) {
     // Resolve [packagesFile] in case it's relative to work around sdk#33177.
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 6e545fd..9a29ff7 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -15,6 +15,7 @@
 
 import 'dart.dart' as dart;
 import 'exceptions.dart';
+import 'executable.dart';
 import 'http.dart' as http;
 import 'io.dart';
 import 'lock_file.dart';
@@ -75,10 +76,6 @@
   /// the network.
   final SystemCache cache;
 
-  /// Whether this entrypoint is in memory only, as opposed to representing a
-  /// real directory on disk.
-  final bool _inMemory;
-
   /// Whether this entrypoint exists within the package cache.
   bool get isCached => root.dir != null && p.isWithin(cache.rootDir, root.dir);
 
@@ -122,31 +119,50 @@
 
   PackageGraph _packageGraph;
 
+  /// Where the lock file and package configurations are to be found.
+  ///
+  /// Global packages (except those from path source)
+  /// store these in the global cache.
+  String get _configRoot =>
+      isCached ? p.join(cache.rootDir, 'global_packages', root.name) : root.dir;
+
   /// The path to the entrypoint's "packages" directory.
   String get packagesPath => root.path('packages');
 
   /// The path to the entrypoint's ".packages" file.
-  String get packagesFile => root.path('.packages');
+  String get packagesFile => p.join(_configRoot, '.packages');
 
   /// The path to the entrypoint's ".dart_tool/package_config.json" file.
   String get packageConfigFile =>
-      root.path('.dart_tool', 'package_config.json');
+      p.join(_configRoot, '.dart_tool', 'package_config.json');
 
   /// The path to the entrypoint package's pubspec.
   String get pubspecPath => root.path('pubspec.yaml');
 
   /// The path to the entrypoint package's lockfile.
-  String get lockFilePath => root.path('pubspec.lock');
+  String get lockFilePath => p.join(_configRoot, 'pubspec.lock');
 
   /// The path to the entrypoint package's `.dart_tool/pub` cache directory.
   ///
   /// If the old-style `.pub` directory is being used, this returns that
   /// instead.
+  ///
+  /// For globally activated packages from path, this is not the same as
+  /// [configRoot], because the snapshots should be stored in the global cache,
+  /// but the configuration is stored at the package itself.
   String get cachePath {
-    var newPath = root.path('.dart_tool/pub');
-    var oldPath = root.path('.pub');
-    if (!dirExists(newPath) && dirExists(oldPath)) return oldPath;
-    return newPath;
+    if (isGlobal) {
+      return p.join(
+        cache.rootDir,
+        'global_packages',
+        root.name,
+      );
+    } else {
+      var newPath = root.path('.dart_tool/pub');
+      var oldPath = root.path('.pub');
+      if (!dirExists(newPath) && dirExists(oldPath)) return oldPath;
+      return newPath;
+    }
   }
 
   /// The path to the directory containing dependency executable snapshots.
@@ -155,27 +171,21 @@
   /// Loads the entrypoint for the package at the current directory.
   Entrypoint.current(this.cache)
       : root = Package.load(null, '.', cache.sources, isRootPackage: true),
-        _inMemory = false,
         isGlobal = false;
 
   /// Loads the entrypoint from a package at [rootDir].
   Entrypoint(String rootDir, this.cache)
       : root = Package.load(null, rootDir, cache.sources, isRootPackage: true),
-        _inMemory = false,
-        isGlobal = true;
+        isGlobal = false;
 
   /// Creates an entrypoint given package and lockfile objects.
-  Entrypoint.inMemory(this.root, this._lockFile, this.cache)
-      : _inMemory = true,
-        isGlobal = true;
-
-  /// Creates an entrypoint given a package and a [solveResult], from which the
-  /// package graph and lockfile will be computed.
-  Entrypoint.fromSolveResult(this.root, this.cache, SolveResult solveResult)
-      : _inMemory = true,
-        isGlobal = true {
-    _packageGraph = PackageGraph.fromSolveResult(this, solveResult);
-    _lockFile = _packageGraph.lockFile;
+  /// If a SolveResult is already created it can be passes as an optimization.
+  Entrypoint.global(this.root, this._lockFile, this.cache,
+      {SolveResult solveResult})
+      : isGlobal = true {
+    if (solveResult != null) {
+      _packageGraph = PackageGraph.fromSolveResult(this, solveResult);
+    }
   }
 
   /// Writes .packages and .dart_tool/package_config.json
@@ -274,59 +284,98 @@
     }
   }
 
-  /// Precompiles all executables from dependencies that don't transitively
-  /// depend on [this] or on a path dependency.
-  Future precompileExecutables({Iterable<String> changed}) async {
-    migrateCache();
-    _deleteExecutableSnapshots(changed: changed);
-
-    var executables = mapMap<String, PackageRange, String, List<String>>(
-        root.immediateDependencies,
-        value: (name, _) => _executablesForPackage(name));
-
-    for (var package in executables.keys.toList()) {
-      if (executables[package].isEmpty) executables.remove(package);
+  /// All executables that should be snapshotted from this entrypoint.
+  ///
+  /// This is all executables in direct dependencies.
+  /// that don't transitively depend on [this] or on a mutable dependency.
+  ///
+  /// Except globally activated packages they should precompile executables from
+  /// the package itself if they are immutable.
+  List<Executable> get precompiledExecutables {
+    if (isGlobal) {
+      if (isCached) {
+        return root.executablePaths
+            .map((path) => Executable(root.name, path))
+            .toList();
+      } else {
+        return <Executable>[];
+      }
     }
+    final r = root.immediateDependencies.keys.expand((packageName) {
+      if (packageGraph.isPackageMutable(packageName)) {
+        return <Executable>[];
+      }
+      final package = packageGraph.packages[packageName];
+      return package.executablePaths
+          .map((path) => Executable(packageName, path));
+    }).toList();
+    return r;
+  }
+
+  /// Precompiles all [precompiledExecutables].
+  Future<void> precompileExecutables({Iterable<String> changed}) async {
+    migrateCache();
+
+    final executables = precompiledExecutables;
 
     if (executables.isEmpty) return;
 
     await log.progress('Precompiling executables', () async {
-      ensureDir(_snapshotPath);
-
-      // Make sure there's a trailing newline so our version file matches the
-      // SDK's.
-      writeTextFile(p.join(_snapshotPath, 'sdk-version'), '${sdk.version}\n');
-
-      await _precompileExecutables(executables);
+      if (isGlobal) {
+        /// Global snapshots might linger in the cache if we don't remove old
+        /// snapshots when it is re-activated.
+        cleanDir(_snapshotPath);
+      } else {
+        ensureDir(_snapshotPath);
+      }
+      return waitAndPrintErrors(executables.map((executable) {
+        var dir = p.dirname(snapshotPathOfExecutable(executable));
+        cleanDir(dir);
+        return waitAndPrintErrors(executables.map(_precompileExecutable));
+      }));
     });
   }
 
-  //// Precompiles [executables] to snapshots from the filesystem.
-  Future<void> _precompileExecutables(Map<String, List<String>> executables) {
-    return waitAndPrintErrors(executables.keys.map((package) {
-      var dir = p.join(_snapshotPath, package);
-      cleanDir(dir);
-      return waitAndPrintErrors(executables[package].map((path) =>
-          _precompileExecutable(
-              package, p.join(packageGraph.packages[package].dir, path))));
-    }));
-  }
-
   /// Precompiles executable .dart file at [path] to a snapshot.
-  Future<void> precompileExecutable(String package, String path) async {
+  Future<void> precompileExecutable(Executable executable) async {
     return await log.progress('Precompiling executable', () async {
-      var dir = p.join(_snapshotPath, package);
-      ensureDir(dir);
-      return waitAndPrintErrors([_precompileExecutable(package, path)]);
+      ensureDir(p.dirname(snapshotPathOfExecutable(executable)));
+      return waitAndPrintErrors([_precompileExecutable(executable)]);
     });
   }
 
-  Future<void> _precompileExecutable(String package, String path) async {
-    var dir = p.join(_snapshotPath, package);
+  Future<void> _precompileExecutable(Executable executable) async {
+    final package = executable.package;
     await dart.snapshot(
-        p.toUri(path), p.join(dir, p.basename(path) + '.snapshot.dart2'),
+        resolveExecutable(executable), snapshotPathOfExecutable(executable),
         packagesFile: p.toUri(packagesFile),
-        name: '$package:${p.basenameWithoutExtension(path)}');
+        name:
+            '$package:${p.basenameWithoutExtension(executable.relativePath)}');
+  }
+
+  /// The location of the snapshot of the dart program at [path] in [package]
+  /// will be stored here.
+  ///
+  /// We use the sdk version to make sure we don't run snapshots from a
+  /// different sdk.
+  ///
+  /// [path] must be relative.
+  String snapshotPathOfExecutable(Executable executable) {
+    assert(p.isRelative(executable.relativePath));
+    final versionSuffix = sdk.version;
+    return isGlobal
+        ? p.join(_snapshotPath,
+            '${p.basename(executable.relativePath)}-$versionSuffix.snapshot')
+        : p.join(_snapshotPath, executable.package,
+            '${p.basename(executable.relativePath)}-$versionSuffix.snapshot');
+  }
+
+  /// The absolute path of [executable] resolved relative to [this].
+  String resolveExecutable(Executable executable) {
+    return p.join(
+      packageGraph.packages[executable.package].dir,
+      executable.relativePath,
+    );
   }
 
   /// Deletes outdated cached executable snapshots.
@@ -368,34 +417,6 @@
     }
   }
 
-  /// Returns the list of all paths within [packageName] that should be
-  /// precompiled.
-  List<String> _executablesForPackage(String packageName) {
-    var package = packageGraph.packages[packageName];
-    var binDir = package.path('bin');
-    if (!dirExists(binDir)) return [];
-    if (packageGraph.isPackageMutable(packageName)) return [];
-
-    var executables = package.executablePaths;
-
-    // If any executables don't exist, recompile all executables.
-    //
-    // Normally, [_deleteExecutableSnapshots] will ensure that all the outdated
-    // executable directories will be deleted, any checking for any non-existent
-    // executable will save us a few IO operations over checking each one. If
-    // some executables do exist and some do not, the directory is corrupted and
-    // it's good to start from scratch anyway.
-    var executablesExist = executables.every((executable) {
-      var snapshotPath = p.join(_snapshotPath, packageName,
-          '${p.basename(executable)}.snapshot.dart2');
-      return fileExists(snapshotPath);
-    });
-    if (!executablesExist) return executables;
-
-    // Otherwise, we don't need to recompile.
-    return [];
-  }
-
   /// Makes sure the package at [id] is locally available.
   ///
   /// This automatically downloads the package to the system-wide cache as well
@@ -414,7 +435,7 @@
   /// `.dart_tool/package_config.json` file doesn't exist or if it's out-of-date
   /// relative to the lockfile or the pubspec.
   void assertUpToDate() {
-    if (_inMemory) return;
+    if (isCached) return;
 
     if (!entryExists(lockFilePath)) {
       dataError('No pubspec.lock file found, please run "pub get" first.');
@@ -761,7 +782,10 @@
   /// If the entrypoint uses the old-style `.pub` cache directory, migrates it
   /// to the new-style `.dart_tool/pub` directory.
   void migrateCache() {
-    var oldPath = root.path('.pub');
+    // Cached packages don't have these.
+    if (isCached) return;
+
+    var oldPath = p.join(_configRoot, '.pub');
     if (!dirExists(oldPath)) return;
 
     var newPath = root.path('.dart_tool/pub');
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 720691c..04e5d78 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -7,6 +7,7 @@
 import 'dart:isolate';
 
 import 'package:path/path.dart' as p;
+import 'package:pedantic/pedantic.dart';
 
 import 'entrypoint.dart';
 import 'exit_codes.dart' as exit_codes;
@@ -15,6 +16,14 @@
 import 'log.dart' as log;
 import 'utils.dart';
 
+/// Take a list of experiments to enable and turn them into a list with a single
+/// argument to pass to the VM enabling the same experiments.
+List<String> vmArgFromExperiments(List<String> experiments) {
+  return [
+    if (experiments.isNotEmpty) "--enable-experiment=${experiments.join(',')}"
+  ];
+}
+
 /// Runs [executable] from [package] reachable from [entrypoint].
 ///
 /// The [executable] is a relative path to a Dart file within [package], which
@@ -27,24 +36,22 @@
 /// If [packagesFile] is passed, it's used as the package config file path for
 /// the executable. Otherwise, `entrypoint.packagesFile` is used.
 ///
-/// If [snapshotPath] is passed, this will run the executable from that snapshot
-/// if it exists. If [recompile] is passed, it's called if the snapshot is
-/// out-of-date or nonexistent. It's expected to regenerate a snapshot at
-/// [snapshotPath], after which the snapshot will be re-run. It's ignored if
-/// [snapshotPath] isn't passed.
+/// If the executable is in an immutable package and we pass no [vmArgs], it
+/// run from snapshot (and precompiled if the snapshot doesn't already exist).
 ///
 /// Returns the exit code of the spawned app.
-Future<int> runExecutable(Entrypoint entrypoint, String package,
-    String executable, Iterable<String> args,
+Future<int> runExecutable(
+    Entrypoint entrypoint, Executable executable, Iterable<String> args,
     {bool enableAsserts = false,
     String packagesFile,
-    String snapshotPath,
-    Future<void> Function() recompile}) async {
+    Future<void> Function(Executable) recompile,
+    List<String> vmArgs = const []}) async {
+  final package = executable.package;
   packagesFile ??= entrypoint.packagesFile;
 
   // Make sure the package is an immediate dependency of the entrypoint or the
   // entrypoint itself.
-  if (entrypoint.root.name != package &&
+  if (entrypoint.root.name != executable.package &&
       !entrypoint.root.immediateDependencies.containsKey(package)) {
     if (entrypoint.packageGraph.packages.containsKey(package)) {
       dataError('Package "$package" is not an immediate dependency.\n'
@@ -57,130 +64,155 @@
 
   entrypoint.migrateCache();
 
-  // Unless the user overrides the verbosity, we want to filter out the
-  // normal pub output that may be shown when recompiling snapshots if we are
-  // not attached to a terminal. This is to not pollute stdout when the output
-  // of `pub run` is piped somewhere.
-  return await log.warningsOnlyUnlessTerminal(() async {
-    // Uncached packages are run from source.
-    if (snapshotPath != null) {
-      // Since we don't access the package graph, this doesn't happen
-      // automatically.
-      entrypoint.assertUpToDate();
+  var snapshotPath = entrypoint.snapshotPathOfExecutable(executable);
 
-      var result = await _runOrCompileSnapshot(snapshotPath, args,
-          packagesFile: packagesFile,
-          enableAsserts: enableAsserts,
-          recompile: recompile);
-      if (result != null) return result;
+  // Don't compile snapshots for mutable packages, since their code may
+  // change later on.
+  //
+  // Also we don't snapshot if we have non-default arguments to the VM, as
+  // these would be inconsistent if another set of settings are given in a
+  // later invocation.
+  var useSnapshot =
+      !entrypoint.packageGraph.isPackageMutable(package) && vmArgs.isEmpty;
+
+  var executablePath = entrypoint.resolveExecutable(executable);
+  if (!fileExists(executablePath)) {
+    var message =
+        'Could not find ${log.bold(p.normalize(executable.relativePath))}';
+    if (entrypoint.isGlobal || package != entrypoint.root.name) {
+      message += ' in package ${log.bold(package)}';
     }
+    log.error('$message.');
+    return exit_codes.NO_INPUT;
+  }
 
-    // If the command has a path separator, then it's a path relative to the
-    // root of the package. Otherwise, it's implicitly understood to be in
-    // "bin".
-    if (p.split(executable).length == 1) executable = p.join('bin', executable);
+  if (useSnapshot) {
+    // Since we don't access the package graph, this doesn't happen
+    // automatically.
+    entrypoint.assertUpToDate();
 
-    var executablePath = await _executablePath(entrypoint, package, executable);
-
+    if (!fileExists(snapshotPath)) {
+      await recompile(executable);
+    }
+    executablePath = snapshotPath;
+  } else {
     if (executablePath == null) {
-      var message = 'Could not find ${log.bold(executable)}';
+      var message =
+          'Could not find ${log.bold(p.normalize(executable.relativePath))}';
       if (entrypoint.isGlobal || package != entrypoint.root.name) {
         message += ' in package ${log.bold(package)}';
       }
       log.error('$message.');
       return exit_codes.NO_INPUT;
     }
-
-    // We use an absolute path here not because the VM insists but because it's
-    // helpful for the subprocess to be able to spawn Dart with
-    // Platform.executableArguments and have that work regardless of the working
-    // directory.
-    var packageConfig = p.toUri(p.absolute(packagesFile));
-
-    await isolate.runUri(p.toUri(executablePath), args.toList(), null,
-        enableAsserts: enableAsserts,
-        automaticPackageResolution: packageConfig == null,
-        packageConfig: packageConfig);
-    return exitCode;
-  });
-}
-
-/// Returns the full path the VM should use to load the executable at [path].
-///
-/// [path] must be relative to the root of [package]. If [path] doesn't exist,
-/// returns `null`. If the executable is global and doesn't already have a
-/// `.packages` file one will be created.
-Future<String> _executablePath(
-    Entrypoint entrypoint, String package, String path) async {
-  assert(p.isRelative(path));
-
-  var fullPath = entrypoint.packageGraph.packages[package].path(path);
-  if (!fileExists(fullPath)) return null;
-  return p.absolute(fullPath);
-}
-
-/// Like [_runSnapshot], but runs [recompile] if [path] doesn't exist yet.
-///
-/// Returns `null` if [path] doesn't exist and isn't generated by [recompile].
-Future<int> _runOrCompileSnapshot(String path, Iterable<String> args,
-    {Future<void> Function() recompile,
-    String packagesFile,
-    bool enableAsserts = false}) async {
-  if (!fileExists(path)) {
-    if (recompile == null) return null;
-    await recompile();
-    if (!fileExists(path)) return null;
   }
 
-  return await _runSnapshot(path, args,
-      recompile: recompile,
-      packagesFile: packagesFile,
-      enableAsserts: enableAsserts);
-}
+  // We use an absolute path here not because the VM insists but because it's
+  // helpful for the subprocess to be able to spawn Dart with
+  // Platform.executableArguments and have that work regardless of the working
+  // directory.
+  var packageConfig = p.absolute(packagesFile);
 
-/// Runs the snapshot at [path] with [args] and hooks its stdout, stderr, and
-/// sdtin to this process's.
-///
-/// If [recompile] is passed, it's called if the snapshot is out-of-date. It's
-/// expected to regenerate a snapshot at [path], after which the snapshot will
-/// be re-run.
-///
-/// If [enableAsserts] is set, runs the snapshot with assertions enabled.
-///
-/// Returns the snapshot's exit code.
-///
-/// This doesn't do any validation of the snapshot's SDK version.
-Future<int> _runSnapshot(String path, Iterable<String> args,
-    {Future<void> Function() recompile,
-    String packagesFile,
-    bool enableAsserts = false}) async {
-  Uri packageConfig;
-  if (packagesFile != null) {
-    // We use an absolute path here not because the VM insists but because it's
-    // helpful for the subprocess to be able to spawn Dart with
-    // Platform.executableArguments and have that work regardless of the working
-    // directory.
-    packageConfig = p.toUri(p.absolute(packagesFile));
-  }
-
-  var url = p.toUri(p.absolute(path));
-  var argList = args.toList();
   try {
-    await isolate.runUri(url, argList, null,
-        enableAsserts: enableAsserts,
-        automaticPackageResolution: packageConfig == null,
-        packageConfig: packageConfig);
+    return await _runDartProgram(executablePath, args, packageConfig,
+        enableAsserts: enableAsserts, vmArgs: vmArgs);
   } on IsolateSpawnException catch (error) {
-    if (recompile == null) rethrow;
-    if (!error.message.contains('Invalid kernel binary format version')) {
+    if (!useSnapshot ||
+        !error.message.contains('Invalid kernel binary format version')) {
       rethrow;
     }
 
     log.fine('Precompiled executable is out of date.');
-    await recompile();
-    await isolate.runUri(url, argList, null,
-        enableAsserts: enableAsserts, packageConfig: packageConfig);
+    await recompile(executable);
+    return _runDartProgram(executablePath, args, packageConfig,
+        enableAsserts: enableAsserts, vmArgs: vmArgs);
   }
+}
 
-  return exitCode;
+/// Runs the dart program (can be a snapshot) at [path] with [args] and hooks
+/// its stdout, stderr, and sdtin to this process's.
+///
+/// [packageConfig] is the path to the ".dart_tool/package_config.json" file.
+///
+/// If [enableAsserts] is set, runs the program with assertions enabled.
+///
+/// Passes [vmArgs] to the vm.
+///
+/// Returns the programs's exit code.
+Future<int> _runDartProgram(
+    String path, List<String> args, String packageConfig,
+    {bool enableAsserts, List<String> vmArgs}) async {
+  path = p.absolute(path);
+  packageConfig = p.absolute(packageConfig);
+
+  // We use Isolate.spawnUri when there are no extra vm-options.
+  // That provides better signal handling, and possibly faster startup.
+  if (vmArgs.isEmpty) {
+    var argList = args.toList();
+    await isolate.runUri(p.toUri(path), argList, null,
+        enableAsserts: enableAsserts,
+        automaticPackageResolution: packageConfig == null,
+        packageConfig: p.toUri(packageConfig));
+    return exitCode;
+  } else {
+    // By ignoring sigint, only the child process will get it when
+    // they are sent to the current process group. That is what happens when
+    // you send signals from the terminal.
+    //
+    // This allows the child to not be orphaned if it sets up handlers for these
+    // signals.
+    //
+    // We do not drain sighub because it is generally a bad idea to have
+    // non-default handling for it.
+    //
+    // We do not drain sigterm and sigusr1/sigusr2 because it does not seem to
+    // work well in manual tests.
+    //
+    // We do not drain sigquit because dart doesn't support listening to it.
+    // https://github.com/dart-lang/sdk/issues/41961 .
+    //
+    // TODO(sigurdm) To handle signals better we would ideally have `exec`
+    // semantics without `fork` for starting the subprocess.
+    // https://github.com/dart-lang/sdk/issues/41966.
+    unawaited(ProcessSignal.sigint.watch().drain());
+
+    final process = await Process.start(
+      Platform.resolvedExecutable,
+      [
+        '--packages=$packageConfig',
+        ...vmArgs,
+        if (enableAsserts) '--enable-asserts',
+        p.toUri(path).toString(),
+        ...args,
+      ],
+      mode: ProcessStartMode.inheritStdio,
+    );
+
+    return process.exitCode;
+  }
+}
+
+/// An executable in a package
+class Executable {
+  String package;
+  // The relative path to the executable inside the root of [package].
+  String relativePath;
+
+  // Adapts the program-name following conventions of dart run
+  Executable.adaptProgramName(this.package, String program)
+      : relativePath = _adaptProgramToPath(program);
+
+  Executable(this.package, this.relativePath);
+
+  static String _adaptProgramToPath(String program) {
+    // If the command has a path separator, then it's a path relative to the
+    // root of the package. Otherwise, it's implicitly understood to be in
+    // "bin".
+    if (p.split(program).length == 1) program = p.join('bin', program);
+
+    // The user may pass in an executable without an extension, but the file
+    // to actually execute will always have one.
+    if (p.extension(program) != '.dart') program += '.dart';
+    return program;
+  }
 }
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 2cb0b64..f052b60 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -5,13 +5,13 @@
 import 'dart:async';
 import 'dart:io';
 
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 
-import 'dart.dart' as dart;
 import 'entrypoint.dart';
 import 'exceptions.dart';
-import 'executable.dart' as exe;
+import 'executable.dart';
 import 'http.dart' as http;
 import 'io.dart';
 import 'lock_file.dart';
@@ -104,8 +104,6 @@
   /// Finds the latest version of the hosted package with [name] that matches
   /// [constraint] and makes it the active global version.
   ///
-  /// The [features] map controls which features of the package to activate.
-  ///
   /// [executables] is the names of the executables that should have binstubs.
   /// If `null`, all executables in the package will get binstubs. If empty, no
   /// binstubs will be created.
@@ -162,7 +160,7 @@
     var binDir = p.join(_directory, name, 'bin');
     if (dirExists(binDir)) deleteEntry(binDir);
 
-    _updateBinStubs(entrypoint.root, executables,
+    _updateBinStubs(entrypoint, entrypoint.root, executables,
         overwriteBinStubs: overwriteBinStubs);
     log.message('Activated ${_formatPackage(id)}.');
   }
@@ -206,69 +204,41 @@
 
     var lockFile = result.lockFile;
     _writeLockFile(dep.name, lockFile);
+    await _writePackageConfigFiles(dep.name, lockFile);
+
+    // We want the entrypoint to be rooted at 'dep' not the dummy-package.
+    result.packages.removeWhere((id) => id.name == 'pub global activate');
+
+    var id = lockFile.packages[dep.name];
+    // Load the package graph from [result] so we don't need to re-parse all
+    // the pubspecs.
+    final entrypoint = Entrypoint.global(
+        Package(result.pubspecs[dep.name],
+            cache.source(dep.source).getDirectory(id)),
+        result.lockFile,
+        cache,
+        solveResult: result);
+    await entrypoint.precompileExecutables();
+
+    _updateBinStubs(
+      entrypoint,
+      cache.load(entrypoint.lockFile.packages[dep.name]),
+      executables,
+      overwriteBinStubs: overwriteBinStubs,
+    );
+
+    log.message('Activated ${_formatPackage(id)}.');
+  }
+
+  Future<void> _writePackageConfigFiles(
+      String package, LockFile lockFile) async {
     // TODO(sigurdm): Use [Entrypoint.writePackagesFiles] instead.
-    final packagesFilePath = _getPackagesFilePath(dep.name);
-    final packageConfigFilePath = _getPackageConfigFilePath(dep.name);
+    final packagesFilePath = _getPackagesFilePath(package);
+    final packageConfigFilePath = _getPackageConfigFilePath(package);
     writeTextFile(packagesFilePath, lockFile.packagesFile(cache));
     ensureDir(p.dirname(packageConfigFilePath));
     writeTextFile(
         packageConfigFilePath, await lockFile.packageConfigFile(cache));
-
-    // Load the package graph from [result] so we don't need to re-parse all
-    // the pubspecs.
-    var entrypoint = Entrypoint.fromSolveResult(root, cache, result);
-    var snapshots = await _precompileExecutables(entrypoint, dep.name);
-
-    _updateBinStubs(entrypoint.packageGraph.packages[dep.name], executables,
-        overwriteBinStubs: overwriteBinStubs, snapshots: snapshots);
-
-    var id = lockFile.packages[dep.name];
-    log.message('Activated ${_formatPackage(id)}.');
-  }
-
-  /// Precompiles the executables for [packageName] and saves them in the global
-  /// cache.
-  ///
-  /// Returns a map from executable name to path for the snapshots that were
-  /// successfully precompiled.
-  Future<Map<String, String>> _precompileExecutables(
-      Entrypoint entrypoint, String packageName) {
-    return log.progress('Precompiling executables', () async {
-      var binDir = p.join(_directory, packageName, 'bin');
-      cleanDir(binDir);
-
-      final packagesFilePath = _getPackagesFilePath(packageName);
-      final packageConfigFilePath = _getPackageConfigFilePath(packageName);
-      if (!fileExists(packagesFilePath) || !fileExists(packageConfigFilePath)) {
-        // TODO(sigurdm): Use [entrypoint.writePackagesFiles] instead.
-        // The `.packages` file may not already exist if the global executable
-        // has a 1.6-style lock file instead.
-        // Similarly, the `.dart_tool/package_config.json` may not exist if the
-        // global executable was activated before 2.6
-        writeTextFile(
-            packagesFilePath, entrypoint.lockFile.packagesFile(cache));
-        ensureDir(p.dirname(packageConfigFilePath));
-        writeTextFile(
-          packageConfigFilePath,
-          await entrypoint.lockFile.packageConfigFile(cache),
-        );
-      }
-
-      // Try to avoid starting up an asset server to precompile packages if
-      // possible. This is faster and produces better error messages.
-      var package = entrypoint.packageGraph.packages[packageName];
-      var precompiled = <String, String>{};
-      await waitAndPrintErrors(package.executablePaths.map((path) async {
-        var url = p.toUri(p.join(package.dir, path));
-        var basename = p.basename(path);
-        var snapshotPath = p.join(binDir, '$basename.snapshot.dart2');
-        await dart.snapshot(url, snapshotPath,
-            packagesFile: p.toUri(_getPackagesFilePath(package.name)),
-            name: '${package.name}:${p.basenameWithoutExtension(path)}');
-        precompiled[p.withoutExtension(basename)] = snapshotPath;
-      }));
-      return precompiled;
-    });
   }
 
   /// Finishes activating package [package] by saving [lockFile] in the cache.
@@ -330,7 +300,7 @@
   /// Finds the active package with [name].
   ///
   /// Returns an [Entrypoint] loaded with the active package if found.
-  Entrypoint find(String name) {
+  Future<Entrypoint> find(String name) async {
     var lockFilePath = _getLockFilePath(name);
     LockFile lockFile;
     try {
@@ -350,6 +320,8 @@
       // Move the old lockfile to its new location.
       ensureDir(p.dirname(lockFilePath));
       File(oldLockFilePath).renameSync(lockFilePath);
+      // Just make sure these files are created as well.
+      await _writePackageConfigFiles(name, lockFile);
     }
 
     // Remove the package itself from the lockfile. We put it in there so we
@@ -363,7 +335,7 @@
     if (source is CachedSource) {
       // For cached sources, the package itself is in the cache and the
       // lockfile is the one we just loaded.
-      entrypoint = Entrypoint.inMemory(cache.load(id), lockFile, cache);
+      entrypoint = Entrypoint.global(cache.load(id), lockFile, cache);
     } else {
       // For uncached sources (i.e. path), the ID just points to the real
       // directory for the package.
@@ -399,20 +371,16 @@
   ///
   /// Returns the exit code from the executable.
   Future<int> runExecutable(
-      String package, String executable, Iterable<String> args,
-      {bool enableAsserts = false}) {
-    var entrypoint = find(package);
-    return exe.runExecutable(
-        entrypoint, package, p.join('bin', '$executable.dart'), args,
+      Entrypoint entrypoint, Executable executable, Iterable<String> args,
+      {bool enableAsserts = false,
+      String packagesFile,
+      Future<void> Function(Executable) recompile,
+      List<String> vmArgs = const []}) async {
+    return await runExecutable(entrypoint, executable, args,
         enableAsserts: enableAsserts,
-        packagesFile:
-            entrypoint.isCached ? _getPackagesFilePath(package) : null,
-        // Don't use snapshots for executables activated from paths.
-        snapshotPath: entrypoint.isCached
-            ? p.join(
-                _directory, package, 'bin', '$executable.dart.snapshot.dart2')
-            : null,
-        recompile: () => _precompileExecutables(entrypoint, package));
+        packagesFile: packagesFile,
+        recompile: recompile,
+        vmArgs: vmArgs);
   }
 
   /// Gets the path to the lock file for an activated cached package with
@@ -515,14 +483,13 @@
           id = _loadPackageId(entry);
           log.message('Reactivating ${log.bold(id.name)} ${id.version}...');
 
-          var entrypoint = find(id.name);
-          var snapshots = await _precompileExecutables(entrypoint, id.name);
+          var entrypoint = await find(id.name);
+
+          await _writePackageConfigFiles(id.name, entrypoint.lockFile);
+          await entrypoint.precompileExecutables();
           var packageExecutables = executables.remove(id.name) ?? [];
-          _updateBinStubs(
-              entrypoint.packageGraph.packages[id.name], packageExecutables,
-              overwriteBinStubs: true,
-              snapshots: snapshots,
-              suggestIfNotOnPath: false);
+          _updateBinStubs(entrypoint, cache.load(id), packageExecutables,
+              overwriteBinStubs: true, suggestIfNotOnPath: false);
           successes.add(id.name);
         } catch (error, stackTrace) {
           var message = 'Failed to reactivate '
@@ -573,18 +540,11 @@
   /// existing binstubs in other packages will be overwritten by this one's.
   /// Otherwise, the previous ones will be preserved.
   ///
-  /// If [snapshots] is given, it is a map of the names of executables whose
-  /// snapshots were precompiled to the paths of those snapshots. Binstubs for
-  /// those will run the snapshot directly and skip pub entirely.
-  ///
   /// If [suggestIfNotOnPath] is `true` (the default), this will warn the user if
   /// the bin directory isn't on their path.
-  void _updateBinStubs(Package package, List<String> executables,
-      {bool overwriteBinStubs,
-      Map<String, String> snapshots,
-      bool suggestIfNotOnPath = true}) {
-    snapshots ??= const {};
-
+  void _updateBinStubs(
+      Entrypoint entrypoint, Package package, List<String> executables,
+      {bool overwriteBinStubs, bool suggestIfNotOnPath = true}) {
     // Remove any previously activated binstubs for this package, in case the
     // list of executables has changed.
     _deleteBinStubs(package.name);
@@ -604,8 +564,15 @@
 
       var script = package.pubspec.executables[executable];
 
-      var previousPackage = _createBinStub(package, executable, script,
-          overwrite: overwriteBinStubs, snapshot: snapshots[script]);
+      var previousPackage = _createBinStub(
+        package,
+        executable,
+        script,
+        overwrite: overwriteBinStubs,
+        snapshot: entrypoint.snapshotPathOfExecutable(
+          Executable.adaptProgramName(package.name, script),
+        ),
+      );
       if (previousPackage != null) {
         collided[executable] = previousPackage;
 
@@ -652,7 +619,7 @@
     // produced by a transformer. Do something better.
     var binFiles = package
         .listFiles(beneath: 'bin', recursive: false)
-        .map((path) => package.relative(path))
+        .map(package.relative)
         .toList();
     for (var executable in installed) {
       var script = package.pubspec.executables[executable];
@@ -673,13 +640,19 @@
   /// If [overwrite] is `true`, this will replace an existing binstub with that
   /// name for another package.
   ///
-  /// If [snapshot] is non-null, it is a path to a snapshot file. The binstub
-  /// will invoke that directly. Otherwise, it will run `pub global run`.
+  /// [snapshot] is a path to a snapshot file. If that snapshot exists the
+  /// binstub will invoke that directly. Otherwise, it will run
+  /// `pub global run`.
   ///
   /// If a collision occurs, returns the name of the package that owns the
   /// existing binstub. Otherwise returns `null`.
-  String _createBinStub(Package package, String executable, String script,
-      {bool overwrite, String snapshot}) {
+  String _createBinStub(
+    Package package,
+    String executable,
+    String script, {
+    @required bool overwrite,
+    @required String snapshot,
+  }) {
     var binStubPath = p.join(_binStubDir, executable);
     if (Platform.isWindows) binStubPath += '.bat';
 
@@ -696,19 +669,29 @@
       }
     }
 
-    // If the script was precompiled to a snapshot, just invoke that directly
-    // and skip pub global run entirely.
+    // If the script was precompiled to a snapshot, just try to invoke that
+    // directly and skip pub global run entirely.
     String invocation;
-    if (snapshot != null) {
-      // We expect absolute paths from the precompiler since relative ones
-      // won't be relative to the right directory when the user runs this.
-      assert(p.isAbsolute(snapshot));
-      invocation = 'dart "$snapshot"';
-    } else {
-      invocation = 'pub global run ${package.name}:$script';
-    }
-
     if (Platform.isWindows) {
+      if (snapshot != null && fileExists(snapshot)) {
+        // We expect absolute paths from the precompiler since relative ones
+        // won't be relative to the right directory when the user runs this.
+        assert(p.isAbsolute(snapshot));
+        invocation = '''
+if exist "$snapshot" (
+  dart "$snapshot" %*
+  rem The VM exits with code 253 if the snapshot version is out-of-date.	
+  rem If it is, we need to delete it and run "pub global" manually.	
+  if not errorlevel 253 (	
+    exit /b %errorlevel%	
+  )
+  pub global run ${package.name}:$script %*
+) else (
+  pub global run ${package.name}:$script %*
+)''';
+      } else {
+        invocation = 'pub global run ${package.name}:$script %*';
+      }
       var batch = '''
 @echo off
 rem This file was created by pub v${sdk.version}.
@@ -716,24 +699,31 @@
 rem Version: ${package.version}
 rem Executable: $executable
 rem Script: $script
-$invocation %*
+$invocation
 ''';
-
-      if (snapshot != null) {
-        batch += '''
-
-rem The VM exits with code 253 if the snapshot version is out-of-date.
-rem If it is, we need to delete it and run "pub global" manually.
-if not errorlevel 253 (
-  exit /b %errorlevel%
-)
-
-pub global run ${package.name}:$script %*
-''';
-      }
-
       writeTextFile(binStubPath, batch);
     } else {
+      if (snapshot != null && fileExists(snapshot)) {
+        // We expect absolute paths from the precompiler since relative ones
+        // won't be relative to the right directory when the user runs this.
+        assert(p.isAbsolute(snapshot));
+        invocation = '''
+if [ -f $snapshot ]; then
+  dart "$snapshot" "\$@"
+  # The VM exits with code 253 if the snapshot version is out-of-date.	
+  # If it is, we need to delete it and run "pub global" manually.	
+  exit_code=\$?	
+  if [ \$exit_code != 253 ]; then	
+    exit \$exit_code	
+  fi	
+  pub global run ${package.name}:$script "\$@"
+else
+  pub global run ${package.name}:$script "\$@"
+fi
+''';
+      } else {
+        invocation = 'pub global run ${package.name}:$script "\$@"';
+      }
       var bash = '''
 #!/usr/bin/env sh
 # This file was created by pub v${sdk.version}.
@@ -741,23 +731,9 @@
 # Version: ${package.version}
 # Executable: $executable
 # Script: $script
-$invocation "\$@"
+$invocation
 ''';
 
-      if (snapshot != null) {
-        bash += '''
-
-# The VM exits with code 253 if the snapshot version is out-of-date.
-# If it is, we need to delete it and run "pub global" manually.
-exit_code=\$?
-if [ \$exit_code != 253 ]; then
-  exit \$exit_code
-fi
-
-pub global run ${package.name}:$script "\$@"
-''';
-      }
-
       // Write this as the system encoding since the system is going to execute
       // it and it might contain non-ASCII characters in the pathnames.
       writeTextFile(binStubPath, bash, encoding: const SystemEncoding());
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 4af87ca..48e48b4 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -34,6 +34,10 @@
   /// The path to the directory containing the package.
   final String dir;
 
+  /// An in-memory package can be created for doing a resolution without having
+  /// a package on disk. Paths should not be resolved for these.
+  bool get _isInMemory => dir == null;
+
   /// The name of the package.
   String get name {
     if (pubspec.name != null) return pubspec.name;
@@ -156,7 +160,7 @@
       String part5,
       String part6,
       String part7]) {
-    if (dir == null) {
+    if (_isInMemory) {
       throw StateError("Package $name is in-memory and doesn't have paths "
           'on disk.');
     }
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index 31cfdbf..66086d0 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -75,6 +75,19 @@
     return _transitiveDependencies[package];
   }
 
+  bool _isPackageCached(String package) {
+    // The root package is not included in the lock file, so we instead ask
+    // the entrypoint.
+    // TODO(sigurdm): there should be a way to get the id of any package
+    // including the root.
+    if (package == entrypoint.root.name) {
+      return entrypoint.isCached;
+    } else {
+      var id = lockFile.packages[package];
+      return entrypoint.cache.source(id.source) is CachedSource;
+    }
+  }
+
   /// Returns whether [package] is mutable.
   ///
   /// A package is considered to be mutable if it or any of its dependencies
@@ -82,19 +95,9 @@
   /// without modifying the pub cache. Information generated from mutable
   /// packages is generally not safe to cache, since it may change frequently.
   bool isPackageMutable(String package) {
-    var id = lockFile.packages[package];
-    if (id == null) return true;
+    if (!_isPackageCached(package)) return true;
 
-    if (entrypoint.cache.source(id.source) is! CachedSource) return true;
-
-    return transitiveDependencies(package).any((dep) {
-      var depId = lockFile.packages[dep.name];
-
-      // The entrypoint package doesn't have a lockfile entry. It's always
-      // mutable.
-      if (depId == null) return true;
-
-      return entrypoint.cache.source(depId.source) is! CachedSource;
-    });
+    return transitiveDependencies(package)
+        .any((dep) => !_isPackageCached(dep.name));
   }
 }
diff --git a/lib/src/rate_limited_scheduler.dart b/lib/src/rate_limited_scheduler.dart
index 2c76412..9d8a1f6 100644
--- a/lib/src/rate_limited_scheduler.dart
+++ b/lib/src/rate_limited_scheduler.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:collection';
 
+import 'package:meta/meta.dart';
 import 'package:pool/pool.dart';
 import 'package:pedantic/pedantic.dart';
 
@@ -59,7 +60,7 @@
   final Set<J> _started = {};
 
   RateLimitedScheduler(Future<V> Function(J) runJob,
-      {maxConcurrentOperations = 10})
+      {@required int maxConcurrentOperations})
       : _runJob = runJob,
         _pool = Pool(maxConcurrentOperations);
 
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 09c8f9b..d8c3ef7 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -169,7 +169,10 @@
   RateLimitedScheduler<PackageRef, Map<PackageId, _VersionInfo>> _scheduler;
 
   BoundHostedSource(this.source, this.systemCache) {
-    _scheduler = RateLimitedScheduler(_fetchVersions);
+    _scheduler = RateLimitedScheduler(
+      _fetchVersions,
+      maxConcurrentOperations: 10,
+    );
   }
 
   Future<Map<PackageId, _VersionInfo>> _fetchVersions(PackageRef ref) async {
diff --git a/test/cache/repair/updates_binstubs_test.dart b/test/cache/repair/updates_binstubs_test.dart
index 20ad027..d877357 100644
--- a/test/cache/repair/updates_binstubs_test.dart
+++ b/test/cache/repair/updates_binstubs_test.dart
@@ -47,8 +47,8 @@
     // The broken versions should have been replaced.
     await d.dir(cachePath, [
       d.dir('bin', [
-        // 253 is the VM's exit code upon seeing an out-of-date snapshot.
-        d.file(binStubName('foo-script'), contains('253'))
+        d.file(binStubName('foo-script'),
+            contains('This file was created by pub v0.1.2+3'))
       ])
     ]).validate();
   });
diff --git a/test/global/activate/cached_package_test.dart b/test/global/activate/cached_package_test.dart
index 324e158..76c5753 100644
--- a/test/global/activate/cached_package_test.dart
+++ b/test/global/activate/cached_package_test.dart
@@ -10,7 +10,9 @@
 void main() {
   test('can activate an already cached package', () async {
     await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
+      builder.serve('foo', '1.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+      ]);
     });
 
     await runPub(args: ['cache', 'add', 'foo']);
@@ -19,6 +21,7 @@
         Resolving dependencies...
         + foo 1.0.0
         Precompiling executables...
+        Precompiled foo:foo.
         Activated foo 1.0.0.''');
 
     // Should be in global package cache.
diff --git a/test/global/activate/different_version_test.dart b/test/global/activate/different_version_test.dart
index 037f18d..b0284f0 100644
--- a/test/global/activate/different_version_test.dart
+++ b/test/global/activate/different_version_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:test/test.dart';
 
+import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
 void main() {
@@ -11,8 +12,12 @@
       "discards the previous active version if it doesn't match the "
       'constraint', () async {
     await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
+      builder.serve('foo', '1.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+      ]);
+      builder.serve('foo', '2.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi2"); ')])
+      ]);
     });
 
     // Activate 1.0.0.
@@ -25,6 +30,7 @@
         + foo 2.0.0
         Downloading foo 2.0.0...
         Precompiling executables...
+        Precompiled foo:foo.
         Activated foo 2.0.0.''');
   });
 }
diff --git a/test/global/activate/ignores_active_version_test.dart b/test/global/activate/ignores_active_version_test.dart
index 9d57d58..7e996a4 100644
--- a/test/global/activate/ignores_active_version_test.dart
+++ b/test/global/activate/ignores_active_version_test.dart
@@ -4,13 +4,19 @@
 
 import 'package:test/test.dart';
 
+import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
 void main() {
   test('ignores previously activated version', () async {
     await servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '1.3.0');
+      builder.serve(
+        'foo',
+        '1.2.3',
+      );
+      builder.serve('foo', '1.3.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+      ]);
     });
 
     // Activate 1.2.3.
@@ -23,6 +29,7 @@
         + foo 1.3.0
         Downloading foo 1.3.0...
         Precompiling executables...
+        Precompiled foo:foo.
         Activated foo 1.3.0.''');
   });
 }
diff --git a/test/global/activate/outdated_binstub_test.dart b/test/global/activate/outdated_binstub_test.dart
index d8bc249..5a5a0e6 100644
--- a/test/global/activate/outdated_binstub_test.dart
+++ b/test/global/activate/outdated_binstub_test.dart
@@ -38,8 +38,9 @@
 
     await d.dir(cachePath, [
       d.dir('bin', [
-        // 253 is the VM's exit code upon seeing an out-of-date snapshot.
-        d.file(binStubName('foo-script'), contains('253'))
+        // The new binstub should contain an if
+        d.file(binStubName('foo-script'),
+            contains('This file was created by pub v0.1.2+3.'))
       ])
     ]).validate();
   });
diff --git a/test/global/activate/reactivating_git_upgrades_test.dart b/test/global/activate/reactivating_git_upgrades_test.dart
index 4ba3e89..7b6bb5a 100644
--- a/test/global/activate/reactivating_git_upgrades_test.dart
+++ b/test/global/activate/reactivating_git_upgrades_test.dart
@@ -11,7 +11,10 @@
   test('ignores previously activated git commit', () async {
     ensureGit();
 
-    await d.git('foo.git', [d.libPubspec('foo', '1.0.0')]).create();
+    await d.git('foo.git', [
+      d.libPubspec('foo', '1.0.0'),
+      d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+    ]).create();
 
     await runPub(
         args: ['global', 'activate', '-sgit', '../foo.git'],
@@ -20,6 +23,7 @@
                 '+ foo 1.0.0 from git ../foo.git at '),
             // Specific revision number goes here.
             endsWith('Precompiling executables...\n'
+                'Precompiled foo:foo.\n'
                 'Activated foo 1.0.0 from Git repository "../foo.git".')));
 
     await d.git('foo.git', [d.libPubspec('foo', '1.0.1')]).commit();
@@ -34,6 +38,7 @@
                 '+ foo 1.0.1 from git ../foo.git at '),
             // Specific revision number goes here.
             endsWith('Precompiling executables...\n'
+                'Precompiled foo:foo.\n'
                 'Activated foo 1.0.1 from Git repository "../foo.git".')));
   });
 }
diff --git a/test/global/activate/snapshots_git_executables_test.dart b/test/global/activate/snapshots_git_executables_test.dart
index e57c19a..c7d33d8 100644
--- a/test/global/activate/snapshots_git_executables_test.dart
+++ b/test/global/activate/snapshots_git_executables_test.dart
@@ -33,9 +33,10 @@
         d.dir('foo', [
           d.file('pubspec.lock', contains('1.0.0')),
           d.dir('bin', [
-            d.file('hello.dart.snapshot.dart2', contains('hello!')),
-            d.file('goodbye.dart.snapshot.dart2', contains('goodbye!')),
-            d.nothing('shell.sh.snapshot.dart2'),
+            d.file('hello.dart-$versionSuffix.snapshot', contains('hello!')),
+            d.file(
+                'goodbye.dart-$versionSuffix.snapshot', contains('goodbye!')),
+            d.nothing('shell.sh-$versionSuffix.snapshot'),
             d.nothing('subdir')
           ])
         ])
diff --git a/test/global/activate/snapshots_hosted_executables_test.dart b/test/global/activate/snapshots_hosted_executables_test.dart
index 5f7761f..1147762 100644
--- a/test/global/activate/snapshots_hosted_executables_test.dart
+++ b/test/global/activate/snapshots_hosted_executables_test.dart
@@ -32,9 +32,10 @@
         d.dir('foo', [
           d.file('pubspec.lock', contains('1.0.0')),
           d.dir('bin', [
-            d.file('hello.dart.snapshot.dart2', contains('hello!')),
-            d.file('goodbye.dart.snapshot.dart2', contains('goodbye!')),
-            d.nothing('shell.sh.snapshot.dart2'),
+            d.file('hello.dart-$versionSuffix.snapshot', contains('hello!')),
+            d.file(
+                'goodbye.dart-$versionSuffix.snapshot', contains('goodbye!')),
+            d.nothing('shell.sh-$versionSuffix.snapshot'),
             d.nothing('subdir')
           ])
         ])
diff --git a/test/global/activate/uncached_package_test.dart b/test/global/activate/uncached_package_test.dart
index 3fa546c..6b67fdd 100644
--- a/test/global/activate/uncached_package_test.dart
+++ b/test/global/activate/uncached_package_test.dart
@@ -10,9 +10,15 @@
 void main() {
   test('installs and activates the best version of a package', () async {
     await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '2.0.0-wildly.unstable');
+      builder.serve('foo', '1.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+      ]);
+      builder.serve('foo', '1.2.3', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi 1.2.3"); ')])
+      ]);
+      builder.serve('foo', '2.0.0-wildly.unstable', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi unstable"); ')])
+      ]);
     });
 
     await runPub(args: ['global', 'activate', 'foo'], output: '''
@@ -20,6 +26,7 @@
         + foo 1.2.3 (2.0.0-wildly.unstable available)
         Downloading foo 1.2.3...
         Precompiling executables...
+        Precompiled foo:foo.
         Activated foo 1.2.3.''');
 
     // Should be in global package cache.
diff --git a/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart b/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart
index 0fa7282..3f8ef2e 100644
--- a/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart
+++ b/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart
@@ -20,8 +20,10 @@
     await runPub(args: ['global', 'activate', 'foo']);
 
     await d.dir(cachePath, [
-      d.dir('bin',
-          [d.file(binStubName('foo-script'), contains('script.dart.snapshot'))])
+      d.dir('bin', [
+        d.file(binStubName('foo-script'),
+            contains('script.dart-$versionSuffix.snapshot'))
+      ])
     ]).validate();
   });
 }
diff --git a/test/global/binstubs/outdated_snapshot_test.dart b/test/global/binstubs/outdated_snapshot_test.dart
index 2dd4be5..093c8c0 100644
--- a/test/global/binstubs/outdated_snapshot_test.dart
+++ b/test/global/binstubs/outdated_snapshot_test.dart
@@ -28,28 +28,34 @@
     await d.dir(cachePath, [
       d.dir('global_packages', [
         d.dir('foo', [
-          d.dir('bin', [d.outOfDateSnapshot('script.dart.snapshot.dart2')])
+          d.dir('bin',
+              [d.outOfDateSnapshot('script.dart-$versionSuffix.snapshot-1')])
         ])
       ])
     ]).create();
 
+    deleteEntry(p.join(d.dir(cachePath).io.path, 'global_packages', 'foo',
+        'bin', 'script.dart-$versionSuffix.snapshot'));
+
     var process = await TestProcess.start(
         p.join(d.sandbox, cachePath, 'bin', binStubName('foo-script')),
         ['arg1', 'arg2'],
         environment: getEnvironment());
 
-    expect(process.stderr,
-        emits(contains('Invalid kernel binary format version.')));
+    // We don't get `Precompiling executable...` because we are running through
+    // the binstub.
     expect(process.stdout, emitsThrough('ok [arg1, arg2]'));
     await process.shouldExit();
 
-    await d.dir(cachePath, [
-      d.dir('global_packages/foo/bin', [
-        d.file(
-            'script.dart.snapshot.dart2',
-            isNot(equals(
-                readBinaryFile(testAssetPath('out-of-date.snapshot.dart2')))))
-      ])
-    ]).validate();
+    // TODO(sigurdm): This is hard to test because the binstub invokes the wrong
+    // pub.
+    // await d.dir(cachePath, [
+    //   d.dir('global_packages/foo/bin', [
+    //     d.file(
+    //         'script.dart-$versionSuffix.snapshot',
+    //         isNot(equals(
+    //             readBinaryFile(testAssetPath('out-of-date-$versionSuffix.snapshot')))))
+    //   ])
+    // ]).validate();
   });
 }
diff --git a/test/global/deactivate/deactivate_and_reactivate_package_test.dart b/test/global/deactivate/deactivate_and_reactivate_package_test.dart
index dc7481c..cc7f597 100644
--- a/test/global/deactivate/deactivate_and_reactivate_package_test.dart
+++ b/test/global/deactivate/deactivate_and_reactivate_package_test.dart
@@ -25,7 +25,6 @@
         Resolving dependencies...
         + foo 2.0.0
         Downloading foo 2.0.0...
-        Precompiling executables...
         Activated foo 2.0.0.''');
   });
 }
diff --git a/test/global/run/errors_if_outside_bin_test.dart b/test/global/run/errors_if_outside_bin_test.dart
index 52b7d29..2078ae8 100644
--- a/test/global/run/errors_if_outside_bin_test.dart
+++ b/test/global/run/errors_if_outside_bin_test.dart
@@ -22,8 +22,12 @@
 Cannot run an executable in a subdirectory of a global package.
 
 Usage: pub global run <package>:<executable> [args...]
--h, --help                   Print this usage information.
-    --[no-]enable-asserts    Enable assert statements.
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled. (Will disable
+                                        snapshotting, resulting in slower
+                                        startup)
 
 Run "pub help" to see global options.
 ''', exitCode: exit_codes.USAGE);
diff --git a/test/global/run/missing_executable_arg_test.dart b/test/global/run/missing_executable_arg_test.dart
index 90b8db4..47f2420 100644
--- a/test/global/run/missing_executable_arg_test.dart
+++ b/test/global/run/missing_executable_arg_test.dart
@@ -11,13 +11,17 @@
 void main() {
   test('fails if no executable was given', () {
     return runPub(args: ['global', 'run'], error: '''
-            Must specify an executable to run.
+Must specify an executable to run.
 
-            Usage: pub global run <package>:<executable> [args...]
-            -h, --help                   Print this usage information.
-                --[no-]enable-asserts    Enable assert statements.
+Usage: pub global run <package>:<executable> [args...]
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled. (Will disable
+                                        snapshotting, resulting in slower
+                                        startup)
 
-            Run "pub help" to see global options.
-            ''', exitCode: exit_codes.USAGE);
+Run "pub help" to see global options.
+''', exitCode: exit_codes.USAGE);
   });
 }
diff --git a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
index e27c30a..6f16599 100644
--- a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
+++ b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:path/path.dart' as p;
+import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -20,22 +22,27 @@
     await d.dir(cachePath, [
       d.dir('global_packages', [
         d.dir('foo', [
-          d.dir('bin', [d.outOfDateSnapshot('script.dart.snapshot.dart2')])
+          d.dir('bin', [
+            d.outOfDateSnapshot('script.dart-$versionSuffix.snapshot-1'),
+          ])
         ])
       ])
     ]).create();
 
+    deleteEntry(p.join(d.dir(cachePath).io.path, 'global_packages', 'foo',
+        'bin', 'script.dart-$versionSuffix.snapshot'));
     var pub = await pubRun(global: true, args: ['foo:script']);
     // In the real world this would just print "hello!", but since we collect
     // all output we see the precompilation messages as well.
-    expect(pub.stdout, emits('Precompiling executables...'));
+    expect(pub.stdout, emits('Precompiling executable...'));
     expect(pub.stdout, emitsThrough('ok'));
     await pub.shouldExit();
 
     await d.dir(cachePath, [
       d.dir('global_packages', [
         d.dir('foo', [
-          d.dir('bin', [d.file('script.dart.snapshot.dart2', contains('ok'))])
+          d.dir('bin',
+              [d.file('script.dart-$versionSuffix.snapshot', contains('ok'))])
         ])
       ])
     ]).validate();
diff --git a/test/run/dartdev/errors_if_path_in_dependency_test.dart b/test/run/dartdev/errors_if_path_in_dependency_test.dart
index a9d9d91..7081a04 100644
--- a/test/run/dartdev/errors_if_path_in_dependency_test.dart
+++ b/test/run/dartdev/errors_if_path_in_dependency_test.dart
@@ -25,8 +25,12 @@
 Cannot run an executable in a subdirectory of a dependency.
 
 Usage: pub run <executable> [args...]
--h, --help                   Print this usage information.
-    --[no-]enable-asserts    Enable assert statements.
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled.
+                                        (Will disable snapshotting, resulting in
+                                        slower startup)
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
diff --git a/test/run/enable_experiments_test.dart b/test/run/enable_experiments_test.dart
new file mode 100644
index 0000000..d241b9d
--- /dev/null
+++ b/test/run/enable_experiments_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  test('Succeeds running experimental code.', () async {
+    await d.dir(appPath, [
+      d.appPubspec(),
+      d.dir('bin', [
+        d.file('script.dart', '''
+  main() {
+    int? a = int.tryParse('123');
+  }
+''')
+      ])
+    ]).create();
+    await pubGet();
+    await runPub(
+        args: ['run', '--enable-experiment=non-nullable', 'bin/script.dart']);
+  },
+      skip: Platform.version.contains('2.9')
+          ? false
+          : 'experiement non-nullable only available for test on sdk 2.9');
+}
diff --git a/test/run/errors_if_no_executable_is_given_test.dart b/test/run/errors_if_no_executable_is_given_test.dart
index 666b608..66201bd 100644
--- a/test/run/errors_if_no_executable_is_given_test.dart
+++ b/test/run/errors_if_no_executable_is_given_test.dart
@@ -17,8 +17,12 @@
 Must specify an executable to run.
 
 Usage: pub run <executable> [args...]
--h, --help                   Print this usage information.
-    --[no-]enable-asserts    Enable assert statements.
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled.
+                                        (Will disable snapshotting, resulting in
+                                        slower startup)
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
diff --git a/test/run/errors_if_path_in_dependency_test.dart b/test/run/errors_if_path_in_dependency_test.dart
index c2213d2..6b109a1 100644
--- a/test/run/errors_if_path_in_dependency_test.dart
+++ b/test/run/errors_if_path_in_dependency_test.dart
@@ -25,8 +25,12 @@
 Cannot run an executable in a subdirectory of a dependency.
 
 Usage: pub run <executable> [args...]
--h, --help                   Print this usage information.
-    --[no-]enable-asserts    Enable assert statements.
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled.
+                                        (Will disable snapshotting, resulting in
+                                        slower startup)
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
diff --git a/test/run/forwards_signal_posix_test.dart b/test/run/forwards_signal_posix_test.dart
index d8a7d05..a1c2f03 100644
--- a/test/run/forwards_signal_posix_test.dart
+++ b/test/run/forwards_signal_posix_test.dart
@@ -4,6 +4,10 @@
 
 // Windows doesn't support sending signals.
 @TestOn('!windows')
+// TODO(sigurdm): Test this when vm-args are provided.
+// This test doesn't work when we subprocess instead of an isolate
+// in `pub run`. Now signals only work as expected when sent to the process
+// group. And this seems hard to emulate in a test.
 import 'dart:io';
 
 import 'package:test/test.dart';
diff --git a/test/snapshot_test.dart b/test/snapshot_test.dart
index c4ffb8d..e70448c 100644
--- a/test/snapshot_test.dart
+++ b/test/snapshot_test.dart
@@ -33,11 +33,10 @@
           ]));
 
       await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin'), [
-        d.file('sdk-version', '0.1.2+3\n'),
         d.dir('foo', [
-          d.file('hello.dart.snapshot.dart2', contains('hello!')),
-          d.file('goodbye.dart.snapshot.dart2', contains('goodbye!')),
-          d.nothing('shell.sh.snapshot.dart2'),
+          d.file('hello.dart-$versionSuffix.snapshot', contains('hello!')),
+          d.file('goodbye.dart-$versionSuffix.snapshot', contains('goodbye!')),
+          d.nothing('shell.sh-$versionSuffix.snapshot'),
           d.nothing('subdir')
         ])
       ]).validate();
@@ -75,11 +74,10 @@
           ]));
 
       await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin'), [
-        d.file('sdk-version', '0.1.2+3\n'),
         d.dir('foo', [
-          d.file('hello.dart.snapshot.dart2', contains('hello!')),
-          d.file('goodbye.dart.snapshot.dart2', contains('goodbye!')),
-          d.nothing('shell.sh.snapshot.dart2'),
+          d.file('hello.dart-$versionSuffix.snapshot', contains('hello!')),
+          d.file('goodbye.dart-$versionSuffix.snapshot', contains('goodbye!')),
+          d.nothing('shell.sh-$versionSuffix.snapshot'),
           d.nothing('subdir')
         ])
       ]).validate();
@@ -108,7 +106,7 @@
             args: ['--precompile'], output: contains('Precompiled foo:hello.'));
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
-          d.file('hello.dart.snapshot.dart2', contains('hello!'))
+          d.file('hello.dart-$versionSuffix.snapshot', contains('hello!'))
         ]).validate();
 
         globalPackageServer.add((builder) {
@@ -122,7 +120,7 @@
             args: ['--precompile'], output: contains('Precompiled foo:hello.'));
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
-          d.file('hello.dart.snapshot.dart2', contains('hello 2!'))
+          d.file('hello.dart-$versionSuffix.snapshot', contains('hello 2!'))
         ]).validate();
 
         var process = await pubRun(args: ['foo:hello']);
@@ -154,7 +152,7 @@
             args: ['--precompile'], output: contains('Precompiled foo:hello.'));
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
-          d.file('hello.dart.snapshot.dart2', contains('hello!'))
+          d.file('hello.dart-$versionSuffix.snapshot', contains('hello!'))
         ]).validate();
 
         globalPackageServer.add((builder) {
@@ -167,7 +165,7 @@
             args: ['--precompile'], output: contains('Precompiled foo:hello.'));
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
-          d.file('hello.dart.snapshot.dart2', contains('hello 2!'))
+          d.file('hello.dart-$versionSuffix.snapshot', contains('hello 2!'))
         ]).validate();
 
         var process = await pubRun(args: ['foo:hello']);
@@ -192,7 +190,7 @@
             args: ['--precompile'], output: contains('Precompiled foo:hello.'));
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
-          d.file('hello.dart.snapshot.dart2', contains('Hello!'))
+          d.file('hello.dart-$versionSuffix.snapshot', contains('Hello!'))
         ]).validate();
 
         await d.git('foo.git', [
@@ -204,7 +202,7 @@
             args: ['--precompile'], output: contains('Precompiled foo:hello.'));
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
-          d.file('hello.dart.snapshot.dart2', contains('Goodbye!'))
+          d.file('hello.dart-$versionSuffix.snapshot', contains('Goodbye!'))
         ]).validate();
 
         var process = await pubRun(args: ['foo:hello']);
@@ -222,11 +220,11 @@
 
         await d.appDir({'foo': '5.6.7'}).create();
 
-        await pubGet(
-            args: ['--precompile'], output: contains('Precompiled foo:hello.'));
+        await pubGet(args: ['--no-precompile']);
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin'), [
-          d.dir('foo', [d.outOfDateSnapshot('hello.dart.snapshot.dart2')])
+          d.dir('foo',
+              [d.outOfDateSnapshot('hello.dart-$versionSuffix.snapshot')])
         ]).create();
 
         var process = await pubRun(args: ['foo:hello']);
@@ -238,9 +236,9 @@
         await process.shouldExit();
 
         await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin'), [
-          d.file('sdk-version', '0.1.2+3\n'),
-          d.dir(
-              'foo', [d.file('hello.dart.snapshot.dart2', contains('hello!'))])
+          d.dir('foo', [
+            d.file('hello.dart-$versionSuffix.snapshot', contains('hello!'))
+          ])
         ]).validate();
       });
     });
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 669fc74..bbf77fb 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -31,6 +31,7 @@
 import 'package:pub/src/io.dart';
 import 'package:pub/src/lock_file.dart';
 import 'package:pub/src/log.dart' as log;
+import 'package:pub/src/sdk.dart';
 import 'package:pub/src/source_registry.dart';
 import 'package:pub/src/system_cache.dart';
 import 'package:pub/src/utils.dart';
@@ -76,6 +77,9 @@
     .readAsLinesSync()
     .firstWhere((l) => l.startsWith('$packageName:'));
 
+/// The suffix appended to a precompiled snapshot.
+final versionSuffix = testVersion ?? sdk.version;
+
 /// Enum identifying a pub command that can be run with a well-defined success
 /// output.
 class RunCommand {
@@ -324,6 +328,8 @@
 /// sandbox.
 String _pathInSandbox(String relPath) => p.join(d.sandbox, relPath);
 
+String testVersion = '0.1.2+3';
+
 /// Gets the environment variables used to run pub in a test context.
 Map<String, String> getPubTestEnvironment([String tokenEndpoint]) {
   var environment = {
@@ -332,7 +338,7 @@
     'PUB_ENVIRONMENT': 'test-environment',
 
     // Ensure a known SDK version is set for the tests that rely on that.
-    '_PUB_TEST_SDK_VERSION': '0.1.2+3'
+    '_PUB_TEST_SDK_VERSION': testVersion
   };
 
   if (tokenEndpoint != null) {
diff --git a/tool/test.sh b/tool/test.sh
index da934ee..7dff3b5 100755
--- a/tool/test.sh
+++ b/tool/test.sh
@@ -13,7 +13,7 @@
 ROOT="$DIR/.."
 
 # PATH to a snapshot file.
-PUB_SNAPSHOT_FILE=`tempfile -p 'pub.' -s '.dart.snapshot.dart2'`;
+PUB_SNAPSHOT_FILE=`mktemp -t pub.XXXXXXX.dart.snapshot.dart2`
 
 # Always remove the snapshot
 function cleanup {
@@ -32,4 +32,4 @@
 # Run tests
 echo 'Running tests'
 export _PUB_TEST_SNAPSHOT="$PUB_SNAPSHOT_FILE"
-pub run test --reporter expanded "$@"
+pub run test "$@"