| OLD | NEW |
| 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 |
| 1 library pub.pubspec; | 5 library pub.pubspec; |
| 6 |
| 2 import 'package:path/path.dart' as path; | 7 import 'package:path/path.dart' as path; |
| 3 import 'package:pub_semver/pub_semver.dart'; | 8 import 'package:pub_semver/pub_semver.dart'; |
| 4 import 'package:source_span/source_span.dart'; | 9 import 'package:source_span/source_span.dart'; |
| 5 import 'package:yaml/yaml.dart'; | 10 import 'package:yaml/yaml.dart'; |
| 11 |
| 6 import 'barback/transformer_config.dart'; | 12 import 'barback/transformer_config.dart'; |
| 7 import 'exceptions.dart'; | 13 import 'exceptions.dart'; |
| 8 import 'io.dart'; | 14 import 'io.dart'; |
| 9 import 'package.dart'; | 15 import 'package.dart'; |
| 10 import 'source_registry.dart'; | 16 import 'source_registry.dart'; |
| 11 import 'utils.dart'; | 17 import 'utils.dart'; |
| 18 |
| 19 /// The parsed contents of a pubspec file. |
| 20 /// |
| 21 /// The fields of a pubspec are, for the most part, validated when they're first |
| 22 /// accessed. This allows a partially-invalid pubspec to be used if only the |
| 23 /// valid portions are relevant. To get a list of all errors in the pubspec, use |
| 24 /// [allErrors]. |
| 12 class Pubspec { | 25 class Pubspec { |
| 26 // If a new lazily-initialized field is added to this class and the |
| 27 // initialization can throw a [PubspecException], that error should also be |
| 28 // exposed through [allErrors]. |
| 29 |
| 30 /// The registry of sources to use when parsing [dependencies] and |
| 31 /// [devDependencies]. |
| 32 /// |
| 33 /// This will be null if this was created using [new Pubspec] or [new |
| 34 /// Pubspec.empty]. |
| 13 final SourceRegistry _sources; | 35 final SourceRegistry _sources; |
| 36 |
| 37 /// The location from which the pubspec was loaded. |
| 38 /// |
| 39 /// This can be null if the pubspec was created in-memory or if its location |
| 40 /// is unknown. |
| 14 Uri get _location => fields.span.sourceUrl; | 41 Uri get _location => fields.span.sourceUrl; |
| 42 |
| 43 /// All pubspec fields. |
| 44 /// |
| 45 /// This includes the fields from which other properties are derived. |
| 15 final YamlMap fields; | 46 final YamlMap fields; |
| 47 |
| 48 /// The package's name. |
| 16 String get name { | 49 String get name { |
| 17 if (_name != null) return _name; | 50 if (_name != null) return _name; |
| 51 |
| 18 var name = fields['name']; | 52 var name = fields['name']; |
| 19 if (name == null) { | 53 if (name == null) { |
| 20 throw new PubspecException( | 54 throw new PubspecException( |
| 21 'Missing the required "name" field.', | 55 'Missing the required "name" field.', |
| 22 fields.span); | 56 fields.span); |
| 23 } else if (name is! String) { | 57 } else if (name is! String) { |
| 24 throw new PubspecException( | 58 throw new PubspecException( |
| 25 '"name" field must be a string.', | 59 '"name" field must be a string.', |
| 26 fields.nodes['name'].span); | 60 fields.nodes['name'].span); |
| 27 } | 61 } |
| 62 |
| 28 _name = name; | 63 _name = name; |
| 29 return _name; | 64 return _name; |
| 30 } | 65 } |
| 31 String _name; | 66 String _name; |
| 67 |
| 68 /// The package's version. |
| 32 Version get version { | 69 Version get version { |
| 33 if (_version != null) return _version; | 70 if (_version != null) return _version; |
| 71 |
| 34 var version = fields['version']; | 72 var version = fields['version']; |
| 35 if (version == null) { | 73 if (version == null) { |
| 36 _version = Version.none; | 74 _version = Version.none; |
| 37 return _version; | 75 return _version; |
| 38 } | 76 } |
| 77 |
| 39 var span = fields.nodes['version'].span; | 78 var span = fields.nodes['version'].span; |
| 40 if (version is num) { | 79 if (version is num) { |
| 41 var fixed = '$version.0'; | 80 var fixed = '$version.0'; |
| 42 if (version is int) { | 81 if (version is int) { |
| 43 fixed = '$fixed.0'; | 82 fixed = '$fixed.0'; |
| 44 } | 83 } |
| 45 _error('"version" field must have three numeric components: major, ' | 84 _error( |
| 46 'minor, and patch. Instead of "$version", consider "$fixed".', span); | 85 '"version" field must have three numeric components: major, ' |
| 86 'minor, and patch. Instead of "$version", consider "$fixed".', |
| 87 span); |
| 47 } | 88 } |
| 48 if (version is! String) { | 89 if (version is! String) { |
| 49 _error('"version" field must be a string.', span); | 90 _error('"version" field must be a string.', span); |
| 50 } | 91 } |
| 92 |
| 51 _version = | 93 _version = |
| 52 _wrapFormatException('version number', span, () => new Version.parse(ver
sion)); | 94 _wrapFormatException('version number', span, () => new Version.parse(ver
sion)); |
| 53 return _version; | 95 return _version; |
| 54 } | 96 } |
| 55 Version _version; | 97 Version _version; |
| 98 |
| 99 /// The additional packages this package depends on. |
| 56 List<PackageDep> get dependencies { | 100 List<PackageDep> get dependencies { |
| 57 if (_dependencies != null) return _dependencies; | 101 if (_dependencies != null) return _dependencies; |
| 58 _dependencies = _parseDependencies('dependencies'); | 102 _dependencies = _parseDependencies('dependencies'); |
| 59 if (_devDependencies == null) { | 103 if (_devDependencies == null) { |
| 60 _checkDependencyOverlap(_dependencies, devDependencies); | 104 _checkDependencyOverlap(_dependencies, devDependencies); |
| 61 } | 105 } |
| 62 return _dependencies; | 106 return _dependencies; |
| 63 } | 107 } |
| 64 List<PackageDep> _dependencies; | 108 List<PackageDep> _dependencies; |
| 109 |
| 110 /// The packages this package depends on when it is the root package. |
| 65 List<PackageDep> get devDependencies { | 111 List<PackageDep> get devDependencies { |
| 66 if (_devDependencies != null) return _devDependencies; | 112 if (_devDependencies != null) return _devDependencies; |
| 67 _devDependencies = _parseDependencies('dev_dependencies'); | 113 _devDependencies = _parseDependencies('dev_dependencies'); |
| 68 if (_dependencies == null) { | 114 if (_dependencies == null) { |
| 69 _checkDependencyOverlap(dependencies, _devDependencies); | 115 _checkDependencyOverlap(dependencies, _devDependencies); |
| 70 } | 116 } |
| 71 return _devDependencies; | 117 return _devDependencies; |
| 72 } | 118 } |
| 73 List<PackageDep> _devDependencies; | 119 List<PackageDep> _devDependencies; |
| 120 |
| 121 /// The dependency constraints that this package overrides when it is the |
| 122 /// root package. |
| 123 /// |
| 124 /// Dependencies here will replace any dependency on a package with the same |
| 125 /// name anywhere in the dependency graph. |
| 74 List<PackageDep> get dependencyOverrides { | 126 List<PackageDep> get dependencyOverrides { |
| 75 if (_dependencyOverrides != null) return _dependencyOverrides; | 127 if (_dependencyOverrides != null) return _dependencyOverrides; |
| 76 _dependencyOverrides = _parseDependencies('dependency_overrides'); | 128 _dependencyOverrides = _parseDependencies('dependency_overrides'); |
| 77 return _dependencyOverrides; | 129 return _dependencyOverrides; |
| 78 } | 130 } |
| 79 List<PackageDep> _dependencyOverrides; | 131 List<PackageDep> _dependencyOverrides; |
| 132 |
| 133 /// The configurations of the transformers to use for this package. |
| 80 List<Set<TransformerConfig>> get transformers { | 134 List<Set<TransformerConfig>> get transformers { |
| 81 if (_transformers != null) return _transformers; | 135 if (_transformers != null) return _transformers; |
| 136 |
| 82 var transformers = fields['transformers']; | 137 var transformers = fields['transformers']; |
| 83 if (transformers == null) { | 138 if (transformers == null) { |
| 84 _transformers = []; | 139 _transformers = []; |
| 85 return _transformers; | 140 return _transformers; |
| 86 } | 141 } |
| 142 |
| 87 if (transformers is! List) { | 143 if (transformers is! List) { |
| 88 _error( | 144 _error( |
| 89 '"transformers" field must be a list.', | 145 '"transformers" field must be a list.', |
| 90 fields.nodes['transformers'].span); | 146 fields.nodes['transformers'].span); |
| 91 } | 147 } |
| 148 |
| 92 var i = 0; | 149 var i = 0; |
| 93 _transformers = transformers.nodes.map((phase) { | 150 _transformers = transformers.nodes.map((phase) { |
| 94 var phaseNodes = phase is YamlList ? phase.nodes : [phase]; | 151 var phaseNodes = phase is YamlList ? phase.nodes : [phase]; |
| 95 return phaseNodes.map((transformerNode) { | 152 return phaseNodes.map((transformerNode) { |
| 96 var transformer = transformerNode.value; | 153 var transformer = transformerNode.value; |
| 97 if (transformer is! String && transformer is! Map) { | 154 if (transformer is! String && transformer is! Map) { |
| 98 _error( | 155 _error( |
| 99 'A transformer must be a string or map.', | 156 'A transformer must be a string or map.', |
| 100 transformerNode.span); | 157 transformerNode.span); |
| 101 } | 158 } |
| 159 |
| 102 var libraryNode; | 160 var libraryNode; |
| 103 var configurationNode; | 161 var configurationNode; |
| 104 if (transformer is String) { | 162 if (transformer is String) { |
| 105 libraryNode = transformerNode; | 163 libraryNode = transformerNode; |
| 106 } else { | 164 } else { |
| 107 if (transformer.length != 1) { | 165 if (transformer.length != 1) { |
| 108 _error( | 166 _error( |
| 109 'A transformer map must have a single key: the transformer ' 'id
entifier.', | 167 'A transformer map must have a single key: the transformer ' 'id
entifier.', |
| 110 transformerNode.span); | 168 transformerNode.span); |
| 111 } else if (transformer.keys.single is! String) { | 169 } else if (transformer.keys.single is! String) { |
| 112 _error( | 170 _error( |
| 113 'A transformer identifier must be a string.', | 171 'A transformer identifier must be a string.', |
| 114 transformer.nodes.keys.single.span); | 172 transformer.nodes.keys.single.span); |
| 115 } | 173 } |
| 174 |
| 116 libraryNode = transformer.nodes.keys.single; | 175 libraryNode = transformer.nodes.keys.single; |
| 117 configurationNode = transformer.nodes.values.single; | 176 configurationNode = transformer.nodes.values.single; |
| 118 if (configurationNode is! YamlMap) { | 177 if (configurationNode is! YamlMap) { |
| 119 _error( | 178 _error( |
| 120 "A transformer's configuration must be a map.", | 179 "A transformer's configuration must be a map.", |
| 121 configurationNode.span); | 180 configurationNode.span); |
| 122 } | 181 } |
| 123 } | 182 } |
| 183 |
| 124 var config = _wrapSpanFormatException('transformer config', () { | 184 var config = _wrapSpanFormatException('transformer config', () { |
| 125 return new TransformerConfig.parse( | 185 return new TransformerConfig.parse( |
| 126 libraryNode.value, | 186 libraryNode.value, |
| 127 libraryNode.span, | 187 libraryNode.span, |
| 128 configurationNode); | 188 configurationNode); |
| 129 }); | 189 }); |
| 190 |
| 130 var package = config.id.package; | 191 var package = config.id.package; |
| 131 if (package != name && | 192 if (package != name && |
| 132 !config.id.isBuiltInTransformer && | 193 !config.id.isBuiltInTransformer && |
| 133 !dependencies.any((ref) => ref.name == package) && | 194 !dependencies.any((ref) => ref.name == package) && |
| 134 !devDependencies.any((ref) => ref.name == package) && | 195 !devDependencies.any((ref) => ref.name == package) && |
| 135 !dependencyOverrides.any((ref) => ref.name == package)) { | 196 !dependencyOverrides.any((ref) => ref.name == package)) { |
| 136 _error('"$package" is not a dependency.', libraryNode.span); | 197 _error('"$package" is not a dependency.', libraryNode.span); |
| 137 } | 198 } |
| 199 |
| 138 return config; | 200 return config; |
| 139 }).toSet(); | 201 }).toSet(); |
| 140 }).toList(); | 202 }).toList(); |
| 203 |
| 141 return _transformers; | 204 return _transformers; |
| 142 } | 205 } |
| 143 List<Set<TransformerConfig>> _transformers; | 206 List<Set<TransformerConfig>> _transformers; |
| 207 |
| 208 /// The environment-related metadata. |
| 144 PubspecEnvironment get environment { | 209 PubspecEnvironment get environment { |
| 145 if (_environment != null) return _environment; | 210 if (_environment != null) return _environment; |
| 211 |
| 146 var yaml = fields['environment']; | 212 var yaml = fields['environment']; |
| 147 if (yaml == null) { | 213 if (yaml == null) { |
| 148 _environment = new PubspecEnvironment(VersionConstraint.any); | 214 _environment = new PubspecEnvironment(VersionConstraint.any); |
| 149 return _environment; | 215 return _environment; |
| 150 } | 216 } |
| 217 |
| 151 if (yaml is! Map) { | 218 if (yaml is! Map) { |
| 152 _error( | 219 _error( |
| 153 '"environment" field must be a map.', | 220 '"environment" field must be a map.', |
| 154 fields.nodes['environment'].span); | 221 fields.nodes['environment'].span); |
| 155 } | 222 } |
| 223 |
| 156 _environment = | 224 _environment = |
| 157 new PubspecEnvironment(_parseVersionConstraint(yaml.nodes['sdk'])); | 225 new PubspecEnvironment(_parseVersionConstraint(yaml.nodes['sdk'])); |
| 158 return _environment; | 226 return _environment; |
| 159 } | 227 } |
| 160 PubspecEnvironment _environment; | 228 PubspecEnvironment _environment; |
| 229 |
| 230 /// The URL of the server that the package should default to being published |
| 231 /// to, "none" if the package should not be published, or `null` if it should |
| 232 /// be published to the default server. |
| 233 /// |
| 234 /// If this does return a URL string, it will be a valid parseable URL. |
| 161 String get publishTo { | 235 String get publishTo { |
| 162 if (_parsedPublishTo) return _publishTo; | 236 if (_parsedPublishTo) return _publishTo; |
| 237 |
| 163 var publishTo = fields['publish_to']; | 238 var publishTo = fields['publish_to']; |
| 164 if (publishTo != null) { | 239 if (publishTo != null) { |
| 165 var span = fields.nodes['publish_to'].span; | 240 var span = fields.nodes['publish_to'].span; |
| 241 |
| 166 if (publishTo is! String) { | 242 if (publishTo is! String) { |
| 167 _error('"publish_to" field must be a string.', span); | 243 _error('"publish_to" field must be a string.', span); |
| 168 } | 244 } |
| 245 |
| 246 // It must be "none" or a valid URL. |
| 169 if (publishTo != "none") { | 247 if (publishTo != "none") { |
| 170 _wrapFormatException( | 248 _wrapFormatException( |
| 171 '"publish_to" field', | 249 '"publish_to" field', |
| 172 span, | 250 span, |
| 173 () => Uri.parse(publishTo)); | 251 () => Uri.parse(publishTo)); |
| 174 } | 252 } |
| 175 } | 253 } |
| 254 |
| 176 _parsedPublishTo = true; | 255 _parsedPublishTo = true; |
| 177 _publishTo = publishTo; | 256 _publishTo = publishTo; |
| 178 return _publishTo; | 257 return _publishTo; |
| 179 } | 258 } |
| 180 bool _parsedPublishTo = false; | 259 bool _parsedPublishTo = false; |
| 181 String _publishTo; | 260 String _publishTo; |
| 261 |
| 262 /// The executables that should be placed on the user's PATH when this |
| 263 /// package is globally activated. |
| 264 /// |
| 265 /// It is a map of strings to string. Each key is the name of the command |
| 266 /// that will be placed on the user's PATH. The value is the name of the |
| 267 /// .dart script (without extension) in the package's `bin` directory that |
| 268 /// should be run for that command. Both key and value must be "simple" |
| 269 /// strings: alphanumerics, underscores and hypens only. If a value is |
| 270 /// omitted, it is inferred to use the same name as the key. |
| 182 Map<String, String> get executables { | 271 Map<String, String> get executables { |
| 183 if (_executables != null) return _executables; | 272 if (_executables != null) return _executables; |
| 273 |
| 184 _executables = {}; | 274 _executables = {}; |
| 185 var yaml = fields['executables']; | 275 var yaml = fields['executables']; |
| 186 if (yaml == null) return _executables; | 276 if (yaml == null) return _executables; |
| 277 |
| 187 if (yaml is! Map) { | 278 if (yaml is! Map) { |
| 188 _error( | 279 _error( |
| 189 '"executables" field must be a map.', | 280 '"executables" field must be a map.', |
| 190 fields.nodes['executables'].span); | 281 fields.nodes['executables'].span); |
| 191 } | 282 } |
| 283 |
| 192 yaml.nodes.forEach((key, value) { | 284 yaml.nodes.forEach((key, value) { |
| 193 validateName(name, description) {} | 285 // Don't allow path separators or other stuff meaningful to the shell. |
| 286 validateName(name, description) { |
| 287 } |
| 288 |
| 194 if (key.value is! String) { | 289 if (key.value is! String) { |
| 195 _error('"executables" keys must be strings.', key.span); | 290 _error('"executables" keys must be strings.', key.span); |
| 196 } | 291 } |
| 292 |
| 197 final keyPattern = new RegExp(r"^[a-zA-Z0-9_-]+$"); | 293 final keyPattern = new RegExp(r"^[a-zA-Z0-9_-]+$"); |
| 198 if (!keyPattern.hasMatch(key.value)) { | 294 if (!keyPattern.hasMatch(key.value)) { |
| 199 _error( | 295 _error( |
| 200 '"executables" keys may only contain letters, ' | 296 '"executables" keys may only contain letters, ' |
| 201 'numbers, hyphens and underscores.', | 297 'numbers, hyphens and underscores.', |
| 202 key.span); | 298 key.span); |
| 203 } | 299 } |
| 300 |
| 204 if (value.value == null) { | 301 if (value.value == null) { |
| 205 value = key; | 302 value = key; |
| 206 } else if (value.value is! String) { | 303 } else if (value.value is! String) { |
| 207 _error('"executables" values must be strings or null.', value.span); | 304 _error('"executables" values must be strings or null.', value.span); |
| 208 } | 305 } |
| 306 |
| 209 final valuePattern = new RegExp(r"[/\\]"); | 307 final valuePattern = new RegExp(r"[/\\]"); |
| 210 if (valuePattern.hasMatch(value.value)) { | 308 if (valuePattern.hasMatch(value.value)) { |
| 211 _error( | 309 _error( |
| 212 '"executables" values may not contain path separators.', | 310 '"executables" values may not contain path separators.', |
| 213 value.span); | 311 value.span); |
| 214 } | 312 } |
| 313 |
| 215 _executables[key.value] = value.value; | 314 _executables[key.value] = value.value; |
| 216 }); | 315 }); |
| 316 |
| 217 return _executables; | 317 return _executables; |
| 218 } | 318 } |
| 219 Map<String, String> _executables; | 319 Map<String, String> _executables; |
| 320 |
| 321 /// Whether the package is private and cannot be published. |
| 322 /// |
| 323 /// This is specified in the pubspec by setting "publish_to" to "none". |
| 220 bool get isPrivate => publishTo == "none"; | 324 bool get isPrivate => publishTo == "none"; |
| 325 |
| 326 /// Whether or not the pubspec has no contents. |
| 221 bool get isEmpty => | 327 bool get isEmpty => |
| 222 name == null && version == Version.none && dependencies.isEmpty; | 328 name == null && version == Version.none && dependencies.isEmpty; |
| 329 |
| 330 /// Loads the pubspec for a package located in [packageDir]. |
| 331 /// |
| 332 /// If [expectedName] is passed and the pubspec doesn't have a matching name |
| 333 /// field, this will throw a [PubspecError]. |
| 223 factory Pubspec.load(String packageDir, SourceRegistry sources, | 334 factory Pubspec.load(String packageDir, SourceRegistry sources, |
| 224 {String expectedName}) { | 335 {String expectedName}) { |
| 225 var pubspecPath = path.join(packageDir, 'pubspec.yaml'); | 336 var pubspecPath = path.join(packageDir, 'pubspec.yaml'); |
| 226 var pubspecUri = path.toUri(pubspecPath); | 337 var pubspecUri = path.toUri(pubspecPath); |
| 227 if (!fileExists(pubspecPath)) { | 338 if (!fileExists(pubspecPath)) { |
| 228 throw new FileException( | 339 throw new FileException( |
| 229 'Could not find a file named "pubspec.yaml" in "$packageDir".', | 340 'Could not find a file named "pubspec.yaml" in "$packageDir".', |
| 230 pubspecPath); | 341 pubspecPath); |
| 231 } | 342 } |
| 343 |
| 232 return new Pubspec.parse( | 344 return new Pubspec.parse( |
| 233 readTextFile(pubspecPath), | 345 readTextFile(pubspecPath), |
| 234 sources, | 346 sources, |
| 235 expectedName: expectedName, | 347 expectedName: expectedName, |
| 236 location: pubspecUri); | 348 location: pubspecUri); |
| 237 } | 349 } |
| 350 |
| 238 Pubspec(this._name, {Version version, Iterable<PackageDep> dependencies, | 351 Pubspec(this._name, {Version version, Iterable<PackageDep> dependencies, |
| 239 Iterable<PackageDep> devDependencies, Iterable<PackageDep> dependencyOverr
ides, | 352 Iterable<PackageDep> devDependencies, Iterable<PackageDep> dependencyOverr
ides, |
| 240 VersionConstraint sdkConstraint, | 353 VersionConstraint sdkConstraint, |
| 241 Iterable<Iterable<TransformerConfig>> transformers, Map fields, | 354 Iterable<Iterable<TransformerConfig>> transformers, Map fields, |
| 242 SourceRegistry sources}) | 355 SourceRegistry sources}) |
| 243 : _version = version, | 356 : _version = version, |
| 244 _dependencies = dependencies == null ? null : dependencies.toList(), | 357 _dependencies = dependencies == null ? null : dependencies.toList(), |
| 245 _devDependencies = devDependencies == null ? | 358 _devDependencies = devDependencies == null ? |
| 246 null : | 359 null : |
| 247 devDependencies.toList(), | 360 devDependencies.toList(), |
| 248 _dependencyOverrides = dependencyOverrides == null ? | 361 _dependencyOverrides = dependencyOverrides == null ? |
| 249 null : | 362 null : |
| 250 dependencyOverrides.toList(), | 363 dependencyOverrides.toList(), |
| 251 _environment = new PubspecEnvironment(sdkConstraint), | 364 _environment = new PubspecEnvironment(sdkConstraint), |
| 252 _transformers = transformers == null ? | 365 _transformers = transformers == null ? |
| 253 [] : | 366 [] : |
| 254 transformers.map((phase) => phase.toSet()).toList(), | 367 transformers.map((phase) => phase.toSet()).toList(), |
| 255 fields = fields == null ? new YamlMap() : new YamlMap.wrap(fields), | 368 fields = fields == null ? new YamlMap() : new YamlMap.wrap(fields), |
| 256 _sources = sources; | 369 _sources = sources; |
| 370 |
| 257 Pubspec.empty() | 371 Pubspec.empty() |
| 258 : _sources = null, | 372 : _sources = null, |
| 259 _name = null, | 373 _name = null, |
| 260 _version = Version.none, | 374 _version = Version.none, |
| 261 _dependencies = <PackageDep>[], | 375 _dependencies = <PackageDep>[], |
| 262 _devDependencies = <PackageDep>[], | 376 _devDependencies = <PackageDep>[], |
| 263 _environment = new PubspecEnvironment(), | 377 _environment = new PubspecEnvironment(), |
| 264 _transformers = <Set<TransformerConfig>>[], | 378 _transformers = <Set<TransformerConfig>>[], |
| 265 fields = new YamlMap(); | 379 fields = new YamlMap(); |
| 380 |
| 381 /// Returns a Pubspec object for an already-parsed map representing its |
| 382 /// contents. |
| 383 /// |
| 384 /// If [expectedName] is passed and the pubspec doesn't have a matching name |
| 385 /// field, this will throw a [PubspecError]. |
| 386 /// |
| 387 /// [location] is the location from which this pubspec was loaded. |
| 266 Pubspec.fromMap(Map fields, this._sources, {String expectedName, | 388 Pubspec.fromMap(Map fields, this._sources, {String expectedName, |
| 267 Uri location}) | 389 Uri location}) |
| 268 : fields = fields is YamlMap ? | 390 : fields = fields is YamlMap ? |
| 269 fields : | 391 fields : |
| 270 new YamlMap.wrap(fields, sourceUrl: location) { | 392 new YamlMap.wrap(fields, sourceUrl: location) { |
| 393 // If [expectedName] is passed, ensure that the actual 'name' field exists |
| 394 // and matches the expectation. |
| 271 if (expectedName == null) return; | 395 if (expectedName == null) return; |
| 272 if (name == expectedName) return; | 396 if (name == expectedName) return; |
| 397 |
| 273 throw new PubspecException( | 398 throw new PubspecException( |
| 274 '"name" field doesn\'t match expected name ' '"$expectedName".', | 399 '"name" field doesn\'t match expected name ' '"$expectedName".', |
| 275 this.fields.nodes["name"].span); | 400 this.fields.nodes["name"].span); |
| 276 } | 401 } |
| 402 |
| 403 /// Parses the pubspec stored at [filePath] whose text is [contents]. |
| 404 /// |
| 405 /// If the pubspec doesn't define a version for itself, it defaults to |
| 406 /// [Version.none]. |
| 277 factory Pubspec.parse(String contents, SourceRegistry sources, | 407 factory Pubspec.parse(String contents, SourceRegistry sources, |
| 278 {String expectedName, Uri location}) { | 408 {String expectedName, Uri location}) { |
| 279 var pubspecNode = loadYamlNode(contents, sourceUrl: location); | 409 var pubspecNode = loadYamlNode(contents, sourceUrl: location); |
| 280 if (pubspecNode is YamlScalar && pubspecNode.value == null) { | 410 if (pubspecNode is YamlScalar && pubspecNode.value == null) { |
| 281 pubspecNode = new YamlMap(sourceUrl: location); | 411 pubspecNode = new YamlMap(sourceUrl: location); |
| 282 } else if (pubspecNode is! YamlMap) { | 412 } else if (pubspecNode is! YamlMap) { |
| 283 throw new PubspecException( | 413 throw new PubspecException( |
| 284 'The pubspec must be a YAML mapping.', | 414 'The pubspec must be a YAML mapping.', |
| 285 pubspecNode.span); | 415 pubspecNode.span); |
| 286 } | 416 } |
| 417 |
| 287 return new Pubspec.fromMap( | 418 return new Pubspec.fromMap( |
| 288 pubspecNode, | 419 pubspecNode, |
| 289 sources, | 420 sources, |
| 290 expectedName: expectedName, | 421 expectedName: expectedName, |
| 291 location: location); | 422 location: location); |
| 292 } | 423 } |
| 424 |
| 425 /// Returns a list of most errors in this pubspec. |
| 426 /// |
| 427 /// This will return at most one error for each field. |
| 293 List<PubspecException> get allErrors { | 428 List<PubspecException> get allErrors { |
| 294 var errors = <PubspecException>[]; | 429 var errors = <PubspecException>[]; |
| 295 _getError(fn()) { | 430 _getError(fn()) { |
| 296 try { | 431 try { |
| 297 fn(); | 432 fn(); |
| 298 } on PubspecException catch (e) { | 433 } on PubspecException catch (e) { |
| 299 errors.add(e); | 434 errors.add(e); |
| 300 } | 435 } |
| 301 } | 436 } |
| 437 |
| 302 _getError(() => this.name); | 438 _getError(() => this.name); |
| 303 _getError(() => this.version); | 439 _getError(() => this.version); |
| 304 _getError(() => this.dependencies); | 440 _getError(() => this.dependencies); |
| 305 _getError(() => this.devDependencies); | 441 _getError(() => this.devDependencies); |
| 306 _getError(() => this.transformers); | 442 _getError(() => this.transformers); |
| 307 _getError(() => this.environment); | 443 _getError(() => this.environment); |
| 308 _getError(() => this.publishTo); | 444 _getError(() => this.publishTo); |
| 309 return errors; | 445 return errors; |
| 310 } | 446 } |
| 447 |
| 448 /// Parses the dependency field named [field], and returns the corresponding |
| 449 /// list of dependencies. |
| 311 List<PackageDep> _parseDependencies(String field) { | 450 List<PackageDep> _parseDependencies(String field) { |
| 312 var dependencies = <PackageDep>[]; | 451 var dependencies = <PackageDep>[]; |
| 452 |
| 313 var yaml = fields[field]; | 453 var yaml = fields[field]; |
| 454 // Allow an empty dependencies key. |
| 314 if (yaml == null) return dependencies; | 455 if (yaml == null) return dependencies; |
| 456 |
| 315 if (yaml is! Map) { | 457 if (yaml is! Map) { |
| 316 _error('"$field" field must be a map.', fields.nodes[field].span); | 458 _error('"$field" field must be a map.', fields.nodes[field].span); |
| 317 } | 459 } |
| 460 |
| 318 var nonStringNode = | 461 var nonStringNode = |
| 319 yaml.nodes.keys.firstWhere((e) => e.value is! String, orElse: () => null
); | 462 yaml.nodes.keys.firstWhere((e) => e.value is! String, orElse: () => null
); |
| 320 if (nonStringNode != null) { | 463 if (nonStringNode != null) { |
| 321 _error('A dependency name must be a string.', nonStringNode.span); | 464 _error('A dependency name must be a string.', nonStringNode.span); |
| 322 } | 465 } |
| 466 |
| 323 yaml.nodes.forEach((nameNode, specNode) { | 467 yaml.nodes.forEach((nameNode, specNode) { |
| 324 var name = nameNode.value; | 468 var name = nameNode.value; |
| 325 var spec = specNode.value; | 469 var spec = specNode.value; |
| 326 if (fields['name'] != null && name == this.name) { | 470 if (fields['name'] != null && name == this.name) { |
| 327 _error('A package may not list itself as a dependency.', nameNode.span); | 471 _error('A package may not list itself as a dependency.', nameNode.span); |
| 328 } | 472 } |
| 473 |
| 329 var descriptionNode; | 474 var descriptionNode; |
| 330 var sourceName; | 475 var sourceName; |
| 476 |
| 331 var versionConstraint = new VersionRange(); | 477 var versionConstraint = new VersionRange(); |
| 332 if (spec == null) { | 478 if (spec == null) { |
| 333 descriptionNode = nameNode; | 479 descriptionNode = nameNode; |
| 334 sourceName = _sources.defaultSource.name; | 480 sourceName = _sources.defaultSource.name; |
| 335 } else if (spec is String) { | 481 } else if (spec is String) { |
| 336 descriptionNode = nameNode; | 482 descriptionNode = nameNode; |
| 337 sourceName = _sources.defaultSource.name; | 483 sourceName = _sources.defaultSource.name; |
| 338 versionConstraint = _parseVersionConstraint(specNode); | 484 versionConstraint = _parseVersionConstraint(specNode); |
| 339 } else if (spec is Map) { | 485 } else if (spec is Map) { |
| 486 // Don't write to the immutable YAML map. |
| 340 spec = new Map.from(spec); | 487 spec = new Map.from(spec); |
| 488 |
| 341 if (spec.containsKey('version')) { | 489 if (spec.containsKey('version')) { |
| 342 spec.remove('version'); | 490 spec.remove('version'); |
| 343 versionConstraint = | 491 versionConstraint = |
| 344 _parseVersionConstraint(specNode.nodes['version']); | 492 _parseVersionConstraint(specNode.nodes['version']); |
| 345 } | 493 } |
| 494 |
| 346 var sourceNames = spec.keys.toList(); | 495 var sourceNames = spec.keys.toList(); |
| 347 if (sourceNames.length > 1) { | 496 if (sourceNames.length > 1) { |
| 348 _error('A dependency may only have one source.', specNode.span); | 497 _error('A dependency may only have one source.', specNode.span); |
| 349 } | 498 } |
| 499 |
| 350 sourceName = sourceNames.single; | 500 sourceName = sourceNames.single; |
| 351 if (sourceName is! String) { | 501 if (sourceName is! String) { |
| 352 _error( | 502 _error( |
| 353 'A source name must be a string.', | 503 'A source name must be a string.', |
| 354 specNode.nodes.keys.single.span); | 504 specNode.nodes.keys.single.span); |
| 355 } | 505 } |
| 506 |
| 356 descriptionNode = specNode.nodes[sourceName]; | 507 descriptionNode = specNode.nodes[sourceName]; |
| 357 } else { | 508 } else { |
| 358 _error( | 509 _error( |
| 359 'A dependency specification must be a string or a mapping.', | 510 'A dependency specification must be a string or a mapping.', |
| 360 specNode.span); | 511 specNode.span); |
| 361 } | 512 } |
| 513 |
| 514 // Let the source validate the description. |
| 362 var description = | 515 var description = |
| 363 _wrapFormatException('description', descriptionNode.span, () { | 516 _wrapFormatException('description', descriptionNode.span, () { |
| 364 var pubspecPath; | 517 var pubspecPath; |
| 365 if (_location != null && _isFileUri(_location)) { | 518 if (_location != null && _isFileUri(_location)) { |
| 366 pubspecPath = path.fromUri(_location); | 519 pubspecPath = path.fromUri(_location); |
| 367 } | 520 } |
| 521 |
| 368 return _sources[sourceName].parseDescription( | 522 return _sources[sourceName].parseDescription( |
| 369 pubspecPath, | 523 pubspecPath, |
| 370 descriptionNode.value, | 524 descriptionNode.value, |
| 371 fromLockFile: false); | 525 fromLockFile: false); |
| 372 }); | 526 }); |
| 527 |
| 373 dependencies.add( | 528 dependencies.add( |
| 374 new PackageDep(name, sourceName, versionConstraint, description)); | 529 new PackageDep(name, sourceName, versionConstraint, description)); |
| 375 }); | 530 }); |
| 531 |
| 376 return dependencies; | 532 return dependencies; |
| 377 } | 533 } |
| 534 |
| 535 /// Parses [node] to a [VersionConstraint]. |
| 378 VersionConstraint _parseVersionConstraint(YamlNode node) { | 536 VersionConstraint _parseVersionConstraint(YamlNode node) { |
| 379 if (node.value == null) return VersionConstraint.any; | 537 if (node.value == null) return VersionConstraint.any; |
| 380 if (node.value is! String) { | 538 if (node.value is! String) { |
| 381 _error('A version constraint must be a string.', node.span); | 539 _error('A version constraint must be a string.', node.span); |
| 382 } | 540 } |
| 541 |
| 383 return _wrapFormatException( | 542 return _wrapFormatException( |
| 384 'version constraint', | 543 'version constraint', |
| 385 node.span, | 544 node.span, |
| 386 () => new VersionConstraint.parse(node.value)); | 545 () => new VersionConstraint.parse(node.value)); |
| 387 } | 546 } |
| 547 |
| 548 /// Makes sure the same package doesn't appear as both a regular and dev |
| 549 /// dependency. |
| 388 void _checkDependencyOverlap(List<PackageDep> dependencies, | 550 void _checkDependencyOverlap(List<PackageDep> dependencies, |
| 389 List<PackageDep> devDependencies) { | 551 List<PackageDep> devDependencies) { |
| 390 var dependencyNames = dependencies.map((dep) => dep.name).toSet(); | 552 var dependencyNames = dependencies.map((dep) => dep.name).toSet(); |
| 391 var collisions = | 553 var collisions = |
| 392 dependencyNames.intersection(devDependencies.map((dep) => dep.name).toSe
t()); | 554 dependencyNames.intersection(devDependencies.map((dep) => dep.name).toSe
t()); |
| 393 if (collisions.isEmpty) return; | 555 if (collisions.isEmpty) return; |
| 556 |
| 394 var span = fields["dependencies"].nodes.keys.firstWhere( | 557 var span = fields["dependencies"].nodes.keys.firstWhere( |
| 395 (key) => collisions.contains(key.value)).span; | 558 (key) => collisions.contains(key.value)).span; |
| 559 |
| 560 // TODO(nweiz): associate source range info with PackageDeps and use it |
| 561 // here. |
| 396 _error( | 562 _error( |
| 397 '${pluralize('Package', collisions.length)} ' | 563 '${pluralize('Package', collisions.length)} ' |
| 398 '${toSentence(collisions.map((package) => '"$package"'))} cannot ' | 564 '${toSentence(collisions.map((package) => '"$package"'))} cannot ' |
| 399 'appear in both "dependencies" and "dev_dependencies".', | 565 'appear in both "dependencies" and "dev_dependencies".', |
| 400 span); | 566 span); |
| 401 } | 567 } |
| 568 |
| 569 /// Runs [fn] and wraps any [FormatException] it throws in a |
| 570 /// [PubspecException]. |
| 571 /// |
| 572 /// [description] should be a noun phrase that describes whatever's being |
| 573 /// parsed or processed by [fn]. [span] should be the location of whatever's |
| 574 /// being processed within the pubspec. |
| 402 _wrapFormatException(String description, SourceSpan span, fn()) { | 575 _wrapFormatException(String description, SourceSpan span, fn()) { |
| 403 try { | 576 try { |
| 404 return fn(); | 577 return fn(); |
| 405 } on FormatException catch (e) { | 578 } on FormatException catch (e) { |
| 406 _error('Invalid $description: ${e.message}', span); | 579 _error('Invalid $description: ${e.message}', span); |
| 407 } | 580 } |
| 408 } | 581 } |
| 582 |
| 409 _wrapSpanFormatException(String description, fn()) { | 583 _wrapSpanFormatException(String description, fn()) { |
| 410 try { | 584 try { |
| 411 return fn(); | 585 return fn(); |
| 412 } on SourceSpanFormatException catch (e) { | 586 } on SourceSpanFormatException catch (e) { |
| 413 _error('Invalid $description: ${e.message}', e.span); | 587 _error('Invalid $description: ${e.message}', e.span); |
| 414 } | 588 } |
| 415 } | 589 } |
| 590 |
| 591 /// Throws a [PubspecException] with the given message. |
| 416 void _error(String message, SourceSpan span) { | 592 void _error(String message, SourceSpan span) { |
| 417 var name; | 593 var name; |
| 418 try { | 594 try { |
| 419 name = this.name; | 595 name = this.name; |
| 420 } on PubspecException catch (_) {} | 596 } on PubspecException catch (_) { |
| 597 // [name] is null. |
| 598 } |
| 599 |
| 421 throw new PubspecException(message, span); | 600 throw new PubspecException(message, span); |
| 422 } | 601 } |
| 423 } | 602 } |
| 603 |
| 604 /// The environment-related metadata in the pubspec. |
| 605 /// |
| 606 /// Corresponds to the data under the "environment:" key in the pubspec. |
| 424 class PubspecEnvironment { | 607 class PubspecEnvironment { |
| 608 /// The version constraint specifying which SDK versions this package works |
| 609 /// with. |
| 425 final VersionConstraint sdkVersion; | 610 final VersionConstraint sdkVersion; |
| 611 |
| 426 PubspecEnvironment([VersionConstraint sdk]) | 612 PubspecEnvironment([VersionConstraint sdk]) |
| 427 : sdkVersion = sdk != null ? sdk : VersionConstraint.any; | 613 : sdkVersion = sdk != null ? sdk : VersionConstraint.any; |
| 428 } | 614 } |
| 615 |
| 616 /// An exception thrown when parsing a pubspec. |
| 617 /// |
| 618 /// These exceptions are often thrown lazily while accessing pubspec properties. |
| 429 class PubspecException extends SourceSpanFormatException implements | 619 class PubspecException extends SourceSpanFormatException implements |
| 430 ApplicationException { | 620 ApplicationException { |
| 431 PubspecException(String message, SourceSpan span) : super(message, span); | 621 PubspecException(String message, SourceSpan span) |
| 622 : super(message, span); |
| 432 } | 623 } |
| 624 |
| 625 /// Returns whether [uri] is a file URI. |
| 626 /// |
| 627 /// This is slightly more complicated than just checking if the scheme is |
| 628 /// 'file', since relative URIs also refer to the filesystem on the VM. |
| 433 bool _isFileUri(Uri uri) => uri.scheme == 'file' || uri.scheme == ''; | 629 bool _isFileUri(Uri uri) => uri.scheme == 'file' || uri.scheme == ''; |
| OLD | NEW |