| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'package:collection/collection.dart' hide mapMap; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| import 'package:source_span/source_span.dart'; | |
| import 'package:yaml/yaml.dart'; | |
| import 'exceptions.dart'; | |
| import 'io.dart'; | |
| import 'language_version.dart'; | |
| import 'package.dart'; | |
| import 'package_name.dart'; | |
| import 'path.dart'; | |
| import 'pubspec_parse.dart'; | |
| import 'sdk.dart'; | |
| import 'source.dart'; | |
| import 'source/root.dart'; | |
| import 'system_cache.dart'; | |
| export 'pubspec_parse.dart' hide PubspecBase; | |
| /// The default SDK upper bound constraint for packages that don't declare one. | |
| /// | |
| /// This provides a sane default for packages that don't have an upper bound. | |
| final VersionRange _defaultUpperBoundSdkConstraint = | |
| VersionConstraint.parse('<2.0.0') as VersionRange; | |
| /// The parsed contents of a pubspec file. | |
| /// | |
| /// The fields of a pubspec are, for the most part, validated when they're first | |
| /// accessed. This allows a partially-invalid pubspec to be used if only the | |
| /// valid portions are relevant. To get a list of all errors in the pubspec, use | |
| /// [allErrors]. | |
| class Pubspec extends PubspecBase { | |
| // If a new lazily-initialized field is added to this class and the | |
| // initialization can throw a [PubspecException], that error should also be | |
| // exposed through [allErrors]. | |
| /// The fields of [pubspecOverridesFilename]. `null` if no such file exists or | |
| /// has to be considered. | |
| final YamlMap? _overridesFileFields; | |
| String? get _packageName => fields['name'] != null ? name : null; | |
| final bool dependencyOverridesFromOverridesFile; | |
| /// The name of the manifest file. | |
| static const pubspecYamlFilename = 'pubspec.yaml'; | |
| /// The filename of the pubspec overrides file. | |
| /// | |
| /// This file can contain dependency_overrides that override those in | |
| /// pubspec.yaml. | |
| static const pubspecOverridesFilename = 'pubspec_overrides.yaml'; | |
| /// The registry of sources to use when parsing [dependencies] and | |
| /// [devDependencies]. | |
| final SourceRegistry sources; | |
| /// It is used to resolve relative paths. And to resolve path-descriptions | |
| /// from a git dependency as git-descriptions. | |
| final ResolvedDescription _containingDescription; | |
| /// Directories of packages that should resolve together with this package. | |
| late List<String> workspace = () { | |
| final result = <String>[]; | |
| final workspaceNode = | |
| _overridesFileFields?.nodes['workspace'] ?? fields.nodes['workspace']; | |
| if (workspaceNode != null && !languageVersion.supportsWorkspaces) { | |
| _error( | |
| '`workspace` and `resolution` requires at least language version ' | |
| '${LanguageVersion.firstVersionWithWorkspaces}', | |
| workspaceNode.span, | |
| hint: ''' | |
| Consider updating the SDK constraint to: | |
| environment: | |
| sdk: '^${sdk.version}' | |
| ''', | |
| ); | |
| } | |
| if (workspaceNode == null || workspaceNode.value == null) return <String>[]; | |
| if (workspaceNode is! YamlList) { | |
| _error('"workspace" must be a list of strings', workspaceNode.span); | |
| } | |
| for (final t in workspaceNode.nodes) { | |
| final value = t.value; | |
| if (value is! String) { | |
| _error('"workspace" must be a list of strings', t.span); | |
| } | |
| if (!p.isRelative(value)) { | |
| _error('"workspace" members must be relative paths', t.span); | |
| } | |
| if (p.equals(value, '.') || !p.isWithin('.', value)) { | |
| _error('"workspace" members must be subdirectories', t.span); | |
| } | |
| result.add(value); | |
| } | |
| return result; | |
| }(); | |
| /// The resolution mode. | |
| late Resolution resolution = () { | |
| final resolutionNode = | |
| _overridesFileFields?.nodes['resolution'] ?? fields.nodes['resolution']; | |
| if (resolutionNode != null && !languageVersion.supportsWorkspaces) { | |
| _error( | |
| '`workspace` and `resolution` requires at least language version ' | |
| '${LanguageVersion.firstVersionWithWorkspaces}', | |
| resolutionNode.span, | |
| hint: ''' | |
| Consider updating the SDK constraint to: | |
| environment: | |
| sdk: '^${sdk.version}' | |
| ''', | |
| ); | |
| } | |
| return switch (resolutionNode?.value) { | |
| null => Resolution.none, | |
| 'local' => Resolution.local, | |
| 'workspace' => Resolution.workspace, | |
| 'external' => Resolution.external, | |
| _ => _error( | |
| '"resolution" must be one of `workspace`, `local`, `external`', | |
| resolutionNode!.span, | |
| ), | |
| }; | |
| }(); | |
| /// The additional packages this package depends on. | |
| Map<String, PackageRange> get dependencies => | |
| _dependencies ??= _parseDependencies( | |
| 'dependencies', | |
| fields.nodes['dependencies'], | |
| sources, | |
| languageVersion, | |
| _packageName, | |
| _containingDescription, | |
| ); | |
| Map<String, PackageRange>? _dependencies; | |
| /// The packages this package depends on when it is the root package. | |
| Map<String, PackageRange> get devDependencies => | |
| _devDependencies ??= _parseDependencies( | |
| 'dev_dependencies', | |
| fields.nodes['dev_dependencies'], | |
| sources, | |
| languageVersion, | |
| _packageName, | |
| _containingDescription, | |
| ); | |
| Map<String, PackageRange>? _devDependencies; | |
| /// The dependency constraints that this package overrides when it is the root | |
| /// package. | |
| /// | |
| /// Dependencies here will replace any dependency on a package with the same | |
| /// name anywhere in the dependency graph. | |
| /// | |
| /// These can occur both in the pubspec.yaml file and the | |
| /// [pubspecOverridesFilename]. | |
| Map<String, PackageRange> get dependencyOverrides { | |
| if (_dependencyOverrides != null) return _dependencyOverrides!; | |
| final pubspecOverridesFields = _overridesFileFields; | |
| if (pubspecOverridesFields != null) { | |
| if (pubspecOverridesFields.containsKey('dependency_overrides')) { | |
| _dependencyOverrides = _parseDependencies( | |
| 'dependency_overrides', | |
| pubspecOverridesFields.nodes['dependency_overrides'], | |
| sources, | |
| languageVersion, | |
| _packageName, | |
| _containingDescription, | |
| fileType: _FileType.pubspecOverrides, | |
| ); | |
| } | |
| } | |
| return _dependencyOverrides ??= _parseDependencies( | |
| 'dependency_overrides', | |
| fields.nodes['dependency_overrides'], | |
| sources, | |
| languageVersion, | |
| _packageName, | |
| _containingDescription, | |
| ); | |
| } | |
| Map<String, PackageRange>? _dependencyOverrides; | |
| SdkConstraint get dartSdkConstraint => sdkConstraints['dart']!; | |
| /// A map from SDK identifiers to constraints on those SDK versions. | |
| late final Map<String, SdkConstraint> sdkConstraints = | |
| _givenSdkConstraints ?? UnmodifiableMapView(_parseEnvironment(fields)); | |
| final Map<String, SdkConstraint>? _givenSdkConstraints; | |
| /// Whether or not to apply the [_defaultUpperBoundSdkConstraint] to this | |
| /// pubspec. | |
| final bool _includeDefaultSdkConstraint; | |
| /// Parses the "environment" field in [parent] and returns a map from SDK | |
| /// identifiers to constraints on those SDKs. | |
| Map<String, SdkConstraint> _parseEnvironment(YamlMap parent) { | |
| final yaml = parent['environment']; | |
| final VersionConstraint originalDartSdkConstraint; | |
| if (yaml == null) { | |
| originalDartSdkConstraint = VersionConstraint.any; | |
| } else if (yaml is! YamlMap) { | |
| _error( | |
| '"environment" field must be a map.', | |
| parent.nodes['environment']!.span, | |
| ); | |
| } else { | |
| originalDartSdkConstraint = _parseVersionConstraint( | |
| yaml.nodes['sdk'], | |
| _packageName, | |
| _FileType.pubspec, | |
| ); | |
| } | |
| final dartConstraint = SdkConstraint.interpretDartSdkConstraint( | |
| originalDartSdkConstraint, | |
| defaultUpperBoundConstraint: | |
| _includeDefaultSdkConstraint ? _defaultUpperBoundSdkConstraint : null, | |
| ); | |
| final constraints = {'dart': dartConstraint}; | |
| if (yaml is YamlMap) { | |
| yaml.nodes.forEach((nameNode, constraintNode) { | |
| if (nameNode is! YamlNode) throw AssertionError('Bad state'); | |
| final name = nameNode.value; | |
| if (name is! String) { | |
| _error('SDK names must be strings.', nameNode.span); | |
| } else if (name == 'dart') { | |
| _error('Use "sdk" to for Dart SDK constraints.', nameNode.span); | |
| } | |
| if (name == 'sdk') return; | |
| final constraint = _parseVersionConstraint( | |
| constraintNode, | |
| _packageName, | |
| _FileType.pubspec, | |
| ); | |
| constraints[name] = | |
| name == 'flutter' | |
| ? SdkConstraint.interpretFlutterSdkConstraint( | |
| constraint, | |
| isRoot: _containingDescription is ResolvedRootDescription, | |
| languageVersion: dartConstraint.languageVersion, | |
| ) | |
| : SdkConstraint(constraint); | |
| }); | |
| } | |
| return constraints; | |
| } | |
| /// The language version implied by the sdk constraint. | |
| LanguageVersion get languageVersion { | |
| return LanguageVersion.fromSdkConstraint( | |
| dartSdkConstraint.originalConstraint, | |
| ); | |
| } | |
| /// Loads the pubspec for a package located in [packageDir]. | |
| /// | |
| /// If [expectedName] is passed and the pubspec doesn't have a matching name | |
| /// field, this will throw a [SourceSpanApplicationException]. | |
| /// | |
| /// If [allowOverridesFile] is `true` [pubspecOverridesFilename] is loaded and | |
| /// is allowed to override dependency_overrides from `pubspec.yaml`. | |
| factory Pubspec.load( | |
| String packageDir, | |
| SourceRegistry sources, { | |
| String? expectedName, | |
| bool allowOverridesFile = false, | |
| required ResolvedDescription containingDescription, | |
| }) { | |
| final pubspecPath = p.join(packageDir, pubspecYamlFilename); | |
| final overridesPath = p.join(packageDir, pubspecOverridesFilename); | |
| if (!fileExists(pubspecPath)) { | |
| throw FileException( | |
| // Make the package dir absolute because for the entrypoint it'll just | |
| // be ".", which may be confusing. | |
| 'Could not find a file named "pubspec.yaml" in ' | |
| '"${canonicalize(packageDir)}".', | |
| pubspecPath, | |
| ); | |
| } | |
| final overridesFileContents = | |
| allowOverridesFile && fileExists(overridesPath) | |
| ? readTextFile(overridesPath) | |
| : null; | |
| return Pubspec.parse( | |
| readTextFile(pubspecPath), | |
| sources, | |
| expectedName: expectedName, | |
| location: p.toUri(pubspecPath), | |
| overridesFileContents: overridesFileContents, | |
| overridesLocation: p.toUri(overridesPath), | |
| containingDescription: containingDescription, | |
| ); | |
| } | |
| /// Convenience helper to pass to [Package.load]. | |
| static Pubspec Function( | |
| String dir, { | |
| String? expectedName, | |
| required bool withPubspecOverrides, | |
| }) | |
| loadRootWithSources(SourceRegistry sources) { | |
| return ( | |
| String dir, { | |
| String? expectedName, | |
| required bool withPubspecOverrides, | |
| }) => Pubspec.load( | |
| dir, | |
| sources, | |
| expectedName: expectedName, | |
| allowOverridesFile: withPubspecOverrides, | |
| containingDescription: ResolvedRootDescription.fromDir(dir), | |
| ); | |
| } | |
| Pubspec( | |
| String name, { | |
| super.version, | |
| Iterable<PackageRange>? dependencies, | |
| Iterable<PackageRange>? devDependencies, | |
| Iterable<PackageRange>? dependencyOverrides, | |
| Map? fields, | |
| SourceRegistry? sources, | |
| Map<String, SdkConstraint>? sdkConstraints, | |
| this.workspace = const <String>[], | |
| this.dependencyOverridesFromOverridesFile = false, | |
| this.resolution = Resolution.none, | |
| }) : _dependencies = | |
| dependencies == null | |
| ? null | |
| : {for (final d in dependencies) d.name: d}, | |
| _devDependencies = | |
| devDependencies == null | |
| ? null | |
| : {for (final d in devDependencies) d.name: d}, | |
| _dependencyOverrides = | |
| dependencyOverrides == null | |
| ? null | |
| : {for (final d in dependencyOverrides) d.name: d}, | |
| _givenSdkConstraints = | |
| sdkConstraints ?? | |
| UnmodifiableMapView({'dart': SdkConstraint(VersionConstraint.any)}), | |
| _includeDefaultSdkConstraint = false, | |
| sources = | |
| sources ?? | |
| ((String? name) => throw StateError('No source registry given')), | |
| _overridesFileFields = null, | |
| // This is a dummy value. Dependencies should already be resolved, so we | |
| // never need to do relative resolutions. | |
| _containingDescription = ResolvedRootDescription.fromDir('.'), | |
| super(fields == null ? YamlMap() : YamlMap.wrap(fields), name: name); | |
| /// Returns a Pubspec object for an already-parsed map representing its | |
| /// contents. | |
| /// | |
| /// If [expectedName] is passed and the pubspec doesn't have a matching name | |
| /// field, this will throw an [ApplicationException]. | |
| /// | |
| /// [location] is the location from which this pubspec was loaded. | |
| Pubspec.fromMap( | |
| Map fields, | |
| this.sources, { | |
| YamlMap? overridesFields, | |
| String? expectedName, | |
| Uri? location, | |
| required ResolvedDescription containingDescription, | |
| }) : _overridesFileFields = overridesFields, | |
| _includeDefaultSdkConstraint = true, | |
| _givenSdkConstraints = null, | |
| dependencyOverridesFromOverridesFile = | |
| overridesFields != null && | |
| overridesFields.containsKey('dependency_overrides'), | |
| _containingDescription = containingDescription, | |
| super( | |
| fields is YamlMap ? fields : YamlMap.wrap(fields, sourceUrl: location), | |
| ) { | |
| if (overridesFields != null) { | |
| overridesFields.nodes.forEach((key, _) { | |
| final keyNode = key as YamlNode; | |
| if (!const { | |
| 'dependency_overrides', | |
| 'resolution', | |
| 'workspace', | |
| }.contains(keyNode.value)) { | |
| throw SourceSpanApplicationException( | |
| 'pubspec_overrides.yaml only supports the ' | |
| '`dependency_overrides`, `resolution` and `workspace` fields.', | |
| keyNode.span, | |
| ); | |
| } | |
| }); | |
| } | |
| // If [expectedName] is passed, ensure that the actual 'name' field exists | |
| // and matches the expectation. | |
| if (expectedName == null) return; | |
| if (name == expectedName) return; | |
| throw SourceSpanApplicationException( | |
| '"name" field doesn\'t match expected name ' | |
| '"$expectedName".', | |
| this.fields.nodes['name']!.span, | |
| ); | |
| } | |
| /// Parses the pubspec stored at [location] whose text is [contents]. | |
| /// | |
| /// If the pubspec doesn't define a version for itself, it defaults to | |
| /// [Version.none]. | |
| factory Pubspec.parse( | |
| String contents, | |
| SourceRegistry sources, { | |
| String? expectedName, | |
| Uri? location, | |
| String? overridesFileContents, | |
| Uri? overridesLocation, | |
| required ResolvedDescription containingDescription, | |
| }) { | |
| final YamlMap pubspecMap; | |
| YamlMap? overridesFileMap; | |
| try { | |
| pubspecMap = _ensureMap(loadYamlNode(contents, sourceUrl: location)); | |
| if (overridesFileContents != null) { | |
| overridesFileMap = _ensureMap( | |
| loadYamlNode(overridesFileContents, sourceUrl: overridesLocation), | |
| ); | |
| } | |
| } on YamlException catch (error) { | |
| throw SourceSpanApplicationException(error.message, error.span); | |
| } | |
| return Pubspec.fromMap( | |
| pubspecMap, | |
| sources, | |
| overridesFields: overridesFileMap, | |
| expectedName: expectedName, | |
| location: location, | |
| containingDescription: containingDescription, | |
| ); | |
| } | |
| Pubspec copyWith({ | |
| String? name, | |
| Version? version, | |
| Iterable<PackageRange>? dependencies, | |
| Iterable<PackageRange>? devDependencies, | |
| Iterable<PackageRange>? dependencyOverrides, | |
| Map? fields, | |
| Map<String, SdkConstraint>? sdkConstraints, | |
| List<String>? workspace, | |
| //this.dependencyOverridesFromOverridesFile = false, | |
| Resolution? resolution, | |
| }) { | |
| return Pubspec( | |
| name ?? this.name, | |
| version: version ?? this.version, | |
| dependencies: dependencies ?? this.dependencies.values, | |
| devDependencies: devDependencies ?? this.devDependencies.values, | |
| dependencyOverrides: | |
| dependencyOverrides ?? this.dependencyOverrides.values, | |
| sdkConstraints: sdkConstraints ?? this.sdkConstraints, | |
| workspace: workspace ?? this.workspace, | |
| resolution: resolution ?? this.resolution, | |
| ); | |
| } | |
| /// Ensures that [node] is a mapping. | |
| /// | |
| /// If [node] is already a map it is returned. | |
| /// If [node] is yaml-null an empty map is returned. | |
| /// Otherwise an exception is thrown. | |
| static YamlMap _ensureMap(YamlNode node) { | |
| if (node is YamlScalar && node.value == null) { | |
| return YamlMap(sourceUrl: node.span.sourceUrl); | |
| } else if (node is YamlMap) { | |
| return node; | |
| } else { | |
| throw SourceSpanApplicationException( | |
| 'The pubspec must be a YAML mapping.', | |
| node.span, | |
| ); | |
| } | |
| } | |
| List<SourceSpanApplicationException> _collectErrorsFor( | |
| List<dynamic Function()> toCheck, | |
| ) { | |
| final errors = <SourceSpanApplicationException>[]; | |
| void collectError(void Function() fn) { | |
| try { | |
| fn(); | |
| } on SourceSpanApplicationException catch (e) { | |
| errors.add(e); | |
| } | |
| } | |
| for (final fn in toCheck) { | |
| collectError(fn); | |
| } | |
| return errors; | |
| } | |
| /// Returns a list of errors relevant to consuming this pubspec as a | |
| /// dependency | |
| /// | |
| /// This will return at most one error for each field. | |
| List<SourceSpanApplicationException> get dependencyErrors => | |
| _collectErrorsFor([ | |
| () => name, | |
| () => version, | |
| () => dependencies, | |
| () => executables, | |
| () => ignoredAdvisories, | |
| ]); | |
| /// Returns a list of most errors in this pubspec. | |
| /// | |
| /// This will return at most one error for each field. | |
| List<SourceSpanApplicationException> get allErrors => _collectErrorsFor([ | |
| () => name, | |
| () => version, | |
| () => dependencies, | |
| () => devDependencies, | |
| () => publishTo, | |
| () => executables, | |
| () => falseSecrets, | |
| () => sdkConstraints, | |
| () => ignoredAdvisories, | |
| ]); | |
| /// Returns the type of dependency from this package onto [name]. | |
| DependencyType dependencyType(String? name) { | |
| if (dependencies.containsKey(name)) { | |
| return DependencyType.direct; | |
| } else if (devDependencies.containsKey(name)) { | |
| return DependencyType.dev; | |
| } else { | |
| return DependencyType.none; | |
| } | |
| } | |
| } | |
| /// The type of dependency from one package to another. | |
| enum DependencyType { direct, dev, none } | |
| /// Parses the dependency field named [field], and returns the corresponding | |
| /// map of dependency names to dependencies. | |
| Map<String, PackageRange> _parseDependencies( | |
| String field, | |
| YamlNode? node, | |
| SourceRegistry sources, | |
| LanguageVersion languageVersion, | |
| String? packageName, | |
| ResolvedDescription containingDescription, { | |
| _FileType fileType = _FileType.pubspec, | |
| }) { | |
| final dependencies = <String, PackageRange>{}; | |
| // Allow an empty dependencies key. | |
| if (node == null || node.value == null) return dependencies; | |
| if (node is! YamlMap) { | |
| _error('"$field" field must be a map.', node.span); | |
| } | |
| final nonStringNode = node.nodes.keys.firstWhereOrNull( | |
| (e) => e is YamlScalar && e.value is! String, | |
| ); | |
| if (nonStringNode != null) { | |
| _error( | |
| 'A dependency name must be a string.', | |
| (nonStringNode as YamlNode).span, | |
| ); | |
| } | |
| node.nodes.forEach((nameNode, specNode) { | |
| final name = (nameNode as YamlNode).value; | |
| if (name is! String) { | |
| _error('A dependency name must be a string.', nameNode.span); | |
| } | |
| if (!packageNameRegExp.hasMatch(name)) { | |
| _error('Not a valid package name.', nameNode.span); | |
| } | |
| final spec = specNode.value; | |
| if (packageName != null && name == packageName) { | |
| _error('A package may not list itself as a dependency.', nameNode.span); | |
| } | |
| final String? sourceName; | |
| VersionConstraint versionConstraint = VersionRange(); | |
| YamlNode? descriptionNode; | |
| if (spec == null) { | |
| sourceName = null; | |
| } else if (spec is String) { | |
| sourceName = null; | |
| versionConstraint = _parseVersionConstraint( | |
| specNode, | |
| packageName, | |
| fileType, | |
| ); | |
| } else if (specNode is YamlMap) { | |
| // Don't write to the immutable YAML map. | |
| final versionNode = specNode.nodes['version']; | |
| versionConstraint = _parseVersionConstraint( | |
| versionNode, | |
| packageName, | |
| fileType, | |
| ); | |
| final otherEntries = | |
| specNode.nodes.entries | |
| .where((entry) => (entry.key as YamlNode).value != 'version') | |
| .toList(); | |
| if (otherEntries.length > 1) { | |
| _error('A dependency may only have one source.', specNode.span); | |
| } else if (otherEntries.isEmpty) { | |
| // Default to a hosted dependency if no source is specified. | |
| sourceName = 'hosted'; | |
| } else { | |
| switch (otherEntries.single) { | |
| case MapEntry(key: YamlScalar(value: final String s), value: final d): | |
| sourceName = s; | |
| descriptionNode = d; | |
| case MapEntry(key: final k, value: _): | |
| _error('A source name must be a string.', (k as YamlNode).span); | |
| } | |
| } | |
| } else { | |
| _error( | |
| 'A dependency specification must be a string or a mapping.', | |
| specNode.span, | |
| ); | |
| } | |
| // Let the source validate the description. | |
| final ref = _wrapFormatException( | |
| 'description', | |
| descriptionNode?.span, | |
| () { | |
| return sources(sourceName).parseRef( | |
| name, | |
| descriptionNode?.value, | |
| containingDescription: containingDescription, | |
| languageVersion: languageVersion, | |
| ); | |
| }, | |
| packageName, | |
| fileType, | |
| targetPackage: name, | |
| ); | |
| dependencies[name] = ref.withConstraint(versionConstraint); | |
| }); | |
| return dependencies; | |
| } | |
| /// Parses [node] to a [VersionConstraint]. | |
| /// | |
| /// `null` is interpreted as [VersionConstraint.any]. | |
| /// | |
| /// A String is parsed with [VersionConstraint.parse]. | |
| VersionConstraint _parseVersionConstraint( | |
| YamlNode? node, | |
| String? packageName, | |
| _FileType fileType, | |
| ) { | |
| if (node?.value == null) { | |
| return VersionConstraint.any; | |
| } | |
| final value = node!.value; | |
| if (value is! String) { | |
| _error('A version constraint must be a string.', node.span); | |
| } | |
| return _wrapFormatException( | |
| 'version constraint', | |
| node.span, | |
| () { | |
| final constraint = VersionConstraint.parse(value); | |
| return constraint; | |
| }, | |
| packageName, | |
| fileType, | |
| ); | |
| } | |
| /// Runs [fn] and wraps any [FormatException] it throws in a | |
| /// [SourceSpanApplicationException]. | |
| /// | |
| /// [description] should be a noun phrase that describes whatever's being | |
| /// parsed or processed by [fn]. [span] should be the location of whatever's | |
| /// being processed within the pubspec. | |
| /// | |
| /// If [targetPackage] is provided, the value is used to describe the | |
| /// dependency that caused the problem. | |
| T _wrapFormatException<T>( | |
| String description, | |
| SourceSpan? span, | |
| T Function() fn, | |
| String? packageName, | |
| _FileType fileType, { | |
| String? targetPackage, | |
| }) { | |
| try { | |
| return fn(); | |
| } on FormatException catch (e) { | |
| // If we already have a pub exception with a span, re-use that | |
| if (e is SourceSpanApplicationException) rethrow; | |
| var msg = 'Invalid $description'; | |
| final typeName = _fileTypeName(fileType); | |
| if (targetPackage != null) { | |
| msg = | |
| '$msg in the "$packageName" $typeName on the "$targetPackage" ' | |
| 'dependency'; | |
| } | |
| msg = '$msg: ${e.message}'; | |
| _error(msg, span); | |
| } | |
| } | |
| /// Throws a [SourceSpanApplicationException] with the given message. | |
| Never _error(String message, SourceSpan? span, {String? hint}) { | |
| throw SourceSpanApplicationException(message, span, hint: hint); | |
| } | |
| enum _FileType { pubspec, pubspecOverrides } | |
| String _fileTypeName(_FileType type) { | |
| switch (type) { | |
| case _FileType.pubspec: | |
| return 'pubspec'; | |
| case _FileType.pubspecOverrides: | |
| return 'pubspec override'; | |
| } | |
| } | |
| /// There are special rules or interpreting SDK constraints, we take care to | |
| /// save the original constraint as found in pubspec.yaml. | |
| class SdkConstraint { | |
| /// The constraint as written in the pubspec.yaml. | |
| final VersionConstraint originalConstraint; | |
| /// The constraint as interpreted by pub. | |
| final VersionConstraint effectiveConstraint; | |
| SdkConstraint( | |
| this.effectiveConstraint, { | |
| VersionConstraint? originalConstraint, | |
| }) : originalConstraint = originalConstraint ?? effectiveConstraint; | |
| /// Implement support for down to 2.12 in the dart 3 series. Note that this | |
| /// function has to be be idempotent, because we apply it both when we write | |
| /// and read lock-file constraints, so applying this function a second time | |
| /// should have no further effect. | |
| factory SdkConstraint.interpretDartSdkConstraint( | |
| VersionConstraint originalConstraint, { | |
| required VersionConstraint? defaultUpperBoundConstraint, | |
| }) { | |
| var constraint = originalConstraint; | |
| if (defaultUpperBoundConstraint != null && | |
| constraint is VersionRange && | |
| constraint.max == null && | |
| defaultUpperBoundConstraint.allowsAny(constraint)) { | |
| constraint = VersionConstraint.intersection([ | |
| constraint, | |
| defaultUpperBoundConstraint, | |
| ]); | |
| } | |
| // If a package is null safe it should also be compatible with dart 3. | |
| // Therefore we rewrite a null-safety enabled constraint with the upper | |
| // bound <3.0.0 to be have upper bound <4.0.0 | |
| // | |
| // Only do this rewrite after dart 3. | |
| if (sdk.version.major >= 3 && | |
| constraint is VersionRange && | |
| LanguageVersion.fromSdkConstraint(constraint) >= | |
| LanguageVersion.firstVersionWithNullSafety && | |
| // <3.0.0 is parsed into a max of 3.0.0-0, so that is what we look for | |
| // here. | |
| constraint.max == Version(3, 0, 0).firstPreRelease && | |
| constraint.includeMax == false) { | |
| constraint = VersionRange( | |
| min: constraint.min, | |
| includeMin: constraint.includeMin, | |
| // We don't have to use .firstPreRelease as the constructor will do that | |
| // if needed. | |
| max: Version(4, 0, 0), | |
| ); | |
| } | |
| return SdkConstraint(constraint, originalConstraint: originalConstraint); | |
| } | |
| /// Flutter constraints get special treatment, as Flutter won't be using | |
| /// semantic versioning to mark breaking releases. We simply ignore upper | |
| /// bounds for dependencies. | |
| /// | |
| /// After language version | |
| /// [LanguageVersion.firstVersionRespectingFlutterBoundInRoots] for root | |
| /// packages we use the upper bound, allowing app developers to constrain the | |
| /// Flutter version. | |
| factory SdkConstraint.interpretFlutterSdkConstraint( | |
| VersionConstraint constraint, { | |
| required bool isRoot, | |
| required LanguageVersion languageVersion, | |
| }) { | |
| if (constraint is VersionRange) { | |
| if (!(isRoot && languageVersion.respectsFlutterBoundInRoots)) { | |
| return SdkConstraint( | |
| VersionRange(min: constraint.min, includeMin: constraint.includeMin), | |
| originalConstraint: constraint, | |
| ); | |
| } | |
| } | |
| return SdkConstraint(constraint); | |
| } | |
| /// The language version of a constraint is determined from how it is written. | |
| LanguageVersion get languageVersion => | |
| LanguageVersion.fromSdkConstraint(originalConstraint); | |
| // We currently don't call this anywhere - so this is only for debugging | |
| // purposes. | |
| @override | |
| String toString() { | |
| if (effectiveConstraint != originalConstraint) { | |
| return '$originalConstraint (interpreted as $effectiveConstraint)'; | |
| } | |
| return effectiveConstraint.toString(); | |
| } | |
| @override | |
| bool operator ==(Object other) => | |
| other is SdkConstraint && | |
| other.effectiveConstraint == effectiveConstraint && | |
| other.originalConstraint == originalConstraint; | |
| @override | |
| int get hashCode => Object.hash(effectiveConstraint, originalConstraint); | |
| } | |
| enum Resolution { | |
| // Still unused. | |
| external, | |
| // This package is a member of a workspace, and should be resolved with a | |
| // pubspec.yaml located higher. | |
| workspace, | |
| // Still unused. | |
| local, | |
| // This package is at the root of a workspace. | |
| none, | |
| } |