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 pub.lock_file; | 5 library pub.lock_file; |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 import 'dart:collection'; |
| 8 | 8 |
| 9 import 'package:path/path.dart' as p; | 9 import 'package:path/path.dart' as p; |
| 10 import 'package:package_config/packages_file.dart' as packages_file; | 10 import 'package:package_config/packages_file.dart' as packages_file; |
| 11 import 'package:pub_semver/pub_semver.dart'; | 11 import 'package:pub_semver/pub_semver.dart'; |
| 12 import 'package:source_span/source_span.dart'; | 12 import 'package:source_span/source_span.dart'; |
| 13 import 'package:yaml/yaml.dart'; | 13 import 'package:yaml/yaml.dart'; |
| 14 | 14 |
| 15 import 'io.dart'; | 15 import 'io.dart'; |
| 16 import 'package.dart'; | 16 import 'package.dart'; |
| 17 import 'source_registry.dart'; | 17 import 'source_registry.dart'; |
| 18 import 'utils.dart'; | 18 import 'utils.dart'; |
| 19 | 19 |
| 20 /// A parsed and validated `pubspec.lock` file. | 20 /// A parsed and validated `pubspec.lock` file. |
| 21 class LockFile { | 21 class LockFile { |
| 22 /// The source registry with which the lock file's IDs are interpreted. | 22 /// The source registry with which the lock file's IDs are interpreted. |
| 23 final SourceRegistry _sources; | 23 final SourceRegistry _sources; |
| 24 | 24 |
| 25 /// The packages this lockfile pins. | 25 /// The packages this lockfile pins. |
| 26 final Map<String, PackageId> packages; | 26 final Map<String, PackageId> packages; |
| 27 | 27 |
| 28 /// The intersection of all SDK constraints for all locked packages. | |
| 29 final VersionConstraint sdkConstraint; | |
| 30 | |
| 28 /// Creates a new lockfile containing [ids]. | 31 /// Creates a new lockfile containing [ids]. |
| 29 factory LockFile(Iterable<PackageId> ids, SourceRegistry sources) { | 32 /// |
| 30 var packages = new Map.fromIterable( | 33 /// If passed, [sdkConstraint] represents the intersection of all SKD |
| 31 ids.where((id) => !id.isRoot), | 34 /// constraints for all locked packages. It defaults to |
| 32 key: (id) => id.name); | 35 /// [VersionConstraint.any]. |
| 33 return new LockFile._(packages, sources); | 36 LockFile(Iterable<PackageId> ids, SourceRegistry sources, |
| 34 } | 37 {VersionConstraint sdkConstraint}) |
| 38 : this._( | |
| 39 new Map.fromIterable( | |
| 40 ids.where((id) => !id.isRoot), | |
| 41 key: (id) => id.name), | |
| 42 sdkConstraint ?? VersionConstraint.any, | |
| 43 sources); | |
| 35 | 44 |
| 36 LockFile._(Map<String, PackageId> packages, this._sources) | 45 LockFile._(Map<String, PackageId> packages, this.sdkConstraint, this._sources) |
| 37 : packages = new UnmodifiableMapView(packages); | 46 : packages = new UnmodifiableMapView(packages); |
| 38 | 47 |
| 39 LockFile.empty(this._sources) | 48 LockFile.empty(this._sources) |
| 40 : packages = const {}; | 49 : packages = const {}, |
| 50 sdkConstraint = VersionConstraint.any; | |
| 41 | 51 |
| 42 /// Loads a lockfile from [filePath]. | 52 /// Loads a lockfile from [filePath]. |
| 43 factory LockFile.load(String filePath, SourceRegistry sources) { | 53 factory LockFile.load(String filePath, SourceRegistry sources) { |
| 44 return LockFile._parse(filePath, readTextFile(filePath), sources); | 54 return LockFile._parse(filePath, readTextFile(filePath), sources); |
| 45 } | 55 } |
| 46 | 56 |
| 47 /// Parses a lockfile whose text is [contents]. | 57 /// Parses a lockfile whose text is [contents]. |
| 48 factory LockFile.parse(String contents, SourceRegistry sources) { | 58 factory LockFile.parse(String contents, SourceRegistry sources) { |
| 49 return LockFile._parse(null, contents, sources); | 59 return LockFile._parse(null, contents, sources); |
| 50 } | 60 } |
| 51 | 61 |
| 52 /// Parses the lockfile whose text is [contents]. | 62 /// Parses the lockfile whose text is [contents]. |
| 53 /// | 63 /// |
| 54 /// [filePath] is the system-native path to the lockfile on disc. It may be | 64 /// [filePath] is the system-native path to the lockfile on disc. It may be |
| 55 /// `null`. | 65 /// `null`. |
| 56 static LockFile _parse(String filePath, String contents, | 66 static LockFile _parse(String filePath, String contents, |
| 57 SourceRegistry sources) { | 67 SourceRegistry sources) { |
| 58 var packages = {}; | |
| 59 | |
| 60 if (contents.trim() == '') return new LockFile.empty(sources); | 68 if (contents.trim() == '') return new LockFile.empty(sources); |
| 61 | 69 |
| 62 var sourceUrl; | 70 var sourceUrl; |
| 63 if (filePath != null) sourceUrl = p.toUri(filePath); | 71 if (filePath != null) sourceUrl = p.toUri(filePath); |
| 64 var parsed = loadYamlNode(contents, sourceUrl: sourceUrl); | 72 var parsed = loadYamlNode(contents, sourceUrl: sourceUrl); |
| 65 | 73 |
| 66 _validate(parsed is Map, 'The lockfile must be a YAML mapping.', parsed); | 74 _validate(parsed is Map, 'The lockfile must be a YAML mapping.', parsed); |
| 67 | 75 |
| 76 var sdkConstraint = VersionConstraint.any; | |
| 77 var sdkConstraintText = parsed['sdk']; | |
| 78 if (sdkConstraintText != null) { | |
| 79 _validate(sdkConstraintText is String, 'The "sdk" field must be a string.' , | |
|
Bob Nystrom
2015/12/21 20:55:03
Long line.
nweiz
2016/01/04 23:27:29
Done.
| |
| 80 parsed.nodes['sdk']); | |
| 81 | |
| 82 sdkConstraint = _wrapFormatException( | |
| 83 'version constraint', | |
| 84 parsed.nodes['sdk'], | |
| 85 () => new VersionConstraint.parse(sdkConstraintText)); | |
| 86 } | |
| 87 | |
| 88 var packages = {}; | |
| 68 var packageEntries = parsed['packages']; | 89 var packageEntries = parsed['packages']; |
| 69 if (packageEntries != null) { | 90 if (packageEntries != null) { |
| 70 _validate(packageEntries is Map, 'The "packages" field must be a map.', | 91 _validate(packageEntries is Map, 'The "packages" field must be a map.', |
| 71 parsed.nodes['packages']); | 92 parsed.nodes['packages']); |
| 72 | 93 |
| 73 packageEntries.forEach((name, spec) { | 94 packageEntries.forEach((name, spec) { |
| 74 // Parse the version. | 95 // Parse the version. |
| 75 _validate(spec.containsKey('version'), | 96 _validate(spec.containsKey('version'), |
| 76 'Package $name is missing a version.', spec); | 97 'Package $name is missing a version.', spec); |
| 77 var version = new Version.parse(spec['version']); | 98 var version = new Version.parse(spec['version']); |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 96 } | 117 } |
| 97 | 118 |
| 98 // Validate the name. | 119 // Validate the name. |
| 99 _validate(name == id.name, | 120 _validate(name == id.name, |
| 100 "Package name $name doesn't match ${id.name}.", spec); | 121 "Package name $name doesn't match ${id.name}.", spec); |
| 101 | 122 |
| 102 packages[name] = id; | 123 packages[name] = id; |
| 103 }); | 124 }); |
| 104 } | 125 } |
| 105 | 126 |
| 106 return new LockFile._(packages, sources); | 127 return new LockFile._(packages, sdkConstraint, sources); |
| 128 } | |
| 129 | |
| 130 /// Runs [fn] and wraps any [FormatException] it throws in a | |
| 131 /// [SourceSpanFormatException]. | |
| 132 /// | |
| 133 /// [description] should be a noun phrase that describes whatever's being | |
| 134 /// parsed or processed by [fn]. [span] should be the location of whatever's | |
| 135 /// being processed within the pubspec. | |
| 136 static _wrapFormatException(String description, SourceSpan span, fn()) { | |
| 137 try { | |
| 138 return fn(); | |
| 139 } on FormatException catch (e) { | |
| 140 throw new SourceSpanFormatException( | |
| 141 'Invalid $description: ${e.message}', span); | |
| 142 } | |
| 107 } | 143 } |
|
Bob Nystrom
2015/12/21 20:55:03
Do we do this elsewhere? Maybe move it into utils?
nweiz
2016/01/04 23:27:29
We do something similar in pubspec.dart, but it th
| |
| 108 | 144 |
| 109 /// If [condition] is `false` throws a format error with [message] for [node]. | 145 /// If [condition] is `false` throws a format error with [message] for [node]. |
| 110 static void _validate(bool condition, String message, YamlNode node) { | 146 static void _validate(bool condition, String message, YamlNode node) { |
| 111 if (condition) return; | 147 if (condition) return; |
| 112 throw new SourceSpanFormatException(message, node.span); | 148 throw new SourceSpanFormatException(message, node.span); |
| 113 } | 149 } |
| 114 | 150 |
| 115 /// Returns a copy of this LockFile with [id] added. | 151 /// Returns a copy of this LockFile with [id] added. |
| 116 /// | 152 /// |
| 117 /// If there's already an ID with the same name as [id] in the LockFile, it's | 153 /// If there's already an ID with the same name as [id] in the LockFile, it's |
| 118 /// overwritten. | 154 /// overwritten. |
| 119 LockFile setPackage(PackageId id) { | 155 LockFile setPackage(PackageId id) { |
| 120 if (id.isRoot) return this; | 156 if (id.isRoot) return this; |
| 121 | 157 |
| 122 var packages = new Map.from(this.packages); | 158 var packages = new Map.from(this.packages); |
| 123 packages[id.name] = id; | 159 packages[id.name] = id; |
| 124 return new LockFile._(packages, _sources); | 160 return new LockFile._(packages, sdkConstraint, _sources); |
| 125 } | 161 } |
| 126 | 162 |
| 127 /// Returns a copy of this LockFile with a package named [name] removed. | 163 /// Returns a copy of this LockFile with a package named [name] removed. |
| 128 /// | 164 /// |
| 129 /// Returns an identical [LockFile] if there's no package named [name]. | 165 /// Returns an identical [LockFile] if there's no package named [name]. |
| 130 LockFile removePackage(String name) { | 166 LockFile removePackage(String name) { |
| 131 if (!this.packages.containsKey(name)) return this; | 167 if (!this.packages.containsKey(name)) return this; |
| 132 | 168 |
| 133 var packages = new Map.from(this.packages); | 169 var packages = new Map.from(this.packages); |
| 134 packages.remove(name); | 170 packages.remove(name); |
| 135 return new LockFile._(packages, _sources); | 171 return new LockFile._(packages, sdkConstraint, _sources); |
| 136 } | 172 } |
| 137 | 173 |
| 138 /// Returns the contents of the `.packages` file generated from this lockfile. | 174 /// Returns the contents of the `.packages` file generated from this lockfile. |
| 139 /// | 175 /// |
| 140 /// If [entrypoint] is passed, a relative entry is added for its "lib/" | 176 /// If [entrypoint] is passed, a relative entry is added for its "lib/" |
| 141 /// directory. | 177 /// directory. |
| 142 String packagesFile([String entrypoint]) { | 178 String packagesFile([String entrypoint]) { |
| 143 var header = "Generated by pub on ${new DateTime.now()}."; | 179 var header = "Generated by pub on ${new DateTime.now()}."; |
| 144 | 180 |
| 145 var map = new Map.fromIterable(ordered(packages.keys), value: (name) { | 181 var map = new Map.fromIterable(ordered(packages.keys), value: (name) { |
| 146 var id = packages[name]; | 182 var id = packages[name]; |
| 147 var source = _sources[id.source]; | 183 var source = _sources[id.source]; |
| 148 return p.toUri(p.join(source.getDirectory(id), "lib")); | 184 return p.toUri(p.join(source.getDirectory(id), "lib")); |
| 149 }); | 185 }); |
| 150 | 186 |
| 151 if (entrypoint != null) map[entrypoint] = Uri.parse("lib/"); | 187 if (entrypoint != null) map[entrypoint] = Uri.parse("lib/"); |
| 152 | 188 |
| 153 var text = new StringBuffer(); | 189 var text = new StringBuffer(); |
| 154 packages_file.write(text, map, comment: header); | 190 packages_file.write(text, map, comment: header); |
| 155 return text.toString(); | 191 return text.toString(); |
| 156 } | 192 } |
| 157 | 193 |
| 158 /// Returns the serialized YAML text of the lock file. | 194 /// Returns the serialized YAML text of the lock file. |
| 159 /// | 195 /// |
| 160 /// [packageDir] is the containing directory of the root package, used to | 196 /// [packageDir] is the containing directory of the root package, used to |
| 161 /// properly serialize package descriptions. | 197 /// properly serialize package descriptions. |
| 162 String serialize(String packageDir) { | 198 String serialize(String packageDir) { |
| 163 // Convert the dependencies to a simple object. | 199 // Convert the dependencies to a simple object. |
| 164 var data = {}; | 200 var packageMap = {}; |
| 165 packages.forEach((name, package) { | 201 packages.forEach((name, package) { |
| 166 var description = _sources[package.source] | 202 var description = _sources[package.source] |
| 167 .serializeDescription(packageDir, package.description); | 203 .serializeDescription(packageDir, package.description); |
| 168 | 204 |
| 169 data[name] = { | 205 packageMap[name] = { |
| 170 'version': package.version.toString(), | 206 'version': package.version.toString(), |
| 171 'source': package.source, | 207 'source': package.source, |
| 172 'description': description | 208 'description': description |
| 173 }; | 209 }; |
| 174 }); | 210 }); |
| 175 | 211 |
| 212 var data = {'sdk': sdkConstraint.toString(), 'packages': packageMap}; | |
| 176 return """ | 213 return """ |
| 177 # Generated by pub | 214 # Generated by pub |
| 178 # See http://pub.dartlang.org/doc/glossary.html#lockfile | 215 # See http://pub.dartlang.org/doc/glossary.html#lockfile |
| 179 ${yamlToString({'packages': data})} | 216 ${yamlToString(data)} |
| 180 """; | 217 """; |
| 181 } | 218 } |
| 182 } | 219 } |
| OLD | NEW |