44
55import 'dart:convert' ;
66import 'dart:io' ;
7+
8+ import 'package:pub_semver/pub_semver.dart' ;
79import 'package:test/test.dart' ;
10+ import 'package:yaml/yaml.dart' ;
11+
12+ /// These are the sdk packages living inside the Flutter SDK that apps can
13+ /// depend on.
14+ const List <String > sdkPackages = < String > ['flutter' , 'flutter_test' , 'flutter_localizations' ];
815
916/// List of allowed external packages that Flutter framework packages can depend on.
1017/// Subject to review and approval when adding new packages.
@@ -61,11 +68,9 @@ Future<void> main() async {
6168 );
6269 final packageGraph = jsonDecode (packageGraphFile.readAsStringSync ()) as Map <String , Object ?>;
6370
64- // These are the sdk packages that Flutter apps can depend on.
65- final roots = < String > ['flutter' , 'flutter_test' , 'flutter_localizations' ];
6671 final allowedPackages = < String > {
6772 // Allow depending on other Flutter framework sdk packages.
68- ...roots ,
73+ ...sdkPackages ,
6974 // Allow depending on allowed external packages.
7075 ...allowedExternalPackages,
7176 };
@@ -75,9 +80,9 @@ Future<void> main() async {
7580 (package! as Map <String , Object ?>)['name' ]! as String : package as Map <String , Object ?>,
7681 };
7782
78- // Do a transitive parse of the package graph rooted in `roots ` to find any
83+ // Do a transitive parse of the package graph rooted in `sdkPackages ` to find any
7984 // disallowed dependencies.
80- final toVisit = < String ? > [...roots ];
85+ final toVisit = < String ? > [...sdkPackages ];
8186 final visited = < String > {};
8287 final stack = < String > [];
8388 while (toVisit.isNotEmpty) {
@@ -118,4 +123,81 @@ See: packages/flutter_tools/test/integration.shard/only_allowed_dependencies_tes
118123''' );
119124 }
120125 });
126+
127+ test ('All dependencies are locked to their lower bound' , () {
128+ // This test will ensure that the lower bound of the version constraints from
129+ // the sdk packages, are all equal to the version of the package in the lockfile.
130+ //
131+ // This is to ensure that we test against the lower bounds of our dependencies.
132+ // So we don't accidentally rely on features added after the lower bound version.
133+ final String flutterRoot = Platform .environment['FLUTTER_ROOT' ]! ;
134+ final flutterRootDirectory = Directory (flutterRoot);
135+ // These are the sdk packages that Flutter apps can depend on.
136+ final lockfile = File .fromUri (flutterRootDirectory.uri.resolve ('pubspec.lock' ));
137+ final lockfileJson = loadYaml (lockfile.readAsStringSync ()) as YamlMap ;
138+ final lockedDependencies = lockfileJson['packages' ]! as YamlMap ;
139+
140+ for (final String sdkPackage in sdkPackages) {
141+ final pubspecFile = File .fromUri (
142+ flutterRootDirectory.uri.resolve ('packages/$sdkPackage /pubspec.yaml' ),
143+ );
144+ final pubspec = loadYaml (pubspecFile.readAsStringSync ()) as YamlMap ;
145+ final dependencies = pubspec['dependencies' ]! as YamlMap ;
146+ for (final MapEntry <Object ?, Object ?> dependency in dependencies.entries) {
147+ final dependencyName = dependency.key! as String ;
148+ final Object ? descriptor = dependency.value;
149+ final VersionConstraint constraint;
150+ if (descriptor is String ) {
151+ constraint = VersionConstraint .parse (descriptor);
152+ } else if (descriptor is YamlMap ) {
153+ if (descriptor['sdk' ] == 'flutter' ) {
154+ // These are allowed.
155+ continue ;
156+ }
157+ if (descriptor['hosted' ] == null ) {
158+ fail ('''
159+ Dependency "$dependencyName " in "${pubspecFile .path }" is not a hosted or sdkpackage.
160+ ''' );
161+ }
162+ final versionString = descriptor['version' ] as String ? ;
163+ if (versionString == null ) {
164+ fail ('''
165+ Dependency "$dependencyName " in "${pubspecFile .path }" has no version constraint.
166+ ''' );
167+ }
168+ constraint = VersionConstraint .parse (versionString);
169+ } else {
170+ fail ('''
171+ Dependency "$dependencyName " in "${pubspecFile .path }" is not a hosted or sdk package.
172+ ''' );
173+ }
174+ if (constraint is ! VersionRange ) {
175+ fail ('''
176+ Dependency "$dependencyName " in "${pubspecFile .path }" is not a version range.
177+ ''' );
178+ }
179+ if (constraint.min == null ) {
180+ fail ('''
181+ Dependency "$dependencyName " in "${pubspecFile .path }" has no lower bound.
182+ ''' );
183+ }
184+ if (constraint.min != constraint.max) {
185+ fail ('''
186+ Dependency "$dependencyName " in "${pubspecFile .path }" is not locked at its lower bound.
187+ ''' );
188+ }
189+
190+ final currentVersion = Version .parse (
191+ (lockedDependencies[dependencyName]! as YamlMap )['version' ]! as String ,
192+ );
193+
194+ if (currentVersion != constraint.min) {
195+ fail ('''
196+ Dependency "$dependencyName " is not locked at its lower bound in "${pubspecFile .path }".
197+ Either upgrade the constraint to $currentVersion in pubspec.yaml or downgrade the version to ${constraint .min } in pubspec.lock.
198+ ''' );
199+ }
200+ }
201+ }
202+ });
121203}
0 commit comments