diff --git a/lib/src/command_lish.dart b/lib/src/command_lish.dart index 1e5673da28f9b82319315a44d9776cff89cd891e..4a370d7d67a1815dce0c76525bb35b7b3c167b9e 100644 --- a/lib/src/command_lish.dart +++ b/lib/src/command_lish.dart @@ -109,7 +109,7 @@ class LishCommand extends PubCommand { /// The basenames of directories that are automatically excluded from /// archives. - final _BLACKLISTED_DIRECTORIES = const ['packages']; + final _BLACKLISTED_DIRS = const ['packages']; /// Returns a list of files that should be included in the published package. /// If this is a Git repository, this will respect .gitignore; otherwise, it @@ -117,11 +117,8 @@ class LishCommand extends PubCommand { Future<List<String>> get _filesToPublish { var rootDir = entrypoint.root.dir; - return Future.wait([ - dirExists(join(rootDir, '.git')), - git.isInstalled - ]).then((results) { - if (results[0] && results[1]) { + return git.isInstalled.then((gitInstalled) { + if (dirExists(join(rootDir, '.git')) && gitInstalled) { // List all files that aren't gitignored, including those not checked // in to Git. return git.run(["ls-files", "--cached", "--others", @@ -129,27 +126,17 @@ class LishCommand extends PubCommand { } return listDir(rootDir, recursive: true).then((entries) { - return Future.wait(entries.map((entry) { - return fileExists(entry).then((isFile) { - // Skip directories. - if (!isFile) return null; - - // TODO(rnystrom): Making these relative will break archive - // creation if the cwd is ever *not* the package root directory. - // Should instead only make these relative right before generating - // the tree display (which is what really needs them to be). - // Make it relative to the package root. - return relativeTo(entry, rootDir); - }); - })); + return entries + .where(fileExists) // Skip directories. + .map((entry) => relativeTo(entry, rootDir)); }); - }).then((files) => files.where((file) { - if (file == null || _BLACKLISTED_FILES.contains(basename(file))) { - return false; - } + }).then((files) => files.where(_shouldPublish).toList()); + } - return !splitPath(file).any(_BLACKLISTED_DIRECTORIES.contains); - }).toList()); + /// Returns `true` if [file] should be published. + bool _shouldPublish(String file) { + if (_BLACKLISTED_FILES.contains(basename(file))) return false; + return !splitPath(file).any(_BLACKLISTED_DIRS.contains); } /// Returns the value associated with [key] in [map]. Throws a user-friendly diff --git a/lib/src/command_uploader.dart b/lib/src/command_uploader.dart index 445b61e58a921a08037465a085b296ced9f0ee03..a8c2100647518f3241258eb42d21441a91f02bad 100644 --- a/lib/src/command_uploader.dart +++ b/lib/src/command_uploader.dart @@ -58,11 +58,10 @@ class UploaderCommand extends PubCommand { exit(exit_codes.USAGE); } - return new Future.immediate(null).then((_) { + return defer(() { var package = commandOptions['package']; if (package != null) return package; - return Entrypoint.load(path.current, cache) - .then((entrypoint) => entrypoint.root.name); + return new Entrypoint(path.current, cache).root.name; }).then((package) { var uploader = commandOptions.rest[0]; return oauth2.withClient(cache, (client) { diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 7a940d41ceb4a3b7cdd2ade8f6560b3fa318c820..973081a29223ffeb0aea021bd450b156e801ed61 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -40,16 +40,12 @@ class Entrypoint { /// Packages which are either currently being asynchronously installed to the /// directory, or have already been installed. - final Map<PackageId, Future<PackageId>> _installs; - - Entrypoint(this.root, this.cache) - : _installs = new Map<PackageId, Future<PackageId>>(); + final _installs = new Map<PackageId, Future<PackageId>>(); /// Loads the entrypoint from a package at [rootDir]. - static Future<Entrypoint> load(String rootDir, SystemCache cache) { - return Package.load(null, rootDir, cache.sources).then((package) => - new Entrypoint(package, cache)); - } + Entrypoint(String rootDir, SystemCache cache) + : root = new Package(null, rootDir, cache.sources), + cache = cache; // TODO(rnystrom): Make this path configurable. /// The path to this "packages" directory. @@ -71,10 +67,10 @@ class Entrypoint { if (pendingOrCompleted != null) return pendingOrCompleted; var packageDir = join(path, id.name); - var future = ensureDir(dirname(packageDir)).then((_) { - return exists(packageDir); - }).then((exists) { - if (!exists) return; + var future = defer(() { + ensureDir(dirname(packageDir)); + if (!dirExists(packageDir)) return; + // TODO(nweiz): figure out when to actually delete the directory, and when // we can just re-use the existing symlink. log.fine("Deleting package directory for ${id.name} before install."); @@ -101,9 +97,9 @@ class Entrypoint { /// directory, respecting the [LockFile] if present. Returns a [Future] that /// completes when all dependencies are installed. Future installDependencies() { - return loadLockFile() - .then((lockFile) => resolveVersions(cache.sources, root, lockFile)) - .then(_installDependencies); + return defer(() { + return resolveVersions(cache.sources, root, loadLockFile()); + }).then(_installDependencies); } /// Installs the latest available versions of all dependencies of the [root] @@ -111,19 +107,19 @@ class Entrypoint { /// [Future] that completes when all dependencies are installed. Future updateAllDependencies() { return resolveVersions(cache.sources, root, new LockFile.empty()) - .then(_installDependencies); + .then(_installDependencies); } /// Installs the latest available versions of [dependencies], while leaving /// other dependencies as specified by the [LockFile] if possible. Returns a /// [Future] that completes when all dependencies are installed. Future updateDependencies(List<String> dependencies) { - return loadLockFile().then((lockFile) { - var versionSolver = new VersionSolver(cache.sources, root, lockFile); + return defer(() { + var solver = new VersionSolver(cache.sources, root, loadLockFile()); for (var dependency in dependencies) { - versionSolver.useLatestVersion(dependency); + solver.useLatestVersion(dependency); } - return versionSolver.solve(); + return solver.solve(); }).then(_installDependencies); } @@ -144,7 +140,8 @@ class Entrypoint { /// reached packages. This should only be called after the lockfile has been /// successfully generated. Future<List<Package>> walkDependencies() { - return loadLockFile().then((lockFile) { + return defer(() { + var lockFile = loadLockFile(); var group = new FutureGroup<Package>(); var visited = new Set<String>(); @@ -199,43 +196,33 @@ class Entrypoint { /// Loads the list of concrete package versions from the `pubspec.lock`, if it /// exists. If it doesn't, this completes to an empty [LockFile]. - Future<LockFile> loadLockFile() { + LockFile loadLockFile() { var lockFilePath = join(root.dir, 'pubspec.lock'); - - log.fine("Loading lockfile."); - return fileExists(lockFilePath).then((exists) { - if (!exists) { - log.fine("No lock file at $lockFilePath, creating empty one."); - return new LockFile.empty(); - } - - return readTextFile(lockFilePath).then((text) => - new LockFile.parse(text, cache.sources)); - }); + if (!fileExists(lockFilePath)) return new LockFile.empty(); + return new LockFile.parse(readTextFile(lockFilePath), cache.sources); } /// Saves a list of concrete package versions to the `pubspec.lock` file. - Future _saveLockFile(List<PackageId> packageIds) { + void _saveLockFile(List<PackageId> packageIds) { var lockFile = new LockFile.empty(); for (var id in packageIds) { if (!id.isRoot) lockFile.packages[id.name] = id; } var lockFilePath = join(root.dir, 'pubspec.lock'); - log.fine("Saving lockfile."); - return writeTextFile(lockFilePath, lockFile.serialize()); + writeTextFile(lockFilePath, lockFile.serialize()); } /// Installs a self-referential symlink in the `packages` directory that will /// allow a package to import its own files using `package:`. Future _installSelfReference(_) { - var linkPath = join(path, root.name); - return exists(linkPath).then((exists) { + return defer(() { + var linkPath = join(path, root.name); // Create the symlink if it doesn't exist. - if (exists) return; - return ensureDir(path).then( - (_) => createPackageSymlink(root.name, root.dir, linkPath, - isSelfLink: true)); + if (entryExists(linkPath)) return; + ensureDir(path); + return createPackageSymlink(root.name, root.dir, linkPath, + isSelfLink: true); }); } @@ -248,8 +235,8 @@ class Entrypoint { var testDir = join(root.dir, 'test'); var toolDir = join(root.dir, 'tool'); var webDir = join(root.dir, 'web'); - return dirExists(binDir).then((exists) { - if (!exists) return; + return defer(() { + if (!dirExists(binDir)) return; return _linkSecondaryPackageDir(binDir); }).then((_) => _linkSecondaryPackageDirsRecursively(exampleDir)) .then((_) => _linkSecondaryPackageDirsRecursively(testDir)) @@ -260,14 +247,14 @@ class Entrypoint { /// Creates a symlink to the `packages` directory in [dir] and all its /// subdirectories. Future _linkSecondaryPackageDirsRecursively(String dir) { - return dirExists(dir).then((exists) { - if (!exists) return; + return defer(() { + if (!dirExists(dir)) return; return _linkSecondaryPackageDir(dir) - .then((_) => _listDirWithoutPackages(dir)) - .then((files) { + .then((_) => _listDirWithoutPackages(dir)) + .then((files) { return Future.wait(files.map((file) { - return dirExists(file).then((isDir) { - if (!isDir) return; + return defer(() { + if (!dirExists(file)) return; return _linkSecondaryPackageDir(file); }); })); @@ -282,8 +269,8 @@ class Entrypoint { return listDir(dir).then((files) { return Future.wait(files.map((file) { if (basename(file) == 'packages') return new Future.immediate([]); - return dirExists(file).then((isDir) { - if (!isDir) return []; + return defer(() { + if (!dirExists(file)) return []; return _listDirWithoutPackages(file); }).then((subfiles) { var fileAndSubfiles = [file]; @@ -296,9 +283,9 @@ class Entrypoint { /// Creates a symlink to the `packages` directory in [dir] if none exists. Future _linkSecondaryPackageDir(String dir) { - var to = join(dir, 'packages'); - return exists(to).then((exists) { - if (exists) return; + return defer(() { + var to = join(dir, 'packages'); + if (entryExists(to)) return; return createSymlink(path, to); }); } diff --git a/lib/src/error_group.dart b/lib/src/error_group.dart index 0bdf5e5568d6b9e4d3a8a3d79feceba921170b6e..90edfa5179678e276142788b61472519e1f06280 100644 --- a/lib/src/error_group.dart +++ b/lib/src/error_group.dart @@ -6,6 +6,8 @@ library error_group; import 'dart:async'; +import 'utils.dart'; + /// An [ErrorGroup] entangles the errors of multiple [Future]s and [Stream]s /// with one another. This allows APIs to expose multiple [Future]s and /// [Stream]s that have identical error conditions without forcing API consumers @@ -260,7 +262,7 @@ class _ErrorGroupStream extends Stream { if (_isDone) return; _subscription.cancel(); // Call these asynchronously to work around issue 7913. - new Future.immediate(null).then((_) { + defer(() { _controller.signalError(e.error, e.stackTrace); _controller.close(); }); diff --git a/lib/src/git_source.dart b/lib/src/git_source.dart index 2b46706c8a8b168187fa463610ade1b2dddad1dd..b53c7257ad6d78431ffcd11a9807f3d478cef8c4 100644 --- a/lib/src/git_source.dart +++ b/lib/src/git_source.dart @@ -42,21 +42,19 @@ class GitSource extends Source { "Please ensure Git is correctly installed."); } - return ensureDir(join(systemCacheRoot, 'cache')); - }).then((_) => _ensureRepoCache(id)) - .then((_) => _revisionCachePath(id)) + ensureDir(join(systemCacheRoot, 'cache')); + return _ensureRepoCache(id); + }).then((_) => _revisionCachePath(id)) .then((path) { revisionCachePath = path; - return exists(revisionCachePath); - }).then((exists) { - if (exists) return; + if (entryExists(revisionCachePath)) return; return _clone(_repoCachePath(id), revisionCachePath, mirror: false); }).then((_) { var ref = _getEffectiveRef(id); if (ref == 'HEAD') return; return _checkOut(revisionCachePath, ref); }).then((_) { - return Package.load(id.name, revisionCachePath, systemCache.sources); + return new Package(id.name, revisionCachePath, systemCache.sources); }); } @@ -106,10 +104,9 @@ class GitSource extends Source { /// future that completes once this is finished and throws an exception if it /// fails. Future _ensureRepoCache(PackageId id) { - var path = _repoCachePath(id); - return exists(path).then((exists) { - if (!exists) return _clone(_getUrl(id), path, mirror: true); - + return defer(() { + var path = _repoCachePath(id); + if (!entryExists(path)) return _clone(_getUrl(id), path, mirror: true); return git.run(["fetch"], workingDir: path).then((result) => null); }); } @@ -135,9 +132,10 @@ class GitSource extends Source { /// the working tree, but instead makes the repository a local mirror of the /// remote repository. See the manpage for `git clone` for more information. Future _clone(String from, String to, {bool mirror: false}) { - // Git on Windows does not seem to automatically create the destination - // directory. - return ensureDir(to).then((_) { + return defer(() { + // Git on Windows does not seem to automatically create the destination + // directory. + ensureDir(to); var args = ["clone", from, to]; if (mirror) args.insertRange(1, 1, "--mirror"); return git.run(args); diff --git a/lib/src/io.dart b/lib/src/io.dart index 88697bb12381a782e56df4aa0660a22e89aa4eb4..c81da000b73e479d9366db42cbd8b9770f14138c 100644 --- a/lib/src/io.dart +++ b/lib/src/io.dart @@ -54,59 +54,17 @@ bool isBeneath(entry, dir) { /// Returns the path to [target] from [base]. String relativeTo(target, base) => path.relative(target, from: base); -/// Asynchronously determines if [path], which can be a [String] file path, a -/// [File], or a [Directory] exists on the file system. Returns a [Future] that -/// completes with the result. -Future<bool> exists(path) { - path = _getPath(path); - return Future.wait([fileExists(path), dirExists(path)]).then((results) { - return results[0] || results[1]; - }); -} - -/// Asynchronously determines if [file], which can be a [String] file path or a -/// [File], exists on the file system. Returns a [Future] that completes with -/// the result. -Future<bool> fileExists(file) { - var path = _getPath(file); - return log.ioAsync("Seeing if file $path exists.", - new File(path).exists(), - (exists) => "File $path ${exists ? 'exists' : 'does not exist'}."); -} +/// Determines if [path], which can be a [String] file path, a [File], or a +/// [Directory] exists on the file system. +bool entryExists(path) => fileExists(path) || dirExists(path); -// TODO(rnystrom): Get rid of this and only use sync. -/// Reads the contents of the text file [file], which can either be a [String] -/// or a [File]. -Future<String> readTextFile(file) { - var path = _getPath(file); - return log.ioAsync("Reading text file $path.", - new File(path).readAsString(Encoding.UTF_8), - (contents) { - // Sanity check: don't spew a huge file. - if (contents.length < 1024 * 1024) { - return "Read $path. Contents:\n$contents"; - } else { - return "Read ${contents.length} characters from $path."; - } - }); -} +/// Determines if [file], which can be a [String] file path or a [File], exists +/// on the file system. +bool fileExists(file) => _getFile(file).existsSync(); /// Reads the contents of the text file [file], which can either be a [String] /// or a [File]. -String readTextFileSync(file) { - var path = _getPath(file); - log.io("Reading text file $path."); - var contents = new File(path).readAsStringSync(Encoding.UTF_8); - - // Sanity check: don't spew a huge file. - if (contents.length < 1024 * 1024) { - log.fine("Read $path. Contents:\n$contents"); - } else { - log.fine("Read ${contents.length} characters from $path."); - } - - return contents; -} +String readTextFile(file) => _getFile(file).readAsStringSync(Encoding.UTF_8); /// Reads the contents of the binary file [file], which can either be a [String] /// or a [File]. @@ -119,10 +77,10 @@ List<int> readBinaryFile(file) { } /// Creates [file] (which can either be a [String] or a [File]), and writes -/// [contents] to it. Completes when the file is written and closed. +/// [contents] to it. /// /// If [dontLogContents] is true, the contents of the file will never be logged. -Future<File> writeTextFile(file, String contents, {dontLogContents: false}) { +File writeTextFile(file, String contents, {dontLogContents: false}) { var path = _getPath(file); file = new File(path); @@ -132,16 +90,13 @@ Future<File> writeTextFile(file, String contents, {dontLogContents: false}) { log.fine("Contents:\n$contents"); } - return file.open(FileMode.WRITE).then((opened) { - return opened.writeString(contents).then((ignore) { - return opened.close().then((_) { - log.fine("Wrote text file $path."); - return file; - }); - }); - }); + return file..writeAsStringSync(contents); } +/// Deletes [file], which can be a [String] or a [File]. Returns a [Future] +/// that completes when the deletion is done. +File deleteFile(file) => _getFile(file)..delete(); + /// Creates [file] (which can either be a [String] or a [File]), and writes /// [contents] to it. File writeBinaryFile(file, List<int> contents) { @@ -156,14 +111,6 @@ File writeBinaryFile(file, List<int> contents) { return file; } -/// Asynchronously deletes [file], which can be a [String] or a [File]. Returns -/// a [Future] that completes when the deletion is done. -Future<File> deleteFile(file) { - var path = _getPath(file); - return log.ioAsync("delete file $path", - new File(path).delete()); -} - /// Writes [stream] to a new file at [path], which may be a [String] or a /// [File]. Will replace any file already at that path. Completes when the file /// is done being written. @@ -178,42 +125,32 @@ Future<File> createFileFromStream(Stream<List<int>> stream, path) { }); } -/// Creates a directory [dir]. Returns a [Future] that completes when the -/// directory is created. -Future<Directory> createDir(dir) { - dir = _getDirectory(dir); - return log.ioAsync("create directory ${dir.path}", - dir.create()); -} +/// Creates a directory [dir]. +Directory createDir(dir) => _getDirectory(dir)..createSync(); /// Ensures that [path] and all its parent directories exist. If they don't -/// exist, creates them. Returns a [Future] that completes once all the -/// directories are created. -Future<Directory> ensureDir(path) { +/// exist, creates them. +Directory ensureDir(path) { path = _getPath(path); + log.fine("Ensuring directory $path exists."); - if (path == '.') return new Future.immediate(new Directory('.')); + var dir = new Directory(path); + if (path == '.' || dirExists(path)) return dir; - return dirExists(path).then((exists) { - if (exists) { - log.fine("Directory $path already exists."); - return new Directory(path); - } + ensureDir(dirname(path)); - return ensureDir(dirname(path)).then((_) { - return createDir(path).catchError((asyncError) { - if (asyncError.error is! DirectoryIOException) throw asyncError; - // Error 17 means the directory already exists (or 183 on Windows). - if (asyncError.error.osError.errorCode == 17 || - asyncError.error.osError.errorCode == 183) { - log.fine("Got 'already exists' error when creating directory."); - return _getDirectory(path); - } + try { + createDir(dir); + } on DirectoryIOException catch (ex) { + // Error 17 means the directory already exists (or 183 on Windows). + if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { + log.fine("Got 'already exists' error when creating directory."); + } else { + throw ex; + } + } - throw asyncError; - }); - }); - }); + return dir; } /// Creates a temp directory whose name will be based on [dir] with a unique @@ -307,29 +244,16 @@ Future<List<String>> listDir(dir, return doList(_getDirectory(dir), new Set<String>()); } -// TODO(rnystrom): Migrate everything over to the sync one and get rid of this. -/// Asynchronously determines if [dir], which can be a [String] directory path -/// or a [Directory], exists on the file system. Returns a [Future] that -/// completes with the result. -Future<bool> dirExists(dir) { - dir = _getDirectory(dir); - return log.ioAsync("Seeing if directory ${dir.path} exists.", - dir.exists(), - (exists) => "Directory ${dir.path} " - "${exists ? 'exists' : 'does not exist'}."); -} - /// Determines if [dir], which can be a [String] directory path or a -/// [Directory], exists on the file system. Returns a [Future] that completes -/// with the result. -bool dirExistsSync(dir) => _getDirectory(dir).existsSync(); +/// [Directory], exists on the file system. +bool dirExists(dir) => _getDirectory(dir).existsSync(); /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a /// new empty directory will be created. Returns a [Future] that completes when /// the new clean directory is created. Future<Directory> cleanDir(dir) { - return dirExists(dir).then((exists) { - if (exists) { + return defer(() { + if (dirExists(dir)) { // Delete it first. return deleteDir(dir).then((_) => createDir(dir)); } else { @@ -416,11 +340,11 @@ Future<File> createSymlink(from, to) { /// appropriate and then does nothing. Future<File> createPackageSymlink(String name, from, to, {bool isSelfLink: false}) { - // See if the package has a "lib" directory. - from = join(from, 'lib'); - return dirExists(from).then((exists) { + return defer(() { + // See if the package has a "lib" directory. + from = join(from, 'lib'); log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); - if (exists) return createSymlink(from, to); + if (dirExists(from)) return createSymlink(from, to); // It's OK for the self link (i.e. the root package) to not have a lib // directory since it may just be a leaf application that only has @@ -430,7 +354,7 @@ Future<File> createPackageSymlink(String name, from, to, 'you will not be able to import any libraries from it.'); } - return to; + return _getFile(to); }); } @@ -982,6 +906,14 @@ class PubProcessResult { bool get success => exitCode == 0; } +/// Gets a dart:io [File] for [entry], which can either already be a File or be +/// a path string. +File _getFile(entry) { + if (entry is File) return entry; + if (entry is String) return new File(entry); + throw 'Entry $entry is not a supported type.'; +} + /// Gets the path string for [entry], which can either already be a path string, /// or be a [File] or [Directory]. Allows working generically with "file-like" /// objects. diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart index b9aa38ed42d42903e628c7ac4226f76ac3883276..616b63891ea8a9b0d21f4eae64454555a9307d20 100644 --- a/lib/src/oauth2.dart +++ b/lib/src/oauth2.dart @@ -51,12 +51,12 @@ final _scopes = ['https://www.googleapis.com/auth/userinfo.email']; Credentials _credentials; /// Delete the cached credentials, if they exist. -Future clearCredentials(SystemCache cache) { +void clearCredentials(SystemCache cache) { _credentials = null; var credentialsFile = _credentialsFile(cache); - return fileExists(credentialsFile).then((exists) { - if (exists) return deleteFile(credentialsFile); - }); + if (!fileExists(credentialsFile)) return; + + deleteFile(credentialsFile); } /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when @@ -71,7 +71,7 @@ Future withClient(SystemCache cache, Future fn(Client client)) { return fn(client).whenComplete(() { client.close(); // Be sure to save the credentials even when an error happens. - return _saveCredentials(cache, client.credentials); + _saveCredentials(cache, client.credentials); }); }).catchError((asyncError) { if (asyncError.error is ExpirationException) { @@ -84,7 +84,8 @@ Future withClient(SystemCache cache, Future fn(Client client)) { message = "$message (${asyncError.error.description})"; } log.error("$message."); - return clearCredentials(cache).then((_) => withClient(cache, fn)); + clearCredentials(cache); + return withClient(cache, fn); } else { throw asyncError; } @@ -94,58 +95,52 @@ Future withClient(SystemCache cache, Future fn(Client client)) { /// Gets a new OAuth2 client. If saved credentials are available, those are /// used; otherwise, the user is prompted to authorize the pub client. Future<Client> _getClient(SystemCache cache) { - return _loadCredentials(cache).then((credentials) { + return defer(() { + var credentials = _loadCredentials(cache); if (credentials == null) return _authorize(); - return new Client(_identifier, _secret, credentials, - httpClient: httpClient); - }).then((client) { - return _saveCredentials(cache, client.credentials).then((_) => client); + + var client = new Client(_identifier, _secret, credentials, + httpClient: curlClient); + _saveCredentials(cache, client.credentials); + return client; }); } /// Loads the user's OAuth2 credentials from the in-memory cache or the /// filesystem if possible. If the credentials can't be loaded for any reason, /// the returned [Future] will complete to null. -Future<Credentials> _loadCredentials(SystemCache cache) { +Credentials _loadCredentials(SystemCache cache) { log.fine('Loading OAuth2 credentials.'); - if (_credentials != null) { - log.fine('Using already-loaded credentials.'); - return new Future.immediate(_credentials); - } + try { + if (_credentials != null) return _credentials; - var path = _credentialsFile(cache); - return fileExists(path).then((credentialsExist) { - if (!credentialsExist) { - log.fine('No credentials found at $path.'); - return; - } + var path = _credentialsFile(cache); + if (!fileExists(path)) return; - return readTextFile(_credentialsFile(cache)).then((credentialsJson) { - var credentials = new Credentials.fromJson(credentialsJson); - if (credentials.isExpired && !credentials.canRefresh) { - log.error("Pub's authorization to upload packages has expired and " - "can't be automatically refreshed."); - return null; // null means re-authorize - } + var credentials = new Credentials.fromJson(readTextFile(path)); + if (credentials.isExpired && !credentials.canRefresh) { + log.error("Pub's authorization to upload packages has expired and " + "can't be automatically refreshed."); + return null; // null means re-authorize. + } - return credentials; - }); - }).catchError((e) { + return credentials; + } catch (e) { log.error('Warning: could not load the saved OAuth2 credentials: $e\n' 'Obtaining new credentials...'); - return null; // null means re-authorize - }); + return null; // null means re-authorize. + } } /// Save the user's OAuth2 credentials to the in-memory cache and the /// filesystem. -Future _saveCredentials(SystemCache cache, Credentials credentials) { +void _saveCredentials(SystemCache cache, Credentials credentials) { log.fine('Saving OAuth2 credentials.'); _credentials = credentials; var path = _credentialsFile(cache); - return ensureDir(dirname(path)).then((_) => - writeTextFile(path, credentials.toJson(), dontLogContents: true)); + ensureDir(dirname(path)); + writeTextFile(path, credentials.toJson(), dontLogContents: true); } /// The path to the file in which the user's OAuth2 credentials are stored. @@ -177,7 +172,7 @@ Future<Client> _authorize() { var server = new HttpServer(); server.addRequestHandler((request) => request.path == "/", (request, response) { - chainToCompleter(new Future.immediate(null).then((_) { + chainToCompleter(defer(() { log.message('Authorization received, processing...'); var queryString = request.queryString; if (queryString == null) queryString = ''; diff --git a/lib/src/package.dart b/lib/src/package.dart index 72c5c0e19c77476b616d4bcb8a48ed9034e95100..ac9cc39703353a377d922bc8d4dbcf6a23772981 100644 --- a/lib/src/package.dart +++ b/lib/src/package.dart @@ -15,31 +15,6 @@ final _README_REGEXP = new RegExp(r"^README($|\.)", caseSensitive: false); /// A named, versioned, unit of code and resource reuse. class Package { - /// Loads the package whose root directory is [packageDir]. [name] is the - /// expected name of that package (e.g. the name given in the dependency), or - /// null if the package being loaded is the entrypoint package. - static Future<Package> load(String name, String packageDir, - SourceRegistry sources) { - var pubspecPath = join(packageDir, 'pubspec.yaml'); - - return fileExists(pubspecPath).then((exists) { - if (!exists) throw new PubspecNotFoundException(name); - return readTextFile(pubspecPath); - }).then((contents) { - try { - var pubspec = new Pubspec.parse(contents, sources); - - if (pubspec.name == null) throw new PubspecHasNoNameException(name); - if (name != null && pubspec.name != name) { - throw new PubspecNameMismatchException(name, pubspec.name); - } - return new Package._(packageDir, pubspec); - } on FormatException catch (ex) { - throw 'Could not parse $pubspecPath:\n${ex.message}'; - } - }); - } - /// The path to the directory containing the package. final String dir; @@ -80,6 +55,30 @@ class Package { }); } + /// Loads the package whose root directory is [packageDir]. [name] is the + /// expected name of that package (e.g. the name given in the dependency), or + /// `null` if the package being loaded is the entrypoint package. + factory Package(String name, String packageDir, SourceRegistry sources) { + var pubspecPath = join(packageDir, 'pubspec.yaml'); + if (!fileExists(pubspecPath)) throw new PubspecNotFoundException(name); + + try { + var pubspec = new Pubspec.parse(readTextFile(pubspecPath), sources); + + if (pubspec.name == null) { + throw new PubspecHasNoNameException(name); + } + + if (name != null && pubspec.name != name) { + throw new PubspecNameMismatchException(name, pubspec.name); + } + + return new Package._(packageDir, pubspec); + } on FormatException catch (ex) { + throw 'Could not parse $pubspecPath:\n${ex.message}'; + } + } + /// Constructs a package with the given pubspec. The package will have no /// directory associated with it. Package.inMemory(this.pubspec) diff --git a/lib/src/pub.dart b/lib/src/pub.dart index dcd76c68c4d8895399ab7c5702362109e6c6775f..db628d03157cdb811e2d28078b7397952053c0fa 100644 --- a/lib/src/pub.dart +++ b/lib/src/pub.dart @@ -235,43 +235,34 @@ abstract class PubCommand { exit(_chooseExitCode(error)); } - var future = new Future.immediate(null); - if (requiresEntrypoint) { - // TODO(rnystrom): Will eventually need better logic to walk up - // subdirectories until we hit one that looks package-like. For now, just - // assume the cwd is it. - future = Entrypoint.load(path.current, cache); - } - - future = future.then((entrypoint) { - this.entrypoint = entrypoint; - try { - var commandFuture = onRun(); - if (commandFuture == null) return true; + defer(() { + if (requiresEntrypoint) { + // TODO(rnystrom): Will eventually need better logic to walk up + // subdirectories until we hit one that looks package-like. For now, + // just assume the cwd is it. + entrypoint = new Entrypoint(path.current, cache); + } - return commandFuture; - } catch (error, trace) { - handleError(error, trace); + var commandFuture = onRun(); + if (commandFuture == null) return true; + + return commandFuture; + }).whenComplete(() => cache_.deleteTempDir()).catchError((asyncError) { + var e = asyncError.error; + if (e is PubspecNotFoundException && e.name == null) { + e = 'Could not find a file named "pubspec.yaml" in the directory ' + '${path.current}.'; + } else if (e is PubspecHasNoNameException && e.name == null) { + e = 'pubspec.yaml is missing the required "name" field (e.g. "name: ' + '${basename(path.current)}").'; } - }); - future - .then((_) => cache_.deleteTempDir()) - .catchError((asyncError) { - var e = asyncError.error; - if (e is PubspecNotFoundException && e.name == null) { - e = 'Could not find a file named "pubspec.yaml" in the directory ' - '${path.current}.'; - } else if (e is PubspecHasNoNameException && e.name == null) { - e = 'pubspec.yaml is missing the required "name" field (e.g. "name: ' - '${basename(path.current)}").'; - } - - handleError(e, asyncError.stackTrace); - }) + handleError(e, asyncError.stackTrace); + }).then((_) { // Explicitly exit on success to ensure that any dangling dart:io handles // don't cause the process to never terminate. - .then((_) => exit(0)); + exit(0); + }); } /// Override this to perform the specific command. Return a future that diff --git a/lib/src/sdk_source.dart b/lib/src/sdk_source.dart index e0782b08cc3d0494c96f2d9c8b2d4b89c70ddeeb..124d2651bb1cd76d066ac412e0ff67a05bcd133a 100644 --- a/lib/src/sdk_source.dart +++ b/lib/src/sdk_source.dart @@ -10,6 +10,7 @@ import 'package.dart'; import 'pubspec.dart'; import 'sdk.dart' as sdk; import 'source.dart'; +import 'utils.dart'; import 'version.dart'; /// A package source that uses libraries from the Dart SDK. @@ -20,10 +21,10 @@ class SdkSource extends Source { /// SDK packages are not individually versioned. Instead, their version is /// inferred from the revision number of the SDK itself. Future<Pubspec> describe(PackageId id) { - return _getPackagePath(id).then((packageDir) { + return defer(() { + var packageDir = _getPackagePath(id); // TODO(rnystrom): What if packageDir is null? - return Package.load(id.name, packageDir, systemCache.sources); - }).then((package) { + var package = new Package(id.name, packageDir, systemCache.sources); // Ignore the pubspec's version, and use the SDK's. return new Pubspec(id.name, sdk.version, package.pubspec.dependencies, package.pubspec.environment); @@ -33,18 +34,18 @@ class SdkSource extends Source { /// Since all the SDK files are already available locally, installation just /// involves symlinking the SDK library into the packages directory. Future<bool> install(PackageId id, String destPath) { - return _getPackagePath(id).then((path) { - if (path == null) return new Future<bool>.immediate(false); + return defer(() { + var path = _getPackagePath(id); + if (path == null) return false; - return createPackageSymlink(id.name, path, destPath).then( - (_) => true); + return createPackageSymlink(id.name, path, destPath).then((_) => true); }); } /// Gets the path in the SDK's "pkg" directory to the directory containing /// package [id]. Returns `null` if the package could not be found. - Future<String> _getPackagePath(PackageId id) { + String _getPackagePath(PackageId id) { var pkgPath = join(sdk.rootDirectory, "pkg", id.description); - return dirExists(pkgPath).then((found) => found ? pkgPath : null); + return dirExists(pkgPath) ? pkgPath : null; } } diff --git a/lib/src/source.dart b/lib/src/source.dart index d086374f3bffcde40dd21c2b202bcdd582bfbc80..977efad3a59c73e369039d1216d5952bbf6fd26d 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -9,6 +9,7 @@ import 'io.dart'; import 'package.dart'; import 'pubspec.dart'; import 'system_cache.dart'; +import 'utils.dart'; import 'version.dart'; /// A source from which to install packages. @@ -104,14 +105,15 @@ abstract class Source { /// /// By default, this uses [systemCacheDirectory] and [install]. Future<Package> installToSystemCache(PackageId id) { - return systemCacheDirectory(id).then((path) { - return exists(path).then((exists) { - if (exists) return new Future<bool>.immediate(true); - return ensureDir(dirname(path)).then((_) => install(id, path)); - }).then((found) { - if (!found) throw 'Package $id not found.'; - return Package.load(id.name, path, systemCache.sources); - }); + var path; + return systemCacheDirectory(id).then((p) { + path = p; + if (dirExists(path)) return true; + ensureDir(dirname(path)); + return install(id, path); + }).then((found) { + if (!found) throw 'Package $id not found.'; + return new Package(id.name, path, systemCache.sources); }); } diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart index 634dc173257b0e9742a549f86e6903fae9b855be..2fec00262e77ce2ee2baa039a81240b1ed755fa1 100644 --- a/lib/src/system_cache.dart +++ b/lib/src/system_cache.dart @@ -71,8 +71,8 @@ class SystemCache { // Try to get it from the system cache first. if (id.source.shouldCache) { return id.systemCacheDirectory.then((packageDir) { - if (!dirExistsSync(packageDir)) return getUncached(); - return Package.load(id.name, packageDir, sources); + if (!dirExists(packageDir)) return getUncached(); + return new Package(id.name, packageDir, sources); }); } @@ -105,7 +105,8 @@ class SystemCache { /// temp directory to ensure that it's on the same volume as the pub system /// cache so that it can move the directory from it. Future<Directory> createTempDir() { - return ensureDir(tempDir).then((temp) { + return defer(() { + var temp = ensureDir(tempDir); return io.createTempDir(join(temp, 'dir')); }); } @@ -113,8 +114,8 @@ class SystemCache { /// Delete's the system cache's internal temp directory. Future deleteTempDir() { log.fine('Clean up system cache temp directory $tempDir.'); - return dirExists(tempDir).then((exists) { - if (!exists) return; + return defer(() { + if (!dirExists(tempDir)) return; return deleteDir(tempDir); }); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 1b41a4e1a055d36db417a53c2663a54847b65ab3..7a257d4e16df187fbdfd7b97bc3a18269a109c99 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -145,6 +145,16 @@ String sha1(String source) { return CryptoUtils.bytesToHex(sha.close()); } +/// Invokes the given callback asynchronously. Returns a [Future] that completes +/// to the result of [callback]. +/// +/// This is also used to wrap synchronous code that may thrown an exception to +/// ensure that methods that have both sync and async code only report errors +/// asynchronously. +Future defer(callback()) { + return new Future.immediate(null).then((_) => callback()); +} + /// Returns a [Future] that completes in [milliseconds]. Future sleep(int milliseconds) { var completer = new Completer(); diff --git a/lib/src/validator/compiled_dartdoc.dart b/lib/src/validator/compiled_dartdoc.dart index c2c93f3a8708610624d1abdfce09a95813c4ce47..e5a7353d9927f2c20d1d1519ca6370bdf507ec73 100644 --- a/lib/src/validator/compiled_dartdoc.dart +++ b/lib/src/validator/compiled_dartdoc.dart @@ -21,27 +21,26 @@ class CompiledDartdocValidator extends Validator { Future validate() { return listDir(entrypoint.root.dir, recursive: true).then((entries) { - return futureWhere(entries, (entry) { + for (var entry in entries) { if (basename(entry) != "nav.json") return false; var dir = dirname(entry); // Look for tell-tale Dartdoc output files all in the same directory. - return Future.wait([ - fileExists(entry), - fileExists(join(dir, "index.html")), - fileExists(join(dir, "styles.css")), - fileExists(join(dir, "dart-logo-small.png")), - fileExists(join(dir, "client-live-nav.js")) - ]).then((results) => results.every((val) => val)); - }).then((files) { - for (var dartdocDir in files.mappedBy(dirname)) { - var relativePath = path.relative(dartdocDir); + var files = [ + entry, + join(dir, "index.html"), + join(dir, "styles.css"), + join(dir, "dart-logo-small.png"), + join(dir, "client-live-nav.js") + ]; + + if (files.every((val) => fileExists(val))) { warnings.add("Avoid putting generated documentation in " - "$relativePath.\n" + "${path.relative(dir)}.\n" "Generated documentation bloats the package with redundant " "data."); } - }); + } }); } } diff --git a/lib/src/validator/dependency.dart b/lib/src/validator/dependency.dart index 18cff6624b013321e2dc78b7713349f05f89e5f1..a848c545574db233111e1e1e698822e96915c8c1 100644 --- a/lib/src/validator/dependency.dart +++ b/lib/src/validator/dependency.dart @@ -38,7 +38,7 @@ class DependencyValidator extends Validator { // should warn about unittest. Until then, it's reasonable not to put // a constraint on it. dependency.name != 'unittest') { - return _warnAboutConstraint(dependency); + _warnAboutConstraint(dependency); } return new Future.immediate(null); @@ -74,21 +74,20 @@ class DependencyValidator extends Validator { } /// Warn that dependencies should have version constraints. - Future _warnAboutConstraint(PackageRef ref) { - return entrypoint.loadLockFile().then((lockFile) { - var message = 'Your dependency on "${ref.name}" should have a version ' - 'constraint.'; - var locked = lockFile.packages[ref.name]; - if (locked != null) { - message = '$message For example:\n' - '\n' - 'dependencies:\n' - ' ${ref.name}: ${_constraintForVersion(locked.version)}\n'; - } - warnings.add("$message\n" - "Without a constraint, you're promising to support all future " - "versions of ${ref.name}."); - }); + void _warnAboutConstraint(PackageRef ref) { + var lockFile = entrypoint.loadLockFile(); + var message = 'Your dependency on "${ref.name}" should have a version ' + 'constraint.'; + var locked = lockFile.packages[ref.name]; + if (locked != null) { + message = '$message For example:\n' + '\n' + 'dependencies:\n' + ' ${ref.name}: ${_constraintForVersion(locked.version)}\n'; + } + warnings.add("$message\n" + "Without a constraint, you're promising to support all future " + "versions of ${ref.name}."); } /// Returns the suggested version constraint for a dependency that was tested diff --git a/lib/src/validator/directory.dart b/lib/src/validator/directory.dart index 09a6c7e952b713e94a8f5e103bdf59530693437c..6ab932ca64224ac591826a9de4c575231545093c 100644 --- a/lib/src/validator/directory.dart +++ b/lib/src/validator/directory.dart @@ -8,6 +8,7 @@ import 'dart:async'; import '../entrypoint.dart'; import '../io.dart'; +import '../utils.dart'; import '../validator.dart'; /// A validator that validates a package's top-level directories. @@ -19,29 +20,27 @@ class DirectoryValidator extends Validator { Future validate() { return listDir(entrypoint.root.dir).then((dirs) { - return Future.wait(dirs.map((dir) { - return dirExists(dir).then((exists) { - if (!exists) return; - - dir = basename(dir); - if (_PLURAL_NAMES.contains(dir)) { - // Cut off the "s" - var singularName = dir.substring(0, dir.length - 1); - warnings.add('Rename the top-level "$dir" directory to ' - '"$singularName".\n' - 'The Pub layout convention is to use singular directory ' - 'names.\n' - 'Plural names won\'t be correctly identified by Pub and other ' - 'tools.'); - } - - if (dir.contains(new RegExp(r"^samples?$"))) { - warnings.add('Rename the top-level "$dir" directory to "example".\n' - 'This allows Pub to find your examples and create "packages" ' - 'directories for them.\n'); - } - }); - })); + for (var dir in dirs) { + if (!dirExists(dir)) continue; + + dir = basename(dir); + if (_PLURAL_NAMES.contains(dir)) { + // Cut off the "s" + var singularName = dir.substring(0, dir.length - 1); + warnings.add('Rename the top-level "$dir" directory to ' + '"$singularName".\n' + 'The Pub layout convention is to use singular directory ' + 'names.\n' + 'Plural names won\'t be correctly identified by Pub and other ' + 'tools.'); + } + + if (dir.contains(new RegExp(r"^samples?$"))) { + warnings.add('Rename the top-level "$dir" directory to "example".\n' + 'This allows Pub to find your examples and create "packages" ' + 'directories for them.\n'); + } + } }); } } diff --git a/lib/src/validator/lib.dart b/lib/src/validator/lib.dart index faee7290231abda4caa52fa2be39bc1ab66c36b0..7346223ebaf27a68231d6330af382a763bfcf87f 100644 --- a/lib/src/validator/lib.dart +++ b/lib/src/validator/lib.dart @@ -23,8 +23,8 @@ class LibValidator extends Validator { Future validate() { var libDir = join(entrypoint.root.dir, "lib"); - return dirExists(libDir).then((libDirExists) { - if (!libDirExists) { + return defer(() { + if (!dirExists(libDir)) { errors.add('You must have a "lib" directory.\n' "Without that, users cannot import any code from your package."); return; diff --git a/lib/src/validator/name.dart b/lib/src/validator/name.dart index 7c4778c8af4604b849c9b16b4e9a4c6587d4a7c9..0386089c9821fa9c226cec98ed0338037159c4ba 100644 --- a/lib/src/validator/name.dart +++ b/lib/src/validator/name.dart @@ -10,6 +10,7 @@ import 'dart:io'; import '../../../pkg/path/lib/path.dart' as path; import '../entrypoint.dart'; import '../io.dart'; +import '../utils.dart'; import '../validator.dart'; /// Dart reserved words, from the Dart spec. @@ -47,8 +48,8 @@ class NameValidator extends Validator { /// to the package's root directory. Future<List<String>> get _libraries { var libDir = join(entrypoint.root.dir, "lib"); - return dirExists(libDir).then((libDirExists) { - if (!libDirExists) return []; + return defer(() { + if (!dirExists(libDir)) return []; return listDir(libDir, recursive: true); }).then((files) { return files diff --git a/test/io_test.dart b/test/io_test.dart index 94e8a52d871ee15d5e15d0a276b48c4e4199f868..29e970982d0e47a1b12d25f5a1a1348333e424f7 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -6,16 +6,19 @@ library io_test; import '../../../pkg/unittest/lib/unittest.dart'; import '../../pub/io.dart'; +import '../../pub/utils.dart'; main() { group('listDir', () { test('lists a simple directory non-recursively', () { expect(withTempDir((path) { - var future = writeTextFile(join(path, 'file1.txt'), '') - .then((_) => writeTextFile(join(path, 'file2.txt'), '')) - .then((_) => createDir(join(path, 'subdir'))) - .then((_) => writeTextFile(join(path, 'subdir', 'file3.txt'), '')) - .then((_) => listDir(path)); + var future = defer(() { + writeTextFile(join(path, 'file1.txt'), ''); + writeTextFile(join(path, 'file2.txt'), ''); + createDir(join(path, 'subdir')); + writeTextFile(join(path, 'subdir', 'file3.txt'), ''); + return listDir(path); + }); expect(future, completion(unorderedEquals([ join(path, 'file1.txt'), join(path, 'file2.txt'), @@ -27,11 +30,13 @@ main() { test('lists a simple directory recursively', () { expect(withTempDir((path) { - var future = writeTextFile(join(path, 'file1.txt'), '') - .then((_) => writeTextFile(join(path, 'file2.txt'), '')) - .then((_) => createDir(join(path, 'subdir'))) - .then((_) => writeTextFile(join(path, 'subdir', 'file3.txt'), '')) - .then((_) => listDir(path, recursive: true)); + var future = defer(() { + writeTextFile(join(path, 'file1.txt'), ''); + writeTextFile(join(path, 'file2.txt'), ''); + createDir(join(path, 'subdir')); + writeTextFile(join(path, 'subdir', 'file3.txt'), ''); + return listDir(path, recursive: true); + }); expect(future, completion(unorderedEquals([ join(path, 'file1.txt'), join(path, 'file2.txt'), @@ -44,12 +49,14 @@ main() { test('ignores hidden files by default', () { expect(withTempDir((path) { - var future = writeTextFile(join(path, 'file1.txt'), '') - .then((_) => writeTextFile(join(path, 'file2.txt'), '')) - .then((_) => writeTextFile(join(path, '.file3.txt'), '')) - .then((_) => createDir(join(path, '.subdir'))) - .then((_) => writeTextFile(join(path, '.subdir', 'file3.txt'), '')) - .then((_) => listDir(path, recursive: true)); + var future = defer(() { + writeTextFile(join(path, 'file1.txt'), ''); + writeTextFile(join(path, 'file2.txt'), ''); + writeTextFile(join(path, '.file3.txt'), ''); + createDir(join(path, '.subdir')); + writeTextFile(join(path, '.subdir', 'file3.txt'), ''); + return listDir(path, recursive: true); + }); expect(future, completion(unorderedEquals([ join(path, 'file1.txt'), join(path, 'file2.txt') @@ -60,14 +67,14 @@ main() { test('includes hidden files when told to', () { expect(withTempDir((path) { - var future = writeTextFile(join(path, 'file1.txt'), '') - .then((_) => writeTextFile(join(path, 'file2.txt'), '')) - .then((_) => writeTextFile(join(path, '.file3.txt'), '')) - .then((_) => createDir(join(path, '.subdir'))) - .then((_) => writeTextFile(join(path, '.subdir', 'file3.txt'), '')) - .then((_) { - return listDir(path, recursive: true, includeHiddenFiles: true); - }); + var future = defer(() { + writeTextFile(join(path, 'file1.txt'), ''); + writeTextFile(join(path, 'file2.txt'), ''); + writeTextFile(join(path, '.file3.txt'), ''); + createDir(join(path, '.subdir')); + writeTextFile(join(path, '.subdir', 'file3.txt'), ''); + return listDir(path, recursive: true, includeHiddenFiles: true); + }); expect(future, completion(unorderedEquals([ join(path, 'file1.txt'), join(path, 'file2.txt'), @@ -82,19 +89,18 @@ main() { test('returns the unresolved paths for symlinks', () { expect(withTempDir((path) { var dirToList = join(path, 'dir-to-list'); - var future = writeTextFile(join(path, 'file1.txt'), '') - .then((_) => writeTextFile(join(path, 'file2.txt'), '')) - .then((_) => createDir(dirToList)) - .then((_) { - return createSymlink( - join(path, 'file1.txt'), - join(dirToList, 'link1')); - }).then((_) => createDir(join(dirToList, 'subdir'))) - .then((_) { - return createSymlink( + var future = defer(() { + writeTextFile(join(path, 'file1.txt'), ''); + writeTextFile(join(path, 'file2.txt'), ''); + createDir(dirToList); + return createSymlink(join(path, 'file1.txt'), + join(dirToList, 'link1')); + }).then((_) { + createDir(join(dirToList, 'subdir')); + return createSymlink( join(path, 'file2.txt'), join(dirToList, 'subdir', 'link2')); - }).then((_) => listDir(dirToList, recursive: true)); + }).then((_) => listDir(dirToList, recursive: true)); expect(future, completion(unorderedEquals([ join(dirToList, 'link1'), join(dirToList, 'subdir'), @@ -106,9 +112,10 @@ main() { test('works with recursive symlinks', () { expect(withTempDir((path) { - var future = writeTextFile(join(path, 'file1.txt'), '') - .then((_) => createSymlink(path, join(path, 'linkdir'))) - .then((_) => listDir(path, recursive: true)); + var future = defer(() { + writeTextFile(join(path, 'file1.txt'), ''); + return createSymlink(path, join(path, 'linkdir')); + }).then((_) => listDir(path, recursive: true)); expect(future, completion(unorderedEquals([ join(path, 'file1.txt'), join(path, 'linkdir') diff --git a/test/test_pub.dart b/test/test_pub.dart index e8c4e5cf14a363716ecd752dddb1b75f2717f1c5..3b9bc20e793df2aca43e1c859a45e99c9fb15a5f 100644 --- a/test/test_pub.dart +++ b/test/test_pub.dart @@ -604,14 +604,15 @@ void confirmPublish(ScheduledProcess pub) { /// [Future] may have a type other than [Process]. Future _doPub(Function fn, sandboxDir, List args, Future<Uri> tokenEndpoint) { String pathInSandbox(path) => join(getFullPath(sandboxDir), path); - - return Future.wait([ - ensureDir(pathInSandbox(appPath)), - _awaitObject(args), - tokenEndpoint == null ? new Future.immediate(null) : tokenEndpoint - ]).then((results) { - var args = results[1]; - var tokenEndpoint = results[2]; + return defer(() { + ensureDir(pathInSandbox(appPath)); + return Future.wait([ + _awaitObject(args), + tokenEndpoint == null ? new Future.immediate(null) : tokenEndpoint + ]); + }).then((results) { + var args = results[0]; + var tokenEndpoint = results[1]; // Find a Dart executable we can use to spawn. Use the same one that was // used to run this script itself. var dartBin = new Options().executable; @@ -819,14 +820,14 @@ abstract class Descriptor { } /// Validates that at least one file in [dir] matching [name] is valid - /// according to [validate]. [validate] should complete to an exception if - /// the input path is invalid. + /// according to [validate]. [validate] should throw or complete to an + /// exception if the input path is invalid. Future _validateOneMatch(String dir, Future validate(String path)) { // Special-case strings to support multi-level names like "myapp/packages". if (name is String) { var path = join(dir, name); - return exists(path).then((exists) { - if (!exists) { + return defer(() { + if (!entryExists(path)) { throw new ExpectException('File $name in $dir not found.'); } return validate(path); @@ -897,25 +898,23 @@ class FileDescriptor extends Descriptor { /// Creates the file within [dir]. Returns a [Future] that is completed after /// the creation is done. - Future<File> create(dir) => new Future.immediate(null).then((_) => - writeBinaryFile(join(dir, _stringName), contents)); + Future<File> create(dir) => + defer(() => writeBinaryFile(join(dir, _stringName), contents)); /// Deletes the file within [dir]. Returns a [Future] that is completed after /// the deletion is done. - Future delete(dir) { - return deleteFile(join(dir, _stringName)); - } + Future delete(dir) => + defer(() => deleteFile(join(dir, _stringName))); /// Validates that this file correctly matches the actual file at [path]. Future validate(String path) { return _validateOneMatch(path, (file) { - return readTextFile(file).then((text) { - if (text == textContents) return null; + var text = readTextFile(file); + if (text == textContents) return null; - throw new ExpectException( - 'File $file should contain:\n\n$textContents\n\n' - 'but contained:\n\n$text'); - }); + throw new ExpectException( + 'File $file should contain:\n\n$textContents\n\n' + 'but contained:\n\n$text'); }); } @@ -944,13 +943,13 @@ class DirectoryDescriptor extends Descriptor { /// Creates the file within [dir]. Returns a [Future] that is completed after /// the creation is done. Future<Directory> create(parentDir) { - // Create the directory. - return ensureDir(join(parentDir, _stringName)).then((dir) { - if (contents == null) return new Future<Directory>.immediate(dir); + return defer(() { + // Create the directory. + var dir = ensureDir(join(parentDir, _stringName)); + if (contents == null) return dir; // Recursively create all of its children. - final childFutures = - contents.map((child) => child.create(dir)).toList(); + var childFutures = contents.map((child) => child.create(dir)).toList(); // Only complete once all of the children have been created too. return Future.wait(childFutures).then((_) => dir); }); @@ -1154,8 +1153,8 @@ class NothingDescriptor extends Descriptor { Future delete(dir) => new Future.immediate(null); Future validate(String dir) { - return exists(join(dir, name)).then((exists) { - if (exists) { + return defer(() { + if (entryExists(join(dir, name))) { throw new ExpectException('File $name in $dir should not exist.'); } }); @@ -1179,12 +1178,10 @@ typedef Validator ValidatorCreator(Entrypoint entrypoint); Future<Pair<List<String>, List<String>>> schedulePackageValidation( ValidatorCreator fn) { return _scheduleValue((sandboxDir) { - var cache = new SystemCache.withSources( - join(sandboxDir, cachePath)); + var cache = new SystemCache.withSources(join(sandboxDir, cachePath)); - return Entrypoint.load(join(sandboxDir, appPath), cache) - .then((entrypoint) { - var validator = fn(entrypoint); + return defer(() { + var validator = fn(new Entrypoint(join(sandboxDir, appPath), cache)); return validator.validate().then((_) { return new Pair(validator.errors, validator.warnings); }); @@ -1529,7 +1526,7 @@ class ScheduledServer { /// Raises an error complaining of an unexpected request. void _awaitHandle(HttpRequest request, HttpResponse response) { if (_ignored.contains(new Pair(request.method, request.path))) return; - var future = timeout(new Future.immediate(null).then((_) { + var future = timeout(defer(() { if (_handlers.isEmpty) { fail('Unexpected ${request.method} request to ${request.path}.'); }