blob: 6e1d566656336a00828209bd18f79b962e7c3d4f [file] [log] [blame]
[email protected]bdd5c002012-05-18 23:14:18 +00001// 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
Jacob MacDonald9e216e02017-09-21 16:51:09 -07005import 'dart:io';
6
Kevin Moore00b36392020-07-01 00:23:23 -07007import 'package:collection/collection.dart' hide mapMap;
[email protected]6192b3d2013-07-12 17:54:48 +00008import 'package:path/path.dart' as path;
[email protected]3d164a92014-10-01 17:57:05 +00009import 'package:pub_semver/pub_semver.dart';
[email protected]f6065d92014-07-23 23:44:09 +000010import 'package:source_span/source_span.dart';
[email protected]8d000352014-06-24 23:54:03 +000011import 'package:yaml/yaml.dart';
[email protected]0e3af512013-02-01 23:45:26 +000012
[email protected]1d0ac9a2014-06-25 23:28:48 +000013import 'exceptions.dart';
[email protected]f0ff41d2013-02-02 00:47:26 +000014import 'io.dart';
Sigurd Meldgaard152e4742020-07-24 17:06:05 +020015import 'language_version.dart';
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070016import 'log.dart';
Natalie Weizenbaumbc2e5012017-06-20 16:23:18 -070017import 'package_name.dart';
István Soósdf5db1f2021-10-01 12:32:50 +020018import 'pubspec_parse.dart';
Natalie Weizenbaum711f5152018-04-11 13:04:47 -070019import 'sdk.dart';
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +010020import 'system_cache.dart';
[email protected]05c9cc02012-10-09 01:25:43 +000021import 'utils.dart';
[email protected]bdd5c002012-05-18 23:14:18 +000022
István Soósdf5db1f2021-10-01 12:32:50 +020023export 'pubspec_parse.dart' hide PubspecBase;
Natalie Weizenbaum8c091bf2015-12-01 16:30:13 -080024
Jacob MacDonald6c8357c2017-09-07 12:30:43 -070025/// The default SDK upper bound constraint for packages that don't declare one.
Natalie Weizenbaum62f917a2017-07-14 13:23:47 -070026///
Jacob MacDonald6c8357c2017-09-07 12:30:43 -070027/// This provides a sane default for packages that don't have an upper bound.
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070028final VersionRange _defaultUpperBoundSdkConstraint =
Sarah Zakarias400f21e2021-10-16 21:23:33 +020029 VersionConstraint.parse('<2.0.0') as VersionRange;
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070030
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070031/// Whether or not to allow the pre-release SDK for packages that have an
32/// upper bound Dart SDK constraint of <2.0.0.
33///
34/// If enabled then a Dart SDK upper bound of <2.0.0 is always converted to
35/// <2.0.0-dev.infinity.
36///
37/// This has a default value of `true` but can be overridden with the
38/// PUB_ALLOW_PRERELEASE_SDK system environment variable.
Kevin Moore2e821bf2018-04-25 09:40:39 -070039bool get _allowPreReleaseSdk => _allowPreReleaseSdkValue != 'false';
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070040
41/// The value of the PUB_ALLOW_PRERELEASE_SDK environment variable, defaulted
42/// to `true`.
Kevin Moore2e821bf2018-04-25 09:40:39 -070043final String _allowPreReleaseSdkValue = () {
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070044 var value =
Nate Boschceaa86f2020-01-06 14:12:36 -080045 Platform.environment['PUB_ALLOW_PRERELEASE_SDK']?.toLowerCase() ?? 'true';
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -070046 if (!['true', 'quiet', 'false'].contains(value)) {
47 warning(yellow('''
48The environment variable PUB_ALLOW_PRERELEASE_SDK is set as `$value`.
49The expected value is either `true`, `quiet` (true but no logging), or `false`.
50Using a default value of `true`.
51'''));
52 value = 'true';
53 }
54 return value;
55}();
56
57/// Whether or not to warn about pre-release SDK overrides.
Kevin Moore2e821bf2018-04-25 09:40:39 -070058bool get warnAboutPreReleaseSdkOverrides => _allowPreReleaseSdkValue != 'quiet';
Natalie Weizenbaum62f917a2017-07-14 13:23:47 -070059
[email protected]84ee8ed2013-09-23 22:21:44 +000060/// The parsed contents of a pubspec file.
61///
62/// The fields of a pubspec are, for the most part, validated when they're first
63/// accessed. This allows a partially-invalid pubspec to be used if only the
64/// valid portions are relevant. To get a list of all errors in the pubspec, use
65/// [allErrors].
István Soósdf5db1f2021-10-01 12:32:50 +020066class Pubspec extends PubspecBase {
[email protected]84ee8ed2013-09-23 22:21:44 +000067 // If a new lazily-initialized field is added to this class and the
[email protected]8d000352014-06-24 23:54:03 +000068 // initialization can throw a [PubspecException], that error should also be
[email protected]84ee8ed2013-09-23 22:21:44 +000069 // exposed through [allErrors].
[email protected]cfd21482012-08-24 18:48:00 +000070
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +010071 /// The fields of [pubspecOverridesFilename]. `null` if no such file exists or has
72 /// to be considered.
73 final YamlMap? _overridesFileFields;
74
75 String? get _packageName => fields['name'] != null ? name : null;
76
77 /// The name of the manifest file.
78 static const pubspecYamlFilename = 'pubspec.yaml';
79
80 /// The filename of the pubspec overrides file.
81 ///
82 /// This file can contain dependency_overrides that override those in
83 /// pubspec.yaml.
84 static const pubspecOverridesFilename = 'pubspec_overrides.yaml';
85
[email protected]84ee8ed2013-09-23 22:21:44 +000086 /// The registry of sources to use when parsing [dependencies] and
87 /// [devDependencies].
88 ///
Sigurd Meldgaard6aeb1792022-06-07 14:27:16 +020089 /// This will be null if this was created using [Pubspec] or [Pubspec.empty].
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +010090 final SourceRegistry _sources;
[email protected]bdd5c002012-05-18 23:14:18 +000091
[email protected]84ee8ed2013-09-23 22:21:44 +000092 /// The location from which the pubspec was loaded.
93 ///
94 /// This can be null if the pubspec was created in-memory or if its location
[email protected]8d000352014-06-24 23:54:03 +000095 /// is unknown.
Sarah Zakarias400f21e2021-10-16 21:23:33 +020096 Uri? get _location => fields.span.sourceUrl;
[email protected]f8b08f72013-01-23 17:41:40 +000097
[email protected]84ee8ed2013-09-23 22:21:44 +000098 /// The additional packages this package depends on.
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +010099 Map<String, PackageRange> get dependencies =>
100 _dependencies ??= _parseDependencies(
101 'dependencies',
102 fields.nodes['dependencies'],
103 _sources,
104 languageVersion,
105 _packageName,
106 _location);
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700107
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200108 Map<String, PackageRange>? _dependencies;
[email protected]84ee8ed2013-09-23 22:21:44 +0000109
110 /// The packages this package depends on when it is the root package.
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100111 Map<String, PackageRange> get devDependencies =>
112 _devDependencies ??= _parseDependencies(
113 'dev_dependencies',
114 fields.nodes['dev_dependencies'],
115 _sources,
116 languageVersion,
117 _packageName,
118 _location,
119 );
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700120
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200121 Map<String, PackageRange>? _devDependencies;
[email protected]84ee8ed2013-09-23 22:21:44 +0000122
[email protected]8bf75812013-11-18 21:51:24 +0000123 /// The dependency constraints that this package overrides when it is the
124 /// root package.
125 ///
126 /// Dependencies here will replace any dependency on a package with the same
127 /// name anywhere in the dependency graph.
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100128 ///
129 /// These can occur both in the pubspec.yaml file and the [pubspecOverridesFilename].
130 Map<String, PackageRange> get dependencyOverrides {
131 if (_dependencyOverrides != null) return _dependencyOverrides!;
132 final pubspecOverridesFields = _overridesFileFields;
133 if (pubspecOverridesFields != null) {
134 pubspecOverridesFields.nodes.forEach((key, _) {
135 if (!const {'dependency_overrides'}.contains(key.value)) {
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200136 throw SourceSpanApplicationException(
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100137 'pubspec_overrides.yaml only supports the `dependency_overrides` field.',
138 key.span,
139 );
140 }
141 });
142 if (pubspecOverridesFields.containsKey('dependency_overrides')) {
143 _dependencyOverrides = _parseDependencies(
144 'dependency_overrides',
145 pubspecOverridesFields.nodes['dependency_overrides'],
146 _sources,
147 languageVersion,
148 _packageName,
149 _location,
150 fileType: _FileType.pubspecOverrides,
151 );
152 }
153 }
154 return _dependencyOverrides ??= _parseDependencies(
155 'dependency_overrides',
156 fields.nodes['dependency_overrides'],
157 _sources,
158 languageVersion,
159 _packageName,
160 _location,
161 );
162 }
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700163
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200164 Map<String, PackageRange>? _dependencyOverrides;
[email protected]8bf75812013-11-18 21:51:24 +0000165
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700166 /// A map from SDK identifiers to constraints on those SDK versions.
167 Map<String, VersionConstraint> get sdkConstraints {
Natalie Weizenbaum8b03f3f2017-07-07 16:27:48 -0700168 _ensureEnvironment();
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200169 return _sdkConstraints!;
Natalie Weizenbaum9a178f32016-07-21 17:45:13 -0700170 }
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700171
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200172 Map<String, VersionConstraint>? _sdkConstraints;
Natalie Weizenbaum9a178f32016-07-21 17:45:13 -0700173
István Soósdf5db1f2021-10-01 12:32:50 +0200174 /// Whether or not to apply the [_defaultUpperBoundsSdkConstraint] to this
175 /// pubspec.
176 final bool _includeDefaultSdkConstraint;
177
178 /// Whether or not the SDK version was overridden from <2.0.0 to
179 /// <2.0.0-dev.infinity.
180 bool get dartSdkWasOverridden => _dartSdkWasOverridden;
181 bool _dartSdkWasOverridden = false;
182
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700183 /// The original Dart SDK constraint as written in the pubspec.
184 ///
185 /// If [dartSdkWasOverridden] is `false`, this will be identical to
186 /// `sdkConstraints["dart"]`.
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700187 VersionConstraint get originalDartSdkConstraint {
188 _ensureEnvironment();
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200189 return _originalDartSdkConstraint ?? sdkConstraints['dart']!;
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700190 }
191
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200192 VersionConstraint? _originalDartSdkConstraint;
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700193
Natalie Weizenbaum8b03f3f2017-07-07 16:27:48 -0700194 /// Ensures that the top-level "environment" field has been parsed and
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700195 /// [_sdkConstraints] is set accordingly.
Natalie Weizenbaum8b03f3f2017-07-07 16:27:48 -0700196 void _ensureEnvironment() {
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700197 if (_sdkConstraints != null) return;
[email protected]84ee8ed2013-09-23 22:21:44 +0000198
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700199 var sdkConstraints = _parseEnvironment(fields);
Nate Boschceaa86f2020-01-06 14:12:36 -0800200 var parsedDartSdkConstraint = sdkConstraints['dart'];
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700201
Jacob MacDonald04c78272017-09-15 13:58:38 -0700202 if (parsedDartSdkConstraint is VersionRange &&
203 _shouldEnableCurrentSdk(parsedDartSdkConstraint)) {
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700204 _originalDartSdkConstraint = parsedDartSdkConstraint;
205 _dartSdkWasOverridden = true;
Nate Boschceaa86f2020-01-06 14:12:36 -0800206 sdkConstraints['dart'] = VersionRange(
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700207 min: parsedDartSdkConstraint.min,
208 includeMin: parsedDartSdkConstraint.includeMin,
Jacob MacDonald04c78272017-09-15 13:58:38 -0700209 max: sdk.version,
210 includeMax: true);
Jacob MacDonaldaad65fe2017-09-14 14:48:14 -0700211 }
212
Nate Bosch59629082018-08-07 12:47:19 -0700213 _sdkConstraints = UnmodifiableMapView(sdkConstraints);
Natalie Weizenbaum8b03f3f2017-07-07 16:27:48 -0700214 }
215
Jacob MacDonald04c78272017-09-15 13:58:38 -0700216 /// Whether or not we should override [sdkConstraint] to be <= the user's
217 /// current SDK version.
218 ///
219 /// This is true if the following conditions are met:
220 ///
Kevin Moore2e821bf2018-04-25 09:40:39 -0700221 /// - [_allowPreReleaseSdk] is `true`
Jacob MacDonald04c78272017-09-15 13:58:38 -0700222 /// - The user's current SDK is a pre-release version.
223 /// - The original [sdkConstraint] max version is exclusive (`includeMax`
224 /// is `false`).
225 /// - The original [sdkConstraint] is not a pre-release version.
226 /// - The original [sdkConstraint] matches the exact same major, minor, and
227 /// patch versions as the user's current SDK.
228 bool _shouldEnableCurrentSdk(VersionRange sdkConstraint) {
Kevin Moore2e821bf2018-04-25 09:40:39 -0700229 if (!_allowPreReleaseSdk) return false;
Jacob MacDonald04c78272017-09-15 13:58:38 -0700230 if (!sdk.version.isPreRelease) return false;
231 if (sdkConstraint.includeMax) return false;
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200232 var minSdkConstraint = sdkConstraint.min;
233 if (minSdkConstraint != null &&
234 minSdkConstraint.isPreRelease &&
235 equalsIgnoringPreRelease(sdkConstraint.min!, sdk.version)) {
Natalie Weizenbaumf1e09a32018-01-03 14:15:02 -0800236 return false;
237 }
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200238 var maxSdkConstraint = sdkConstraint.max;
239 if (maxSdkConstraint == null) return false;
240 if (maxSdkConstraint.max.isPreRelease &&
241 !maxSdkConstraint.isFirstPreRelease) {
Natalie Weizenbaum21647132018-05-02 16:18:57 -0700242 return false;
243 }
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200244 return equalsIgnoringPreRelease(maxSdkConstraint, sdk.version);
Jacob MacDonald04c78272017-09-15 13:58:38 -0700245 }
246
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700247 /// Parses the "environment" field in [parent] and returns a map from SDK
248 /// identifiers to constraints on those SDKs.
249 Map<String, VersionConstraint> _parseEnvironment(YamlMap parent) {
Natalie Weizenbaum8b03f3f2017-07-07 16:27:48 -0700250 var yaml = parent['environment'];
[email protected]84ee8ed2013-09-23 22:21:44 +0000251 if (yaml == null) {
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700252 return {
Nate Boschceaa86f2020-01-06 14:12:36 -0800253 'dart': _includeDefaultSdkConstraint
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700254 ? _defaultUpperBoundSdkConstraint
255 : VersionConstraint.any
256 };
[email protected]84ee8ed2013-09-23 22:21:44 +0000257 }
258
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200259 if (yaml is! YamlMap) {
[email protected]8d000352014-06-24 23:54:03 +0000260 _error('"environment" field must be a map.',
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200261 parent.nodes['environment']!.span);
[email protected]84ee8ed2013-09-23 22:21:44 +0000262 }
263
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700264 var constraints = {
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100265 'dart': _parseVersionConstraint(
266 yaml.nodes['sdk'], _packageName, _FileType.pubspec,
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700267 defaultUpperBoundConstraint: _includeDefaultSdkConstraint
268 ? _defaultUpperBoundSdkConstraint
269 : null)
270 };
271 yaml.nodes.forEach((name, constraint) {
272 if (name.value is! String) {
273 _error('SDK names must be strings.', name.span);
Nate Boschceaa86f2020-01-06 14:12:36 -0800274 } else if (name.value == 'dart') {
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700275 _error('Use "sdk" to for Dart SDK constraints.', name.span);
276 }
Nate Boschceaa86f2020-01-06 14:12:36 -0800277 if (name.value == 'sdk') return;
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700278
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100279 constraints[name.value as String] =
280 _parseVersionConstraint(constraint, _packageName, _FileType.pubspec,
281 // Flutter constraints get special treatment, as Flutter won't be
282 // using semantic versioning to mark breaking releases.
283 ignoreUpperBound: name.value == 'flutter');
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700284 });
285
286 return constraints;
[email protected]84ee8ed2013-09-23 22:21:44 +0000287 }
[email protected]bdd5c002012-05-18 23:14:18 +0000288
Sigurd Meldgaard152e4742020-07-24 17:06:05 +0200289 /// The language version implied by the sdk constraint.
Jonas Finnemann Jensen3ea2b832020-11-12 13:50:27 +0100290 LanguageVersion get languageVersion =>
291 LanguageVersion.fromSdkConstraint(originalDartSdkConstraint);
Sigurd Meldgaard152e4742020-07-24 17:06:05 +0200292
[email protected]84ee8ed2013-09-23 22:21:44 +0000293 /// Loads the pubspec for a package located in [packageDir].
294 ///
295 /// If [expectedName] is passed and the pubspec doesn't have a matching name
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200296 /// field, this will throw a [SourceSpanApplicationException].
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100297 ///
298 /// If [allowOverridesFile] is `true` [pubspecOverridesFilename] is loaded and
299 /// is allowed to override dependency_overrides from `pubspec.yaml`.
[email protected]84ee8ed2013-09-23 22:21:44 +0000300 factory Pubspec.load(String packageDir, SourceRegistry sources,
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100301 {String? expectedName, bool allowOverridesFile = false}) {
302 var pubspecPath = path.join(packageDir, pubspecYamlFilename);
303 var overridesPath = path.join(packageDir, pubspecOverridesFilename);
[email protected]84ee8ed2013-09-23 22:21:44 +0000304 if (!fileExists(pubspecPath)) {
Nate Bosch59629082018-08-07 12:47:19 -0700305 throw FileException(
Natalie Weizenbaum072c5d82015-07-09 16:42:47 -0700306 // Make the package dir absolute because for the entrypoint it'll just
307 // be ".", which may be confusing.
308 'Could not find a file named "pubspec.yaml" in '
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700309 '"${canonicalize(packageDir)}".',
[email protected]7fae9a62014-07-17 22:14:21 +0000310 pubspecPath);
[email protected]84ee8ed2013-09-23 22:21:44 +0000311 }
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100312 String? overridesFileContents =
313 allowOverridesFile && fileExists(overridesPath)
314 ? readTextFile(overridesPath)
315 : null;
[email protected]84ee8ed2013-09-23 22:21:44 +0000316
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100317 return Pubspec.parse(
318 readTextFile(pubspecPath),
319 sources,
320 expectedName: expectedName,
321 location: path.toUri(pubspecPath),
322 overridesFileContents: overridesFileContents,
323 overridesLocation: path.toUri(overridesPath),
324 );
[email protected]84ee8ed2013-09-23 22:21:44 +0000325 }
326
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100327 Pubspec(
328 String name, {
329 Version? version,
330 Iterable<PackageRange>? dependencies,
331 Iterable<PackageRange>? devDependencies,
332 Iterable<PackageRange>? dependencyOverrides,
333 Map? fields,
334 SourceRegistry? sources,
335 Map<String, VersionConstraint>? sdkConstraints,
336 }) : _dependencies = dependencies == null
Natalie Weizenbaum73433d12017-11-27 14:18:38 -0800337 ? null
Nate Bosch59629082018-08-07 12:47:19 -0700338 : Map.fromIterable(dependencies, key: (range) => range.name),
Natalie Weizenbaum73433d12017-11-27 14:18:38 -0800339 _devDependencies = devDependencies == null
340 ? null
Nate Bosch59629082018-08-07 12:47:19 -0700341 : Map.fromIterable(devDependencies, key: (range) => range.name),
Natalie Weizenbaum73433d12017-11-27 14:18:38 -0800342 _dependencyOverrides = dependencyOverrides == null
343 ? null
Nate Bosch59629082018-08-07 12:47:19 -0700344 : Map.fromIterable(dependencyOverrides, key: (range) => range.name),
Sigurd Meldgaarde02b7c62020-03-12 15:26:33 +0100345 _sdkConstraints = sdkConstraints ??
346 UnmodifiableMapView({'dart': VersionConstraint.any}),
Natalie Weizenbaum711f5152018-04-11 13:04:47 -0700347 _includeDefaultSdkConstraint = false,
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100348 _sources = sources ??
349 ((String? name) => throw StateError('No source registry given')),
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100350 _overridesFileFields = null,
István Soósdf5db1f2021-10-01 12:32:50 +0200351 super(
352 fields == null ? YamlMap() : YamlMap.wrap(fields),
353 name: name,
354 version: version,
355 );
[email protected]84ee8ed2013-09-23 22:21:44 +0000356
[email protected]12fd1042013-06-04 21:05:00 +0000357 /// Returns a Pubspec object for an already-parsed map representing its
358 /// contents.
359 ///
[email protected]84ee8ed2013-09-23 22:21:44 +0000360 /// If [expectedName] is passed and the pubspec doesn't have a matching name
361 /// field, this will throw a [PubspecError].
362 ///
363 /// [location] is the location from which this pubspec was loaded.
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700364 Pubspec.fromMap(Map fields, this._sources,
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100365 {YamlMap? overridesFields, String? expectedName, Uri? location})
366 : _overridesFileFields = overridesFields,
367 _includeDefaultSdkConstraint = true,
István Soósdf5db1f2021-10-01 12:32:50 +0200368 super(fields is YamlMap
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700369 ? fields
István Soósdf5db1f2021-10-01 12:32:50 +0200370 : YamlMap.wrap(fields, sourceUrl: location)) {
[email protected]84ee8ed2013-09-23 22:21:44 +0000371 // If [expectedName] is passed, ensure that the actual 'name' field exists
372 // and matches the expectation.
[email protected]8d000352014-06-24 23:54:03 +0000373 if (expectedName == null) return;
374 if (name == expectedName) return;
[email protected]84ee8ed2013-09-23 22:21:44 +0000375
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200376 throw SourceSpanApplicationException(
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700377 '"name" field doesn\'t match expected name '
378 '"$expectedName".',
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200379 this.fields.nodes['name']!.span);
[email protected]84ee8ed2013-09-23 22:21:44 +0000380 }
381
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100382 /// Parses the pubspec stored at [location] whose text is [contents].
[email protected]7ce95642014-06-18 20:46:51 +0000383 ///
384 /// If the pubspec doesn't define a version for itself, it defaults to
385 /// [Version.none].
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100386 factory Pubspec.parse(
387 String contents,
388 SourceRegistry sources, {
389 String? expectedName,
390 Uri? location,
391 String? overridesFileContents,
392 Uri? overridesLocation,
393 }) {
394 late final YamlMap pubspecMap;
395 YamlMap? overridesFileMap;
Natalie Weizenbaum3dc67772016-12-07 14:17:25 -0800396 try {
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100397 pubspecMap = _ensureMap(loadYamlNode(contents, sourceUrl: location));
398 if (overridesFileContents != null) {
399 overridesFileMap = _ensureMap(
400 loadYamlNode(overridesFileContents, sourceUrl: overridesLocation));
401 }
Natalie Weizenbaum3dc67772016-12-07 14:17:25 -0800402 } on YamlException catch (error) {
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200403 throw SourceSpanApplicationException(error.message, error.span);
Natalie Weizenbaum3dc67772016-12-07 14:17:25 -0800404 }
405
Nate Bosch59629082018-08-07 12:47:19 -0700406 return Pubspec.fromMap(pubspecMap, sources,
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100407 overridesFields: overridesFileMap,
408 expectedName: expectedName,
409 location: location);
410 }
411
412 /// Ensures that [node] is a mapping.
413 ///
414 /// If [node] is already a map it is returned.
415 /// If [node] is yaml-null an empty map is returned.
416 /// Otherwise an exception is thrown.
417 static YamlMap _ensureMap(YamlNode node) {
418 if (node is YamlScalar && node.value == null) {
419 return YamlMap(sourceUrl: node.span.sourceUrl);
420 } else if (node is YamlMap) {
421 return node;
422 } else {
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200423 throw SourceSpanApplicationException(
424 'The pubspec must be a YAML mapping.', node.span);
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100425 }
[email protected]0d9a8cc2013-02-09 00:16:15 +0000426 }
427
[email protected]84ee8ed2013-09-23 22:21:44 +0000428 /// Returns a list of most errors in this pubspec.
429 ///
430 /// This will return at most one error for each field.
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200431 List<SourceSpanApplicationException> get allErrors {
432 var errors = <SourceSpanApplicationException>[];
Sigurd Meldgaard6aeb1792022-06-07 14:27:16 +0200433 void collectError(void Function() fn) {
[email protected]84ee8ed2013-09-23 22:21:44 +0000434 try {
435 fn();
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200436 } on SourceSpanApplicationException catch (e) {
[email protected]84ee8ed2013-09-23 22:21:44 +0000437 errors.add(e);
438 }
[email protected]12fd1042013-06-04 21:05:00 +0000439 }
[email protected]84ee8ed2013-09-23 22:21:44 +0000440
Sigurd Meldgaard6aeb1792022-06-07 14:27:16 +0200441 collectError(() => name);
442 collectError(() => version);
443 collectError(() => dependencies);
444 collectError(() => devDependencies);
445 collectError(() => publishTo);
446 collectError(() => executables);
447 collectError(() => falseSecrets);
448 collectError(_ensureEnvironment);
[email protected]84ee8ed2013-09-23 22:21:44 +0000449 return errors;
[email protected]12fd1042013-06-04 21:05:00 +0000450 }
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100451}
[email protected]12fd1042013-06-04 21:05:00 +0000452
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100453/// Parses the dependency field named [field], and returns the corresponding
454/// map of dependency names to dependencies.
455Map<String, PackageRange> _parseDependencies(
456 String field,
457 YamlNode? node,
458 SourceRegistry sources,
459 LanguageVersion languageVersion,
460 String? packageName,
461 Uri? location, {
462 _FileType fileType = _FileType.pubspec,
463}) {
464 var dependencies = <String, PackageRange>{};
[email protected]12fd1042013-06-04 21:05:00 +0000465
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100466 // Allow an empty dependencies key.
467 if (node == null || node.value == null) return dependencies;
[email protected]12fd1042013-06-04 21:05:00 +0000468
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100469 if (node is! YamlMap) {
470 _error('"$field" field must be a map.', node.span);
471 }
[email protected]84ee8ed2013-09-23 22:21:44 +0000472
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100473 var nonStringNode =
474 node.nodes.keys.firstWhere((e) => e.value is! String, orElse: () => null);
475 if (nonStringNode != null) {
476 _error('A dependency name must be a string.', nonStringNode.span);
477 }
[email protected]8d000352014-06-24 23:54:03 +0000478
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100479 node.nodes.forEach(
480 (nameNode, specNode) {
[email protected]8d000352014-06-24 23:54:03 +0000481 var name = nameNode.value;
482 var spec = specNode.value;
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100483 if (packageName != null && name == packageName) {
Jacob MacDonaldedd32952017-03-15 11:12:46 -0700484 _error('A package may not list itself as a dependency.', nameNode.span);
[email protected]84ee8ed2013-09-23 22:21:44 +0000485 }
486
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200487 YamlNode? descriptionNode;
488 String? sourceName;
[email protected]84ee8ed2013-09-23 22:21:44 +0000489
Sigurd Meldgaardfdf7b052020-12-22 12:13:26 +0100490 VersionConstraint versionConstraint = VersionRange();
[email protected]84ee8ed2013-09-23 22:21:44 +0000491 if (spec == null) {
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100492 sourceName = null;
[email protected]84ee8ed2013-09-23 22:21:44 +0000493 } else if (spec is String) {
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100494 sourceName = null;
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100495 versionConstraint =
496 _parseVersionConstraint(specNode, packageName, fileType);
[email protected]84ee8ed2013-09-23 22:21:44 +0000497 } else if (spec is Map) {
[email protected]bc2bb142014-06-03 20:33:29 +0000498 // Don't write to the immutable YAML map.
Nate Bosch59629082018-08-07 12:47:19 -0700499 spec = Map.from(spec);
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700500 var specMap = specNode as YamlMap;
[email protected]bc2bb142014-06-03 20:33:29 +0000501
[email protected]84ee8ed2013-09-23 22:21:44 +0000502 if (spec.containsKey('version')) {
[email protected]8d000352014-06-24 23:54:03 +0000503 spec.remove('version');
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100504 versionConstraint = _parseVersionConstraint(
505 specMap.nodes['version'],
506 packageName,
507 fileType,
508 );
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700509 }
510
[email protected]84ee8ed2013-09-23 22:21:44 +0000511 var sourceNames = spec.keys.toList();
512 if (sourceNames.length > 1) {
[email protected]8d000352014-06-24 23:54:03 +0000513 _error('A dependency may only have one source.', specNode.span);
Natalie Weizenbaum597233a2015-10-21 14:06:21 -0700514 } else if (sourceNames.isEmpty) {
Kevin Moore23a59a32020-02-19 11:40:17 -0800515 // Default to a hosted dependency if no source is specified.
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700516 sourceName = 'hosted';
[email protected]84ee8ed2013-09-23 22:21:44 +0000517 }
518
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700519 sourceName ??= sourceNames.single;
[email protected]84ee8ed2013-09-23 22:21:44 +0000520 if (sourceName is! String) {
[email protected]8d000352014-06-24 23:54:03 +0000521 _error('A source name must be a string.',
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700522 specMap.nodes.keys.single.span);
[email protected]84ee8ed2013-09-23 22:21:44 +0000523 }
524
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700525 descriptionNode ??= specMap.nodes[sourceName];
[email protected]84ee8ed2013-09-23 22:21:44 +0000526 } else {
[email protected]8d000352014-06-24 23:54:03 +0000527 _error('A dependency specification must be a string or a mapping.',
528 specNode.span);
[email protected]84ee8ed2013-09-23 22:21:44 +0000529 }
530
[email protected]b2b68e92014-04-23 23:47:49 +0000531 // Let the source validate the description.
Natalie Weizenbaum3137e312017-07-05 15:00:25 -0700532 var ref = _wrapFormatException('description', descriptionNode?.span, () {
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100533 String? pubspecDir;
Sarah Zakarias400f21e2021-10-16 21:23:33 +0200534 if (location != null && _isFileUri(location)) {
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100535 pubspecDir = path.dirname(path.fromUri(location));
[email protected]b2b68e92014-04-23 23:47:49 +0000536 }
537
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100538 return sources(sourceName).parseRef(
Simon Bindera2dbcff2021-10-26 15:12:36 +0200539 name,
540 descriptionNode?.value,
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100541 containingDir: pubspecDir,
Simon Bindera2dbcff2021-10-26 15:12:36 +0200542 languageVersion: languageVersion,
543 );
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100544 }, packageName, fileType, targetPackage: name);
[email protected]84ee8ed2013-09-23 22:21:44 +0000545
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100546 dependencies[name] = ref.withConstraint(versionConstraint);
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100547 },
548 );
[email protected]84ee8ed2013-09-23 22:21:44 +0000549
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100550 return dependencies;
551}
552
[email protected]84ee8ed2013-09-23 22:21:44 +0000553/// Returns whether [uri] is a file URI.
554///
555/// This is slightly more complicated than just checking if the scheme is
556/// 'file', since relative URIs also refer to the filesystem on the VM.
557bool _isFileUri(Uri uri) => uri.scheme == 'file' || uri.scheme == '';
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100558
559/// Parses [node] to a [VersionConstraint].
560///
561/// If or [defaultUpperBoundConstraint] is specified then it will be set as
562/// the max constraint if the original constraint doesn't have an upper
563/// bound and it is compatible with [defaultUpperBoundConstraint].
564///
565/// If [ignoreUpperBound] the max constraint is ignored.
566VersionConstraint _parseVersionConstraint(
567 YamlNode? node, String? packageName, _FileType fileType,
568 {VersionConstraint? defaultUpperBoundConstraint,
569 bool ignoreUpperBound = false}) {
570 if (node?.value == null) {
571 return defaultUpperBoundConstraint ?? VersionConstraint.any;
572 }
573 if (node!.value is! String) {
574 _error('A version constraint must be a string.', node.span);
575 }
576
577 return _wrapFormatException('version constraint', node.span, () {
578 var constraint = VersionConstraint.parse(node.value);
579 if (defaultUpperBoundConstraint != null &&
580 constraint is VersionRange &&
581 constraint.max == null &&
582 defaultUpperBoundConstraint.allowsAny(constraint)) {
583 constraint = VersionConstraint.intersection(
584 [constraint, defaultUpperBoundConstraint]);
585 }
586 if (ignoreUpperBound && constraint is VersionRange) {
587 return VersionRange(
588 min: constraint.min, includeMin: constraint.includeMin);
589 }
590 return constraint;
591 }, packageName, fileType);
592}
593
594/// Runs [fn] and wraps any [FormatException] it throws in a
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200595/// [SourceSpanApplicationException].
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100596///
597/// [description] should be a noun phrase that describes whatever's being
598/// parsed or processed by [fn]. [span] should be the location of whatever's
599/// being processed within the pubspec.
600///
601/// If [targetPackage] is provided, the value is used to describe the
602/// dependency that caused the problem.
Sigurd Meldgaard610ce7f2022-03-24 16:46:23 +0100603T _wrapFormatException<T>(
604 String description,
605 SourceSpan? span,
606 T Function() fn,
607 String? packageName,
608 _FileType fileType, {
609 String? targetPackage,
610}) {
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100611 try {
612 return fn();
613 } on FormatException catch (e) {
614 // If we already have a pub exception with a span, re-use that
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200615 if (e is SourceSpanApplicationException) rethrow;
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100616
617 var msg = 'Invalid $description';
618 final typeName = _fileTypeName(fileType);
619 if (targetPackage != null) {
620 msg = '$msg in the "$packageName" $typeName on the "$targetPackage" '
621 'dependency';
622 }
623 msg = '$msg: ${e.message}';
624 _error(msg, span);
625 }
626}
627
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200628/// Throws a [SourceSpanApplicationException] with the given message.
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100629Never _error(String message, SourceSpan? span) {
Sigurd Meldgaard29d7d862022-10-04 11:41:32 +0200630 throw SourceSpanApplicationException(message, span);
Gabriel Terwesten7a6ea392022-03-24 14:47:41 +0100631}
632
633enum _FileType {
634 pubspec,
635 pubspecOverrides,
636}
637
638String _fileTypeName(_FileType type) {
639 switch (type) {
640 case _FileType.pubspec:
641 return 'pubspec';
642 case _FileType.pubspecOverrides:
643 return 'pubspec override';
644 }
645}