[tool/ci] Add minimum supported SDK validation (#7028)

Adds options to `pubspec.yaml` to check that the minimum supported SDK range for Flutter/Dart is at least a given version, to add CI enforcement that we're updating all of our support claims when we update our tested versions (rather than it being something we have to remember to do), and enables it in CI.

As part of enabling it, fixes some violations:
- path_provider_foundation had been temporarily dropped back to 2.10 as part of pushing out a regression fix.
- a number of examples were missing Flutter constraints even though they used Flutter.
- the non-Flutter `plugin_platform_interface` package hadn't been update since I hadn't thought about Dart-only constraints in the past.
diff --git a/.cirrus.yml b/.cirrus.yml
index 85f8a2a..4a5d604 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -101,7 +101,9 @@
       always:
         format_script: ./script/tool_runner.sh format --fail-on-change
         license_script: $PLUGIN_TOOL_COMMAND license-check
-        pubspec_script: ./script/tool_runner.sh pubspec-check
+        # The major and minor versions here should match the lowest version
+        # analyzed in legacy_version_analyze.
+        pubspec_script: ./script/tool_runner.sh pubspec-check --min-min-flutter-version=3.0.0 --min-min-dart-version=2.17.0
         readme_script:
           - ./script/tool_runner.sh readme-check
           # Re-run with --require-excerpts, skipping packages that still need
@@ -171,6 +173,7 @@
     - name: legacy_version_analyze
       depends_on: analyze
       matrix:
+        # Change the arguments to pubspec-check when changing these values.
         env:
           CHANNEL: "3.0.5"
           DART_VERSION: "2.17.6"
diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml
index 011d958..ff9d6d0 100644
--- a/packages/file_selector/file_selector/example/pubspec.yaml
+++ b/packages/file_selector/file_selector/example/pubspec.yaml
@@ -6,6 +6,7 @@
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   file_selector:
diff --git a/packages/file_selector/file_selector_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml
index 5a2eaa6..175ec6c 100644
--- a/packages/file_selector/file_selector_ios/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml
@@ -5,6 +5,7 @@
 
 environment:
   sdk: ">=2.14.4 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   # The following adds the Cupertino Icons font to your application.
@@ -28,4 +29,4 @@
     sdk: flutter
 
 flutter:
-  uses-material-design: true
\ No newline at end of file
+  uses-material-design: true
diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml
index 912a082..f90d1c8 100644
--- a/packages/file_selector/file_selector_linux/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml
@@ -5,6 +5,7 @@
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   file_selector_linux:
diff --git a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml
index e732497..4c97e6c 100644
--- a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml
+++ b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml
@@ -4,6 +4,7 @@
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   flutter:
diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md
index 17e45d3..7adb04f 100644
--- a/packages/path_provider/path_provider_foundation/CHANGELOG.md
+++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Updates minimum supported Flutter version to 3.0.
+
 ## 2.1.1
 
 * Fixes a regression in the path retured by `getApplicationSupportDirectory` on iOS.
diff --git a/packages/path_provider/path_provider_foundation/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml
index 9ceb115..30dd655 100644
--- a/packages/path_provider/path_provider_foundation/pubspec.yaml
+++ b/packages/path_provider/path_provider_foundation/pubspec.yaml
@@ -6,7 +6,7 @@
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=2.10.0"
+  flutter: ">=3.0.0"
 
 flutter:
   plugin:
diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md
index 0b5a6b6..93e45c8 100644
--- a/packages/plugin_platform_interface/CHANGELOG.md
+++ b/packages/plugin_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Updates minimum supported Dart version.
+
 ## 2.1.3
 
 * Minor fixes for new analysis options.
diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml
index 6a4bc48..25189d9 100644
--- a/packages/plugin_platform_interface/pubspec.yaml
+++ b/packages/plugin_platform_interface/pubspec.yaml
@@ -18,7 +18,7 @@
 version: 2.1.3
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: ">=2.17.0 <3.0.0"
 
 dependencies:
   meta: ^1.3.0
diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
index 0daacb0..0fc0daf 100644
--- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
@@ -4,6 +4,7 @@
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   flutter:
diff --git a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml
index 782817e..4685135 100644
--- a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml
@@ -4,6 +4,7 @@
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   flutter:
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
index 7ccb302..718eb28 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
@@ -4,6 +4,7 @@
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
+  flutter: ">=3.0.0"
 
 dependencies:
   flutter:
diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md
index 55b5aeb..34def6e 100644
--- a/script/tool/CHANGELOG.md
+++ b/script/tool/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.13.4
+
+* Adds the ability to validate minimum supported Dart/Flutter versions in
+  `pubspec-check`.
+
 ## 0.13.3
 
 * Renames `podspecs` to `podspec-check`. The old name will continue to work.
diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart
index 5682ba0..aefa316 100644
--- a/script/tool/lib/src/pubspec_check_command.dart
+++ b/script/tool/lib/src/pubspec_check_command.dart
@@ -5,6 +5,7 @@
 import 'package:file/file.dart';
 import 'package:git/git.dart';
 import 'package:platform/platform.dart';
+import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 
 import 'common/core.dart';
@@ -29,7 +30,23 @@
           processRunner: processRunner,
           platform: platform,
           gitDir: gitDir,
-        );
+        ) {
+    argParser.addOption(
+      _minMinDartVersionFlag,
+      help:
+          'The minimum Dart version to allow as the minimum SDK constraint.\n\n'
+          'This is only enforced for non-Flutter packages; Flutter packages '
+          'use --$_minMinFlutterVersionFlag',
+    );
+    argParser.addOption(
+      _minMinFlutterVersionFlag,
+      help:
+          'The minimum Flutter version to allow as the minimum SDK constraint.',
+    );
+  }
+
+  static const String _minMinDartVersionFlag = 'min-min-dart-version';
+  static const String _minMinFlutterVersionFlag = 'min-min-flutter-version';
 
   // Section order for plugins. Because the 'flutter' section is critical
   // information for plugins, and usually small, it goes near the top unlike in
