Chromium Code Reviews| OLD | NEW | 
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a | 
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 #library('entrypoint'); | 5 #library('entrypoint'); | 
| 6 | 6 | 
| 7 #import('io.dart'); | 7 #import('io.dart'); | 
| 8 #import('lock_file.dart'); | |
| 8 #import('package.dart'); | 9 #import('package.dart'); | 
| 9 #import('root_source.dart'); | 10 #import('root_source.dart'); | 
| 10 #import('system_cache.dart'); | 11 #import('system_cache.dart'); | 
| 11 #import('version.dart'); | 12 #import('version.dart'); | 
| 12 #import('version_solver.dart'); | 13 #import('version_solver.dart'); | 
| 13 #import('utils.dart'); | 14 #import('utils.dart'); | 
| 14 | 15 | 
| 15 /** | 16 /** | 
| 16 * Pub operates over a directed graph of dependencies that starts at a root | 17 * Pub operates over a directed graph of dependencies that starts at a root | 
| 17 * "entrypoint" package. This is typically the package where the current | 18 * "entrypoint" package. This is typically the package where the current | 
| (...skipping 17 matching lines...) Expand all Loading... | |
| 35 */ | 36 */ | 
| 36 final Package root; | 37 final Package root; | 
| 37 | 38 | 
| 38 /** | 39 /** | 
| 39 * The system-wide cache which caches packages that need to be fetched over | 40 * The system-wide cache which caches packages that need to be fetched over | 
| 40 * the network. | 41 * the network. | 
| 41 */ | 42 */ | 
| 42 final SystemCache cache; | 43 final SystemCache cache; | 
| 43 | 44 | 
| 44 /** | 45 /** | 
| 45 * Packages which have already been loaded into memory. | 46 * Packages which are either currently being asynchronously installed to the | 
| 47 * directory, or have already been installed. | |
| 46 */ | 48 */ | 
| 47 final Map<PackageId, Package> _loadedPackages; | 49 final Map<PackageId, Future<PackageId>> _installs; | 
| 48 | |
| 49 /** | |
| 50 * Packages which are currently being asynchronously installed to the | |
| 51 * directory. | |
| 52 */ | |
| 53 final Map<PackageId, Future<Package>> _pendingInstalls; | |
| 54 | 50 | 
| 55 Entrypoint(this.root, this.cache) | 51 Entrypoint(this.root, this.cache) | 
| 56 : _loadedPackages = new Map<PackageId, Package>(), | 52 : _installs = new Map<PackageId, Future<PackageId>>(); | 
| 57 _pendingInstalls = new Map<PackageId, Future<Package>>(); | |
| 58 | 53 | 
| 59 /** | 54 /** | 
| 60 * The path to this "packages" directory. | 55 * The path to this "packages" directory. | 
| 61 */ | 56 */ | 
| 62 // TODO(rnystrom): Make this path configurable. | 57 // TODO(rnystrom): Make this path configurable. | 
| 63 String get path() => join(root.dir, 'packages'); | 58 String get path() => join(root.dir, 'packages'); | 
| 64 | 59 | 
| 65 /** | 60 /** | 
| 66 * Ensures that the package identified by [id] is installed to the directory, | 61 * Ensures that the package identified by [id] is installed to the directory. | 
| 67 * loads it, and returns it. | 62 * Returns the resolved [PackageId]. | 
| 68 * | 63 * | 
| 69 * If this completes successfully, the package is guaranteed to be importable | 64 * If this completes successfully, the package is guaranteed to be importable | 
| 70 * using the `package:` scheme. | 65 * using the `package:` scheme. | 
| 71 * | 66 * | 
| 72 * This will automatically install the package to the system-wide cache as | 67 * This will automatically install the package to the system-wide cache as | 
| 73 * well if it requires network access to retrieve (specifically, if | 68 * well if it requires network access to retrieve (specifically, if | 
| 74 * `id.source.shouldCache` is true). | 69 * `id.source.shouldCache` is true). | 
| 75 * | 70 * | 
| 76 * See also [installTransitively]. | 71 * See also [installDependencies]. | 
| 77 */ | 72 */ | 
| 78 Future<Package> install(PackageId id) { | 73 Future<PackageId> install(PackageId id) { | 
| 79 var package = _loadedPackages[id]; | 74 var pendingOrCompleted = _installs[id]; | 
| 80 if (package != null) return new Future<Package>.immediate(package); | 75 if (pendingOrCompleted != null) return pendingOrCompleted; | 
| 81 | |
| 82 var pending = _pendingInstalls[id]; | |
| 83 if (pending != null) return new Future<Package>.immediate(package); | |
| 84 | 76 | 
| 85 var packageDir = join(path, id.name); | 77 var packageDir = join(path, id.name); | 
| 86 var future = ensureDir(dirname(packageDir)).chain((_) { | 78 var future = ensureDir(dirname(packageDir)).chain((_) { | 
| 87 return exists(packageDir); | 79 return exists(packageDir); | 
| 88 }).chain((exists) { | 80 }).chain((exists) { | 
| 89 // If the package already exists in the directory, no need to re-install. | 81 if (!exists) return new Future.immediate(null); | 
| 90 if (exists) return new Future.immediate(null); | 82 // TODO(nweiz): figure out when to actually delete the directory, and when | 
| 91 | 83 // we can just re-use the existing symlink. | 
| 84 return deleteDir(packageDir); | |
| 
 
Jennifer Messerly
2012/07/19 18:45:34
the idea here is to try and delete if if it alread
 
nweiz
2012/07/19 18:56:54
That's right. We want to get rid of any already-in
 
 | |
| 85 }).chain((_) { | |
| 92 if (id.source.shouldCache) { | 86 if (id.source.shouldCache) { | 
| 93 return cache.install(id).chain( | 87 return cache.install(id).chain( | 
| 94 (pkg) => createSymlink(pkg.dir, packageDir)); | 88 (pkg) => createSymlink(pkg.dir, packageDir)); | 
| 95 } else { | 89 } else { | 
| 96 return id.source.install(id, packageDir).transform((found) { | 90 return id.source.install(id, packageDir).transform((found) { | 
| 97 if (found) return null; | 91 if (found) return null; | 
| 98 // TODO(nweiz): More robust error-handling. | 92 // TODO(nweiz): More robust error-handling. | 
| 99 throw 'Package ${id.name} not found in source "${id.source.name}".'; | 93 throw 'Package ${id.name} not found in source "${id.source.name}".'; | 
| 100 }); | 94 }); | 
| 101 } | 95 } | 
| 102 }).chain((_) => Package.load(packageDir, cache.sources)); | 96 }).chain((_) => id.resolved); | 
| 103 | 97 | 
| 104 future.then((pkg) => _loadedPackages[id] = pkg); | 98 _installs[id] = future; | 
| 105 always(future, () => _pendingInstalls.remove(id)); | |
| 106 _pendingInstalls[id] = future; | |
| 107 | 99 | 
| 108 return future; | 100 return future; | 
| 109 } | 101 } | 
| 110 | 102 | 
| 111 /** | 103 /** | 
| 112 * Installs all dependencies of the [root] package to its "packages" | 104 * Installs all dependencies of the [root] package to its "packages" | 
| 113 * directory. Returns a [Future] that completes when all dependencies are | 105 * directory. Returns a [Future] that completes when all dependencies are | 
| 114 * installed. | 106 * installed. | 
| 115 */ | 107 */ | 
| 116 Future installDependencies() { | 108 Future installDependencies() { | 
| 117 return resolveVersions(cache.sources, root).chain((packageVersions) { | 109 return _getResolvedDependencies().chain((packageVersions) { | 
| 118 // TODO(nweiz): persist packageVersions to a lockfile. | |
| 119 return Futures.wait(packageVersions.map((id) { | 110 return Futures.wait(packageVersions.map((id) { | 
| 120 if (id.source is RootSource) return new Future.immediate(null); | 111 if (id.source is RootSource) return new Future.immediate(id); | 
| 121 return install(id); | 112 return install(id); | 
| 122 })); | 113 })); | 
| 114 }).chain(_saveLockFile); | |
| 115 } | |
| 116 | |
| 117 /** | |
| 118 * Gets a list of all the [PackageId]s that this entrypoint transitively | |
| 119 * depends on. The concrete versions of these ids are given by the | |
| 120 * [VersionSolver] and the `pubspec.lock` file, if it exists. | |
| 121 */ | |
| 122 Future<List<PackageId>> _getResolvedDependencies() { | |
| 123 return _loadLockFile().chain((lockFile) => | |
| 124 resolveVersions(cache.sources, root, lockFile)); | |
| 125 } | |
| 126 | |
| 127 /** | |
| 128 * Loads the list of concrete package versions from the `pubspec.lock`, if it | |
| 129 * exists. If it doesn't, this completes to an empty [LockFile]. | |
| 130 * | |
| 131 * If there's an error reading the `pubspec.lock` file, this will print a | |
| 132 * warning message and act as though the file doesn't exist. | |
| 133 */ | |
| 134 Future<LockFile> _loadLockFile() { | |
| 135 var completer = new Completer<LockFile>(); | |
| 136 var lockFilePath = join(root.dir, 'pubspec.lock'); | |
| 137 var future = readTextFile(lockFilePath); | |
| 138 | |
| 139 future.handleException((_) { | |
| 140 completer.complete(new LockFile.empty()); | |
| 141 | |
| 142 // If we failed to load the lockfile but it does exist, something's | |
| 143 // probably wrong and we should notify the user. | |
| 144 fileExists(lockFilePath).then((exists) { | |
| 145 if (!exists) return; | |
| 146 printError("Error reading pubspec.lock: ${future.exception}"); | |
| 147 }); | |
| 148 | |
| 149 return true; | |
| 123 }); | 150 }); | 
| 151 | |
| 152 future.then((text) => | |
| 153 completer.complete(new LockFile.parse(text, cache.sources))); | |
| 154 return completer.future; | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Saves a list of concrete package versions to the `pubspec.lock` file. | |
| 159 */ | |
| 160 Future _saveLockFile(List<PackageId> packageIds) { | |
| 161 var lockFile = new LockFile.empty(); | |
| 162 for (var id in packageIds) { | |
| 163 if (id.source is! RootSource) lockFile.packages[id.name] = id; | |
| 164 } | |
| 165 | |
| 166 return writeTextFile(join(root.dir, 'pubspec.lock'), lockFile.serialize()); | |
| 124 } | 167 } | 
| 125 } | 168 } | 
| OLD | NEW |