Index: utils/pub/entrypoint.dart |
diff --git a/utils/pub/entrypoint.dart b/utils/pub/entrypoint.dart |
index e33b9f91d7cc6e224a33086ffa47d6aed526e623..a8202ae6aa45263fbb79688f9361ca8f27bca944 100644 |
--- a/utils/pub/entrypoint.dart |
+++ b/utils/pub/entrypoint.dart |
@@ -5,6 +5,7 @@ |
#library('entrypoint'); |
#import('io.dart'); |
+#import('lock_file.dart'); |
#import('package.dart'); |
#import('root_source.dart'); |
#import('system_cache.dart'); |
@@ -42,19 +43,13 @@ class Entrypoint { |
final SystemCache cache; |
/** |
- * Packages which have already been loaded into memory. |
+ * Packages which are either currently being asynchronously installed to the |
+ * directory, or have already been installed. |
*/ |
- final Map<PackageId, Package> _loadedPackages; |
- |
- /** |
- * Packages which are currently being asynchronously installed to the |
- * directory. |
- */ |
- final Map<PackageId, Future<Package>> _pendingInstalls; |
+ final Map<PackageId, Future<PackageId>> _installs; |
Entrypoint(this.root, this.cache) |
- : _loadedPackages = new Map<PackageId, Package>(), |
- _pendingInstalls = new Map<PackageId, Future<Package>>(); |
+ : _installs = new Map<PackageId, Future<PackageId>>(); |
/** |
* The path to this "packages" directory. |
@@ -63,8 +58,8 @@ class Entrypoint { |
String get path() => join(root.dir, 'packages'); |
/** |
- * Ensures that the package identified by [id] is installed to the directory, |
- * loads it, and returns it. |
+ * Ensures that the package identified by [id] is installed to the directory. |
+ * Returns the resolved [PackageId]. |
* |
* If this completes successfully, the package is guaranteed to be importable |
* using the `package:` scheme. |
@@ -73,22 +68,21 @@ class Entrypoint { |
* well if it requires network access to retrieve (specifically, if |
* `id.source.shouldCache` is true). |
* |
- * See also [installTransitively]. |
+ * See also [installDependencies]. |
*/ |
- Future<Package> install(PackageId id) { |
- var package = _loadedPackages[id]; |
- if (package != null) return new Future<Package>.immediate(package); |
- |
- var pending = _pendingInstalls[id]; |
- if (pending != null) return new Future<Package>.immediate(package); |
+ Future<PackageId> install(PackageId id) { |
+ var pendingOrCompleted = _installs[id]; |
+ if (pendingOrCompleted != null) return pendingOrCompleted; |
var packageDir = join(path, id.name); |
var future = ensureDir(dirname(packageDir)).chain((_) { |
return exists(packageDir); |
}).chain((exists) { |
- // If the package already exists in the directory, no need to re-install. |
- if (exists) return new Future.immediate(null); |
- |
+ if (!exists) return new Future.immediate(null); |
+ // TODO(nweiz): figure out when to actually delete the directory, and when |
+ // we can just re-use the existing symlink. |
+ 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
|
+ }).chain((_) { |
if (id.source.shouldCache) { |
return cache.install(id).chain( |
(pkg) => createSymlink(pkg.dir, packageDir)); |
@@ -99,11 +93,9 @@ class Entrypoint { |
throw 'Package ${id.name} not found in source "${id.source.name}".'; |
}); |
} |
- }).chain((_) => Package.load(packageDir, cache.sources)); |
+ }).chain((_) => id.resolved); |
- future.then((pkg) => _loadedPackages[id] = pkg); |
- always(future, () => _pendingInstalls.remove(id)); |
- _pendingInstalls[id] = future; |
+ _installs[id] = future; |
return future; |
} |
@@ -114,12 +106,63 @@ class Entrypoint { |
* installed. |
*/ |
Future installDependencies() { |
- return resolveVersions(cache.sources, root).chain((packageVersions) { |
- // TODO(nweiz): persist packageVersions to a lockfile. |
+ return _getResolvedDependencies().chain((packageVersions) { |
return Futures.wait(packageVersions.map((id) { |
- if (id.source is RootSource) return new Future.immediate(null); |
+ if (id.source is RootSource) return new Future.immediate(id); |
return install(id); |
})); |
+ }).chain(_saveLockFile); |
+ } |
+ |
+ /** |
+ * Gets a list of all the [PackageId]s that this entrypoint transitively |
+ * depends on. The concrete versions of these ids are given by the |
+ * [VersionSolver] and the `pubspec.lock` file, if it exists. |
+ */ |
+ Future<List<PackageId>> _getResolvedDependencies() { |
+ return _loadLockFile().chain((lockFile) => |
+ resolveVersions(cache.sources, root, lockFile)); |
+ } |
+ |
+ /** |
+ * Loads the list of concrete package versions from the `pubspec.lock`, if it |
+ * exists. If it doesn't, this completes to an empty [LockFile]. |
+ * |
+ * If there's an error reading the `pubspec.lock` file, this will print a |
+ * warning message and act as though the file doesn't exist. |
+ */ |
+ Future<LockFile> _loadLockFile() { |
+ var completer = new Completer<LockFile>(); |
+ var lockFilePath = join(root.dir, 'pubspec.lock'); |
+ var future = readTextFile(lockFilePath); |
+ |
+ future.handleException((_) { |
+ completer.complete(new LockFile.empty()); |
+ |
+ // If we failed to load the lockfile but it does exist, something's |
+ // probably wrong and we should notify the user. |
+ fileExists(lockFilePath).then((exists) { |
+ if (!exists) return; |
+ printError("Error reading pubspec.lock: ${future.exception}"); |
+ }); |
+ |
+ return true; |
}); |
+ |
+ future.then((text) => |
+ completer.complete(new LockFile.parse(text, cache.sources))); |
+ return completer.future; |
+ } |
+ |
+ /** |
+ * Saves a list of concrete package versions to the `pubspec.lock` file. |
+ */ |
+ Future _saveLockFile(List<PackageId> packageIds) { |
+ var lockFile = new LockFile.empty(); |
+ for (var id in packageIds) { |
+ if (id.source is! RootSource) lockFile.packages[id.name] = id; |
+ } |
+ |
+ return writeTextFile(join(root.dir, 'pubspec.lock'), lockFile.serialize()); |
} |
} |