| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library entrypoint; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:pathos/path.dart' as path; | |
| 10 | |
| 11 import 'io.dart'; | |
| 12 import 'lock_file.dart'; | |
| 13 import 'log.dart' as log; | |
| 14 import 'package.dart'; | |
| 15 import 'pubspec.dart'; | |
| 16 import 'sdk.dart' as sdk; | |
| 17 import 'system_cache.dart'; | |
| 18 import 'utils.dart'; | |
| 19 import 'version.dart'; | |
| 20 import 'solver/version_solver.dart'; | |
| 21 | |
| 22 /// Pub operates over a directed graph of dependencies that starts at a root | |
| 23 /// "entrypoint" package. This is typically the package where the current | |
| 24 /// working directory is located. An entrypoint knows the [root] package it is | |
| 25 /// associated with and is responsible for managing the "packages" directory | |
| 26 /// for it. | |
| 27 /// | |
| 28 /// That directory contains symlinks to all packages used by an app. These links | |
| 29 /// point either to the [SystemCache] or to some other location on the local | |
| 30 /// filesystem. | |
| 31 /// | |
| 32 /// While entrypoints are typically applications, a pure library package may end | |
| 33 /// up being used as an entrypoint. Also, a single package may be used as an | |
| 34 /// entrypoint in one context but not in another. For example, a package that | |
| 35 /// contains a reusable library may not be the entrypoint when used by an app, | |
| 36 /// but may be the entrypoint when you're running its tests. | |
| 37 class Entrypoint { | |
| 38 /// The root package this entrypoint is associated with. | |
| 39 final Package root; | |
| 40 | |
| 41 /// The system-wide cache which caches packages that need to be fetched over | |
| 42 /// the network. | |
| 43 final SystemCache cache; | |
| 44 | |
| 45 /// Packages which are either currently being asynchronously installed to the | |
| 46 /// directory, or have already been installed. | |
| 47 final _installs = new Map<PackageId, Future<PackageId>>(); | |
| 48 | |
| 49 /// Loads the entrypoint from a package at [rootDir]. | |
| 50 Entrypoint(String rootDir, SystemCache cache) | |
| 51 : root = new Package.load(null, rootDir, cache.sources), | |
| 52 cache = cache; | |
| 53 | |
| 54 // TODO(rnystrom): Make this path configurable. | |
| 55 /// The path to the entrypoint's "packages" directory. | |
| 56 String get packagesDir => path.join(root.dir, 'packages'); | |
| 57 | |
| 58 /// Ensures that the package identified by [id] is installed to the directory. | |
| 59 /// Returns the resolved [PackageId]. | |
| 60 /// | |
| 61 /// If this completes successfully, the package is guaranteed to be importable | |
| 62 /// using the `package:` scheme. | |
| 63 /// | |
| 64 /// This will automatically install the package to the system-wide cache as | |
| 65 /// well if it requires network access to retrieve (specifically, if | |
| 66 /// `id.source.shouldCache` is true). | |
| 67 /// | |
| 68 /// See also [installDependencies]. | |
| 69 Future<PackageId> install(PackageId id) { | |
| 70 var pendingOrCompleted = _installs[id]; | |
| 71 if (pendingOrCompleted != null) return pendingOrCompleted; | |
| 72 | |
| 73 var packageDir = path.join(packagesDir, id.name); | |
| 74 var future = new Future.sync(() { | |
| 75 ensureDir(path.dirname(packageDir)); | |
| 76 | |
| 77 if (entryExists(packageDir)) { | |
| 78 // TODO(nweiz): figure out when to actually delete the directory, and | |
| 79 // when we can just re-use the existing symlink. | |
| 80 log.fine("Deleting package directory for ${id.name} before install."); | |
| 81 deleteEntry(packageDir); | |
| 82 } | |
| 83 | |
| 84 if (id.source.shouldCache) { | |
| 85 return cache.install(id).then( | |
| 86 (pkg) => createPackageSymlink(id.name, pkg.dir, packageDir)); | |
| 87 } else { | |
| 88 return id.source.install(id, packageDir).then((found) { | |
| 89 if (found) return null; | |
| 90 fail('Package ${id.name} not found in source "${id.source.name}".'); | |
| 91 }); | |
| 92 } | |
| 93 }).then((_) => id.resolved); | |
| 94 | |
| 95 _installs[id] = future; | |
| 96 | |
| 97 return future; | |
| 98 } | |
| 99 | |
| 100 /// Installs all dependencies of the [root] package to its "packages" | |
| 101 /// directory, respecting the [LockFile] if present. Returns a [Future] that | |
| 102 /// completes when all dependencies are installed. | |
| 103 Future installDependencies() { | |
| 104 return new Future.sync(() { | |
| 105 return resolveVersions(cache.sources, root, lockFile: loadLockFile()); | |
| 106 }).then(_installDependencies); | |
| 107 } | |
| 108 | |
| 109 /// Installs the latest available versions of all dependencies of the [root] | |
| 110 /// package to its "package" directory, writing a new [LockFile]. Returns a | |
| 111 /// [Future] that completes when all dependencies are installed. | |
| 112 Future updateAllDependencies() { | |
| 113 return resolveVersions(cache.sources, root).then(_installDependencies); | |
| 114 } | |
| 115 | |
| 116 /// Installs the latest available versions of [dependencies], while leaving | |
| 117 /// other dependencies as specified by the [LockFile] if possible. Returns a | |
| 118 /// [Future] that completes when all dependencies are installed. | |
| 119 Future updateDependencies(List<String> dependencies) { | |
| 120 return new Future.sync(() { | |
| 121 return resolveVersions(cache.sources, root, | |
| 122 lockFile: loadLockFile(), useLatest: dependencies); | |
| 123 }).then(_installDependencies); | |
| 124 } | |
| 125 | |
| 126 /// Removes the old packages directory, installs all dependencies listed in | |
| 127 /// [result], and writes a [LockFile]. | |
| 128 Future _installDependencies(SolveResult result) { | |
| 129 return new Future.sync(() { | |
| 130 if (!result.succeeded) throw result.error; | |
| 131 | |
| 132 cleanDir(packagesDir); | |
| 133 return Future.wait(result.packages.map((id) { | |
| 134 if (id.isRoot) return new Future.value(id); | |
| 135 return install(id); | |
| 136 }).toList()); | |
| 137 }).then((ids) { | |
| 138 _saveLockFile(ids); | |
| 139 _installSelfReference(); | |
| 140 _linkSecondaryPackageDirs(); | |
| 141 }); | |
| 142 } | |
| 143 | |
| 144 /// Loads the list of concrete package versions from the `pubspec.lock`, if it | |
| 145 /// exists. If it doesn't, this completes to an empty [LockFile]. | |
| 146 LockFile loadLockFile() { | |
| 147 var lockFilePath = path.join(root.dir, 'pubspec.lock'); | |
| 148 if (!entryExists(lockFilePath)) return new LockFile.empty(); | |
| 149 return new LockFile.load(lockFilePath, cache.sources); | |
| 150 } | |
| 151 | |
| 152 /// Saves a list of concrete package versions to the `pubspec.lock` file. | |
| 153 void _saveLockFile(List<PackageId> packageIds) { | |
| 154 var lockFile = new LockFile.empty(); | |
| 155 for (var id in packageIds) { | |
| 156 if (!id.isRoot) lockFile.packages[id.name] = id; | |
| 157 } | |
| 158 | |
| 159 var lockFilePath = path.join(root.dir, 'pubspec.lock'); | |
| 160 writeTextFile(lockFilePath, lockFile.serialize()); | |
| 161 } | |
| 162 | |
| 163 /// Installs a self-referential symlink in the `packages` directory that will | |
| 164 /// allow a package to import its own files using `package:`. | |
| 165 void _installSelfReference() { | |
| 166 var linkPath = path.join(packagesDir, root.name); | |
| 167 // Create the symlink if it doesn't exist. | |
| 168 if (entryExists(linkPath)) return; | |
| 169 ensureDir(packagesDir); | |
| 170 createPackageSymlink(root.name, root.dir, linkPath, | |
| 171 isSelfLink: true, relative: true); | |
| 172 } | |
| 173 | |
| 174 /// If `bin/`, `test/`, or `example/` directories exist, symlink `packages/` | |
| 175 /// into them so that their entrypoints can be run. Do the same for any | |
| 176 /// subdirectories of `test/` and `example/`. | |
| 177 void _linkSecondaryPackageDirs() { | |
| 178 var binDir = path.join(root.dir, 'bin'); | |
| 179 var exampleDir = path.join(root.dir, 'example'); | |
| 180 var testDir = path.join(root.dir, 'test'); | |
| 181 var toolDir = path.join(root.dir, 'tool'); | |
| 182 var webDir = path.join(root.dir, 'web'); | |
| 183 | |
| 184 if (dirExists(binDir)) _linkSecondaryPackageDir(binDir); | |
| 185 for (var dir in ['example', 'test', 'tool', 'web']) { | |
| 186 _linkSecondaryPackageDirsRecursively(path.join(root.dir, dir)); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 /// Creates a symlink to the `packages` directory in [dir] and all its | |
| 191 /// subdirectories. | |
| 192 void _linkSecondaryPackageDirsRecursively(String dir) { | |
| 193 if (!dirExists(dir)) return; | |
| 194 _linkSecondaryPackageDir(dir); | |
| 195 _listDirWithoutPackages(dir) | |
| 196 .where(dirExists) | |
| 197 .forEach(_linkSecondaryPackageDir); | |
| 198 } | |
| 199 | |
| 200 // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed. | |
| 201 /// Recursively lists the contents of [dir], excluding hidden `.DS_Store` | |
| 202 /// files and `package` files. | |
| 203 List<String> _listDirWithoutPackages(dir) { | |
| 204 return flatten(listDir(dir).map((file) { | |
| 205 if (path.basename(file) == 'packages') return []; | |
| 206 if (!dirExists(file)) return []; | |
| 207 var fileAndSubfiles = [file]; | |
| 208 fileAndSubfiles.addAll(_listDirWithoutPackages(file)); | |
| 209 return fileAndSubfiles; | |
| 210 })); | |
| 211 } | |
| 212 | |
| 213 /// Creates a symlink to the `packages` directory in [dir]. Will replace one | |
| 214 /// if already there. | |
| 215 void _linkSecondaryPackageDir(String dir) { | |
| 216 var symlink = path.join(dir, 'packages'); | |
| 217 if (entryExists(symlink)) deleteEntry(symlink); | |
| 218 createSymlink(packagesDir, symlink, relative: true); | |
| 219 } | |
| 220 } | |
| OLD | NEW |