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 |