Ian Hickson | 449f4a6 | 2019-11-27 15:04:02 -0800 | [diff] [blame] | 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Todd Volkert | c11f174 | 2018-01-26 11:35:38 -0800 | [diff] [blame] | 5 | import 'package:meta/meta.dart'; |
KyleWong | 4b4a9400 | 2019-02-13 23:48:03 +0800 | [diff] [blame] | 6 | import 'package:pub_semver/pub_semver.dart'; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 7 | import 'package:yaml/yaml.dart'; |
| 8 | |
| 9 | import 'base/file_system.dart'; |
KyleWong | 1c0a06f | 2019-02-12 08:29:38 +0800 | [diff] [blame] | 10 | import 'base/user_messages.dart'; |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 11 | import 'base/utils.dart'; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 12 | import 'cache.dart'; |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 13 | import 'globals.dart' as globals; |
Kaushik Iska | fc05c37 | 2019-08-29 21:51:31 -0700 | [diff] [blame] | 14 | import 'plugins.dart'; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 15 | |
Alexandre Ardhuin | c02b6a8 | 2018-02-02 23:27:29 +0100 | [diff] [blame] | 16 | /// A wrapper around the `flutter` section in the `pubspec.yaml` file. |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 17 | class FlutterManifest { |
| 18 | FlutterManifest._(); |
| 19 | |
Mikkel Nygaard Ravn | b280074 | 2018-08-02 14:12:25 +0200 | [diff] [blame] | 20 | /// Returns an empty manifest. |
| 21 | static FlutterManifest empty() { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 22 | final FlutterManifest manifest = FlutterManifest._(); |
Mikkel Nygaard Ravn | 651c5ab | 2018-08-02 19:16:32 +0200 | [diff] [blame] | 23 | manifest._descriptor = const <String, dynamic>{}; |
| 24 | manifest._flutterDescriptor = const <String, dynamic>{}; |
| 25 | return manifest; |
| 26 | } |
| 27 | |
Todd Volkert | c11f174 | 2018-01-26 11:35:38 -0800 | [diff] [blame] | 28 | /// Returns null on invalid manifest. Returns empty manifest on missing file. |
Jonah Williams | 4ff4671 | 2019-04-29 08:21:32 -0700 | [diff] [blame] | 29 | static FlutterManifest createFromPath(String path) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 30 | if (path == null || !globals.fs.isFileSync(path)) { |
Todd Volkert | c11f174 | 2018-01-26 11:35:38 -0800 | [diff] [blame] | 31 | return _createFromYaml(null); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 32 | } |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 33 | final String manifest = globals.fs.file(path).readAsStringSync(); |
Todd Volkert | c11f174 | 2018-01-26 11:35:38 -0800 | [diff] [blame] | 34 | return createFromString(manifest); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 35 | } |
Todd Volkert | c11f174 | 2018-01-26 11:35:38 -0800 | [diff] [blame] | 36 | |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 37 | /// Returns null on missing or invalid manifest |
Todd Volkert | c11f174 | 2018-01-26 11:35:38 -0800 | [diff] [blame] | 38 | @visibleForTesting |
Jonah Williams | 4ff4671 | 2019-04-29 08:21:32 -0700 | [diff] [blame] | 39 | static FlutterManifest createFromString(String manifest) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 40 | return _createFromYaml(loadYaml(manifest) as YamlMap); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 41 | } |
| 42 | |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 43 | static FlutterManifest _createFromYaml(YamlMap yamlDocument) { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 44 | final FlutterManifest pubspec = FlutterManifest._(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 45 | if (yamlDocument != null && !_validate(yamlDocument)) { |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 46 | return null; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 47 | } |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 48 | |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 49 | 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 Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 56 | final Map<dynamic, dynamic> flutterMap = pubspec._descriptor['flutter'] as Map<dynamic, dynamic>; |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 57 | if (flutterMap != null) { |
| 58 | pubspec._flutterDescriptor = flutterMap.cast<String, dynamic>(); |
| 59 | } else { |
| 60 | pubspec._flutterDescriptor = <String, dynamic>{}; |
| 61 | } |
| 62 | |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 63 | 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 Ravn | a737c86 | 2018-08-10 21:49:24 +0200 | [diff] [blame] | 72 | /// True if the `pubspec.yaml` file does not exist. |
Sarah Zakarias | 3cbbbf0 | 2017-09-26 23:14:20 +0200 | [diff] [blame] | 73 | bool get isEmpty => _descriptor.isEmpty; |
| 74 | |
Mikkel Nygaard Ravn | a737c86 | 2018-08-10 21:49:24 +0200 | [diff] [blame] | 75 | /// The string value of the top-level `name` property in the `pubspec.yaml` file. |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 76 | String get appName => _descriptor['name'] as String ?? ''; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 77 | |
KyleWong | 1c0a06f | 2019-02-12 08:29:38 +0800 | [diff] [blame] | 78 | // Flag to avoid printing multiple invalid version messages. |
| 79 | bool _hasShowInvalidVersionMsg = false; |
| 80 | |
Ralph Bergmann | c65e9d1 | 2018-05-30 16:51:25 +0200 | [diff] [blame] | 81 | /// 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 { |
KyleWong | 4b4a9400 | 2019-02-13 23:48:03 +0800 | [diff] [blame] | 84 | 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 Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 94 | globals.printStatus(userMessages.invalidVersionSettingHintMessage(verStr), emphasis: true); |
KyleWong | 1c0a06f | 2019-02-12 08:29:38 +0800 | [diff] [blame] | 95 | _hasShowInvalidVersionMsg = true; |
| 96 | } |
| 97 | } |
KyleWong | 4b4a9400 | 2019-02-13 23:48:03 +0800 | [diff] [blame] | 98 | return version?.toString(); |
Ralph Bergmann | c65e9d1 | 2018-05-30 16:51:25 +0200 | [diff] [blame] | 99 | } |
| 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 Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 104 | if (appVersion != null && appVersion.contains('+')) { |
Ralph Bergmann | c65e9d1 | 2018-05-30 16:51:25 +0200 | [diff] [blame] | 105 | return appVersion.split('+')?.elementAt(0); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 106 | } |
| 107 | return appVersion; |
Ralph Bergmann | c65e9d1 | 2018-05-30 16:51:25 +0200 | [diff] [blame] | 108 | } |
| 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. |
KyleWong | 4b4a9400 | 2019-02-13 23:48:03 +0800 | [diff] [blame] | 112 | String get buildNumber { |
Ralph Bergmann | c65e9d1 | 2018-05-30 16:51:25 +0200 | [diff] [blame] | 113 | if (appVersion != null && appVersion.contains('+')) { |
| 114 | final String value = appVersion.split('+')?.elementAt(1); |
KyleWong | 4b4a9400 | 2019-02-13 23:48:03 +0800 | [diff] [blame] | 115 | return value; |
Ralph Bergmann | c65e9d1 | 2018-05-30 16:51:25 +0200 | [diff] [blame] | 116 | } else { |
| 117 | return null; |
| 118 | } |
| 119 | } |
| 120 | |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 121 | bool get usesMaterialDesign { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 122 | return _flutterDescriptor['uses-material-design'] as bool ?? false; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 123 | } |
| 124 | |
Josh Burton | d0e45a2 | 2019-06-01 13:33:02 +1200 | [diff] [blame] | 125 | /// 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 Magder | 7d8f820 | 2019-11-26 14:06:31 -0800 | [diff] [blame] | 129 | if (_flutterDescriptor.containsKey('module')) { |
| 130 | return _flutterDescriptor['module']['androidX'] as bool; |
| 131 | } |
| 132 | return false; |
Josh Burton | d0e45a2 | 2019-06-01 13:33:02 +1200 | [diff] [blame] | 133 | } |
| 134 | |
Greg Spencer | 0ff9e8a | 2018-10-10 11:01:40 -0700 | [diff] [blame] | 135 | /// True if this manifest declares a Flutter module project. |
Mikkel Nygaard Ravn | d89a6b5 | 2018-06-22 18:19:37 +0200 | [diff] [blame] | 136 | /// |
Greg Spencer | 0ff9e8a | 2018-10-10 11:01:40 -0700 | [diff] [blame] | 137 | /// 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 Ravn | d89a6b5 | 2018-06-22 18:19:37 +0200 | [diff] [blame] | 140 | /// |
Greg Spencer | 0ff9e8a | 2018-10-10 11:01:40 -0700 | [diff] [blame] | 141 | /// Such a project can be created using `flutter create -t module`. |
| 142 | bool get isModule => _flutterDescriptor.containsKey('module'); |
Mikkel Nygaard Ravn | 651c5ab | 2018-08-02 19:16:32 +0200 | [diff] [blame] | 143 | |
| 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 Spencer | 0ff9e8a | 2018-10-10 11:01:40 -0700 | [diff] [blame] | 155 | /// module or plugin descriptor. Returns null, if there is no |
Mikkel Nygaard Ravn | 651c5ab | 2018-08-02 19:16:32 +0200 | [diff] [blame] | 156 | /// such declaration. |
| 157 | String get androidPackage { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 158 | if (isModule) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 159 | return _flutterDescriptor['module']['androidPackage'] as String; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 160 | } |
Kaushik Iska | fc05c37 | 2019-08-29 21:51:31 -0700 | [diff] [blame] | 161 | if (isPlugin) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 162 | final YamlMap plugin = _flutterDescriptor['plugin'] as YamlMap; |
Kaushik Iska | fc05c37 | 2019-08-29 21:51:31 -0700 | [diff] [blame] | 163 | if (plugin.containsKey('platforms')) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 164 | return plugin['platforms']['android']['package'] as String; |
Kaushik Iska | fc05c37 | 2019-08-29 21:51:31 -0700 | [diff] [blame] | 165 | } else { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 166 | return plugin['androidPackage'] as String; |
Kaushik Iska | fc05c37 | 2019-08-29 21:51:31 -0700 | [diff] [blame] | 167 | } |
| 168 | } |
Mikkel Nygaard Ravn | 651c5ab | 2018-08-02 19:16:32 +0200 | [diff] [blame] | 169 | return null; |
| 170 | } |
Mikkel Nygaard Ravn | d89a6b5 | 2018-06-22 18:19:37 +0200 | [diff] [blame] | 171 | |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 172 | /// Returns the iOS bundle identifier declared by this manifest in its |
Greg Spencer | 0ff9e8a | 2018-10-10 11:01:40 -0700 | [diff] [blame] | 173 | /// module descriptor. Returns null if there is no such declaration. |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 174 | String get iosBundleIdentifier { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 175 | if (isModule) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 176 | return _flutterDescriptor['module']['iosBundleIdentifier'] as String; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 177 | } |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 178 | return null; |
| 179 | } |
| 180 | |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 181 | List<Map<String, dynamic>> get fontsDescriptor { |
Jason Simmons | 3581b3a | 2018-10-01 14:14:48 -0700 | [diff] [blame] | 182 | return fonts.map((Font font) => font.descriptor).toList(); |
| 183 | } |
| 184 | |
| 185 | List<Map<String, dynamic>> get _rawFontsDescriptor { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 186 | final List<dynamic> fontList = _flutterDescriptor['fonts'] as List<dynamic>; |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 187 | return fontList == null |
| 188 | ? const <Map<String, dynamic>>[] |
| 189 | : fontList.map<Map<String, dynamic>>(castStringKeyedMap).toList(); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 190 | } |
| 191 | |
Jonah Williams | 0af2a84 | 2019-10-02 12:45:51 -0700 | [diff] [blame] | 192 | List<Uri> get assets => _assets ??= _computeAssets(); |
| 193 | List<Uri> _assets; |
| 194 | List<Uri> _computeAssets() { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 195 | final List<dynamic> assets = _flutterDescriptor['assets'] as List<dynamic>; |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 196 | if (assets == null) { |
| 197 | return const <Uri>[]; |
| 198 | } |
Jonah Williams | 0af2a84 | 2019-10-02 12:45:51 -0700 | [diff] [blame] | 199 | final List<Uri> results = <Uri>[]; |
| 200 | for (Object asset in assets) { |
| 201 | if (asset is! String || asset == null || asset == '') { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 202 | globals.printError('Asset manifest contains a null or empty uri.'); |
Jonah Williams | 0af2a84 | 2019-10-02 12:45:51 -0700 | [diff] [blame] | 203 | continue; |
| 204 | } |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 205 | final String stringAsset = asset as String; |
Jonah Williams | 0af2a84 | 2019-10-02 12:45:51 -0700 | [diff] [blame] | 206 | try { |
| 207 | results.add(Uri.parse(Uri.encodeFull(stringAsset))); |
| 208 | } on FormatException { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 209 | globals.printError('Asset manifest contains invalid uri: $asset.'); |
Jonah Williams | 0af2a84 | 2019-10-02 12:45:51 -0700 | [diff] [blame] | 210 | } |
| 211 | } |
| 212 | return results; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 213 | } |
| 214 | |
| 215 | List<Font> _fonts; |
| 216 | |
| 217 | List<Font> get fonts { |
| 218 | _fonts ??= _extractFonts(); |
| 219 | return _fonts; |
| 220 | } |
| 221 | |
| 222 | List<Font> _extractFonts() { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 223 | if (!_flutterDescriptor.containsKey('fonts')) { |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 224 | return <Font>[]; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 225 | } |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 226 | |
| 227 | final List<Font> fonts = <Font>[]; |
Jason Simmons | 3581b3a | 2018-10-01 14:14:48 -0700 | [diff] [blame] | 228 | for (Map<String, dynamic> fontFamily in _rawFontsDescriptor) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 229 | final YamlList fontFiles = fontFamily['fonts'] as YamlList; |
| 230 | final String familyName = fontFamily['family'] as String; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 231 | if (familyName == null) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 232 | globals.printError('Warning: Missing family name for font.', emphasis: true); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 233 | continue; |
| 234 | } |
| 235 | if (fontFiles == null) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 236 | globals.printError('Warning: No fonts specified for font $familyName', emphasis: true); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 237 | continue; |
| 238 | } |
| 239 | |
| 240 | final List<FontAsset> fontAssets = <FontAsset>[]; |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 241 | for (Map<dynamic, dynamic> fontFile in fontFiles.cast<Map<dynamic, dynamic>>()) { |
| 242 | final String asset = fontFile['asset'] as String; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 243 | if (asset == null) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 244 | globals.printError('Warning: Missing asset in fonts for $familyName', emphasis: true); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 245 | continue; |
| 246 | } |
| 247 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 248 | fontAssets.add(FontAsset( |
Michael Goderbauer | 84580b5 | 2018-01-24 13:16:23 -0800 | [diff] [blame] | 249 | Uri.parse(asset), |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 250 | weight: fontFile['weight'] as int, |
| 251 | style: fontFile['style'] as String, |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 252 | )); |
| 253 | } |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 254 | if (fontAssets.isNotEmpty) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 255 | fonts.add(Font(fontFamily['family'] as String, fontAssets)); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 256 | } |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 257 | } |
| 258 | return fonts; |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | class Font { |
Alexandre Ardhuin | 15601fe | 2017-11-08 22:59:49 +0100 | [diff] [blame] | 263 | Font(this.familyName, this.fontAssets) |
| 264 | : assert(familyName != null), |
| 265 | assert(fontAssets != null), |
| 266 | assert(fontAssets.isNotEmpty); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 267 | |
| 268 | final String familyName; |
| 269 | final List<FontAsset> fontAssets; |
| 270 | |
| 271 | Map<String, dynamic> get descriptor { |
| 272 | return <String, dynamic>{ |
| 273 | 'family': familyName, |
Alexandre Ardhuin | f62afdc | 2018-10-01 21:29:08 +0200 | [diff] [blame] | 274 | 'fonts': fontAssets.map<Map<String, dynamic>>((FontAsset a) => a.descriptor).toList(), |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 275 | }; |
| 276 | } |
| 277 | |
| 278 | @override |
| 279 | String toString() => '$runtimeType(family: $familyName, assets: $fontAssets)'; |
| 280 | } |
| 281 | |
| 282 | class FontAsset { |
Michael Goderbauer | 84580b5 | 2018-01-24 13:16:23 -0800 | [diff] [blame] | 283 | FontAsset(this.assetUri, {this.weight, this.style}) |
| 284 | : assert(assetUri != null); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 285 | |
Michael Goderbauer | 84580b5 | 2018-01-24 13:16:23 -0800 | [diff] [blame] | 286 | final Uri assetUri; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 287 | final int weight; |
| 288 | final String style; |
| 289 | |
| 290 | Map<String, dynamic> get descriptor { |
| 291 | final Map<String, dynamic> descriptor = <String, dynamic>{}; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 292 | if (weight != null) { |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 293 | descriptor['weight'] = weight; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 294 | } |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 295 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 296 | if (style != null) { |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 297 | descriptor['style'] = style; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 298 | } |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 299 | |
Michael Goderbauer | 84580b5 | 2018-01-24 13:16:23 -0800 | [diff] [blame] | 300 | descriptor['asset'] = assetUri.path; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 301 | return descriptor; |
| 302 | } |
| 303 | |
| 304 | @override |
Michael Goderbauer | 84580b5 | 2018-01-24 13:16:23 -0800 | [diff] [blame] | 305 | String toString() => '$runtimeType(asset: ${assetUri.path}, weight; $weight, style: $style)'; |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 306 | } |
| 307 | |
fmatosqg | 197d431 | 2018-05-21 10:52:33 +1000 | [diff] [blame] | 308 | @visibleForTesting |
| 309 | String buildSchemaDir(FileSystem fs) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 310 | return globals.fs.path.join( |
| 311 | globals.fs.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema', |
fmatosqg | 197d431 | 2018-05-21 10:52:33 +1000 | [diff] [blame] | 312 | ); |
| 313 | } |
| 314 | |
| 315 | @visibleForTesting |
| 316 | String buildSchemaPath(FileSystem fs) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 317 | return globals.fs.path.join( |
fmatosqg | 197d431 | 2018-05-21 10:52:33 +1000 | [diff] [blame] | 318 | buildSchemaDir(fs), |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 319 | 'pubspec_yaml.json', |
| 320 | ); |
fmatosqg | 197d431 | 2018-05-21 10:52:33 +1000 | [diff] [blame] | 321 | } |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 322 | |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 323 | /// This method should be kept in sync with the schema in |
| 324 | /// `$FLUTTER_ROOT/packages/flutter_tools/schema/pubspec_yaml.json`, |
Greg Spencer | a60bf8e | 2019-11-22 08:43:55 -0800 | [diff] [blame] | 325 | /// but avoid introducing dependencies on packages for simple validation. |
Jonah Williams | 4ff4671 | 2019-04-29 08:21:32 -0700 | [diff] [blame] | 326 | bool _validate(YamlMap manifest) { |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 327 | 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 Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 333 | switch (kvp.key as String) { |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 334 | 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 Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 346 | _validateFlutter(kvp.value as YamlMap, errors); |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 347 | break; |
| 348 | default: |
| 349 | // additionalProperties are allowed. |
| 350 | break; |
| 351 | } |
| 352 | } |
fmatosqg | 197d431 | 2018-05-21 10:52:33 +1000 | [diff] [blame] | 353 | |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 354 | if (errors.isNotEmpty) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 355 | globals.printStatus('Error detected in pubspec.yaml:', emphasis: true); |
| 356 | globals.printError(errors.join('\n')); |
Sarah Zakarias | 49ba974 | 2017-09-26 14:48:52 +0200 | [diff] [blame] | 357 | return false; |
| 358 | } |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 359 | |
| 360 | return true; |
Michael Goderbauer | 84580b5 | 2018-01-24 13:16:23 -0800 | [diff] [blame] | 361 | } |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 362 | |
| 363 | void _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 Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 372 | switch (kvp.key as String) { |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 373 | 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 Anderson | d220631 | 2019-08-14 07:57:30 -0700 | [diff] [blame] | 387 | } else { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 388 | _validateFonts(kvp.value as YamlList, errors); |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 389 | } |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 390 | 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 Burton | d0e45a2 | 2019-06-01 13:33:02 +1200 | [diff] [blame] | 396 | if (kvp.value['androidX'] != null && kvp.value['androidX'] is! bool) { |
| 397 | errors.add('The "androidX" value must be a bool if set.'); |
| 398 | } |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 399 | 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 Williams | 6c91a13 | 2019-10-21 16:46:07 -0700 | [diff] [blame] | 407 | if (kvp.value is! YamlMap || kvp.value == null) { |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 408 | errors.add('Expected "${kvp.key}" to be an object, but got ${kvp.value} (${kvp.value.runtimeType}).'); |
Jonah Williams | 6c91a13 | 2019-10-21 16:46:07 -0700 | [diff] [blame] | 409 | break; |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 410 | } |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 411 | final List<String> pluginErrors = Plugin.validatePluginYaml(kvp.value as YamlMap); |
Kaushik Iska | fc05c37 | 2019-08-29 21:51:31 -0700 | [diff] [blame] | 412 | errors.addAll(pluginErrors); |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 413 | break; |
| 414 | default: |
| 415 | errors.add('Unexpected child "${kvp.key}" found under "flutter".'); |
| 416 | break; |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | |
| 421 | void _validateFonts(YamlList fonts, List<String> errors) { |
| 422 | if (fonts == null) { |
| 423 | return; |
| 424 | } |
Phil Quitslund | 802eca2 | 2019-03-06 11:05:16 -0800 | [diff] [blame] | 425 | const Set<int> fontWeights = <int>{ |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 426 | 100, 200, 300, 400, 500, 600, 700, 800, 900, |
Phil Quitslund | 802eca2 | 2019-03-06 11:05:16 -0800 | [diff] [blame] | 427 | }; |
Zachary Anderson | 500d7c5 | 2019-08-05 13:14:57 -0700 | [diff] [blame] | 428 | 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 Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 433 | final YamlMap fontMap = fontListEntry as YamlMap; |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 434 | 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 Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 440 | if (fontMap['fonts'] == null) { |
| 441 | continue; |
Emmanuel Garcia | 3e65bb0 | 2019-05-01 09:49:39 -0700 | [diff] [blame] | 442 | } else if (fontMap['fonts'] is! YamlList) { |
| 443 | errors.add('Expected "fonts" to either be null or a list.'); |
| 444 | continue; |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 445 | } |
Zachary Anderson | af93b6a | 2019-10-11 12:17:37 -0700 | [diff] [blame] | 446 | 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 Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 451 | final YamlMap fontMapList = fontListItem as YamlMap; |
Zachary Anderson | af93b6a | 2019-10-11 12:17:37 -0700 | [diff] [blame] | 452 | for (final MapEntry<dynamic, dynamic> kvp in fontMapList.entries) { |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 453 | if (kvp.key is! String) { |
| 454 | errors.add('Expected "${kvp.key}" under "fonts" to be a string.'); |
| 455 | } |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 456 | switch(kvp.key as String) { |
Dan Field | 4ed096b | 2019-02-20 15:38:58 -0800 | [diff] [blame] | 457 | 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 Ardhuin | 4c1f4d1 | 2019-03-06 09:37:32 +0100 | [diff] [blame] | 479 | } |