Put shaders under a 'shaders' section in the manifest (#106752)

diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index 1cae495..9dd7272 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -63,9 +63,17 @@
   AssetBundle createBundle();
 }
 
+enum AssetKind {
+  regular,
+  font,
+  shader,
+}
+
 abstract class AssetBundle {
   Map<String, DevFSContent> get entries;
 
+  Map<String, AssetKind> get entryKinds;
+
   /// The files that were specified under the deferred components assets sections
   /// in pubspec.
   Map<String, Map<String, DevFSContent>> get deferredComponentsEntries;
@@ -136,6 +144,9 @@
   final Map<String, DevFSContent> entries = <String, DevFSContent>{};
 
   @override
+  final Map<String, AssetKind> entryKinds = <String, AssetKind>{};
+
+  @override
   final Map<String, Map<String, DevFSContent>> deferredComponentsEntries = <String, Map<String, DevFSContent>>{};
 
   @override
@@ -218,6 +229,7 @@
     _lastBuildTimestamp = DateTime.now();
     if (flutterManifest.isEmpty) {
       entries[_kAssetManifestJson] = DevFSStringContent('{}');
+      entryKinds[_kAssetManifestJson] = AssetKind.regular;
       return 0;
     }
 
@@ -370,6 +382,7 @@
         inputFiles.add(variantFile);
         assert(variantFile.existsSync());
         entries[variant.entryUri.path] ??= DevFSFileContent(variantFile);
+        entryKinds[variant.entryUri.path] ??= variant.assetKind;
       }
     }
     // Save the contents of each deferred component image, image variant, and font
@@ -418,6 +431,7 @@
       final File assetFile = asset.lookupAssetFile(_fileSystem);
       assert(assetFile.existsSync(), 'Missing ${assetFile.path}');
       entries[asset.entryUri.path] ??= DevFSFileContent(assetFile);
+      entryKinds[asset.entryUri.path] ??= asset.assetKind;
     }
 
     // Update wildcard directories we can detect changes in them.
@@ -449,8 +463,8 @@
         _fileSystem.file('DOES_NOT_EXIST_RERUN_FOR_WILDCARD$suffix').absolute);
     }
 
-    _setIfChanged(_kAssetManifestJson, assetManifest);
-    _setIfChanged(kFontManifestJson, fontManifest);
+    _setIfChanged(_kAssetManifestJson, assetManifest, AssetKind.regular);
+    _setIfChanged(kFontManifestJson, fontManifest, AssetKind.regular);
     _setLicenseIfChanged(licenseResult.combinedLicenses, targetPlatform);
     return 0;
   }
@@ -458,14 +472,16 @@
   @override
   List<File> additionalDependencies = <File>[];
 
-  void _setIfChanged(String key, DevFSStringContent content) {
+  void _setIfChanged(String key, DevFSStringContent content, AssetKind assetKind) {
     if (!entries.containsKey(key)) {
       entries[key] = content;
+      entryKinds[key] = assetKind;
       return;
     }
     final DevFSStringContent? oldContent = entries[key] as DevFSStringContent?;
     if (oldContent?.string != content.string) {
       entries[key] = content;
+      entryKinds[key] = assetKind;
     }
   }
 
@@ -477,7 +493,7 @@
     // dart:io to decompress it. So use the standard _setIfChanged to check if
     // the strings still match.
     if (targetPlatform == TargetPlatform.web_javascript) {
-      _setIfChanged(_kNoticeFile, DevFSStringContent(combinedLicenses));
+      _setIfChanged(_kNoticeFile, DevFSStringContent(combinedLicenses), AssetKind.regular);
       return;
     }
 
@@ -495,6 +511,7 @@
         // common English words with domain specific words like copyright.
         hintString: 'copyrightsoftwaretothisinandorofthe',
       );
+      entryKinds[_kNoticeZippedFile] = AssetKind.regular;
     }
   }
 
@@ -519,6 +536,7 @@
           relativeUri: Uri(path: entryUri.pathSegments.last),
           entryUri: entryUri,
           package: null,
