Merge pull request #1901 from dart-lang/dart-2-snapshot
Generate Dart 2 snapshots when running in Dart 2 mode
diff --git a/.travis.yml b/.travis.yml
index 477740d..2b14c55 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,7 @@
# snapshot if it's available.
before_script:
- dart --no-checked --snapshot=bin/pub.dart.snapshot --snapshot-kind=app-jit bin/pub.dart --help
+ - dart --preview-dart-2 --snapshot=bin/pub.dart.snapshot.dart2 bin/pub.dart
# Only building these branches means that we don't run two builds for each pull
# request.
diff --git a/lib/src/command.dart b/lib/src/command.dart
index 4b5772b..cfc7545 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -39,7 +39,7 @@
Entrypoint get entrypoint {
// Lazy load it.
if (_entrypoint == null) {
- _entrypoint = new Entrypoint('.', cache);
+ _entrypoint = new Entrypoint.current(cache);
}
return _entrypoint;
}
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index e480337..93afa3c 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -57,8 +57,23 @@
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";
+
+ var snapshotPath =
+ p.join(entrypoint.cachePath, "bin", package, "$executable.snapshot");
+
+ // 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,
- checked: argResults['checked']);
+ checked: argResults['checked'],
+ snapshotPath: useSnapshot ? snapshotPath : null,
+ recompile: entrypoint.precompileExecutables);
await flushThenExit(exitCode);
}
}
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index 6d16e46..43fd9e5 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -4,10 +4,7 @@
import 'dart:async';
-import 'package:path/path.dart' as path;
-
import '../command.dart';
-import '../entrypoint.dart';
import '../exit_codes.dart' as exit_codes;
import '../http.dart';
import '../io.dart';
@@ -58,7 +55,7 @@
return new Future.sync(() {
var package = argResults['package'];
if (package != null) return package;
- return new Entrypoint(path.current, cache).root.name;
+ return entrypoint.root.name;
})
.then((package) {
var uploader = rest[0];
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index d445b20..6ce6700 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -11,6 +11,7 @@
import 'exceptions.dart';
import 'io.dart';
import 'log.dart' as log;
+import 'utils.dart';
/// Returns whether [dart] looks like an entrypoint file.
bool isEntrypoint(CompilationUnit dart) {
@@ -51,18 +52,41 @@
///
/// If [name] is passed, it is used to describe the executable in logs and error
/// messages.
+///
+/// When running in Dart 2 mode, this automatically creates a Dart 2-compatible
+/// snapshot as well at `$snapshotPath.dart2`.
Future snapshot(Uri executableUrl, String snapshotPath,
{Uri packagesFile, String name}) async {
name = log.bold(name ?? executableUrl.toString());
- var args = ['--snapshot=$snapshotPath', executableUrl.toString()];
- if (packagesFile != null) args.insert(0, "--packages=$packagesFile");
- var result = await runProcess(Platform.executable, args);
+ var dart1Args = ['--snapshot=$snapshotPath', executableUrl.toString()];
- if (result.success) {
+ var dart2Path = '$snapshotPath.dart2';
+ var dart2Args = isDart2
+ ? ['--preview-dart-2', '--snapshot=$dart2Path', executableUrl.toString()]
+ : null;
+
+ if (packagesFile != null) {
+ dart1Args.insert(0, "--packages=$packagesFile");
+
+ // Resolve [packagesFile] in case it's relative to work around sdk#33177.
+ dart2Args?.insert(0, "--packages=${Uri.base.resolveUri(packagesFile)}");
+ }
+
+ var processes = [runProcess(Platform.executable, dart1Args)];
+ if (isDart2) processes.add(runProcess(Platform.executable, dart2Args));
+ var results = await Future.wait(processes);
+
+ var failure =
+ results.firstWhere((result) => !result.success, orElse: () => null);
+ if (failure == null) {
log.message("Precompiled $name.");
} else {
- throw new ApplicationException(
- log.yellow("Failed to precompile $name:\n") + result.stderr.join('\n'));
+ // Don't leave partial results.
+ deleteEntry(snapshotPath);
+ deleteEntry(dart2Path);
+
+ throw new ApplicationException(log.yellow("Failed to precompile $name:\n") +
+ failure.stderr.join('\n'));
}
}
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index b44cc06..c266723 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -74,6 +74,9 @@
/// 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);
+
/// Whether this is an entrypoint for a globally-activated package.
final bool isGlobal;
@@ -138,26 +141,40 @@
return newPath;
}
+ /// Returns the contents of the `.packages` file for this entrypoint.
+ ///
+ /// This is based on the package's lockfile, so it works whether or not a
+ /// `.packages` file has been written.
+ String get packagesFileContents => lockFile.packagesFile(cache, root.name);
+
/// The path to the directory containing dependency executable snapshots.
String get _snapshotPath => p.join(cachePath, 'bin');
+ /// Loads the entrypoint for the package at the current directory.
+ Entrypoint.current(SystemCache cache)
+ : root = new Package.load(null, '.', cache.sources, isRootPackage: true),
+ cache = cache,
+ _inMemory = false,
+ isGlobal = false;
+
/// Loads the entrypoint from a package at [rootDir].
- Entrypoint(String rootDir, SystemCache cache, {this.isGlobal: false})
+ Entrypoint(String rootDir, SystemCache cache)
: root =
new Package.load(null, rootDir, cache.sources, isRootPackage: true),
cache = cache,
- _inMemory = false;
+ _inMemory = false,
+ isGlobal = true;
/// Creates an entrypoint given package and lockfile objects.
- Entrypoint.inMemory(this.root, this._lockFile, this.cache,
- {this.isGlobal: false})
- : _inMemory = true;
+ 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,
- {this.isGlobal: false})
- : _inMemory = true {
+ Entrypoint.fromSolveResult(this.root, this.cache, SolveResult solveResult)
+ : _inMemory = true,
+ isGlobal = true {
_packageGraph = new PackageGraph.fromSolveResult(this, solveResult);
_lockFile = _packageGraph.lockFile;
}
@@ -238,7 +255,7 @@
/// have to reload and reparse all the pubspecs.
_packageGraph = new PackageGraph.fromSolveResult(this, result);
- writeTextFile(packagesFile, lockFile.packagesFile(cache, root.name));
+ writeTextFile(packagesFile, packagesFileContents);
try {
if (precompile) {
@@ -350,8 +367,12 @@
// 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) => fileExists(p.join(
- _snapshotPath, packageName, "${p.basename(executable)}.snapshot")));
+ var executablesExist = executables.every((executable) {
+ var snapshotPath = p.join(
+ _snapshotPath, packageName, "${p.basename(executable)}.snapshot");
+ if (!fileExists(snapshotPath)) return false;
+ if (isDart2 && !fileExists("$snapshotPath.dart2")) return false;
+ });
if (!executablesExist) return executables;
// Otherwise, we don't need to recompile.
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 796b326..6ef158f 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -14,25 +14,34 @@
import 'isolate.dart' as isolate;
import 'log.dart' as log;
import 'utils.dart';
-import 'system_cache.dart';
/// Runs [executable] from [package] reachable from [entrypoint].
///
-/// The executable string is a relative Dart file path using native path
-/// separators with or without a trailing ".dart" extension. It is contained
-/// within [package], which should either be the entrypoint package or an
-/// immediate dependency of it.
+/// The [executable] is a relative path to a Dart file within [package], which
+/// should either be the entrypoint package or an immediate dependency of it.
///
/// Arguments from [args] will be passed to the spawned Dart application.
///
-/// If [checked] is true, the program is run in checked mode. If [mode] is
-/// passed, it's used as the barback mode; it defaults to [BarbackMode.RELEASE].
+/// If [checked] is true, the program is run in checked mode.
+///
+/// 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.
///
/// Returns the exit code of the spawned app.
Future<int> runExecutable(Entrypoint entrypoint, String package,
String executable, Iterable<String> args,
- {bool isGlobal: false, bool checked: false, SystemCache cache}) async {
- assert(!isGlobal || cache != null);
+ {bool checked: false,
+ String packagesFile,
+ String snapshotPath,
+ Future<void> recompile()}) async {
+ packagesFile ??= entrypoint.packagesFile;
+
// Make sure the package is an immediate dependency of the entrypoint or the
// entrypoint itself.
if (entrypoint.root.name != package &&
@@ -49,25 +58,24 @@
entrypoint.migrateCache();
// Unless the user overrides the verbosity, we want to filter out the
- // normal pub output shown while loading the environment.
+ // normal pub output that may be shown when recompiling snapshots.
if (log.verbosity == log.Verbosity.NORMAL) {
log.verbosity = log.Verbosity.WARNING;
}
- // Ensure that there's a trailing extension.
- if (p.extension(executable) != ".dart") executable += ".dart";
-
- var localSnapshotPath =
- p.join(entrypoint.cachePath, "bin", package, "$executable.snapshot");
// Snapshots are compiled in Dart 1 mode, so in Dart 2 mode we always run
// executables from source.
- if (!isDart2 && !isGlobal && fileExists(localSnapshotPath)) {
+ if (snapshotPath != null) {
+ // Look for the Dart 2-specific snapshot when running in Dart 2 mode.
+ if (isDart2) snapshotPath += '.dart2';
+
// Since we don't access the package graph, this doesn't happen
// automatically.
entrypoint.assertUpToDate();
- return _runCachedExecutable(entrypoint, localSnapshotPath, args,
- checked: checked);
+ var result = await _runOrCompileSnapshot(snapshotPath, args,
+ packagesFile: packagesFile, checked: checked, recompile: recompile);
+ if (result != null) return result;
}
// If the command has a path separator, then it's a path relative to the
@@ -75,12 +83,11 @@
// "bin".
if (p.split(executable).length == 1) executable = p.join("bin", executable);
- var executablePath = await _executablePath(entrypoint, package, executable,
- isGlobal: isGlobal, cache: cache);
+ var executablePath = await _executablePath(entrypoint, package, executable);
if (executablePath == null) {
var message = "Could not find ${log.bold(executable)}";
- if (isGlobal || package != entrypoint.root.name) {
+ if (entrypoint.isGlobal || package != entrypoint.root.name) {
message += " in package ${log.bold(package)}";
}
log.error("$message.");
@@ -91,7 +98,7 @@
// 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(entrypoint.packagesFile));
+ var packageConfig = p.toUri(p.absolute(packagesFile));
await isolate.runUri(p.toUri(executablePath), args.toList(), null,
checked: checked,
@@ -106,28 +113,37 @@
/// 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,
- {bool isGlobal: false, SystemCache cache}) async {
+ Entrypoint entrypoint, String package, String path) async {
assert(p.isRelative(path));
- if (!fileExists(entrypoint.packagesFile)) {
- if (!isGlobal) return null;
- // A .packages file may not already exist if the global executable has a
- // 1.6-style lock file instead.
- await writeTextFile(
- entrypoint.packagesFile, entrypoint.lockFile.packagesFile(cache));
- }
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> recompile(),
+ String packagesFile,
+ bool checked: 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, checked: checked);
+}
+
/// 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. It may return a Future.
+/// be re-run.
///
/// If [checked] is set, runs the snapshot in checked mode.
///
@@ -135,7 +151,9 @@
///
/// This doesn't do any validation of the snapshot's SDK version.
Future<int> runSnapshot(String path, Iterable<String> args,
- {recompile(), String packagesFile, bool checked: false}) async {
+ {Future<void> recompile(),
+ String packagesFile,
+ bool checked: false}) async {
Uri packageConfig;
if (packagesFile != null) {
// We use an absolute path here not because the VM insists but because it's
@@ -155,6 +173,8 @@
} on IsolateSpawnException catch (error) {
if (recompile == null) rethrow;
if (!error.message.contains("Wrong script snapshot version")) rethrow;
+
+ log.fine("Precompiled executable is out of date.");
await recompile();
await isolate.runUri(url, argList, null,
checked: checked, packageConfig: packageConfig);
@@ -162,14 +182,3 @@
return exitCode;
}
-
-/// Runs the executable snapshot at [snapshotPath].
-Future<int> _runCachedExecutable(
- Entrypoint entrypoint, String snapshotPath, List<String> args,
- {bool checked: false}) {
- return runSnapshot(snapshotPath, args,
- packagesFile: entrypoint.packagesFile, checked: checked, recompile: () {
- log.fine("Precompiled executable is out of date.");
- return entrypoint.precompileExecutables();
- });
-}
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index c99f9f3..a69d5c4 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -137,7 +137,7 @@
/// Otherwise, the previous ones will be preserved.
Future activatePath(String path, List<String> executables,
{bool overwriteBinStubs}) async {
- var entrypoint = new Entrypoint(path, cache, isGlobal: true);
+ var entrypoint = new Entrypoint(path, cache);
// Get the package's dependencies.
await entrypoint.acquireDependencies(SolveType.GET);
@@ -204,8 +204,7 @@
// Load the package graph from [result] so we don't need to re-parse all
// the pubspecs.
- var entrypoint =
- new Entrypoint.fromSolveResult(root, cache, result, isGlobal: true);
+ var entrypoint = new Entrypoint.fromSolveResult(root, cache, result);
var snapshots = await _precompileExecutables(entrypoint, dep.name);
_updateBinStubs(entrypoint.packageGraph.packages[dep.name], executables,
@@ -226,6 +225,13 @@
var binDir = p.join(_directory, packageName, 'bin');
cleanDir(binDir);
+ var packagesFilePath = p.join(_directory, packageName, '.packages');
+ if (!fileExists(packagesFilePath)) {
+ // A .packages file may not already exist if the global executable has a
+ // 1.6-style lock file instead.
+ await writeTextFile(packagesFilePath, entrypoint.packagesFileContents);
+ }
+
// 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];
@@ -335,14 +341,12 @@
if (source is CachedSource) {
// For cached sources, the package itself is in the cache and the
// lockfile is the one we just loaded.
- entrypoint = new Entrypoint.inMemory(cache.load(id), lockFile, cache,
- isGlobal: true);
+ entrypoint = new Entrypoint.inMemory(cache.load(id), lockFile, cache);
} else {
// For uncached sources (i.e. path), the ID just points to the real
// directory for the package.
entrypoint = new Entrypoint(
- (id.source as PathSource).pathFromDescription(id.description), cache,
- isGlobal: true);
+ (id.source as PathSource).pathFromDescription(id.description), cache);
}
entrypoint.root.pubspec.sdkConstraints.forEach((sdkName, constraint) {
@@ -375,28 +379,17 @@
Future<int> runExecutable(
String package, String executable, Iterable<String> args,
{bool checked: false}) {
- var binDir = p.join(_directory, package, 'bin');
- // Snapshots are compiled in Dart 1 mode, so in Dart 2 mode we always run
- // executables from source.
- if (isDart2 || !fileExists(p.join(binDir, '$executable.dart.snapshot'))) {
- return exe.runExecutable(find(package), package, executable, args,
- isGlobal: true, checked: checked, cache: cache);
- }
-
- // Unless the user overrides the verbosity, we want to filter out the
- // normal pub output shown while loading the environment.
- if (log.verbosity == log.Verbosity.NORMAL) {
- log.verbosity = log.Verbosity.WARNING;
- }
-
- var snapshotPath = p.join(binDir, '$executable.dart.snapshot');
- return exe.runSnapshot(snapshotPath, args, checked: checked,
- recompile: () async {
- log.fine("$package:$executable is out of date and needs to be "
- "recompiled.");
- await _precompileExecutables(
- find(package).packageGraph.entrypoint, package);
- });
+ var entrypoint = find(package);
+ return exe.runExecutable(
+ entrypoint, package, p.join('bin', '$executable.dart'), args,
+ checked: checked,
+ 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')
+ : null,
+ recompile: () => _precompileExecutables(entrypoint, package));
}
/// Gets the path to the lock file for an activated cached package with
diff --git a/test/global/dart2_test.dart b/test/global/dart2_test.dart
new file mode 100644
index 0000000..e619453
--- /dev/null
+++ b/test/global/dart2_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2018, 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 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+ test("doesn't create a Dart 2 snapshot in Dart 1", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "1.0.0", contents: [
+ d.dir('bin', [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await runPub(
+ args: ["global", "activate", "foo"],
+ output: allOf([contains('Precompiled foo:hello.')]));
+
+ await d.dir(p.join(cachePath, 'global_packages', 'foo', 'bin'),
+ [d.nothing('hello.dart.snapshot.dart2')]).validate();
+ });
+
+ test("creates both a Dart 1 and Dart 2 snapshot in Dart 2", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "1.0.0", contents: [
+ d.dir('bin', [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await runPub(
+ args: ["global", "activate", "foo"],
+ output: allOf([contains('Precompiled foo:hello.')]),
+ dart2: true);
+
+ await d.dir(p.join(cachePath, 'global_packages', 'foo', 'bin'), [
+ d.file('hello.dart.snapshot', contains('hello!')),
+ d.file('hello.dart.snapshot.dart2', contains('hello!'))
+ ]).validate();
+ });
+
+ test("creates a Dart 2 snapshot when reactivated with Dart 2", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "1.0.0", contents: [
+ d.dir('bin', [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await runPub(
+ args: ["global", "activate", "foo"],
+ output: allOf([contains('Precompiled foo:hello.')]));
+ await runPub(
+ args: ["global", "activate", "foo"],
+ output: allOf([contains('Precompiled foo:hello.')]),
+ dart2: true);
+
+ await d.dir(p.join(cachePath, 'global_packages', 'foo', 'bin'), [
+ d.file('hello.dart.snapshot', contains('hello!')),
+ d.file('hello.dart.snapshot.dart2', contains('hello!'))
+ ]).validate();
+ });
+
+ test("creates a Dart 2 snapshot when run with Dart 2", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "1.0.0", contents: [
+ d.dir('bin', [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await runPub(
+ args: ["global", "activate", "foo"],
+ output: allOf([contains('Precompiled foo:hello.')]));
+
+ var pub = await pubRun(global: true, args: ["foo:hello"], dart2: true);
+ // 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, emitsThrough("hello!"));
+ await pub.shouldExit();
+
+ await d.dir(p.join(cachePath, 'global_packages', 'foo', 'bin'), [
+ d.file('hello.dart.snapshot', contains('hello!')),
+ d.file('hello.dart.snapshot.dart2', contains('hello!'))
+ ]).validate();
+ });
+}
diff --git a/test/global/run/uses_old_lockfile_test.dart b/test/global/run/uses_old_lockfile_test.dart
index 38e850b..983a61a 100644
--- a/test/global/run/uses_old_lockfile_test.dart
+++ b/test/global/run/uses_old_lockfile_test.dart
@@ -42,7 +42,7 @@
]).create();
var pub = await pubRun(global: true, args: ["foo:script"]);
- expect(pub.stdout, emits("bar 1.0.0"));
+ expect(pub.stdout, emitsThrough("bar 1.0.0"));
await pub.shouldExit();
await d.dir(cachePath, [
diff --git a/test/snapshot_test.dart b/test/snapshot_test.dart
index 9f9837a..64609c3 100644
--- a/test/snapshot_test.dart
+++ b/test/snapshot_test.dart
@@ -91,6 +91,49 @@
await process.shouldExit();
});
+ test("only for Dart 1 in Dart 1 mode", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "1.2.3", contents: [
+ d.dir(
+ "bin", [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await d.appDir({"foo": "1.2.3"}).create();
+
+ await pubGet(output: allOf([contains("Precompiled foo:hello.")]));
+
+ await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'),
+ [d.nothing('hello.dart.snapshot.dart2')]).validate();
+ });
+
+ test("for Dart 1 and 2 in Dart 2 mode", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "1.2.3", contents: [
+ d.dir(
+ "bin", [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await d.appDir({"foo": "1.2.3"}).create();
+
+ await pubGet(
+ output: allOf([contains("Precompiled foo:hello.")]), dart2: true);
+
+ await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
+ d.file('hello.dart.snapshot', contains('hello!')),
+ d.file('hello.dart.snapshot.dart2', contains('hello!'))
+ ]).validate();
+
+ var process = await pubRun(args: ['foo:hello']);
+ expect(process.stdout, emits("hello!"));
+ await process.shouldExit();
+
+ process = await pubRun(args: ['foo:hello'], dart2: true);
+ expect(process.stdout, emits("hello!"));
+ await process.shouldExit();
+ });
+
group("again if", () {
test("its package is updated", () async {
await servePackages((builder) {
@@ -227,6 +270,83 @@
d.dir('foo', [d.file('hello.dart.snapshot', contains('hello!'))])
]).validate();
});
+
+ test("the SDK is out of date", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "5.6.7", contents: [
+ d.dir("bin",
+ [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await d.appDir({"foo": "5.6.7"}).create();
+
+ await pubGet(output: contains("Precompiled foo:hello."));
+
+ await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin'), [
+ d.dir('foo', [d.outOfDateSnapshot('hello.dart.snapshot')])
+ ]).create();
+
+ var process = await pubRun(args: ['foo:hello']);
+
+ // In the real world this would just print "hello!", but since we
+ // collect all output we see the precompilation messages as well.
+ expect(process.stdout, emits("Precompiling executables..."));
+ expect(process.stdout, emitsThrough("hello!"));
+ 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', contains('hello!'))])
+ ]).validate();
+ });
+
+ group("Dart 2 is newly in use for", () {
+ test("pub get", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "5.6.7", contents: [
+ d.dir("bin",
+ [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await d.appDir({"foo": "5.6.7"}).create();
+
+ await pubGet(output: contains("Precompiled foo:hello."));
+ await pubGet(output: contains("Precompiled foo:hello."), dart2: true);
+
+ await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
+ d.file('hello.dart.snapshot', contains('hello!')),
+ d.file('hello.dart.snapshot.dart2', contains('hello!'))
+ ]).validate();
+ });
+
+ test("pub run", () async {
+ await servePackages((builder) {
+ builder.serve("foo", "5.6.7", contents: [
+ d.dir("bin",
+ [d.file("hello.dart", "void main() => print('hello!');")])
+ ]);
+ });
+
+ await d.appDir({"foo": "5.6.7"}).create();
+
+ await pubGet(output: contains("Precompiled foo:hello."));
+
+ var process = await pubRun(args: ['foo:hello'], dart2: true);
+
+ // In the real world this would just print "hello!", but since we
+ // collect all output we see the precompilation messages as well.
+ expect(process.stdout, emits("Precompiling executables..."));
+ expect(process.stdout, emitsThrough("hello!"));
+ await process.shouldExit();
+
+ await d.dir(p.join(appPath, '.dart_tool', 'pub', 'bin', 'foo'), [
+ d.file('hello.dart.snapshot', contains('hello!')),
+ d.file('hello.dart.snapshot.dart2', contains('hello!'))
+ ]).validate();
+ });
+ });
});
});
}
diff --git a/test/test_pub.dart b/test/test_pub.dart
index bd4afdf..42574a1 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -123,7 +123,8 @@
silent,
warning,
int exitCode,
- Map<String, String> environment}) async {
+ Map<String, String> environment,
+ bool dart2: false}) async {
if (error != null && warning != null) {
throw new ArgumentError("Cannot pass both 'error' and 'warning'.");
}
@@ -145,7 +146,8 @@
error: error,
silent: silent,
exitCode: exitCode,
- environment: environment);
+ environment: environment,
+ dart2: dart2);
}
Future pubGet(
@@ -154,14 +156,16 @@
error,
warning,
int exitCode,
- Map<String, String> environment}) =>
+ Map<String, String> environment,
+ bool dart2: false}) =>
pubCommand(RunCommand.get,
args: args,
output: output,
error: error,
warning: warning,
exitCode: exitCode,
- environment: environment);
+ environment: environment,
+ dart2: dart2);
Future pubUpgrade(
{Iterable<String> args,
@@ -169,14 +173,16 @@
error,
warning,
int exitCode,
- Map<String, String> environment}) =>
+ Map<String, String> environment,
+ bool dart2: false}) =>
pubCommand(RunCommand.upgrade,
args: args,
output: output,
error: error,
warning: warning,
exitCode: exitCode,
- environment: environment);
+ environment: environment,
+ dart2: dart2);
Future pubDowngrade(
{Iterable<String> args,
@@ -184,14 +190,16 @@
error,
warning,
int exitCode,
- Map<String, String> environment}) =>
+ Map<String, String> environment,
+ bool dart2: false}) =>
pubCommand(RunCommand.downgrade,
args: args,
output: output,
error: error,
warning: warning,
exitCode: exitCode,
- environment: environment);
+ environment: environment,
+ dart2: dart2);
/// Schedules starting the "pub [global] run" process and validates the
/// expected startup output.
@@ -200,10 +208,11 @@
/// "pub run".
///
/// Returns the `pub run` process.
-Future<PubProcess> pubRun({bool global: false, Iterable<String> args}) async {
+Future<PubProcess> pubRun(
+ {bool global: false, Iterable<String> args, bool dart2: false}) async {
var pubArgs = global ? ["global", "run"] : ["run"];
pubArgs.addAll(args);
- var pub = await startPub(args: pubArgs);
+ var pub = await startPub(args: pubArgs, dart2: dart2);
// Loading sources and transformers isn't normally printed, but the pub test
// infrastructure runs pub in verbose mode, which enables this.
@@ -244,12 +253,16 @@
silent,
int exitCode: exit_codes.SUCCESS,
String workingDirectory,
- Map<String, String> environment}) async {
+ Map<String, String> environment,
+ bool dart2: false}) async {
// Cannot pass both output and outputJson.
assert(output == null || outputJson == null);
var pub = await startPub(
- args: args, workingDirectory: workingDirectory, environment: environment);
+ args: args,
+ workingDirectory: workingDirectory,
+ environment: environment,
+ dart2: dart2);
await pub.shouldExit(exitCode);
expect(() async {
@@ -338,7 +351,8 @@
{Iterable<String> args,
String tokenEndpoint,
String workingDirectory,
- Map<String, String> environment}) async {
+ Map<String, String> environment,
+ bool dart2: false}) async {
args ??= [];
ensureDir(_pathInSandbox(appPath));
@@ -359,13 +373,13 @@
// TODO(nweiz): When the test runner supports plugins, create one to
// auto-generate the snapshot before each run.
var pubPath = p.absolute(p.join(pubRoot, 'bin/pub.dart'));
- if (fileExists('$pubPath.snapshot')) pubPath += '.snapshot';
+ var snapshotPath = '$pubPath.snapshot';
+ if (dart2) snapshotPath += '.dart2';
+ if (fileExists(snapshotPath)) pubPath = snapshotPath;
- var dartArgs = [
- await PackageResolver.current.processArgument,
- pubPath,
- '--verbose'
- ]..addAll(args);
+ var dartArgs = [await PackageResolver.current.processArgument];
+ if (dart2) dartArgs.add('--preview-dart-2');
+ dartArgs..addAll([pubPath, '--verbose'])..addAll(args);
return await PubProcess.start(dartBin, dartArgs,
environment: getPubTestEnvironment(tokenEndpoint)