blob: f3351a322ff002a081bd971f6395782ad6638c89 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Sarah Zakarias49ba9742017-09-26 14:48:52 +02002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Todd Volkertc11f1742018-01-26 11:35:38 -08005import 'package:meta/meta.dart';
KyleWong4b4a94002019-02-13 23:48:03 +08006import 'package:pub_semver/pub_semver.dart';
Sarah Zakarias49ba9742017-09-26 14:48:52 +02007import 'package:yaml/yaml.dart';
8
9import 'base/file_system.dart';
KyleWong1c0a06f2019-02-12 08:29:38 +080010import 'base/user_messages.dart';
Devon Carew9d9836f2018-07-09 12:22:46 -070011import 'base/utils.dart';
Sarah Zakarias49ba9742017-09-26 14:48:52 +020012import 'cache.dart';
Jonah Williamsee7a37f2020-01-06 11:04:20 -080013import 'globals.dart' as globals;
Kaushik Iskafc05c372019-08-29 21:51:31 -070014import 'plugins.dart';
Sarah Zakarias49ba9742017-09-26 14:48:52 +020015
Alexandre Ardhuinc02b6a82018-02-02 23:27:29 +010016/// A wrapper around the `flutter` section in the `pubspec.yaml` file.
Sarah Zakarias49ba9742017-09-26 14:48:52 +020017class FlutterManifest {
18 FlutterManifest._();
19
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +020020 /// Returns an empty manifest.
21 static FlutterManifest empty() {
Alexandre Ardhuind927c932018-09-12 08:29:29 +020022 final FlutterManifest manifest = FlutterManifest._();
Mikkel Nygaard Ravn651c5ab2018-08-02 19:16:32 +020023 manifest._descriptor = const <String, dynamic>{};
24 manifest._flutterDescriptor = const <String, dynamic>{};
25 return manifest;
26 }
27
Todd Volkertc11f1742018-01-26 11:35:38 -080028 /// Returns null on invalid manifest. Returns empty manifest on missing file.
Jonah Williams4ff46712019-04-29 08:21:32 -070029 static FlutterManifest createFromPath(String path) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -080030 if (path == null || !globals.fs.isFileSync(path)) {
Todd Volkertc11f1742018-01-26 11:35:38 -080031 return _createFromYaml(null);
Zachary Andersone2340c62019-09-13 14:51:35 -070032 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -080033 final String manifest = globals.fs.file(path).readAsStringSync();
Todd Volkertc11f1742018-01-26 11:35:38 -080034 return createFromString(manifest);
Sarah Zakarias49ba9742017-09-26 14:48:52 +020035 }
Todd Volkertc11f1742018-01-26 11:35:38 -080036
Sarah Zakarias49ba9742017-09-26 14:48:52 +020037 /// Returns null on missing or invalid manifest
Todd Volkertc11f1742018-01-26 11:35:38 -080038 @visibleForTesting
Jonah Williams4ff46712019-04-29 08:21:32 -070039 static FlutterManifest createFromString(String manifest) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +010040 return _createFromYaml(loadYaml(manifest) as YamlMap);
Sarah Zakarias49ba9742017-09-26 14:48:52 +020041 }
42
Alexandre Ardhuinadc73512019-11-19 07:57:42 +010043 static FlutterManifest _createFromYaml(YamlMap yamlDocument) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +020044 final FlutterManifest pubspec = FlutterManifest._();
Zachary Andersone2340c62019-09-13 14:51:35 -070045 if (yamlDocument != null && !_validate(yamlDocument)) {
Sarah Zakarias49ba9742017-09-26 14:48:52 +020046 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -070047 }
Sarah Zakarias49ba9742017-09-26 14:48:52 +020048
Devon Carew9d9836f2018-07-09 12:22:46 -070049 final Map<dynamic, dynamic> yamlMap = yamlDocument;
50 if (yamlMap != null) {
51 pubspec._descriptor = yamlMap.cast<String, dynamic>();
52 } else {
53 pubspec._descriptor = <String, dynamic>{};
54 }
55
Alexandre Ardhuinadc73512019-11-19 07:57:42 +010056 final Map<dynamic, dynamic> flutterMap = pubspec._descriptor['flutter'] as Map<dynamic, dynamic>;
Devon Carew9d9836f2018-07-09 12:22:46 -070057 if (flutterMap != null) {
58 pubspec._flutterDescriptor = flutterMap.cast<String, dynamic>();
59 } else {
60 pubspec._flutterDescriptor = <String, dynamic>{};
61 }
62
Sarah Zakarias49ba9742017-09-26 14:48:52 +020063 return pubspec;
64 }
65
66 /// A map representation of the entire `pubspec.yaml` file.
67 Map<String, dynamic> _descriptor;
68
69 /// A map representation of the `flutter` section in the `pubspec.yaml` file.
70 Map<String, dynamic> _flutterDescriptor;
71
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +020072 /// True if the `pubspec.yaml` file does not exist.
Sarah Zakarias3cbbbf02017-09-26 23:14:20 +020073 bool get isEmpty => _descriptor.isEmpty;
74
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +020075 /// The string value of the top-level `name` property in the `pubspec.yaml` file.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +010076 String get appName => _descriptor['name'] as String ?? '';
Sarah Zakarias49ba9742017-09-26 14:48:52 +020077
KyleWong1c0a06f2019-02-12 08:29:38 +080078 // Flag to avoid printing multiple invalid version messages.
79 bool _hasShowInvalidVersionMsg = false;
80
Ralph Bergmannc65e9d12018-05-30 16:51:25 +020081 /// The version String from the `pubspec.yaml` file.
82 /// Can be null if it isn't set or has a wrong format.
83 String get appVersion {
KyleWong4b4a94002019-02-13 23:48:03 +080084 final String verStr = _descriptor['version']?.toString();
85 if (verStr == null) {
86 return null;
87 }
88
89 Version version;
90 try {
91 version = Version.parse(verStr);
92 } on Exception {
93 if (!_hasShowInvalidVersionMsg) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -080094 globals.printStatus(userMessages.invalidVersionSettingHintMessage(verStr), emphasis: true);
KyleWong1c0a06f2019-02-12 08:29:38 +080095 _hasShowInvalidVersionMsg = true;
96 }
97 }
KyleWong4b4a94002019-02-13 23:48:03 +080098 return version?.toString();
Ralph Bergmannc65e9d12018-05-30 16:51:25 +020099 }
100
101 /// The build version name from the `pubspec.yaml` file.
102 /// Can be null if version isn't set or has a wrong format.
103 String get buildName {
Zachary Andersone2340c62019-09-13 14:51:35 -0700104 if (appVersion != null && appVersion.contains('+')) {
Ralph Bergmannc65e9d12018-05-30 16:51:25 +0200105 return appVersion.split('+')?.elementAt(0);
Zachary Andersone2340c62019-09-13 14:51:35 -0700106 }
107 return appVersion;
Ralph Bergmannc65e9d12018-05-30 16:51:25 +0200108 }
109
110 /// The build version number from the `pubspec.yaml` file.
111 /// Can be null if version isn't set or has a wrong format.
KyleWong4b4a94002019-02-13 23:48:03 +0800112 String get buildNumber {
Ralph Bergmannc65e9d12018-05-30 16:51:25 +0200113 if (appVersion != null && appVersion.contains('+')) {
114 final String value = appVersion.split('+')?.elementAt(1);
KyleWong4b4a94002019-02-13 23:48:03 +0800115 return value;
Ralph Bergmannc65e9d12018-05-30 16:51:25 +0200116 } else {
117 return null;
118 }
119 }
120
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200121 bool get usesMaterialDesign {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100122 return _flutterDescriptor['uses-material-design'] as bool ?? false;
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200123 }
124
Josh Burtond0e45a22019-06-01 13:33:02 +1200125 /// True if this Flutter module should use AndroidX dependencies.
126 ///
127 /// If false the deprecated Android Support library will be used.
128 bool get usesAndroidX {
Jenn Magder7d8f8202019-11-26 14:06:31 -0800129 if (_flutterDescriptor.containsKey('module')) {
130 return _flutterDescriptor['module']['androidX'] as bool;
131 }
132 return false;
Josh Burtond0e45a22019-06-01 13:33:02 +1200133 }
134
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700135 /// True if this manifest declares a Flutter module project.
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200136 ///
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700137 /// A Flutter project is considered a module when it has a `module:`
138 /// descriptor. A Flutter module project supports integration into an
139 /// existing host app, and has managed platform host code.
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200140 ///
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700141 /// Such a project can be created using `flutter create -t module`.
142 bool get isModule => _flutterDescriptor.containsKey('module');
Mikkel Nygaard Ravn651c5ab2018-08-02 19:16:32 +0200143
144 /// True if this manifest declares a Flutter plugin project.
145 ///
146 /// A Flutter project is considered a plugin when it has a `plugin:`
147 /// descriptor. A Flutter plugin project wraps custom Android and/or
148 /// iOS code in a Dart interface for consumption by other Flutter app
149 /// projects.
150 ///
151 /// Such a project can be created using `flutter create -t plugin`.
152 bool get isPlugin => _flutterDescriptor.containsKey('plugin');
153
154 /// Returns the Android package declared by this manifest in its
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700155 /// module or plugin descriptor. Returns null, if there is no
Mikkel Nygaard Ravn651c5ab2018-08-02 19:16:32 +0200156 /// such declaration.
157 String get androidPackage {
Zachary Andersone2340c62019-09-13 14:51:35 -0700158 if (isModule) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100159 return _flutterDescriptor['module']['androidPackage'] as String;
Zachary Andersone2340c62019-09-13 14:51:35 -0700160 }
Kaushik Iskafc05c372019-08-29 21:51:31 -0700161 if (isPlugin) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100162 final YamlMap plugin = _flutterDescriptor['plugin'] as YamlMap;
Kaushik Iskafc05c372019-08-29 21:51:31 -0700163 if (plugin.containsKey('platforms')) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100164 return plugin['platforms']['android']['package'] as String;
Kaushik Iskafc05c372019-08-29 21:51:31 -0700165 } else {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100166 return plugin['androidPackage'] as String;
Kaushik Iskafc05c372019-08-29 21:51:31 -0700167 }
168 }
Mikkel Nygaard Ravn651c5ab2018-08-02 19:16:32 +0200169 return null;
170 }
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200171
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200172 /// Returns the iOS bundle identifier declared by this manifest in its
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700173 /// module descriptor. Returns null if there is no such declaration.
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200174 String get iosBundleIdentifier {
Zachary Andersone2340c62019-09-13 14:51:35 -0700175 if (isModule) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100176 return _flutterDescriptor['module']['iosBundleIdentifier'] as String;
Zachary Andersone2340c62019-09-13 14:51:35 -0700177 }
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200178 return null;
179 }
180
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200181 List<Map<String, dynamic>> get fontsDescriptor {
Jason Simmons3581b3a2018-10-01 14:14:48 -0700182 return fonts.map((Font font) => font.descriptor).toList();
183 }
184
185 List<Map<String, dynamic>> get _rawFontsDescriptor {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100186 final List<dynamic> fontList = _flutterDescriptor['fonts'] as List<dynamic>;
Devon Carew9d9836f2018-07-09 12:22:46 -0700187 return fontList == null
188 ? const <Map<String, dynamic>>[]
189 : fontList.map<Map<String, dynamic>>(castStringKeyedMap).toList();
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200190 }
191
Jonah Williams0af2a842019-10-02 12:45:51 -0700192 List<Uri> get assets => _assets ??= _computeAssets();
193 List<Uri> _assets;
194 List<Uri> _computeAssets() {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100195 final List<dynamic> assets = _flutterDescriptor['assets'] as List<dynamic>;
Devon Carew9d9836f2018-07-09 12:22:46 -0700196 if (assets == null) {
197 return const <Uri>[];
198 }
Jonah Williams0af2a842019-10-02 12:45:51 -0700199 final List<Uri> results = <Uri>[];
200 for (Object asset in assets) {
201 if (asset is! String || asset == null || asset == '') {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800202 globals.printError('Asset manifest contains a null or empty uri.');
Jonah Williams0af2a842019-10-02 12:45:51 -0700203 continue;
204 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100205 final String stringAsset = asset as String;
Jonah Williams0af2a842019-10-02 12:45:51 -0700206 try {
207 results.add(Uri.parse(Uri.encodeFull(stringAsset)));
208 } on FormatException {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800209 globals.printError('Asset manifest contains invalid uri: $asset.');
Jonah Williams0af2a842019-10-02 12:45:51 -0700210 }
211 }
212 return results;
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200213 }
214
215 List<Font> _fonts;
216
217 List<Font> get fonts {
218 _fonts ??= _extractFonts();
219 return _fonts;
220 }
221
222 List<Font> _extractFonts() {
Zachary Andersone2340c62019-09-13 14:51:35 -0700223 if (!_flutterDescriptor.containsKey('fonts')) {
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200224 return <Font>[];
Zachary Andersone2340c62019-09-13 14:51:35 -0700225 }
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200226
227 final List<Font> fonts = <Font>[];
Jason Simmons3581b3a2018-10-01 14:14:48 -0700228 for (Map<String, dynamic> fontFamily in _rawFontsDescriptor) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100229 final YamlList fontFiles = fontFamily['fonts'] as YamlList;
230 final String familyName = fontFamily['family'] as String;
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200231 if (familyName == null) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800232 globals.printError('Warning: Missing family name for font.', emphasis: true);
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200233 continue;
234 }
235 if (fontFiles == null) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800236 globals.printError('Warning: No fonts specified for font $familyName', emphasis: true);
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200237 continue;
238 }
239
240 final List<FontAsset> fontAssets = <FontAsset>[];
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100241 for (Map<dynamic, dynamic> fontFile in fontFiles.cast<Map<dynamic, dynamic>>()) {
242 final String asset = fontFile['asset'] as String;
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200243 if (asset == null) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800244 globals.printError('Warning: Missing asset in fonts for $familyName', emphasis: true);
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200245 continue;
246 }
247
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200248 fontAssets.add(FontAsset(
Michael Goderbauer84580b52018-01-24 13:16:23 -0800249 Uri.parse(asset),
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100250 weight: fontFile['weight'] as int,
251 style: fontFile['style'] as String,
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200252 ));
253 }
Zachary Andersone2340c62019-09-13 14:51:35 -0700254 if (fontAssets.isNotEmpty) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100255 fonts.add(Font(fontFamily['family'] as String, fontAssets));
Zachary Andersone2340c62019-09-13 14:51:35 -0700256 }
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200257 }
258 return fonts;
259 }
260}
261
262class Font {
Alexandre Ardhuin15601fe2017-11-08 22:59:49 +0100263 Font(this.familyName, this.fontAssets)
264 : assert(familyName != null),
265 assert(fontAssets != null),
266 assert(fontAssets.isNotEmpty);
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200267
268 final String familyName;
269 final List<FontAsset> fontAssets;
270
271 Map<String, dynamic> get descriptor {
272 return <String, dynamic>{
273 'family': familyName,
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200274 'fonts': fontAssets.map<Map<String, dynamic>>((FontAsset a) => a.descriptor).toList(),
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200275 };
276 }
277
278 @override
279 String toString() => '$runtimeType(family: $familyName, assets: $fontAssets)';
280}
281
282class FontAsset {
Michael Goderbauer84580b52018-01-24 13:16:23 -0800283 FontAsset(this.assetUri, {this.weight, this.style})
284 : assert(assetUri != null);
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200285
Michael Goderbauer84580b52018-01-24 13:16:23 -0800286 final Uri assetUri;
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200287 final int weight;
288 final String style;
289
290 Map<String, dynamic> get descriptor {
291 final Map<String, dynamic> descriptor = <String, dynamic>{};
Zachary Andersone2340c62019-09-13 14:51:35 -0700292 if (weight != null) {
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200293 descriptor['weight'] = weight;
Zachary Andersone2340c62019-09-13 14:51:35 -0700294 }
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200295
Zachary Andersone2340c62019-09-13 14:51:35 -0700296 if (style != null) {
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200297 descriptor['style'] = style;
Zachary Andersone2340c62019-09-13 14:51:35 -0700298 }
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200299
Michael Goderbauer84580b52018-01-24 13:16:23 -0800300 descriptor['asset'] = assetUri.path;
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200301 return descriptor;
302 }
303
304 @override
Michael Goderbauer84580b52018-01-24 13:16:23 -0800305 String toString() => '$runtimeType(asset: ${assetUri.path}, weight; $weight, style: $style)';
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200306}
307
fmatosqg197d4312018-05-21 10:52:33 +1000308@visibleForTesting
309String buildSchemaDir(FileSystem fs) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800310 return globals.fs.path.join(
311 globals.fs.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema',
fmatosqg197d4312018-05-21 10:52:33 +1000312 );
313}
314
315@visibleForTesting
316String buildSchemaPath(FileSystem fs) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800317 return globals.fs.path.join(
fmatosqg197d4312018-05-21 10:52:33 +1000318 buildSchemaDir(fs),
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200319 'pubspec_yaml.json',
320 );
fmatosqg197d4312018-05-21 10:52:33 +1000321}
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200322
Dan Field4ed096b2019-02-20 15:38:58 -0800323/// This method should be kept in sync with the schema in
324/// `$FLUTTER_ROOT/packages/flutter_tools/schema/pubspec_yaml.json`,
Greg Spencera60bf8e2019-11-22 08:43:55 -0800325/// but avoid introducing dependencies on packages for simple validation.
Jonah Williams4ff46712019-04-29 08:21:32 -0700326bool _validate(YamlMap manifest) {
Dan Field4ed096b2019-02-20 15:38:58 -0800327 final List<String> errors = <String>[];
328 for (final MapEntry<dynamic, dynamic> kvp in manifest.entries) {
329 if (kvp.key is! String) {
330 errors.add('Expected YAML key to be a a string, but got ${kvp.key}.');
331 continue;
332 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100333 switch (kvp.key as String) {
Dan Field4ed096b2019-02-20 15:38:58 -0800334 case 'name':
335 if (kvp.value is! String) {
336 errors.add('Expected "${kvp.key}" to be a string, but got ${kvp.value}.');
337 }
338 break;
339 case 'flutter':
340 if (kvp.value == null) {
341 continue;
342 }
343 if (kvp.value is! YamlMap) {
344 errors.add('Expected "${kvp.key}" section to be an object or null, but got ${kvp.value}.');
345 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100346 _validateFlutter(kvp.value as YamlMap, errors);
Dan Field4ed096b2019-02-20 15:38:58 -0800347 break;
348 default:
349 // additionalProperties are allowed.
350 break;
351 }
352 }
fmatosqg197d4312018-05-21 10:52:33 +1000353
Dan Field4ed096b2019-02-20 15:38:58 -0800354 if (errors.isNotEmpty) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800355 globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
356 globals.printError(errors.join('\n'));
Sarah Zakarias49ba9742017-09-26 14:48:52 +0200357 return false;
358 }
Dan Field4ed096b2019-02-20 15:38:58 -0800359
360 return true;
Michael Goderbauer84580b52018-01-24 13:16:23 -0800361}
Dan Field4ed096b2019-02-20 15:38:58 -0800362
363void _validateFlutter(YamlMap yaml, List<String> errors) {
364 if (yaml == null || yaml.entries == null) {
365 return;
366 }
367 for (final MapEntry<dynamic, dynamic> kvp in yaml.entries) {
368 if (kvp.key is! String) {
369 errors.add('Expected YAML key to be a a string, but got ${kvp.key} (${kvp.value.runtimeType}).');
370 continue;
371 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100372 switch (kvp.key as String) {
Dan Field4ed096b2019-02-20 15:38:58 -0800373 case 'uses-material-design':
374 if (kvp.value is! bool) {
375 errors.add('Expected "${kvp.key}" to be a bool, but got ${kvp.value} (${kvp.value.runtimeType}).');
376 }
377 break;
378 case 'assets':
379 case 'services':
380 if (kvp.value is! YamlList || kvp.value[0] is! String) {
381 errors.add('Expected "${kvp.key}" to be a list, but got ${kvp.value} (${kvp.value.runtimeType}).');
382 }
383 break;
384 case 'fonts':
385 if (kvp.value is! YamlList || kvp.value[0] is! YamlMap) {
386 errors.add('Expected "${kvp.key}" to be a list, but got ${kvp.value} (${kvp.value.runtimeType}).');
Zachary Andersond2206312019-08-14 07:57:30 -0700387 } else {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100388 _validateFonts(kvp.value as YamlList, errors);
Dan Field4ed096b2019-02-20 15:38:58 -0800389 }
Dan Field4ed096b2019-02-20 15:38:58 -0800390 break;
391 case 'module':
392 if (kvp.value is! YamlMap) {
393 errors.add('Expected "${kvp.key}" to be an object, but got ${kvp.value} (${kvp.value.runtimeType}).');
394 }
395
Josh Burtond0e45a22019-06-01 13:33:02 +1200396 if (kvp.value['androidX'] != null && kvp.value['androidX'] is! bool) {
397 errors.add('The "androidX" value must be a bool if set.');
398 }
Dan Field4ed096b2019-02-20 15:38:58 -0800399 if (kvp.value['androidPackage'] != null && kvp.value['androidPackage'] is! String) {
400 errors.add('The "androidPackage" value must be a string if set.');
401 }
402 if (kvp.value['iosBundleIdentifier'] != null && kvp.value['iosBundleIdentifier'] is! String) {
403 errors.add('The "iosBundleIdentifier" section must be a string if set.');
404 }
405 break;
406 case 'plugin':
Jonah Williams6c91a132019-10-21 16:46:07 -0700407 if (kvp.value is! YamlMap || kvp.value == null) {
Dan Field4ed096b2019-02-20 15:38:58 -0800408 errors.add('Expected "${kvp.key}" to be an object, but got ${kvp.value} (${kvp.value.runtimeType}).');
Jonah Williams6c91a132019-10-21 16:46:07 -0700409 break;
Dan Field4ed096b2019-02-20 15:38:58 -0800410 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100411 final List<String> pluginErrors = Plugin.validatePluginYaml(kvp.value as YamlMap);
Kaushik Iskafc05c372019-08-29 21:51:31 -0700412 errors.addAll(pluginErrors);
Dan Field4ed096b2019-02-20 15:38:58 -0800413 break;
414 default:
415 errors.add('Unexpected child "${kvp.key}" found under "flutter".');
416 break;
417 }
418 }
419}
420
421void _validateFonts(YamlList fonts, List<String> errors) {
422 if (fonts == null) {
423 return;
424 }
Phil Quitslund802eca22019-03-06 11:05:16 -0800425 const Set<int> fontWeights = <int>{
Dan Field4ed096b2019-02-20 15:38:58 -0800426 100, 200, 300, 400, 500, 600, 700, 800, 900,
Phil Quitslund802eca22019-03-06 11:05:16 -0800427 };
Zachary Anderson500d7c52019-08-05 13:14:57 -0700428 for (final dynamic fontListEntry in fonts) {
429 if (fontListEntry is! YamlMap) {
430 errors.add('Unexpected child "$fontListEntry" found under "fonts". Expected a map.');
431 continue;
432 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100433 final YamlMap fontMap = fontListEntry as YamlMap;
Dan Field4ed096b2019-02-20 15:38:58 -0800434 for (dynamic key in fontMap.keys.where((dynamic key) => key != 'family' && key != 'fonts')) {
435 errors.add('Unexpected child "$key" found under "fonts".');
436 }
437 if (fontMap['family'] != null && fontMap['family'] is! String) {
438 errors.add('Font family must either be null or a String.');
439 }
Dan Field4ed096b2019-02-20 15:38:58 -0800440 if (fontMap['fonts'] == null) {
441 continue;
Emmanuel Garcia3e65bb02019-05-01 09:49:39 -0700442 } else if (fontMap['fonts'] is! YamlList) {
443 errors.add('Expected "fonts" to either be null or a list.');
444 continue;
Dan Field4ed096b2019-02-20 15:38:58 -0800445 }
Zachary Andersonaf93b6a2019-10-11 12:17:37 -0700446 for (final dynamic fontListItem in fontMap['fonts']) {
447 if (fontListItem is! YamlMap) {
448 errors.add('Expected "fonts" to be a list of maps.');
449 continue;
450 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100451 final YamlMap fontMapList = fontListItem as YamlMap;
Zachary Andersonaf93b6a2019-10-11 12:17:37 -0700452 for (final MapEntry<dynamic, dynamic> kvp in fontMapList.entries) {
Dan Field4ed096b2019-02-20 15:38:58 -0800453 if (kvp.key is! String) {
454 errors.add('Expected "${kvp.key}" under "fonts" to be a string.');
455 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100456 switch(kvp.key as String) {
Dan Field4ed096b2019-02-20 15:38:58 -0800457 case 'asset':
458 if (kvp.value is! String) {
459 errors.add('Expected font asset ${kvp.value} ((${kvp.value.runtimeType})) to be a string.');
460 }
461 break;
462 case 'weight':
463 if (!fontWeights.contains(kvp.value)) {
464 errors.add('Invalid value ${kvp.value} ((${kvp.value.runtimeType})) for font -> weight.');
465 }
466 break;
467 case 'style':
468 if (kvp.value != 'normal' && kvp.value != 'italic') {
469 errors.add('Invalid value ${kvp.value} ((${kvp.value.runtimeType})) for font -> style.');
470 }
471 break;
472 default:
473 errors.add('Unexpected key ${kvp.key} ((${kvp.value.runtimeType})) under font.');
474 break;
475 }
476 }
477 }
478 }
Alexandre Ardhuin4c1f4d12019-03-06 09:37:32 +0100479}