+          assetKind: AssetKind.font,
         ));
       }
     }
@@ -546,6 +564,7 @@
         relativeUri: Uri(path: entryUri.pathSegments.last),
         entryUri: entryUri,
         package: null,
+        assetKind: AssetKind.shader,
       ));
     }
 
@@ -745,6 +764,21 @@
       }
     }
 
+    for (final Uri shaderUri in flutterManifest.shaders) {
+      _parseAssetFromFile(
+        packageConfig,
+        flutterManifest,
+        assetBase,
+        cache,
+        result,
+        shaderUri,
+        excludeDirs: excludeDirs,
+        packageName: packageName,
+        attributedPackage: attributedPackage,
+        assetKind: AssetKind.shader,
+      );
+    }
+
     // Add assets referenced in the fonts section of the manifest.
     for (final Font font in flutterManifest.fonts) {
       for (final FontAsset fontAsset in font.fontAssets) {
@@ -754,6 +788,7 @@
           fontAsset.assetUri,
           packageName,
           attributedPackage,
+          assetKind: AssetKind.font,
         );
         final File baseAssetFile = baseAsset.lookupAssetFile(_fileSystem);
         if (!baseAssetFile.existsSync()) {
@@ -816,6 +851,7 @@
     List<String> excludeDirs = const <String>[],
     String? packageName,
     Package? attributedPackage,
+    AssetKind assetKind = AssetKind.regular,
   }) {
     final _Asset asset = _resolveAsset(
       packageConfig,
@@ -823,6 +859,7 @@
       assetUri,
       packageName,
       attributedPackage,
+      assetKind: assetKind,
     );
     final List<_Asset> variants = <_Asset>[];
     final File assetFile = asset.lookupAssetFile(_fileSystem);
@@ -839,6 +876,7 @@
             entryUri: entryUri,
             relativeUri: relativeUri,
             package: attributedPackage,
+            assetKind: assetKind,
           ),
         );
       }
@@ -852,8 +890,9 @@
     String assetsBaseDir,
     Uri assetUri,
     String? packageName,
-    Package? attributedPackage,
-  ) {
+    Package? attributedPackage, {
+    AssetKind assetKind = AssetKind.regular,
+  }) {
     final String assetPath = _fileSystem.path.fromUri(assetUri);
     if (assetUri.pathSegments.first == 'packages'
       && !_fileSystem.isFileSync(_fileSystem.path.join(assetsBaseDir, assetPath))) {
@@ -863,6 +902,7 @@
         assetUri,
         packageConfig,
         attributedPackage,
+        assetKind: assetKind,
       );
       if (packageAsset != null) {
         return packageAsset;
@@ -876,10 +916,16 @@
           : Uri(pathSegments: <String>['packages', packageName, ...assetUri.pathSegments]), // Asset from, and declared in $packageName.
       relativeUri: assetUri,
       package: attributedPackage,
+      assetKind: assetKind,
     );
   }
 
-  _Asset? _resolvePackageAsset(Uri assetUri, PackageConfig packageConfig, Package? attributedPackage) {
+  _Asset? _resolvePackageAsset(
+    Uri assetUri,
+    PackageConfig packageConfig,
+    Package? attributedPackage, {
+    AssetKind assetKind = AssetKind.regular,
+  }) {
     assert(assetUri.pathSegments.first == 'packages');
     if (assetUri.pathSegments.length > 1) {
       final String packageName = assetUri.pathSegments[1];
@@ -891,6 +937,7 @@
           entryUri: assetUri,
           relativeUri: Uri(pathSegments: assetUri.pathSegments.sublist(2)),
           package: attributedPackage,
+          assetKind: assetKind,
         );
       }
     }
@@ -910,6 +957,7 @@
     required this.relativeUri,
     required this.entryUri,
     required this.package,
+    this.assetKind = AssetKind.regular,
   });
 
   final String baseDir;
@@ -923,6 +971,8 @@
   /// A platform-independent URL representing the entry for the asset manifest.
   final Uri entryUri;
 
