-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[ci/tool] Add external dependency validation #3466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
6899b5c
e9919ab
313c315
66d83f9
40670fe
7317df2
3c689d1
ec58b25
4df7923
081c18b
039bb2d
47963ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,4 +21,4 @@ dev_dependencies: | |
| sdk: flutter | ||
| integration_test: | ||
| sdk: flutter | ||
| mocktail: ^0.3.0 | ||
| mocktail: 0.3.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,4 +16,4 @@ dependencies: | |
| dev_dependencies: | ||
| flutter_test: | ||
| sdk: flutter | ||
| lcov_parser: ^0.1.2 | ||
| lcov_parser: 0.1.2 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # The list of external dependencies that are allowed as unpinned dependencies. | ||
| # See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies | ||
| # | ||
| # All entries here should have an explanation for why they are here. | ||
|
|
||
| # TODO(stuartmorgan): Eliminate this in favor of standardizing on | ||
| # mockito. | ||
| - mocktail | ||
|
|
||
| # Legacy allowances, for dependencies that predate the tooling. | ||
| # TODO(stuartmorgan): Audit these. See | ||
| # https://github.com/flutter/flutter/issues/122713 | ||
| - jwt_decoder | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - lcov_parser | ||
| - adaptive_dialog | ||
| - provider | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # The list of external dependencies that are allowed as pinned dependencies. | ||
| # See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies | ||
| # | ||
| # All entries here should have an explanation for why they are here, either | ||
| # via being part of one of the default-allowed groups, or a specific reason. | ||
|
|
||
| ## TODO(stuartmorgan): Move these to pinned-only. | ||
| # Google-owned | ||
| - file_testing | ||
| # Dart-owned | ||
| - mockito | ||
|
|
||
| ## Legacy allowances, for dependencies that predate the tooling. | ||
| # TODO(stuartmorgan): Audit these. See | ||
| # https://github.com/flutter/flutter/issues/122713 | ||
| - equatable | ||
| - xml | ||
|
|
||
| ## Explicit allowances | ||
|
|
||
| # Owned by individual Flutter Team members. | ||
| # Ideally we would not do this, since there's no clear plan for what | ||
| # would happen if the individuals left the Flutter Team, and the | ||
| # repositories may or may not meet Flutter's security standards. Be | ||
| # cautious about adding to this list. | ||
| - build_verify | ||
| - google_maps | ||
| - path_to_regexp | ||
| - source_gen_test | ||
| - win32 | ||
|
|
||
| ## Allowed by default | ||
|
|
||
| # Dart-team-owned packages | ||
| - analyzer | ||
| - args | ||
| - async | ||
| - build | ||
| - build_config | ||
| - build_runner | ||
| - collection | ||
| - convert | ||
| - crypto | ||
| - fake_async | ||
| - ffi | ||
| - gcloud | ||
| - html | ||
| - http | ||
| - intl | ||
| - js | ||
| - json_serializable | ||
| - lints | ||
| - logging | ||
| - markdown | ||
| - meta | ||
| - path | ||
| - shelf | ||
| - shelf_static | ||
| - source_gen | ||
| - stream_transform | ||
| - test | ||
| - test_api | ||
| - vm_service | ||
| - wasm | ||
| - yaml | ||
| # Google-owned packages | ||
| - adaptive_navigation | ||
| - file | ||
| - googleapis | ||
| - googleapis_auth | ||
| - json_annotation | ||
| - platform | ||
| - process | ||
| - quiver | ||
| - sanitize_html | ||
| - source_helper | ||
| - vector_math | ||
| - webkit_inspection_protocol |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,8 +4,10 @@ | |
|
|
||
| import 'package:file/file.dart'; | ||
| import 'package:git/git.dart'; | ||
| import 'package:path/path.dart' as p; | ||
| import 'package:platform/platform.dart'; | ||
| import 'package:pub_semver/pub_semver.dart'; | ||
| import 'package:pubspec_parse/pubspec_parse.dart'; | ||
| import 'package:yaml/yaml.dart'; | ||
|
|
||
| import 'common/core.dart'; | ||
|
|
@@ -36,9 +38,24 @@ class PubspecCheckCommand extends PackageLoopingCommand { | |
| help: | ||
| 'The minimum Flutter version to allow as the minimum SDK constraint.', | ||
| ); | ||
| argParser.addMultiOption(_allowDependenciesFlag, | ||
| help: 'Packages (comma separated) that are allowed as dependencies or ' | ||
| 'dev_dependencies.\n\n' | ||
| 'Alternately, a list of one or more YAML files that contain a list ' | ||
| 'of allowed dependencies.', | ||
| defaultsTo: <String>[]); | ||
| argParser.addMultiOption(_allowPinnedDependenciesFlag, | ||
| help: 'Packages (comma separated) that are allowed as dependencies or ' | ||
| 'dev_dependencies only if pinned to an exact version.\n\n' | ||
| 'Alternately, a list of one or more YAML files that contain a list ' | ||
| 'of allowed pinned dependencies.', | ||
| defaultsTo: <String>[]); | ||
| } | ||
|
|
||
| static const String _minMinFlutterVersionFlag = 'min-min-flutter-version'; | ||
| static const String _allowDependenciesFlag = 'allow-dependencies'; | ||
| static const String _allowPinnedDependenciesFlag = | ||
| 'allow-pinned-dependencies'; | ||
|
|
||
| // Section order for plugins. Because the 'flutter' section is critical | ||
| // information for plugins, and usually small, it goes near the top unlike in | ||
|
|
@@ -62,6 +79,13 @@ class PubspecCheckCommand extends PackageLoopingCommand { | |
| static const String _expectedIssueLinkFormat = | ||
| 'https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A'; | ||
|
|
||
| // The names of all published packages in the repository. | ||
| late final Set<String> _localPackages = <String>{}; | ||
|
|
||
| // Packages on the explicit allow list. | ||
| late final Set<String> _allowedUnpinnedPackages = <String>{}; | ||
| late final Set<String> _allowedPinnedPackages = <String>{}; | ||
|
|
||
| @override | ||
| final String name = 'pubspec-check'; | ||
|
|
||
|
|
@@ -76,6 +100,40 @@ class PubspecCheckCommand extends PackageLoopingCommand { | |
| PackageLoopingType get packageLoopingType => | ||
| PackageLoopingType.includeAllSubpackages; | ||
|
|
||
| @override | ||
| Future<void> initializeRun() async { | ||
| // Find all local, published packages. | ||
| await for (final File pubspecFile in packagesDir.parent | ||
| .list(recursive: true, followLinks: false) | ||
| .where((FileSystemEntity entity) => | ||
| entity is File && p.basename(entity.path) == 'pubspec.yaml') | ||
| .map((FileSystemEntity file) => file as File)) { | ||
stuartmorgan-g marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| final Pubspec? pubspec = _tryParsePubspec(pubspecFile.readAsStringSync()); | ||
| if (pubspec != null && pubspec.publishTo != 'none') { | ||
| _localPackages.add(pubspec.name); | ||
| } | ||
| } | ||
| // Load explicitly allowed packages. | ||
| _allowedUnpinnedPackages | ||
| .addAll(_getAllowedPackages(_allowDependenciesFlag)); | ||
| _allowedPinnedPackages | ||
| .addAll(_getAllowedPackages(_allowPinnedDependenciesFlag)); | ||
| } | ||
|
|
||
| Iterable<String> _getAllowedPackages(String flag) { | ||
| return getStringListArg(flag).expand<String>((String item) { | ||
| if (item.endsWith('.yaml')) { | ||
| final File file = packagesDir.fileSystem.file(item); | ||
| final Object? yaml = loadYaml(file.readAsStringSync()); | ||
| if (yaml == null) { | ||
| return <String>[]; | ||
| } | ||
| return (yaml as YamlList).toList().cast<String>(); | ||
| } | ||
| return <String>[item]; | ||
| }); | ||
| } | ||
|
|
||
| @override | ||
| Future<PackageResult> runForPackage(RepositoryPackage package) async { | ||
| final File pubspec = package.pubspecFile; | ||
|
|
@@ -139,6 +197,12 @@ class PubspecCheckCommand extends PackageLoopingCommand { | |
| } | ||
| } | ||
|
|
||
| final String? dependenciesError = _checkDependencies(pubspec); | ||
| if (dependenciesError != null) { | ||
| printError('$indentation$dependenciesError'); | ||
| passing = false; | ||
| } | ||
|
|
||
| // Ignore metadata that's only relevant for published packages if the | ||
| // packages is not intended for publishing. | ||
| if (pubspec.publishTo != 'none') { | ||
|
|
@@ -426,4 +490,51 @@ class PubspecCheckCommand extends PackageLoopingCommand { | |
| } | ||
| return result ?? Version.none; | ||
| } | ||
|
|
||
| // Validates the dependencies for a package, returning an error string if | ||
| // there are any that aren't allowed. | ||
| String? _checkDependencies(Pubspec pubspec) { | ||
| final Set<String> badDependencies = <String>{}; | ||
| for (final Map<String, Dependency> dependencies | ||
| in <Map<String, Dependency>>[ | ||
| pubspec.dependencies, | ||
| pubspec.devDependencies | ||
| ]) { | ||
| for (final MapEntry<String, Dependency> entry in dependencies.entries) { | ||
| final String name = entry.key; | ||
| if (!_allowDependency(name, entry.value)) { | ||
| badDependencies.add(name); | ||
| } | ||
| } | ||
| } | ||
| if (badDependencies.isEmpty) { | ||
| return null; | ||
| } | ||
| return 'The following unexpected non-local dependencies were found:\n' | ||
| '${badDependencies.map((String name) => ' $name').join('\n')}\n' | ||
| 'Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies ' | ||
| 'for more information and next steps.'; | ||
| } | ||
|
|
||
| // Checks whether a given dependency is allowed. | ||
| bool _allowDependency(String name, Dependency dependency) { | ||
|
||
| if (dependency is PathDependency || dependency is SdkDependency) { | ||
| return true; | ||
| } | ||
| if (_localPackages.contains(name) || | ||
| _allowedUnpinnedPackages.contains(name)) { | ||
| return true; | ||
| } | ||
| if (dependency is HostedDependency && | ||
| _allowedPinnedPackages.contains(name)) { | ||
| final VersionConstraint constraint = dependency.version; | ||
| if (constraint is VersionRange && | ||
| constraint.min != null && | ||
| constraint.max != null && | ||
| constraint.min == constraint.max) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(My bad, this should have never happened in the first place)