| 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 source; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:pathos/path.dart' as path; | |
| 10 | |
| 11 import 'io.dart'; | |
| 12 import 'package.dart'; | |
| 13 import 'pubspec.dart'; | |
| 14 import 'system_cache.dart'; | |
| 15 import 'utils.dart'; | |
| 16 import 'version.dart'; | |
| 17 | |
| 18 /// A source from which to install packages. | |
| 19 /// | |
| 20 /// Each source has many packages that it looks up using [PackageId]s. The | |
| 21 /// source is responsible for installing these packages to the package cache. | |
| 22 abstract class Source { | |
| 23 /// The name of the source. Should be lower-case, suitable for use in a | |
| 24 /// filename, and unique accross all sources. | |
| 25 String get name; | |
| 26 | |
| 27 /// Whether or not this source is the default source. | |
| 28 bool get isDefault => systemCache.sources.defaultSource == this; | |
| 29 | |
| 30 /// Whether this source's packages should be cached in Pub's global cache | |
| 31 /// directory. | |
| 32 /// | |
| 33 /// A source should be cached if it requires network access to retrieve | |
| 34 /// packages. It doesn't need to be cached if all packages are available | |
| 35 /// locally. | |
| 36 bool get shouldCache; | |
| 37 | |
| 38 /// The system cache with which this source is registered. | |
| 39 SystemCache get systemCache { | |
| 40 assert(_systemCache != null); | |
| 41 return _systemCache; | |
| 42 } | |
| 43 | |
| 44 /// The system cache variable. Set by [_bind]. | |
| 45 SystemCache _systemCache; | |
| 46 | |
| 47 /// The root directory of this source's cache within the system cache. | |
| 48 /// | |
| 49 /// This shouldn't be overridden by subclasses. | |
| 50 String get systemCacheRoot => path.join(systemCache.rootDir, name); | |
| 51 | |
| 52 /// Records the system cache to which this source belongs. | |
| 53 /// | |
| 54 /// This should only be called once for each source, by | |
| 55 /// [SystemCache.register]. It should not be overridden by base classes. | |
| 56 void bind(SystemCache systemCache) { | |
| 57 assert(_systemCache == null); | |
| 58 this._systemCache = systemCache; | |
| 59 } | |
| 60 | |
| 61 /// Get the list of all versions that exist for the package described by | |
| 62 /// [description]. [name] is the expected name of the package. | |
| 63 /// | |
| 64 /// Note that this does *not* require the packages to be installed, which is | |
| 65 /// the point. This is used during version resolution to determine which | |
| 66 /// package versions are available to be installed (or already installed). | |
| 67 /// | |
| 68 /// By default, this assumes that each description has a single version and | |
| 69 /// uses [describe] to get that version. | |
| 70 Future<List<Version>> getVersions(String name, description) { | |
| 71 return describe(new PackageId(name, this, Version.none, description)) | |
| 72 .then((pubspec) => [pubspec.version]); | |
| 73 } | |
| 74 | |
| 75 /// Loads the (possibly remote) pubspec for the package version identified by | |
| 76 /// [id]. This may be called for packages that have not yet been installed | |
| 77 /// during the version resolution process. | |
| 78 /// | |
| 79 /// For cached sources, by default this uses [installToSystemCache] to get the | |
| 80 /// pubspec. There is no default implementation for non-cached sources; they | |
| 81 /// must implement it manually. | |
| 82 Future<Pubspec> describe(PackageId id) { | |
| 83 if (!shouldCache) { | |
| 84 throw new UnimplementedError("Source $name must implement describe(id)."); | |
| 85 } | |
| 86 return installToSystemCache(id).then((package) => package.pubspec); | |
| 87 } | |
| 88 | |
| 89 /// Installs the package identified by [id] to [path]. Returns a [Future] that | |
| 90 /// completes when the installation was finished. The [Future] should resolve | |
| 91 /// to true if the package was found in the source and false if it wasn't. For | |
| 92 /// all other error conditions, it should complete with an exception. | |
| 93 /// | |
| 94 /// [path] is guaranteed not to exist, and its parent directory is guaranteed | |
| 95 /// to exist. | |
| 96 /// | |
| 97 /// Note that [path] may be deleted. If re-installing a package that has | |
| 98 /// already been installed would be costly or impossible, | |
| 99 /// [installToSystemCache] should be implemented instead of [install]. | |
| 100 /// | |
| 101 /// This doesn't need to be implemented if [installToSystemCache] is | |
| 102 /// implemented. | |
| 103 Future<bool> install(PackageId id, String path) { | |
| 104 throw new UnimplementedError("Either install or installToSystemCache must " | |
| 105 "be implemented for source $name."); | |
| 106 } | |
| 107 | |
| 108 /// Installs the package identified by [id] to the system cache. This is only | |
| 109 /// called for sources with [shouldCache] set to true. | |
| 110 /// | |
| 111 /// By default, this uses [systemCacheDirectory] and [install]. | |
| 112 Future<Package> installToSystemCache(PackageId id) { | |
| 113 var packageDir; | |
| 114 return systemCacheDirectory(id).then((p) { | |
| 115 packageDir = p; | |
| 116 | |
| 117 // See if it's already cached. | |
| 118 if (dirExists(packageDir)) { | |
| 119 if (!_isCachedPackageCorrupted(packageDir)) return true; | |
| 120 // Busted, so wipe out the package and reinstall. | |
| 121 deleteEntry(packageDir); | |
| 122 } | |
| 123 | |
| 124 ensureDir(path.dirname(packageDir)); | |
| 125 return install(id, packageDir); | |
| 126 }).then((found) { | |
| 127 if (!found) fail('Package $id not found.'); | |
| 128 return new Package.load(id.name, packageDir, systemCache.sources); | |
| 129 }); | |
| 130 } | |
| 131 | |
| 132 /// Since pub generates symlinks that point into the system cache (in | |
| 133 /// particular, targeting the "lib" directories of cached packages), it's | |
| 134 /// possible to accidentally break cached packages if something traverses | |
| 135 /// that symlink. | |
| 136 /// | |
| 137 /// This tries to determine if the cached package at [packageDir] has been | |
| 138 /// corrupted. The heuristics are it is corrupted if any of the following are | |
| 139 /// true: | |
| 140 /// | |
| 141 /// * It has an empty "lib" directory. | |
| 142 /// * It has no pubspec. | |
| 143 bool _isCachedPackageCorrupted(String packageDir) { | |
| 144 if (!fileExists(path.join(packageDir, "pubspec.yaml"))) return true; | |
| 145 | |
| 146 var libDir = path.join(packageDir, "lib"); | |
| 147 if (dirExists(libDir)) return listDir(libDir).length == 0; | |
| 148 | |
| 149 // If we got here, it's OK. | |
| 150 return false; | |
| 151 } | |
| 152 | |
| 153 /// Returns the directory in the system cache that the package identified by | |
| 154 /// [id] should be installed to. This should return a path to a subdirectory | |
| 155 /// of [systemCacheRoot]. | |
| 156 /// | |
| 157 /// This doesn't need to be implemented if [shouldCache] is false. | |
| 158 Future<String> systemCacheDirectory(PackageId id) { | |
| 159 return new Future.error( | |
| 160 "systemCacheDirectory() must be implemented if shouldCache is true."); | |
| 161 } | |
| 162 | |
| 163 /// When a [Pubspec] or [LockFile] is parsed, it reads in the description for | |
| 164 /// each dependency. It is up to the dependency's [Source] to determine how | |
| 165 /// that should be interpreted. This will be called during parsing to validate | |
| 166 /// that the given [description] is well-formed according to this source, and | |
| 167 /// to give the source a chance to canonicalize the description. | |
| 168 /// | |
| 169 /// [containingPath] is the path to the local file (pubspec or lockfile) | |
| 170 /// where this description appears. It may be `null` if the description is | |
| 171 /// coming from some in-memory source (such as pulling down a pubspec from | |
| 172 /// pub.dartlang.org). | |
| 173 /// | |
| 174 /// It should return if a (possibly modified) valid description, or throw a | |
| 175 /// [FormatException] if not valid. | |
| 176 /// | |
| 177 /// [fromLockFile] is true when the description comes from a [LockFile], to | |
| 178 /// allow the source to use lockfile-specific descriptions via [resolveId]. | |
| 179 dynamic parseDescription(String containingPath, description, | |
| 180 {bool fromLockFile: false}) { | |
| 181 return description; | |
| 182 } | |
| 183 | |
| 184 /// Returns whether or not [description1] describes the same package as | |
| 185 /// [description2] for this source. This method should be light-weight. It | |
| 186 /// doesn't need to validate that either package exists. | |
| 187 /// | |
| 188 /// By default, just uses regular equality. | |
| 189 bool descriptionsEqual(description1, description2) => | |
| 190 description1 == description2; | |
| 191 | |
| 192 /// For some sources, [PackageId]s can point to different chunks of code at | |
| 193 /// different times. This takes such an [id] and returns a future that | |
| 194 /// completes to a [PackageId] that will uniquely specify a single chunk of | |
| 195 /// code forever. | |
| 196 /// | |
| 197 /// For example, [GitSource] might take an [id] with description | |
| 198 /// `http://github.com/dart-lang/some-lib.git` and return an id with a | |
| 199 /// description that includes the current commit of the Git repository. | |
| 200 /// | |
| 201 /// This will be called after the package identified by [id] is installed, so | |
| 202 /// the source can use the installed package to determine information about | |
| 203 /// the resolved id. | |
| 204 /// | |
| 205 /// The returned [PackageId] may have a description field that's invalid | |
| 206 /// according to [parseDescription], although it must still be serializable | |
| 207 /// to JSON and YAML. It must also be equal to [id] according to | |
| 208 /// [descriptionsEqual]. | |
| 209 /// | |
| 210 /// By default, this just returns [id]. | |
| 211 Future<PackageId> resolveId(PackageId id) => new Future.value(id); | |
| 212 | |
| 213 /// Returns the [Package]s that have been installed in the system cache. | |
| 214 List<Package> getCachedPackages() { | |
| 215 if (shouldCache) { | |
| 216 throw new UnimplementedError("Source $name must implement this."); | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 /// Returns the source's name. | |
| 221 String toString() => name; | |
| 222 } | |
| OLD | NEW |