+  final AssetKind assetKind;
+
   File lookupAssetFile(FileSystem fileSystem) {
     return fileSystem.file(fileSystem.path.join(baseDir, fileSystem.path.fromUri(relativeUri)));
   }
@@ -951,7 +1001,8 @@
     return other is _Asset
         && other.baseDir == baseDir
         && other.relativeUri == relativeUri
-        && other.entryUri == entryUri;
+        && other.entryUri == entryUri
+        && other.assetKind == assetKind;
   }
 
   @override
diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
index a9e186b..5f458bd 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
@@ -88,6 +88,9 @@
     if (skslBundle != null)
       kSkSLShaderBundlePath: skslBundle,
   };
+  final Map<String, AssetKind> entryKinds = <String, AssetKind>{
+    ...assetBundle.entryKinds,
+  };
 
   await Future.wait<void>(
     assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
@@ -100,19 +103,31 @@
         // and the native APIs will look for files this way.
         final File file = environment.fileSystem.file(
           environment.fileSystem.path.join(outputDirectory.path, entry.key));
+        final AssetKind assetKind = entryKinds[entry.key] ?? AssetKind.regular;
         outputs.add(file);
         file.parent.createSync(recursive: true);
         final DevFSContent content = entry.value;
         if (content is DevFSFileContent && content.file is File) {
           inputs.add(content.file as File);
-          if (!await iconTreeShaker.subsetFont(
-            input: content.file as File,
-            outputPath: file.path,
-            relativePath: entry.key,
-          ) && !await shaderCompiler.compileShader(
-            input: content.file as File,
-            outputPath: file.path,
-          )) {
+          bool doCopy = true;
+          switch (assetKind) {
+            case AssetKind.regular:
+              break;
+            case AssetKind.font:
+              doCopy = !await iconTreeShaker.subsetFont(
+                input: content.file as File,
+                outputPath: file.path,
+                relativePath: entry.key,
+              );
+              break;
+            case AssetKind.shader:
+              doCopy = !await shaderCompiler.compileShader(
+                input: content.file as File,
+                outputPath: file.path,
+              );
+              break;
+          }
+          if (doCopy) {
             await (content.file as File).copy(file.path);
           }
         } else {
@@ -127,8 +142,8 @@
   // The assets are included in assetBundle.entries as a normal asset when
   // building as debug.
   if (environment.defines[kDeferredComponents] == 'true' && buildMode != null) {
-    await Future.wait<void>(
-      assetBundle.deferredComponentsEntries.entries.map<Future<void>>((MapEntry<String, Map<String, DevFSContent>> componentEntries) async {
+    await Future.wait<void>(assetBundle.deferredComponentsEntries.entries.map<Future<void>>(
+      (MapEntry<String, Map<String, DevFSContent>> componentEntries) async {
         final Directory componentOutputDir =
             environment.projectDir
                 .childDirectory('build')
diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart
index 9a5f2f1..4317091 100644
--- a/packages/flutter_tools/lib/src/bundle_builder.dart
+++ b/packages/flutter_tools/lib/src/bundle_builder.dart
@@ -134,6 +134,7 @@
 Future<void> writeBundle(
   Directory bundleDir,
   Map<String, DevFSContent> assetEntries,
+  Map<String, AssetKind> entryKinds,
   { Logger? loggerOverride }
 ) async {
   loggerOverride ??= globals.logger;
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index d421070..748d01b 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -477,7 +477,7 @@
     }
     if (_needRebuild(assetBundle.entries)) {
       await writeBundle(globals.fs.directory(globals.fs.path.join('build', 'unit_test_assets')),
-          assetBundle.entries);
+          assetBundle.entries, assetBundle.entryKinds);
     }
   }
 
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index d58f5e0..37753b6 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -370,6 +370,33 @@
     return fonts;
   }
 
+
+  late final List<Uri> shaders = _extractShaders();
+
+  List<Uri> _extractShaders() {
+    if (!_flutterDescriptor.containsKey('shaders')) {
+      return <Uri>[];
+    }
+
+    final List<Object?>? shaders = _flutterDescriptor['shaders'] as List<Object?>?;
+    if (shaders == null) {
+      return const <Uri>[];
+    }
+    final List<Uri> results = <Uri>[];
+    for (final Object? shader in shaders) {
+      if (shader is! String || shader == null || shader == '') {
+        _logger.printError('Shader manifest contains a null or empty uri.');
+        continue;
+      }
+      try {
+        results.add(Uri(pathSegments: shader.split('/')));
+      } on FormatException {
+        _logger.printError('Shader manifest contains invalid uri: $shader.');
+      }
+    }
+    return results;
+  }
+
   /// Whether a synthetic flutter_gen package should be generated.
   ///
   /// This can be provided to the [Pub] interface to inject a new entry
@@ -498,7 +525,17 @@
         break;
       case 'assets':
         if (yamlValue is! YamlList) {
-
+          errors.add('Expected "$yamlKey" to be a list, but got $yamlValue (${yamlValue.runtimeType}).');
+        } else if (yamlValue.isEmpty) {
+          break;
+        } else if (yamlValue[0] is! String) {
+          errors.add(
+            'Expected "$yamlKey" to be a list of strings, but the first element is $yamlValue (${yamlValue.runtimeType}).',
+          );
+        }
+        break;
+      case 'shaders':
+        if (yamlValue is! YamlList) {
           errors.add('Expected "$yamlKey" to be a list, but got $yamlValue (${yamlValue.runtimeType}).');
         } else if (yamlValue.isEmpty) {
           break;
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
index df23f5f..41a07fa 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
@@ -124,9 +124,10 @@
     throwToolExit('Unable to find assets.', exitCode: 1);
   }
 
-  final Map<String, DevFSContent> assetEntries =
-      Map<String, DevFSContent>.of(assets.entries);
-  await writeBundle(globals.fs.directory(assetDir), assetEntries);
+  final Map<String, DevFSContent> assetEntries = Map<String, DevFSContent>.of(
+    assets.entries,
+  );
+  await writeBundle(globals.fs.directory(assetDir), assetEntries, assets.entryKinds);
 
   final String appName = fuchsiaProject.project.manifest.appName;
   final String outDir = getFuchsiaBuildDirectory();
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
index eb38d2e..4291bef 100644
--- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart
+++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -843,6 +843,7 @@
         await writeBundle(
           globals.fs.directory(getAssetBuildDirectory()),
           bundle.entries,
+          bundle.entryKinds,
         );
       }
     }
diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
index 00d0dec..eb469dc 100644
--- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
+++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
@@ -308,7 +308,12 @@
       ..createSync();
     handler.addError(directory, FileSystemOp.delete, const FileSystemException('Expected Error Text'));
 
-    await writeBundle(directory, <String, DevFSContent>{}, loggerOverride: testLogger);
+    await writeBundle(
+      directory,
+      <String, DevFSContent>{},
+      <String, AssetKind>{},
+      loggerOverride: testLogger,
+    );
 
     expect(testLogger.warningText, contains('Expected Error Text'));
   });
@@ -415,14 +420,19 @@
         ..writeAsStringSync(r'''
   name: example
   flutter:
-    assets:
+    shaders:
       - assets/shader.frag
   ''');
       final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
 
       expect(await bundle.build(packagesPath: '.packages'), 0);
 
-      await writeBundle(output, bundle.entries, loggerOverride: testLogger);
+      await writeBundle(
+        output,
+        bundle.entries,
+        bundle.entryKinds,
+        loggerOverride: testLogger,
+      );
 
     }, overrides: <Type, Generator>{
       Artifacts: () => artifacts,
diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart
index e16c09f..0b0579b 100644
--- a/packages/flutter_tools/test/general.shard/devfs_test.dart
+++ b/packages/flutter_tools/test/general.shard/devfs_test.dart
@@ -633,6 +633,9 @@
   Map<String, DevFSContent> get entries => <String, DevFSContent>{};
 
   @override
+  Map<String, AssetKind> get entryKinds => <String, AssetKind>{};
+
+  @override
   List<File> get inputFiles => <File>[];
 
   @override