From 6ce214fe705bebcf1a0411e71e22c6367ecca31c Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" <nweiz@google.com> Date: Wed, 3 Sep 2014 00:20:51 +0000 Subject: [PATCH] Precompile immutable globally-installed pub executables. R=rnystrom@google.com BUG=20483 Review URL: https://codereview.chromium.org//475093003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge@39787 260f80e4-7a28-3924-810f-c04153c831b5 --- lib/src/barback/asset_environment.dart | 49 ++++++ lib/src/command/global_run.dart | 13 +- lib/src/command/run.dart | 12 +- lib/src/entrypoint.dart | 118 +++++--------- lib/src/executable.dart | 41 +++-- lib/src/global_packages.dart | 145 +++++++++++++++--- lib/src/package.dart | 23 ++- .../activate_git_after_hosted_test.dart | 3 + .../activate_hosted_after_git_test.dart | 3 + .../activate_hosted_after_path_test.dart | 3 + test/global/activate/cached_package_test.dart | 4 +- test/global/activate/constraint_test.dart | 2 +- .../activate/different_version_test.dart | 2 + ...doesnt_snapshot_path_executables_test.dart | 32 ++++ test/global/activate/git_package_test.dart | 3 + .../activate/ignores_active_version_test.dart | 2 + .../reactivating_git_upgrades_test.dart | 4 + .../activate/removes_old_lockfile_test.dart | 33 ++++ .../snaphots_hosted_executables_test.dart | 46 ++++++ .../snapshots_git_executables_test.dart | 48 ++++++ ...orts_version_solver_backtracking_test.dart | 2 +- .../activate/uncached_package_test.dart | 4 +- ...eactivate_and_reactivate_package_test.dart | 2 + .../removes_precompiled_snapshots_test.dart | 22 +++ test/global/list_test.dart | 2 +- ...recompiles_if_sdk_is_out_of_date_test.dart | 53 +++++++ test/global/run/uses_old_lockfile_test.dart | 54 +++++++ 27 files changed, 594 insertions(+), 131 deletions(-) create mode 100644 test/global/activate/doesnt_snapshot_path_executables_test.dart create mode 100644 test/global/activate/removes_old_lockfile_test.dart create mode 100644 test/global/activate/snaphots_hosted_executables_test.dart create mode 100644 test/global/activate/snapshots_git_executables_test.dart create mode 100644 test/global/deactivate/removes_precompiled_snapshots_test.dart create mode 100644 test/global/run/recompiles_if_sdk_is_out_of_date_test.dart create mode 100644 test/global/run/uses_old_lockfile_test.dart diff --git a/lib/src/barback/asset_environment.dart b/lib/src/barback/asset_environment.dart index 54d30f15..1eab02d6 100644 --- a/lib/src/barback/asset_environment.dart +++ b/lib/src/barback/asset_environment.dart @@ -12,6 +12,7 @@ import 'package:path/path.dart' as path; import 'package:watcher/watcher.dart'; import '../entrypoint.dart'; +import '../exceptions.dart'; import '../io.dart'; import '../log.dart' as log; import '../package.dart'; @@ -232,6 +233,54 @@ class AssetEnvironment { rootDirectory: "bin")); } + /// Precompiles all of [packageName]'s executables to snapshots in + /// [directory]. + /// + /// If [executableIds] is passed, only those executables are precompiled. + Future precompileExecutables(String packageName, String directory, + {Iterable<AssetId> executableIds}) { + if (executableIds == null) { + executableIds = graph.packages[packageName].executableIds; + } + log.fine("executables for $packageName: $executableIds"); + if (executableIds.isEmpty) return null; + + var package = graph.packages[packageName]; + return servePackageBinDirectory(packageName).then((server) { + return waitAndPrintErrors(executableIds.map((id) { + var basename = path.url.basename(id.path); + var snapshotPath = path.join(directory, "$basename.snapshot"); + return runProcess(Platform.executable, [ + '--snapshot=$snapshotPath', + server.url.resolve(basename).toString() + ]).then((result) { + if (result.success) { + log.message("Precompiled ${_formatExecutable(id)}."); + } else { + // TODO(nweiz): Stop manually deleting this when issue 20504 is + // fixed. + deleteEntry(snapshotPath); + throw new ApplicationException( + log.yellow("Failed to precompile " + "${_formatExecutable(id)}:\n") + + result.stderr.join('\n')); + } + }); + })).whenComplete(() { + // Don't return this future, since we have no need to wait for the + // server to fully shut down. + server.close(); + }); + }); + } + + /// Returns the executable name for [id]. + /// + /// [id] is assumed to be an executable in a bin directory. The return value + /// is intended for log output and may contain formatting. + String _formatExecutable(AssetId id) => + log.bold("${id.package}:${path.basenameWithoutExtension(id.path)}"); + /// Stops the server bound to [rootDirectory]. /// /// Also removes any source files within that directory from barback. Returns diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart index 86eb30bc..ae47a052 100644 --- a/lib/src/command/global_run.dart +++ b/lib/src/command/global_run.dart @@ -6,8 +6,9 @@ library pub.command.global_run; import 'dart:async'; +import 'package:path/path.dart' as p; + import '../command.dart'; -import '../executable.dart'; import '../io.dart'; import '../utils.dart'; @@ -37,10 +38,14 @@ class GlobalRunCommand extends PubCommand { } var args = commandOptions.rest.skip(1).toList(); + if (p.split(executable).length > 1) { + // TODO(nweiz): Use adjacent strings when the new async/await compiler + // lands. + usageError('Cannot run an executable in a subdirectory of a global ' + + 'package.'); + } - var entrypoint = await globals.find(package); - var exitCode = await runExecutable(this, entrypoint, package, executable, - args, isGlobal: true); + var exitCode = await globals.runExecutable(package, executable, args); await flushThenExit(exitCode); } } diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart index 45c0931a..1e3a5013 100644 --- a/lib/src/command/run.dart +++ b/lib/src/command/run.dart @@ -6,6 +6,8 @@ library pub.command.run; import 'dart:async'; +import 'package:path/path.dart' as p; + import '../command.dart'; import '../executable.dart'; import '../io.dart'; @@ -34,10 +36,16 @@ class RunCommand extends PubCommand { var components = split1(executable, ":"); package = components[0]; executable = components[1]; + + if (p.split(executable).length > 1) { + // TODO(nweiz): Use adjacent strings when the new async/await compiler + // lands. + usageError("Cannot run an executable in a subdirectory of a " + + "dependency."); + } } - var exitCode = await runExecutable(this, entrypoint, package, executable, - args); + var exitCode = await runExecutable(entrypoint, package, executable, args); await flushThenExit(exitCode); } } diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 25bb14f1..c1f0e7d5 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -5,13 +5,11 @@ library pub.entrypoint; import 'dart:async'; -import 'dart:io'; import 'package:path/path.dart' as path; import 'package:barback/barback.dart'; import 'barback/asset_environment.dart'; -import 'exceptions.dart'; import 'io.dart'; import 'lock_file.dart'; import 'log.dart' as log; @@ -144,15 +142,8 @@ class Entrypoint { /// Build a package graph from the version solver results so we don't /// have to reload and reparse all the pubspecs. - return Future.wait(ids.map((id) { - return cache.sources[id.source].getDirectory(id).then((dir) { - return new Package(result.pubspecs[id.name], dir); - }); - })); - }).then((packages) { - _packageGraph = new PackageGraph(this, _lockFile, - new Map.fromIterable(packages, key: (package) => package.name)); - + return loadPackageGraph(result); + }).then((packageGraph) { return precompileExecutables(changed: result.changedPackages) .catchError((error, stackTrace) { // Just log exceptions here. Since the method is just about acquiring @@ -207,8 +198,11 @@ class Entrypoint { }); return waitAndPrintErrors(executables.keys.map((package) { - return _precompileExecutablesForPackage( - environment, package, executables[package]); + var dir = path.join(binDir, package); + cleanDir(dir); + return environment.precompileExecutables( + package, dir, + executableIds: executables[package]); })); }); }); @@ -236,15 +230,7 @@ class Entrypoint { }); if (hasUncachedDependency) return []; - var executables = - ordered(package.listFiles(beneath: binDir, recursive: false)) - .where((executable) => path.extension(executable) == '.dart') - .map((executable) { - return new AssetId( - package.name, - path.toUri(path.relative(executable, from: package.dir)) - .toString()); - }).toList(); + var executables = package.executableIds; // If we don't know which packages were changed, always precompile the // executables. @@ -267,51 +253,6 @@ class Entrypoint { return []; } - /// Precompiles all [executables] for [package]. - /// - /// [executables] is assumed to be a list of Dart executables in [package]'s - /// bin directory. - Future _precompileExecutablesForPackage( - AssetEnvironment environment, String package, List<AssetId> executables) { - var cacheDir = path.join('.pub', 'bin', package); - cleanDir(cacheDir); - - // TODO(nweiz): Unserve this directory when we're done with it. - return environment.servePackageBinDirectory(package).then((server) { - return waitAndPrintErrors(executables.map((id) { - var basename = path.url.basename(id.path); - var snapshotPath = path.join(cacheDir, "$basename.snapshot"); - return runProcess(Platform.executable, [ - '--snapshot=$snapshotPath', - server.url.resolve(basename).toString() - ]).then((result) { - if (result.success) { - log.message("Precompiled ${_executableName(id)}."); - } else { - // TODO(nweiz): Stop manually deleting this when issue 20504 is - // fixed. - deleteEntry(snapshotPath); - throw new ApplicationException( - log.yellow("Failed to precompile " - "${_executableName(id)}:\n") + - result.stderr.join('\n')); - } - }); - })).whenComplete(() { - // Don't return this future, since we have no need to wait for the - // server to fully shut down. - server.close(); - }); - }); - } - - /// Returns the executable name for [id]. - /// - /// [id] is assumed to be an executable in a bin directory. The return value - /// is intended for log output and may contain formatting. - String _executableName(AssetId id) => - log.bold("${id.package}:${path.basenameWithoutExtension(id.path)}"); - /// Makes sure the package at [id] is locally available. /// /// This automatically downloads the package to the system-wide cache as well @@ -421,22 +362,37 @@ class Entrypoint { /// Loads the package graph for the application and all of its transitive /// dependencies. /// - /// Before loading, makes sure the lockfile and dependencies are installed - /// and up to date. - Future<PackageGraph> loadPackageGraph() { + /// If [result] is passed, this loads the graph from it without re-parsing the + /// lockfile or any pubspecs. Otherwise, before loading, this makes sure the + /// lockfile and dependencies are installed and up to date. + Future<PackageGraph> loadPackageGraph([SolveResult result]) { if (_packageGraph != null) return new Future.value(_packageGraph); - return ensureLockFileIsUpToDate().then((_) { - return Future.wait(lockFile.packages.values.map((id) { - var source = cache.sources[id.source]; - return source.getDirectory(id) - .then((dir) => new Package.load(id.name, dir, cache.sources)); - })).then((packages) { - var packageMap = new Map.fromIterable(packages, key: (p) => p.name); - packageMap[root.name] = root; - _packageGraph = new PackageGraph(this, lockFile, packageMap); - return _packageGraph; - }); + return syncFuture(() { + if (result != null) { + return Future.wait(result.packages.map((id) { + return cache.sources[id.source].getDirectory(id) + .then((dir) => new Package(result.pubspecs[id.name], dir)); + })).then((packages) { + return new PackageGraph(this, new LockFile(result.packages), + new Map.fromIterable(packages, key: (package) => package.name)); + }); + } else { + return ensureLockFileIsUpToDate().then((_) { + return Future.wait(lockFile.packages.values.map((id) { + var source = cache.sources[id.source]; + return source.getDirectory(id) + .then((dir) => new Package.load(id.name, dir, cache.sources)); + })).then((packages) { + var packageMap = new Map.fromIterable(packages, key: (p) => p.name); + packageMap[root.name] = root; + return new PackageGraph(this, lockFile, packageMap); + }); + }); + } + }).then((graph) { + _packageGraph = graph; + return graph; }); } diff --git a/lib/src/executable.dart b/lib/src/executable.dart index 5cecc13d..79513923 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -12,7 +12,6 @@ import 'package:path/path.dart' as p; import 'package:stack_trace/stack_trace.dart'; import 'barback/asset_environment.dart'; -import 'command.dart'; import 'entrypoint.dart'; import 'exit_codes.dart' as exit_codes; import 'io.dart'; @@ -30,19 +29,18 @@ import 'utils.dart'; /// Arguments from [args] will be passed to the spawned Dart application. /// /// Returns the exit code of the spawned app. -Future<int> runExecutable(PubCommand command, Entrypoint entrypoint, - String package, String executable, Iterable<String> args, - {bool isGlobal: false}) { +Future<int> runExecutable(Entrypoint entrypoint, String package, + String executable, Iterable<String> args, {bool isGlobal: false}) { // 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(".pub", "bin", package, + var localSnapshotPath = p.join(".pub", "bin", package, "$executable.dart.snapshot"); - if (!isGlobal && fileExists(snapshotPath)) { - return _runCachedExecutable(entrypoint, snapshotPath, args); + if (!isGlobal && fileExists(localSnapshotPath)) { + return _runCachedExecutable(entrypoint, localSnapshotPath, args); } // If the command has a path separator, then it's a path relative to the @@ -51,14 +49,7 @@ Future<int> runExecutable(PubCommand command, Entrypoint entrypoint, var rootDir = "bin"; var parts = p.split(executable); if (parts.length > 1) { - if (isGlobal) { - command.usageError( - 'Cannot run an executable in a subdirectory of a global package.'); - } else if (package != entrypoint.root.name) { - command.usageError( - "Cannot run an executable in a subdirectory of a dependency."); - } - + assert(!isGlobal && package == entrypoint.root.name); rootDir = parts.first; } else { executable = p.join("bin", executable); @@ -139,6 +130,26 @@ Future<int> runExecutable(PubCommand command, Entrypoint entrypoint, }); } +/// Runs the snapshot at [path] with [args] and hooks its stdout, stderr, and +/// sdtin to this process's. +/// +/// 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) { + var vmArgs = [path]..addAll(args); + + return Process.start(Platform.executable, vmArgs).then((process) { + // Note: we're not using process.std___.pipe(std___) here because + // that prevents pub from also writing to the output streams. + process.stderr.listen(stderr.add); + process.stdout.listen(stdout.add); + stdin.listen(process.stdin.add); + + return process.exitCode; + }); +} + /// Runs the executable snapshot at [snapshotPath]. Future _runCachedExecutable(Entrypoint entrypoint, String snapshotPath, List<String> args) { diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index bbb289a5..08f9fb1a 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart @@ -8,14 +8,19 @@ import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as p; +import 'package:barback/barback.dart'; +import 'barback/asset_environment.dart'; import 'entrypoint.dart'; +import 'executable.dart' as exe; import 'io.dart'; import 'lock_file.dart'; import 'log.dart' as log; import 'package.dart'; import 'pubspec.dart'; +import 'package_graph.dart'; import 'system_cache.dart'; +import 'sdk.dart' as sdk; import 'solver/version_solver.dart'; import 'source/cached.dart'; import 'source/git.dart'; @@ -69,6 +74,10 @@ class GlobalPackages { // Call this just to log what the current active package is, if any. _describeActive(name); + // TODO(nweiz): Add some special handling for git repos that contain path + // dependencies. Their executables shouldn't be cached, and there should + // be a mechanism for redoing dependency resolution if a path pubspec has + // changed (see also issue 20499). return _installInCache( new PackageDep(name, "git", VersionConstraint.any, repo)); }); @@ -96,7 +105,13 @@ class GlobalPackages { var fullPath = canonicalize(entrypoint.root.dir); var id = new PackageId(name, "path", entrypoint.root.version, PathSource.describePath(fullPath)); + + // TODO(rnystrom): Look in "bin" and display list of binaries that + // user can run. _writeLockFile(name, new LockFile([id])); + + var binDir = p.join(_directory, name, 'bin'); + if (dirExists(binDir)) deleteEntry(binDir); }); } @@ -120,9 +135,36 @@ class GlobalPackages { result.showReport(SolveType.GET); // Make sure all of the dependencies are locally installed. - return Future.wait(result.packages.map(_cacheDependency)); - }).then((ids) { - _writeLockFile(dep.name, new LockFile(ids)); + return Future.wait(result.packages.map(_cacheDependency)).then((ids) { + var lockFile = new LockFile(ids); + + // Load the package graph from [result] so we don't need to re-parse all + // the pubspecs. + return new Entrypoint.inMemory(root, lockFile, cache) + .loadPackageGraph(result) + .then((graph) => _precompileExecutables(graph.entrypoint, dep.name)) + .then((_) => _writeLockFile(dep.name, lockFile)); + }); + }); + } + + /// Precompiles the executables for [package] and saves them in the global + /// cache. + Future _precompileExecutables(Entrypoint entrypoint, String package) { + return log.progress("Precompiling executables", () { + var binDir = p.join(_directory, package, 'bin'); + var sdkVersionPath = p.join(binDir, 'sdk-version'); + cleanDir(binDir); + writeTextFile(sdkVersionPath, "${sdk.version}\n"); + + return AssetEnvironment.create(entrypoint, BarbackMode.RELEASE, + useDart2JS: false).then((environment) { + environment.barback.errors.listen((error) { + log.error(log.red("Build error:\n$error")); + }); + + return environment.precompileExecutables(package, binDir); + }); }); } @@ -142,15 +184,20 @@ class GlobalPackages { /// Finishes activating package [package] by saving [lockFile] in the cache. void _writeLockFile(String package, LockFile lockFile) { - ensureDir(_directory); + ensureDir(p.join(_directory, package)); + + // TODO(nweiz): This cleans up Dart 1.6's old lockfile location. Remove it + // when Dart 1.6 is old enough that we don't think anyone will have these + // lockfiles anymore (issue 20703). + var oldPath = p.join(_directory, "$package.lock"); + if (fileExists(oldPath)) deleteEntry(oldPath); + writeTextFile(_getLockFilePath(package), lockFile.serialize(cache.rootDir, cache.sources)); var id = lockFile.packages[package]; log.message('Activated ${_formatPackage(id)}.'); - // TODO(rnystrom): Look in "bin" and display list of binaries that - // user can run. } /// Shows the user the currently active package with [name], if any. @@ -184,18 +231,17 @@ class GlobalPackages { /// /// Returns `false` if no package with [name] was currently active. bool deactivate(String name, {bool logDeactivate: false}) { - var lockFilePath = _getLockFilePath(name); - if (!fileExists(lockFilePath)) return false; - - var lockFile = new LockFile.load(lockFilePath, cache.sources); - var id = lockFile.packages[name]; - - deleteEntry(lockFilePath); + var dir = p.join(_directory, name); + if (!dirExists(dir)) return false; if (logDeactivate) { + var lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); + var id = lockFile.packages[name]; log.message('Deactivated package ${_formatPackage(id)}.'); } + deleteEntry(dir); + return true; } @@ -204,12 +250,25 @@ class GlobalPackages { /// Returns an [Entrypoint] loaded with the active package if found. Future<Entrypoint> find(String name) { return syncFuture(() { + var lockFilePath = _getLockFilePath(name); var lockFile; try { - lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); + lockFile = new LockFile.load(lockFilePath, cache.sources); } on IOException catch (error) { - // If we couldn't read the lock file, it's not activated. - dataError("No active package ${log.bold(name)}."); + var oldLockFilePath = p.join(_directory, '$name.lock'); + try { + // TODO(nweiz): This looks for Dart 1.6's old lockfile location. + // Remove it when Dart 1.6 is old enough that we don't think anyone + // will have these lockfiles anymore (issue 20703). + lockFile = new LockFile.load(oldLockFilePath, cache.sources); + } on IOException catch (error) { + // If we couldn't read the lock file, it's not activated. + dataError("No active package ${log.bold(name)}."); + } + + // Move the old lockfile to its new location. + ensureDir(p.dirname(lockFilePath)); + new File(oldLockFilePath).renameSync(lockFilePath); } // Load the package from the cache. @@ -235,25 +294,65 @@ class GlobalPackages { }); } + /// Runs [package]'s [executable] with [args]. + /// + /// If [executable] is available in its precompiled form, that will be + /// recompiled if the SDK has been upgraded since it was first compiled and + /// then run. Otherwise, it will be run from source. + /// + /// Returns the exit code from the executable. + Future<int> runExecutable(String package, String executable, + Iterable<String> args) { + var binDir = p.join(_directory, package, 'bin'); + if (!fileExists(p.join(binDir, '$executable.dart.snapshot'))) { + return find(package).then((entrypoint) { + return exe.runExecutable(entrypoint, package, executable, args, + isGlobal: true); + }); + } + + // 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; + } + + return syncFuture(() { + var sdkVersionPath = p.join(binDir, 'sdk-version'); + var snapshotVersion = readTextFile(sdkVersionPath); + if (snapshotVersion == "${sdk.version}\n") return null; + log.fine("$package:$executable was compiled with Dart " + "${snapshotVersion.trim()} and needs to be recompiled."); + + return find(package) + .then((entrypoint) => entrypoint.loadPackageGraph()) + .then((graph) => _precompileExecutables(graph.entrypoint, package)); + }).then((_) => + exe.runSnapshot(p.join(binDir, '$executable.dart.snapshot'), args)); + } + /// Gets the path to the lock file for an activated cached package with /// [name]. - String _getLockFilePath(name) => p.join(_directory, name + ".lock"); + String _getLockFilePath(String name) => + p.join(_directory, name, "pubspec.lock"); /// Shows to the user formatted list of globally activated packages. void listActivePackages() { if (!dirExists(_directory)) return; // Loads lock [file] and returns [PackageId] of the activated package. - loadPackageId(file) { - var name = p.basenameWithoutExtension(file); + loadPackageId(file, name) { var lockFile = new LockFile.load(p.join(_directory, file), cache.sources); return lockFile.packages[name]; } - var packages = listDir(_directory, includeDirs: false) - .where((file) => p.extension(file) == '.lock') - .map(loadPackageId) - .toList(); + var packages = listDir(_directory).map((entry) { + if (fileExists(entry)) { + return loadPackageId(entry, p.basenameWithoutExtension(entry)); + } else { + return loadPackageId(p.join(entry, 'pubspec.lock'), p.basename(entry)); + } + }).toList(); packages ..sort((id1, id2) => id1.name.compareTo(id2.name)) diff --git a/lib/src/package.dart b/lib/src/package.dart index 211eb37e..3dd84e2d 100644 --- a/lib/src/package.dart +++ b/lib/src/package.dart @@ -7,6 +7,7 @@ library pub.package; import 'dart:io'; import 'package:path/path.dart' as path; +import 'package:barback/barback.dart'; import 'io.dart'; import 'git.dart' as git; @@ -75,6 +76,20 @@ class Package { return deps.values.toSet(); } + /// Returns a list of asset ids for all Dart executables in this package's bin + /// directory. + List<AssetId> get executableIds { + var binDir = path.join(dir, 'bin'); + if (!dirExists(binDir)) return []; + + return ordered(listFiles(beneath: binDir, recursive: false)) + .where((executable) => path.extension(executable) == '.dart') + .map((executable) { + return new AssetId( + name, path.toUri(path.relative(executable, from: dir)).toString()); + }).toList(); + } + /// Returns the path to the README file at the root of the entrypoint, or null /// if no README file is found. /// @@ -154,7 +169,13 @@ class Package { // If we're not listing recursively, strip out paths that contain // separators. Since git always prints forward slashes, we always detect // them. - if (!recursive) files = files.where((file) => !file.contains('/')); + if (!recursive) { + // If we're listing a subdirectory, we only want to look for slashes + // after the subdirectory prefix. + var relativeStart = relativeBeneath == '.' ? 0 : + relativeBeneath.length + 1; + files = files.where((file) => !file.contains('/', relativeStart)); + } // Git always prints files relative to the repository root, but we want // them relative to the working directory. It also prints forward slashes diff --git a/test/global/activate/activate_git_after_hosted_test.dart b/test/global/activate/activate_git_after_hosted_test.dart index 30082928..d50561e3 100644 --- a/test/global/activate/activate_git_after_hosted_test.dart +++ b/test/global/activate/activate_git_after_hosted_test.dart @@ -32,6 +32,9 @@ main() { Package foo is currently active at version 1.0.0. Resolving dependencies... + foo 1.0.0 from git ../foo.git + Precompiling executables... + Loading source assets... + Precompiled foo:foo. Activated foo 1.0.0 from Git repository "../foo.git"."""); // Should now run the git one. diff --git a/test/global/activate/activate_hosted_after_git_test.dart b/test/global/activate/activate_hosted_after_git_test.dart index 09ac4151..88e9913a 100644 --- a/test/global/activate/activate_hosted_after_git_test.dart +++ b/test/global/activate/activate_hosted_after_git_test.dart @@ -34,6 +34,9 @@ main() { Resolving dependencies... + foo 2.0.0 Downloading foo 2.0.0... + Precompiling executables... + Loading source assets... + Precompiled foo:foo. Activated foo 2.0.0."""); // Should now run the hosted one. diff --git a/test/global/activate/activate_hosted_after_path_test.dart b/test/global/activate/activate_hosted_after_path_test.dart index 3115695c..a3d42590 100644 --- a/test/global/activate/activate_hosted_after_path_test.dart +++ b/test/global/activate/activate_hosted_after_path_test.dart @@ -34,6 +34,9 @@ main() { Resolving dependencies... + foo 2.0.0 Downloading foo 2.0.0... + Precompiling executables... + Loading source assets... + Precompiled foo:foo. Activated foo 2.0.0."""); // Should now run the hosted one. diff --git a/test/global/activate/cached_package_test.dart b/test/global/activate/cached_package_test.dart index 348e7677..72671713 100644 --- a/test/global/activate/cached_package_test.dart +++ b/test/global/activate/cached_package_test.dart @@ -19,12 +19,14 @@ main() { schedulePub(args: ["global", "activate", "foo"], output: """ Resolving dependencies... + foo 1.0.0 + Precompiling executables... + Loading source assets... Activated foo 1.0.0."""); // Should be in global package cache. d.dir(cachePath, [ d.dir('global_packages', [ - d.matcherFile('foo.lock', contains('1.0.0')) + d.dir('foo', [d.matcherFile('pubspec.lock', contains('1.0.0'))]) ]) ]).validate(); }); diff --git a/test/global/activate/constraint_test.dart b/test/global/activate/constraint_test.dart index 714e06c5..d651709c 100644 --- a/test/global/activate/constraint_test.dart +++ b/test/global/activate/constraint_test.dart @@ -21,7 +21,7 @@ main() { d.dir(cachePath, [ d.dir('global_packages', [ - d.matcherFile('foo.lock', contains('1.0.1')) + d.dir('foo', [d.matcherFile('pubspec.lock', contains('1.0.1'))]) ]) ]).validate(); }); diff --git a/test/global/activate/different_version_test.dart b/test/global/activate/different_version_test.dart index a21387cb..6a406eac 100644 --- a/test/global/activate/different_version_test.dart +++ b/test/global/activate/different_version_test.dart @@ -22,6 +22,8 @@ main() { Resolving dependencies... + foo 2.0.0 Downloading foo 2.0.0... + Precompiling executables... + Loading source assets... Activated foo 2.0.0."""); }); } diff --git a/test/global/activate/doesnt_snapshot_path_executables_test.dart b/test/global/activate/doesnt_snapshot_path_executables_test.dart new file mode 100644 index 00000000..fafc6df3 --- /dev/null +++ b/test/global/activate/doesnt_snapshot_path_executables_test.dart @@ -0,0 +1,32 @@ +// 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 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration("doesn't snapshots the executables for a path package", () { + d.dir('foo', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("hello.dart", "void main() => print('hello!');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-spath", "../foo"], + output: isNot(contains('Precompiled foo:hello.'))); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.matcherFile('pubspec.lock', contains('1.0.0')), + d.nothing('bin') + ]) + ]) + ]).validate(); + }); +} diff --git a/test/global/activate/git_package_test.dart b/test/global/activate/git_package_test.dart index eea65e8e..d6a887bd 100644 --- a/test/global/activate/git_package_test.dart +++ b/test/global/activate/git_package_test.dart @@ -21,6 +21,9 @@ main() { output: ''' Resolving dependencies... + foo 1.0.0 from git ../foo.git + Precompiling executables... + Loading source assets... + Precompiled foo:foo. Activated foo 1.0.0 from Git repository "../foo.git".'''); }); } diff --git a/test/global/activate/ignores_active_version_test.dart b/test/global/activate/ignores_active_version_test.dart index a34e0c68..3905f5ff 100644 --- a/test/global/activate/ignores_active_version_test.dart +++ b/test/global/activate/ignores_active_version_test.dart @@ -22,6 +22,8 @@ main() { Resolving dependencies... + foo 1.3.0 Downloading foo 1.3.0... + Precompiling executables... + Loading source assets... Activated foo 1.3.0."""); }); } diff --git a/test/global/activate/reactivating_git_upgrades_test.dart b/test/global/activate/reactivating_git_upgrades_test.dart index a07d667e..f328d269 100644 --- a/test/global/activate/reactivating_git_upgrades_test.dart +++ b/test/global/activate/reactivating_git_upgrades_test.dart @@ -19,6 +19,8 @@ main() { output: ''' Resolving dependencies... + foo 1.0.0 from git ../foo.git + Precompiling executables... + Loading source assets... Activated foo 1.0.0 from Git repository "../foo.git".'''); d.git('foo.git', [ @@ -31,6 +33,8 @@ main() { Package foo is currently active from Git repository "../foo.git". Resolving dependencies... + foo 1.0.1 from git ../foo.git + Precompiling executables... + Loading source assets... Activated foo 1.0.1 from Git repository "../foo.git".'''); }); } diff --git a/test/global/activate/removes_old_lockfile_test.dart b/test/global/activate/removes_old_lockfile_test.dart new file mode 100644 index 00000000..8816bcd6 --- /dev/null +++ b/test/global/activate/removes_old_lockfile_test.dart @@ -0,0 +1,33 @@ +// 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 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('removes the 1.6-style lockfile', () { + servePackages((builder) { + builder.serve("foo", "1.0.0"); + }); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.file('foo.lock', 'packages: {foo: {description: foo, source: hosted, ' + 'version: "1.0.0"}}}') + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.nothing('foo.lock'), + d.dir('foo', [d.matcherFile('pubspec.lock', contains('1.0.0'))]) + ]) + ]).validate(); + }); +} diff --git a/test/global/activate/snaphots_hosted_executables_test.dart b/test/global/activate/snaphots_hosted_executables_test.dart new file mode 100644 index 00000000..79ce0d7e --- /dev/null +++ b/test/global/activate/snaphots_hosted_executables_test.dart @@ -0,0 +1,46 @@ +// 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 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('snapshots the executables for a hosted package', () { + servePackages((builder) { + builder.serve("foo", "1.0.0", contents: [ + d.dir('bin', [ + d.file("hello.dart", "void main() => print('hello!');"), + d.file("goodbye.dart", "void main() => print('goodbye!');"), + d.file("shell.sh", "echo shell"), + d.dir("subdir", [ + d.file("sub.dart", "void main() => print('sub!');") + ]) + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"], output: allOf([ + contains('Precompiled foo:hello.'), + contains("Precompiled foo:goodbye.") + ])); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.matcherFile('pubspec.lock', contains('1.0.0')), + d.dir('bin', [ + d.file('sdk-version', '0.1.2+3\n'), + d.matcherFile('hello.dart.snapshot', contains('hello!')), + d.matcherFile('goodbye.dart.snapshot', contains('goodbye!')), + d.nothing('shell.sh.snapshot'), + d.nothing('subdir') + ]) + ]) + ]) + ]).validate(); + }); +} diff --git a/test/global/activate/snapshots_git_executables_test.dart b/test/global/activate/snapshots_git_executables_test.dart new file mode 100644 index 00000000..35274c14 --- /dev/null +++ b/test/global/activate/snapshots_git_executables_test.dart @@ -0,0 +1,48 @@ +// 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 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('snapshots the executables for a Git repo', () { + ensureGit(); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("hello.dart", "void main() => print('hello!');"), + d.file("goodbye.dart", "void main() => print('goodbye!');"), + d.file("shell.sh", "echo shell"), + d.dir("subdir", [ + d.file("sub.dart", "void main() => print('sub!');") + ]) + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"], + output: allOf([ + contains('Precompiled foo:hello.'), + contains("Precompiled foo:goodbye.") + ])); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.matcherFile('pubspec.lock', contains('1.0.0')), + d.dir('bin', [ + d.file('sdk-version', '0.1.2+3\n'), + d.matcherFile('hello.dart.snapshot', contains('hello!')), + d.matcherFile('goodbye.dart.snapshot', contains('goodbye!')), + d.nothing('shell.sh.snapshot'), + d.nothing('subdir') + ]) + ]) + ]) + ]).validate(); + }); +} diff --git a/test/global/activate/supports_version_solver_backtracking_test.dart b/test/global/activate/supports_version_solver_backtracking_test.dart index c4647c2b..97d83de8 100644 --- a/test/global/activate/supports_version_solver_backtracking_test.dart +++ b/test/global/activate/supports_version_solver_backtracking_test.dart @@ -25,7 +25,7 @@ main() { // dummy SDK version 0.1.2+3. d.dir(cachePath, [ d.dir('global_packages', [ - d.matcherFile('foo.lock', contains('1.1.0')) + d.dir('foo', [d.matcherFile('pubspec.lock', contains('1.1.0'))]) ]) ]).validate(); }); diff --git a/test/global/activate/uncached_package_test.dart b/test/global/activate/uncached_package_test.dart index 7194b99a..92b27ff8 100644 --- a/test/global/activate/uncached_package_test.dart +++ b/test/global/activate/uncached_package_test.dart @@ -20,12 +20,14 @@ main() { Resolving dependencies... + foo 1.2.3 (2.0.0-wildly.unstable available) Downloading foo 1.2.3... + Precompiling executables... + Loading source assets... Activated foo 1.2.3."""); // Should be in global package cache. d.dir(cachePath, [ d.dir('global_packages', [ - d.matcherFile('foo.lock', contains('1.2.3')) + d.dir('foo', [d.matcherFile('pubspec.lock', contains('1.2.3'))]) ]) ]).validate(); }); diff --git a/test/global/deactivate/deactivate_and_reactivate_package_test.dart b/test/global/deactivate/deactivate_and_reactivate_package_test.dart index 20b1b7eb..1e8a37cf 100644 --- a/test/global/deactivate/deactivate_and_reactivate_package_test.dart +++ b/test/global/deactivate/deactivate_and_reactivate_package_test.dart @@ -23,6 +23,8 @@ main() { Resolving dependencies... + foo 2.0.0 Downloading foo 2.0.0... + Precompiling executables... + Loading source assets... Activated foo 2.0.0."""); }); } diff --git a/test/global/deactivate/removes_precompiled_snapshots_test.dart b/test/global/deactivate/removes_precompiled_snapshots_test.dart new file mode 100644 index 00000000..482a3f41 --- /dev/null +++ b/test/global/deactivate/removes_precompiled_snapshots_test.dart @@ -0,0 +1,22 @@ +// 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 '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('removes precompiled snapshots', () { + servePackages((builder) => builder.serve("foo", "1.0.0")); + + schedulePub(args: ["global", "activate", "foo"]); + + schedulePub(args: ["global", "deactivate", "foo"], + output: "Deactivated package foo 1.0.0."); + + d.dir(cachePath, [ + d.dir('global_packages', [d.nothing('foo')]) + ]).validate(); + }); +} diff --git a/test/global/list_test.dart b/test/global/list_test.dart index 782c9b0f..02c231ec 100644 --- a/test/global/list_test.dart +++ b/test/global/list_test.dart @@ -11,7 +11,7 @@ import '../test_pub.dart'; main() { initConfig(); - integration('lists an activated hosted package', () { + solo_integration('lists an activated hosted package', () { servePackages((builder) { builder.serve('foo', '1.0.0'); }); diff --git a/test/global/run/recompiles_if_sdk_is_out_of_date_test.dart b/test/global/run/recompiles_if_sdk_is_out_of_date_test.dart new file mode 100644 index 00000000..df4b0297 --- /dev/null +++ b/test/global/run/recompiles_if_sdk_is_out_of_date_test.dart @@ -0,0 +1,53 @@ +// 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 'package:scheduled_test/scheduled_stream.dart'; +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('recompiles a script if the SDK version is out-of-date', () { + servePackages((builder) { + builder.serve("foo", "1.0.0", contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.dir('bin', [ + d.file('sdk-version', '0.0.1\n'), + d.file('script.dart.snapshot', 'junk') + ]) + ]) + ]) + ]).create(); + + var pub = 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. + pub.stdout.expect("Precompiling executables..."); + pub.stdout.expect(consumeThrough("ok")); + pub.shouldExit(); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.dir('bin', [ + d.file('sdk-version', '0.1.2+3\n'), + d.matcherFile('script.dart.snapshot', contains('ok')) + ]) + ]) + ]) + ]).validate(); + }); +} diff --git a/test/global/run/uses_old_lockfile_test.dart b/test/global/run/uses_old_lockfile_test.dart new file mode 100644 index 00000000..21b41ead --- /dev/null +++ b/test/global/run/uses_old_lockfile_test.dart @@ -0,0 +1,54 @@ +// 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 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('uses the 1.6-style lockfile if necessary', () { + servePackages((builder) { + builder.serve("bar", "1.0.0"); + builder.serve("foo", "1.0.0", deps: {"bar": "any"}, contents: [ + d.dir("bin", [ + d.file("script.dart", """ + import 'package:bar/bar.dart' as bar; + + main(args) => print(bar.main());""") + ]) + ]); + }); + + schedulePub(args: ["cache", "add", "foo"]); + schedulePub(args: ["cache", "add", "bar"]); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.file('foo.lock', ''' +packages: + foo: + description: foo + source: hosted + version: "1.0.0" + bar: + description: bar + source: hosted + version: "1.0.0"''') + ]) + ]).create(); + + var pub = pubRun(global: true, args: ["foo:script"]); + pub.stdout.expect("bar 1.0.0"); + pub.shouldExit(); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.nothing('foo.lock'), + d.dir('foo', [d.matcherFile('pubspec.lock', contains('1.0.0'))]) + ]) + ]).validate(); + }); +} -- GitLab