@@ -100,6 +117,24 @@
       printError('$listIndentation${sectionOrder.join('\n$listIndentation')}');
     }
 
+    final String minMinDartVersionString = getStringArg(_minMinDartVersionFlag);
+    final String minMinFlutterVersionString =
+        getStringArg(_minMinFlutterVersionFlag);
+    final String? minVersionError = _checkForMinimumVersionError(
+      pubspec,
+      package,
+      minMinDartVersion: minMinDartVersionString.isEmpty
+          ? null
+          : Version.parse(minMinDartVersionString),
+      minMinFlutterVersion: minMinFlutterVersionString.isEmpty
+          ? null
+          : Version.parse(minMinFlutterVersionString),
+    );
+    if (minVersionError != null) {
+      printError('$indentation$minVersionError');
+      passing = false;
+    }
+
     if (isPlugin) {
       final String? implementsError =
           _checkForImplementsError(pubspec, package: package);
@@ -320,4 +355,43 @@
     final String suffix = packageName.substring(parentName.length);
     return !nonImplementationSuffixes.contains(suffix);
   }
+
+  /// Validates that a Flutter package has a minimum SDK version constraint of
+  /// at least [minMinFlutterVersion] (if provided), or that a non-Flutter
+  /// package has a minimum SDK version constraint of [minMinDartVersion]
+  /// (if provided).
+  ///
+  /// Returns an error string if validation fails.
+  String? _checkForMinimumVersionError(
+    Pubspec pubspec,
+    RepositoryPackage package, {
+    Version? minMinDartVersion,
+    Version? minMinFlutterVersion,
+  }) {
+    final VersionConstraint? dartConstraint = pubspec.environment?['sdk'];
+    final VersionConstraint? flutterConstraint =
+        pubspec.environment?['flutter'];
+
+    if (flutterConstraint != null) {
+      // Validate Flutter packages against the Flutter requirement.
+      if (minMinFlutterVersion != null) {
+        final Version? constraintMin =
+            flutterConstraint is VersionRange ? flutterConstraint.min : null;
+        if ((constraintMin ?? Version(0, 0, 0)) < minMinFlutterVersion) {
+          return 'Minimum allowed Flutter version $constraintMin is less than $minMinFlutterVersion';
+        }
+      }
+    } else {
+      // Validate non-Flutter packages against the Dart requirement.
+      if (minMinDartVersion != null) {
+        final Version? constraintMin =
+            dartConstraint is VersionRange ? dartConstraint.min : null;
+        if ((constraintMin ?? Version(0, 0, 0)) < minMinDartVersion) {
+          return 'Minimum allowed Dart version $constraintMin is less than $minMinDartVersion';
+        }
+      }
+    }
+
+    return null;
+  }
 }
diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml
index abf2a61..a8df2a9 100644
--- a/script/tool/pubspec.yaml
+++ b/script/tool/pubspec.yaml
@@ -1,7 +1,7 @@
 name: flutter_plugin_tools
 description: Productivity utils for flutter/plugins and flutter/packages
 repository: https://github.com/flutter/plugins/tree/main/script/tool
-version: 0.13.3
+version: 0.13.4
 
 dependencies:
   args: ^2.1.0
diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart
index 2c254ca..7a9c0ce 100644
--- a/script/tool/test/pubspec_check_command_test.dart
+++ b/script/tool/test/pubspec_check_command_test.dart
@@ -60,12 +60,16 @@
 ''';
 }
 
-String _environmentSection() {
-  return '''
-environment:
-  sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=2.0.0"
-''';
+String _environmentSection({
+  String dartConstraint = '>=2.12.0 <3.0.0',
+  String? flutterConstraint = '>=2.0.0',
+}) {
+  return <String>[
+    'environment:',
+    '  sdk: "$dartConstraint"',
+    if (flutterConstraint != null) '  flutter: "$flutterConstraint"',
+    '',
+  ].join('\n');
 }
 
 String _flutterSection({
@@ -931,6 +935,163 @@
         ]),
       );
     });
+
+    test('fails when a Flutter package has a too-low minimum Flutter version',
+        () async {
+      final RepositoryPackage package = createFakePackage(
+          'a_package', packagesDir,
+          isFlutter: true, examples: <String>[]);
+
+      package.pubspecFile.writeAsStringSync('''
+${_headerSection('a_package')}
+${_environmentSection(flutterConstraint: '>=2.10.0')}
+${_dependenciesSection()}
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(runner, <String>[
+        'pubspec-check',
+        '--min-min-flutter-version',
+        '3.0.0'
+      ], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Minimum allowed Flutter version 2.10.0 is less than 3.0.0'),
+        ]),
+      );
+    });
+
+    test(
+        'passes when a Flutter package requires exactly the minimum Flutter version',
+        () async {
+      final RepositoryPackage package = createFakePackage(
+          'a_package', packagesDir,
+          isFlutter: true, examples: <String>[]);
+
+      package.pubspecFile.writeAsStringSync('''
+${_headerSection('a_package')}
+${_environmentSection(flutterConstraint: '>=3.0.0')}
+${_dependenciesSection()}
+''');
+
+      final List<String> output = await runCapturingPrint(runner,
+          <String>['pubspec-check', '--min-min-flutter-version', '3.0.0']);
+
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Running for a_package...'),
+          contains('No issues found!'),
+        ]),
+      );
+    });
+
+    test(
+        'passes when a Flutter package requires a higher minimum Flutter version',
+        () async {
+      final RepositoryPackage package = createFakePackage(
+          'a_package', packagesDir,
+          isFlutter: true, examples: <String>[]);
+
+      package.pubspecFile.writeAsStringSync('''
+${_headerSection('a_package')}
+${_environmentSection(flutterConstraint: '>=3.3.0')}
+${_dependenciesSection()}
+''');
+
+      final List<String> output = await runCapturingPrint(runner,
+          <String>['pubspec-check', '--min-min-flutter-version', '3.0.0']);
+
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Running for a_package...'),
+          contains('No issues found!'),
+        ]),
+      );
+    });
+
+    test('fails when a non-Flutter package has a too-low minimum Dart version',
+        () async {
+      final RepositoryPackage package =
+          createFakePackage('a_package', packagesDir, examples: <String>[]);
+
+      package.pubspecFile.writeAsStringSync('''
+${_headerSection('a_package')}
+${_environmentSection(dartConstraint: '>=2.14.0 <3.0.0', flutterConstraint: null)}
+${_dependenciesSection()}
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['pubspec-check', '--min-min-dart-version', '2.17.0'],
+          errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Minimum allowed Dart version 2.14.0 is less than 2.17.0'),
+        ]),
+      );
+    });
+
+    test(
+        'passes when a non-Flutter package requires exactly the minimum Dart version',
+        () async {
+      final RepositoryPackage package = createFakePackage(
+          'a_package', packagesDir,
+          isFlutter: true, examples: <String>[]);
+
+      package.pubspecFile.writeAsStringSync('''
+${_headerSection('a_package')}
+${_environmentSection(dartConstraint: '>=2.17.0 <3.0.0', flutterConstraint: null)}
+${_dependenciesSection()}
+''');
+
+      final List<String> output = await runCapturingPrint(runner,
+          <String>['pubspec-check', '--min-min-dart-version', '2.17.0']);
+
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Running for a_package...'),
+          contains('No issues found!'),
+        ]),
+      );
+    });
+
+    test(
+        'passes when a non-Flutter package requires a higher minimum Dart version',
+        () async {
+      final RepositoryPackage package = createFakePackage(
+          'a_package', packagesDir,
+          isFlutter: true, examples: <String>[]);
+
+      package.pubspecFile.writeAsStringSync('''
+${_headerSection('a_package')}
+${_environmentSection(dartConstraint: '>=2.18.0 <3.0.0', flutterConstraint: null)}
+${_dependenciesSection()}
+''');
+
+      final List<String> output = await runCapturingPrint(runner,
+          <String>['pubspec-check', '--min-min-dart-version', '2.17.0']);
+
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Running for a_package...'),
+          contains('No issues found!'),
+        ]),
+      );
+    });
   });
 
   group('test pubspec_check_command on Windows', () {