| // Copyright (c) 2018, 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:pub_semver/pub_semver.dart'; | |
| import '../exceptions.dart'; | |
| import '../language_version.dart'; | |
| import '../package_name.dart'; | |
| import '../sdk.dart'; | |
| import '../sdk/dart.dart'; | |
| import '../source/sdk.dart'; | |
| import 'incompatibility.dart'; | |
| /// The reason an [Incompatibility]'s terms are incompatible. | |
| sealed class IncompatibilityCause { | |
| const IncompatibilityCause(); | |
| /// Human readable notice / information providing context for this | |
| /// incompatibility. | |
| /// | |
| /// This may be multiple lines, and will be printed before the explanation. | |
| /// This is used highlight information that is useful for understanding the | |
| /// why this conflict happened. | |
| String? get notice => null; | |
| /// Human readable hint indicating how this incompatibility may be resolved. | |
| /// | |
| /// This may be multiple lines, and will be printed after the explanation. | |
| /// This should only be included if it is actionable and likely to resolve the | |
| /// issue for the user. | |
| String? get hint => null; | |
| } | |
| /// The incompatibility represents the requirement that the root package | |
| /// exists. | |
| class RootIncompatibilityCause extends IncompatibilityCause { | |
| factory RootIncompatibilityCause() => const RootIncompatibilityCause._(); | |
| const RootIncompatibilityCause._(); | |
| } | |
| /// The incompatibility represents a package's dependency. | |
| class DependencyIncompatibilityCause extends IncompatibilityCause { | |
| final PackageRange depender; | |
| final PackageRange target; | |
| DependencyIncompatibilityCause(this.depender, this.target); | |
| @override | |
| String? get notice { | |
| final dependerDescription = depender.description; | |
| if (dependerDescription is SdkDescription) { | |
| final targetConstraint = target.constraint; | |
| if (targetConstraint is Version) { | |
| return ''' | |
| Note: ${target.name} is pinned to version $targetConstraint by ${depender.name} from the ${dependerDescription.sdk} SDK. | |
| See https://dart.dev/go/sdk-version-pinning for details. | |
| '''; | |
| } | |
| } | |
| return null; | |
| } | |
| } | |
| /// The incompatibility indicates that the package has no versions that match | |
| /// the given constraint. | |
| class NoVersionsIncompatibilityCause extends IncompatibilityCause { | |
| factory NoVersionsIncompatibilityCause() => | |
| const NoVersionsIncompatibilityCause._(); | |
| const NoVersionsIncompatibilityCause._(); | |
| } | |
| /// The incompatibility indicates that a package version opts in to one or more | |
| /// experiments that the workspace doesn't allow. | |
| /// | |
| /// A dependency may only use experiments that some workspace package opts in | |
| /// to (or that are enabled by default). | |
| class ExperimentIncompatibilityCause extends IncompatibilityCause { | |
| final List<String> experiments; | |
| final Iterable<String> enabledExperiments; | |
| ExperimentIncompatibilityCause(this.experiments, this.enabledExperiments) | |
| : assert(experiments.isNotEmpty); | |
| String get description => | |
| experiments.length == 1 | |
| ? 'the experiment `${experiments.single}`' | |
| : 'the experiments ${experiments.map((e) => '`$e`').join(', ')}'; | |
| @override | |
| String? get hint { | |
| final unknownSdkExperiments = [ | |
| for (final experiment in experiments) | |
| if (!experiment.contains('.') && | |
| !availableExperiments.containsKey(experiment)) | |
| experiment, | |
| ]; | |
| if (unknownSdkExperiments.isNotEmpty) { | |
| final first = unknownSdkExperiments.first; | |
| return ''' | |
| `$first` is not an experiment known by this SDK (${sdk.version}), it may require a newer SDK. | |
| Read more about experiments at https://dart.dev/go/experiments.'''; | |
| } | |
| for (final experiment in experiments) { | |
| if (experiment.contains('.')) continue; | |
| final sdkExperiment = availableExperiments[experiment]; | |
| if (sdkExperiment != null && | |
| !sdkExperiment.isAvailableOnChannel(DartSdk.channel)) { | |
| return ''' | |
| The experiment `$experiment` is only available on the ${sdkExperiment.channels!.join(', ')} channel(s). This SDK is on the ${DartSdk.channel} channel. | |
| Read more about experiments at https://dart.dev/go/experiments.'''; | |
| } | |
| } | |
| final optedIn = | |
| enabledExperiments | |
| .where((e) => !experimentsNotRequiringOptIn.contains(e)) | |
| .toList(); | |
| final enabledExperimentsDescription = | |
| optedIn.isEmpty | |
| ? 'Currently no experiments are enabled.' | |
| : 'Currently the following experiments are enabled: ' | |
| '${optedIn.map((e) => '`$e`').join(', ')}.'; | |
| final notEnabledDescription = | |
| experiments.length == 1 | |
| ? 'The experiment `${experiments.single}` has not been enabled.' | |
| : 'The experiments ${experiments.map((e) => '`$e`').join(', ')} ' | |
| 'have not been enabled.'; | |
| final pronoun = experiments.length == 1 ? 'it' : 'them'; | |
| final yamlEntries = [ | |
| ...optedIn, | |
| for (final e in experiments) | |
| if (!optedIn.contains(e)) e, | |
| ].map((e) => ' - $e').join('\n'); | |
| return ''' | |
| $notEnabledDescription | |
| $enabledExperimentsDescription | |
| To enable $pronoun add to your pubspec.yaml: | |
| ``` | |
| experiments: | |
| enable: | |
| $yamlEntries | |
| ``` | |
| Read more about experiments at https://dart.dev/go/experiments.'''; | |
| } | |
| } | |
| /// The incompatibility indicates that the package has an unknown source. | |
| class UnknownSourceIncompatibilityCause extends IncompatibilityCause { | |
| factory UnknownSourceIncompatibilityCause() => | |
| const UnknownSourceIncompatibilityCause._(); | |
| const UnknownSourceIncompatibilityCause._(); | |
| } | |
| /// The incompatibility was derived from two existing incompatibilities during | |
| /// conflict resolution. | |
| class ConflictCause extends IncompatibilityCause { | |
| /// The incompatibility that was originally found to be in conflict, from | |
| /// which the target incompatibility was derived. | |
| final Incompatibility conflict; | |
| /// The incompatibility that caused the most recent satisfier for [conflict], | |
| /// from which the target incompatibility was derived. | |
| final Incompatibility other; | |
| ConflictCause(this.conflict, this.other); | |
| } | |
| /// The incompatibility represents a package's SDK constraint being | |
| /// incompatible with the current SDK. | |
| class SdkIncompatibilityCause extends IncompatibilityCause { | |
| /// The union of all the incompatible versions' constraints on the SDK. | |
| // TODO(zarah): Investigate if this can be non-nullable | |
| final VersionConstraint? constraint; | |
| /// The SDK with which the package was incompatible. | |
| final Sdk sdk; | |
| bool get noNullSafetyCause => | |
| sdk.isDartSdk && | |
| !LanguageVersion.fromSdkConstraint(constraint).supportsNullSafety && | |
| sdk.version! >= Version(3, 0, 0).firstPreRelease; | |
| @override | |
| String? get notice { | |
| // If the SDK is not available, then we have an actionable [hint] printed | |
| // after the explanation. So we don't need to state that the SDK is not | |
| // available. | |
| if (!sdk.isAvailable) { | |
| return null; | |
| } | |
| // If the SDK is available and we have an incompatibility, then the user has | |
| // the wrong SDK version (one that is not compatible with any solution). | |
| // Thus, it makes sense to highlight the current SDK version. | |
| return 'The current ${sdk.name} SDK version is ${sdk.version}.'; | |
| } | |
| @override | |
| String? get hint { | |
| if (noNullSafetyCause) { | |
| return 'The lower bound of "sdk: \'$constraint\'" must be 2.12.0' | |
| ' or higher to enable null safety.' | |
| '\nFor details, see https://dart.dev/null-safety'; | |
| } | |
| // If the SDK is available, then installing it won't help | |
| if (sdk.isAvailable) { | |
| return null; | |
| } | |
| // Return an install message for the SDK, if there is an install message. | |
| return sdk.installMessage; | |
| } | |
| SdkIncompatibilityCause(this.constraint, this.sdk); | |
| } | |
| /// The incompatibility represents a package that couldn't be found by its | |
| /// source. | |
| class PackageNotFoundIncompatibilityCause extends IncompatibilityCause { | |
| /// The exception indicating why the package couldn't be found. | |
| final PackageNotFoundException exception; | |
| PackageNotFoundIncompatibilityCause(this.exception); | |
| @override | |
| String? get hint => exception.hint; | |
| } | |
| /// The incompatibility represents a package-version that is not allowed to be | |
| /// used in the solve for some external reason. | |
| class PackageVersionForbiddenCause extends IncompatibilityCause { | |
| /// The reason this package version was forbidden. | |
| final String? reason; | |
| PackageVersionForbiddenCause({this.reason}); | |
| @override | |
| String? get hint => reason; | |
| } |