| Index: lib/src/entrypoint.dart
|
| diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
|
| index 1cb84628b32a8ac3982b54fd8242638eefae455d..dc1a4a445f6f95eacab95ac2e73ee75c5f2fddd2 100644
|
| --- a/lib/src/entrypoint.dart
|
| +++ b/lib/src/entrypoint.dart
|
| @@ -109,6 +109,16 @@ class Entrypoint {
|
| /// The path to the entrypoint package's lockfile.
|
| String get lockFilePath => root.path('pubspec.lock');
|
|
|
| + /// The path to the directory containing precompiled dependencies.
|
| + ///
|
| + /// We just precompile the debug version of a package. We're mostly interested
|
| + /// in improving speed for development iteration loops, which usually use
|
| + /// debug mode.
|
| + String get _precompiledDepsPath => root.path('.pub', 'deps', 'debug');
|
| +
|
| + /// The path to the directory containing dependency executable snapshots.
|
| + String get _snapshotPath => root.path('.pub', 'bin');
|
| +
|
| /// Loads the entrypoint from a package at [rootDir].
|
| ///
|
| /// If [packageSymlinks] is `true`, this will create a "packages" directory
|
| @@ -152,9 +162,12 @@ class Entrypoint {
|
| /// the report. Otherwise, only dependencies that were changed are shown. If
|
| /// [dryRun] is `true`, no physical changes are made.
|
| ///
|
| + /// If [precompile] is `true` (the default), this snapshots dependencies'
|
| + /// executables and runs transformers on transformed dependencies.
|
| + ///
|
| /// Updates [lockFile] and [packageRoot] accordingly.
|
| Future acquireDependencies(SolveType type, {List<String> useLatest,
|
| - bool dryRun: false}) async {
|
| + bool dryRun: false, bool precompile: true}) async {
|
| var result = await resolveVersions(type, cache.sources, root,
|
| lockFile: lockFile, useLatest: useLatest);
|
| if (!result.succeeded) throw result.error;
|
| @@ -187,8 +200,16 @@ class Entrypoint {
|
| packageGraph.loadTransformerCache().clearIfOutdated(result.changedPackages);
|
|
|
| try {
|
| - await precompileDependencies(changed: result.changedPackages);
|
| - await precompileExecutables(changed: result.changedPackages);
|
| + if (precompile) {
|
| + await _precompileDependencies(changed: result.changedPackages);
|
| + await precompileExecutables(changed: result.changedPackages);
|
| + } else {
|
| + // If precompilation is disabled, delete any stale cached dependencies
|
| + // or snapshots.
|
| + _deletePrecompiledDependencies(
|
| + _dependenciesToPrecompile(changed: result.changedPackages));
|
| + _deleteExecutableSnapshots(changed: result.changedPackages);
|
| + }
|
| } catch (error, stackTrace) {
|
| // Just log exceptions here. Since the method is just about acquiring
|
| // dependencies, it shouldn't fail unless that fails.
|
| @@ -202,46 +223,11 @@ class Entrypoint {
|
| ///
|
| /// If [changed] is passed, only dependencies whose contents might be changed
|
| /// if one of the given packages changes will be recompiled.
|
| - Future precompileDependencies({Iterable<String> changed}) async {
|
| + Future _precompileDependencies({Iterable<String> changed}) async {
|
| if (changed != null) changed = changed.toSet();
|
|
|
| - // Just precompile the debug version of a package. We're mostly interested
|
| - // in improving speed for development iteration loops, which usually use
|
| - // debug mode.
|
| - var depsDir = p.join('.pub', 'deps', 'debug');
|
| -
|
| - var dependenciesToPrecompile = packageGraph.packages.values
|
| - .where((package) {
|
| - if (package.pubspec.transformers.isEmpty) return false;
|
| - if (packageGraph.isPackageMutable(package.name)) return false;
|
| - if (!dirExists(p.join(depsDir, package.name))) return true;
|
| - if (changed == null) return true;
|
| -
|
| - /// Only recompile [package] if any of its transitive dependencies have
|
| - /// changed. We check all transitive dependencies because it's possible
|
| - /// that a transformer makes decisions based on their contents.
|
| - return overlaps(
|
| - packageGraph.transitiveDependencies(package.name)
|
| - .map((package) => package.name).toSet(),
|
| - changed);
|
| - }).map((package) => package.name).toSet();
|
| -
|
| - if (dirExists(depsDir)) {
|
| - // Delete any cached dependencies that are going to be recached.
|
| - for (var package in dependenciesToPrecompile) {
|
| - deleteEntry(p.join(depsDir, package));
|
| - }
|
| -
|
| - // Also delete any cached dependencies that should no longer be cached.
|
| - for (var subdir in listDir(depsDir)) {
|
| - var package = packageGraph.packages[p.basename(subdir)];
|
| - if (package == null || package.pubspec.transformers.isEmpty ||
|
| - packageGraph.isPackageMutable(package.name)) {
|
| - deleteEntry(subdir);
|
| - }
|
| - }
|
| - }
|
| -
|
| + var dependenciesToPrecompile = _dependenciesToPrecompile(changed: changed);
|
| + _deletePrecompiledDependencies(dependenciesToPrecompile);
|
| if (dependenciesToPrecompile.isEmpty) return;
|
|
|
| try {
|
| @@ -265,7 +251,7 @@ class Entrypoint {
|
| if (!dependenciesToPrecompile.contains(asset.id.package)) return;
|
|
|
| var destPath = p.join(
|
| - depsDir, asset.id.package, p.fromUri(asset.id.path));
|
| + _precompiledDepsPath, asset.id.package, p.fromUri(asset.id.path));
|
| ensureDir(p.dirname(destPath));
|
| await createFileFromStream(asset.read(), destPath);
|
| }));
|
| @@ -278,57 +264,78 @@ class Entrypoint {
|
| // assets (issue 19491), catch and handle compilation errors on a
|
| // per-package basis.
|
| for (var package in dependenciesToPrecompile) {
|
| - deleteEntry(p.join(depsDir, package));
|
| + deleteEntry(p.join(_precompiledDepsPath, package));
|
| }
|
| rethrow;
|
| }
|
| }
|
|
|
| - /// Precompiles all executables from dependencies that don't transitively
|
| - /// depend on [this] or on a path dependency.
|
| - Future precompileExecutables({Iterable<String> changed}) async {
|
| - if (changed != null) changed = changed.toSet();
|
| + /// Returns the set of dependencies that need to be precompiled.
|
| + ///
|
| + /// If [changed] is passed, only dependencies whose contents might be changed
|
| + /// if one of the given packages changes will be returned.
|
| + Set<String> _dependenciesToPrecompile({Iterable<String> changed}) {
|
| + return packageGraph.packages.values.where((package) {
|
| + if (package.pubspec.transformers.isEmpty) return false;
|
| + if (packageGraph.isPackageMutable(package.name)) return false;
|
| + if (!dirExists(p.join(_precompiledDepsPath, package.name))) return true;
|
| + if (changed == null) return true;
|
|
|
| - var binDir = p.join('.pub', 'bin');
|
| - var sdkVersionPath = p.join(binDir, 'sdk-version');
|
| + /// Only recompile [package] if any of its transitive dependencies have
|
| + /// changed. We check all transitive dependencies because it's possible
|
| + /// that a transformer makes decisions based on their contents.
|
| + return overlaps(
|
| + packageGraph.transitiveDependencies(package.name)
|
| + .map((package) => package.name).toSet(),
|
| + changed);
|
| + }).map((package) => package.name).toSet();
|
| + }
|
|
|
| - // If the existing executable was compiled with a different SDK, we need to
|
| - // recompile regardless of what changed.
|
| - // TODO(nweiz): Use the VM to check this when issue 20802 is fixed.
|
| - var sdkMatches = fileExists(sdkVersionPath) &&
|
| - readTextFile(sdkVersionPath) == "${sdk.version}\n";
|
| - if (!sdkMatches) changed = null;
|
| + /// Deletes outdated precompiled dependencies.
|
| + ///
|
| + /// This deletes the precompilations of all packages in [packages], as well as
|
| + /// any packages that are now untransformed or mutable.
|
| + void _deletePrecompiledDependencies([Iterable<String> packages]) {
|
| + if (!dirExists(_precompiledDepsPath)) return;
|
| +
|
| + // Delete any cached dependencies that are going to be recached.
|
| + packages ??= [];
|
| + for (var package in packages) {
|
| + var path = p.join(_precompiledDepsPath, package);
|
| + if (dirExists(path)) deleteEntry(path);
|
| + }
|
|
|
| - // Clean out any outdated snapshots.
|
| - if (dirExists(binDir)) {
|
| - for (var entry in listDir(binDir)) {
|
| - if (!dirExists(entry)) continue;
|
| -
|
| - var package = p.basename(entry);
|
| - if (!packageGraph.packages.containsKey(package) ||
|
| - packageGraph.isPackageMutable(package)) {
|
| - deleteEntry(entry);
|
| - }
|
| + // Also delete any cached dependencies that should no longer be cached.
|
| + for (var subdir in listDir(_precompiledDepsPath)) {
|
| + var package = packageGraph.packages[p.basename(subdir)];
|
| + if (package == null || package.pubspec.transformers.isEmpty ||
|
| + packageGraph.isPackageMutable(package.name)) {
|
| + deleteEntry(subdir);
|
| }
|
| }
|
| + }
|
| +
|
| + /// Precompiles all executables from dependencies that don't transitively
|
| + /// depend on [this] or on a path dependency.
|
| + Future precompileExecutables({Iterable<String> changed}) async {
|
| + _deleteExecutableSnapshots(changed: changed);
|
|
|
| var executables = new Map.fromIterable(root.immediateDependencies,
|
| key: (dep) => dep.name,
|
| - value: (dep) => _executablesForPackage(dep.name, changed));
|
| + value: (dep) => _executablesForPackage(dep.name));
|
|
|
| for (var package in executables.keys.toList()) {
|
| if (executables[package].isEmpty) executables.remove(package);
|
| }
|
|
|
| - if (!sdkMatches) deleteEntry(binDir);
|
| if (executables.isEmpty) return;
|
|
|
| await log.progress("Precompiling executables", () async {
|
| - ensureDir(binDir);
|
| + ensureDir(_snapshotPath);
|
|
|
| // Make sure there's a trailing newline so our version file matches the
|
| // SDK's.
|
| - writeTextFile(sdkVersionPath, "${sdk.version}\n");
|
| + writeTextFile(p.join(_snapshotPath, 'sdk-version'), "${sdk.version}\n");
|
|
|
| var packagesToLoad =
|
| unionAll(executables.keys.map(packageGraph.transitiveDependencies))
|
| @@ -344,7 +351,7 @@ class Entrypoint {
|
| });
|
|
|
| await waitAndPrintErrors(executables.keys.map((package) async {
|
| - var dir = p.join(binDir, package);
|
| + var dir = p.join(_snapshotPath, package);
|
| cleanDir(dir);
|
| await environment.precompileExecutables(package, dir,
|
| executableIds: executables[package]);
|
| @@ -352,13 +359,47 @@ class Entrypoint {
|
| });
|
| }
|
|
|
| + /// Deletes outdated cached executable snapshots.
|
| + ///
|
| + /// If [changed] is passed, only dependencies whose contents might be changed
|
| + /// if one of the given packages changes will have their executables deleted.
|
| + void _deleteExecutableSnapshots({Iterable<String> changed}) {
|
| + if (!dirExists(_snapshotPath)) return;
|
| +
|
| + // If we don't know what changed, we can't safely re-use any snapshots.
|
| + if (changed == null) {
|
| + deleteEntry(_snapshotPath);
|
| + return;
|
| + }
|
| + changed = changed.toSet();
|
| +
|
| + // If the existing executable was compiled with a different SDK, we need to
|
| + // recompile regardless of what changed.
|
| + // TODO(nweiz): Use the VM to check this when issue 20802 is fixed.
|
| + var sdkVersionPath = p.join(_snapshotPath, 'sdk-version');
|
| + if (!fileExists(sdkVersionPath) ||
|
| + readTextFile(sdkVersionPath) != "${sdk.version}\n") {
|
| + deleteEntry(_snapshotPath);
|
| + return;
|
| + }
|
| +
|
| + // Clean out any outdated snapshots.
|
| + for (var entry in listDir(_snapshotPath)) {
|
| + if (!dirExists(entry)) continue;
|
| +
|
| + var package = p.basename(entry);
|
| + if (!packageGraph.packages.containsKey(package) ||
|
| + packageGraph.isPackageMutable(package) ||
|
| + packageGraph.transitiveDependencies(package)
|
| + .any((dep) => changed.contains(dep.name))) {
|
| + deleteEntry(entry);
|
| + }
|
| + }
|
| + }
|
| +
|
| /// Returns the list of all executable assets for [packageName] that should be
|
| /// precompiled.
|
| - ///
|
| - /// If [changed] isn't `null`, executables for [packageName] will only be
|
| - /// compiled if they might depend on a package in [changed].
|
| - List<AssetId> _executablesForPackage(String packageName,
|
| - Set<String> changed) {
|
| + List<AssetId> _executablesForPackage(String packageName) {
|
| var package = packageGraph.packages[packageName];
|
| var binDir = package.path('bin');
|
| if (!dirExists(binDir)) return [];
|
| @@ -366,21 +407,15 @@ class Entrypoint {
|
|
|
| var executables = package.executableIds;
|
|
|
| - // If we don't know which packages were changed, always precompile the
|
| - // executables.
|
| - if (changed == null) return executables;
|
| -
|
| - // If any of the package's dependencies changed, recompile the executables.
|
| - if (packageGraph.transitiveDependencies(packageName)
|
| - .any((package) => changed.contains(package.name))) {
|
| - return executables;
|
| - }
|
| -
|
| - // If any executables don't exist, precompile them regardless of what
|
| - // changed. Since we delete the bin directory before recompiling, we need to
|
| - // recompile all executables.
|
| + // 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) =>
|
| - fileExists(p.join('.pub', 'bin', packageName,
|
| + fileExists(p.join(_snapshotPath, packageName,
|
| "${p.url.basename(executable.path)}.snapshot")));
|
| if (!executablesExist) return executables;
|
|
|
|
|