Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: sdk/lib/_internal/pub_generated/lib/src/pubspec.dart

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

Powered by Google App Engine
This is Rietveld 408576698