Skip to content

Commit 3a18473

Browse files
authored
add parsing of assets transformer declarations in pubspec.yaml (#143557)
In service of flutter/flutter#143348. This PR enables parsing of the pubspec yaml schemes for assets with transformations as described in #143348.
1 parent 848aa50 commit 3a18473

File tree

3 files changed

+331
-35
lines changed

3 files changed

+331
-35
lines changed

packages/flutter_tools/lib/src/flutter_manifest.dart

Lines changed: 162 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,8 @@ void _validateFlutter(YamlMap? yaml, List<String> errors) {
519519
_validateFonts(yamlValue, errors);
520520
}
521521
case 'licenses':
522-
errors.addAll(_validateList<String>(yamlValue, '"$yamlKey"', 'files'));
522+
final (_, List<String> filesErrors) = _parseList<String>(yamlValue, '"$yamlKey"', 'files');
523+
errors.addAll(filesErrors);
523524
case 'module':
524525
if (yamlValue is! YamlMap) {
525526
errors.add('Expected "$yamlKey" to be an object, but got $yamlValue (${yamlValue.runtimeType}).');
@@ -553,11 +554,12 @@ void _validateFlutter(YamlMap? yaml, List<String> errors) {
553554
}
554555
}
555556

556-
List<String> _validateList<T>(Object? yamlList, String context, String typeAlias) {
557+
(List<T>? result, List<String> errors) _parseList<T>(Object? yamlList, String context, String typeAlias) {
557558
final List<String> errors = <String>[];
558559

559560
if (yamlList is! YamlList) {
560-
return <String>['Expected $context to be a list of $typeAlias, but got $yamlList (${yamlList.runtimeType}).'];
561+
final String message = 'Expected $context to be a list of $typeAlias, but got $yamlList (${yamlList.runtimeType}).';
562+
return (null, <String>[message]);
561563
}
562564

563565
for (int i = 0; i < yamlList.length; i++) {
@@ -567,8 +569,9 @@ List<String> _validateList<T>(Object? yamlList, String context, String typeAlias
567569
}
568570
}
569571

570-
return errors;
572+
return errors.isEmpty ? (List<T>.from(yamlList), errors) : (null, errors);
571573
}
574+
572575
void _validateDeferredComponents(MapEntry<Object?, Object?> kvp, List<String> errors) {
573576
final Object? yamlList = kvp.value;
574577
if (yamlList != null && (yamlList is! YamlList || yamlList[0] is! YamlMap)) {
@@ -585,11 +588,12 @@ void _validateDeferredComponents(MapEntry<Object?, Object?> kvp, List<String> er
585588
errors.add('Expected the $i element in "${kvp.key}" to have required key "name" of type String');
586589
}
587590
if (valueMap.containsKey('libraries')) {
588-
errors.addAll(_validateList<String>(
591+
final (_, List<String> librariesErrors) = _parseList<String>(
589592
valueMap['libraries'],
590593
'"libraries" key in the element at index $i of "${kvp.key}"',
591594
'String',
592-
));
595+
);
596+
errors.addAll(librariesErrors);
593597
}
594598
if (valueMap.containsKey('assets')) {
595599
errors.addAll(_validateAssets(valueMap['assets']));
@@ -697,13 +701,16 @@ class AssetsEntry {
697701
const AssetsEntry({
698702
required this.uri,
699703
this.flavors = const <String>{},
704+
this.transformers = const <AssetTransformerEntry>[],
700705
});
701706

702707
final Uri uri;
703708
final Set<String> flavors;
709+
final List<AssetTransformerEntry> transformers;
704710

705711
static const String _pathKey = 'path';
706712
static const String _flavorKey = 'flavors';
713+
static const String _transformersKey = 'transformers';
707714

708715
static AssetsEntry? parseFromYaml(Object? yaml) {
709716
final (AssetsEntry? value, String? error) = parseFromYamlSafe(yaml);
@@ -738,49 +745,83 @@ class AssetsEntry {
738745
}
739746

740747
final Object? path = yaml[_pathKey];
741-
final Object? flavors = yaml[_flavorKey];
742748

743749
if (path == null || path is! String) {
744750
return (null, 'Asset manifest entry is malformed. '
745751
'Expected asset entry to be either a string or a map '
746752
'containing a "$_pathKey" entry. Got ${path.runtimeType} instead.');
747753
}
748754

749-
final Uri uri = Uri(pathSegments: path.split('/'));
750-
751-
if (flavors == null) {
752-
return (AssetsEntry(uri: uri), null);
753-
}
754-
755-
if (flavors is! YamlList) {
756-
return(null, 'Asset manifest entry is malformed. '
757-
'Expected "$_flavorKey" entry to be a list of strings. '
758-
'Got ${flavors.runtimeType} instead.');
759-
}
760-
761-
final List<String> flavorsListErrors = _validateList<String>(
762-
flavors,
763-
'flavors list of entry "$path"',
764-
'String',
765-
);
766-
if (flavorsListErrors.isNotEmpty) {
767-
return (null, 'Asset manifest entry is malformed. '
768-
'Expected "$_flavorKey" entry to be a list of strings.\n'
769-
'${flavorsListErrors.join('\n')}');
755+
final (List<String>? flavors, List<String> flavorsErrors) = _parseFlavorsSection(yaml[_flavorKey]);
756+
final (List<AssetTransformerEntry>? transformers, List<String> transformersErrors) = _parseTransformersSection(yaml[_transformersKey]);
757+
758+
final List<String> errors = <String>[
759+
...flavorsErrors.map((String e) => 'In $_flavorKey section of asset "$path": $e'),
760+
...transformersErrors.map((String e) => 'In $_transformersKey section of asset "$path": $e'),
761+
];
762+
if (errors.isNotEmpty) {
763+
return (
764+
null,
765+
<String>[
766+
'Unable to parse assets section.',
767+
...errors
768+
].join('\n'),
769+
);
770770
}
771771

772-
final AssetsEntry entry = AssetsEntry(
773-
uri: Uri(pathSegments: path.split('/')),
774-
flavors: Set<String>.from(flavors),
772+
return (
773+
AssetsEntry(
774+
uri: Uri(pathSegments: path.split('/')),
775+
flavors: Set<String>.from(flavors ?? <String>[]),
776+
transformers: transformers ?? <AssetTransformerEntry>[],
777+
),
778+
null,
775779
);
776-
777-
return (entry, null);
778780
}
779781

780782
return (null, 'Assets entry had unexpected shape. '
781783
'Expected a string or an object. Got ${yaml.runtimeType} instead.');
782784
}
783785

786+
static (List<String>? flavors, List<String> errors) _parseFlavorsSection(Object? yaml) {
787+
if (yaml == null) {
788+
return (null, <String>[]);
789+
}
790+
791+
return _parseList<String>(yaml, _flavorKey, 'String');
792+
}
793+
794+
static (List<AssetTransformerEntry>?, List<String> errors) _parseTransformersSection(Object? yaml) {
795+
if (yaml == null) {
796+
return (null, <String>[]);
797+
}
798+
final (List<YamlMap>? yamlObjects, List<String> listErrors) = _parseList<YamlMap>(
799+
yaml,
800+
'$_transformersKey list',
801+
'Map',
802+
);
803+
804+
if (listErrors.isNotEmpty) {
805+
return (null, listErrors);
806+
}
807+
808+
final List<AssetTransformerEntry> transformers = <AssetTransformerEntry>[];
809+
final List<String> errors = <String>[];
810+
for (final YamlMap yaml in yamlObjects!) {
811+
final (AssetTransformerEntry? transformerEntry, List<String> transformerErrors) = AssetTransformerEntry.tryParse(yaml);
812+
if (transformerEntry != null) {
813+
transformers.add(transformerEntry);
814+
} else {
815+
errors.addAll(transformerErrors);
816+
}
817+
}
818+
819+
if (errors.isEmpty) {
820+
return (transformers, errors);
821+
}
822+
return (null, errors);
823+
}
824+
784825
@override
785826
bool operator ==(Object other) {
786827
if (other is! AssetsEntry) {
@@ -799,3 +840,91 @@ class AssetsEntry {
799840
@override
800841
String toString() => 'AssetsEntry(uri: $uri, flavors: $flavors)';
801842
}
843+
844+
845+
/// Represents an entry in the "transformers" section of an asset.
846+
@immutable
847+
final class AssetTransformerEntry {
848+
const AssetTransformerEntry({
849+
required this.package,
850+
required List<String>? args,
851+
}): args = args ?? const <String>[];
852+
853+
final String package;
854+
final List<String>? args;
855+
856+
static (AssetTransformerEntry? entry, List<String> errors) tryParse(Object? yaml) {
857+
if (yaml == null) {
858+
return (null, <String>['Transformer entry is null.']);
859+
}
860+
if (yaml is! YamlMap) {
861+
return (null, <String>['Expected entry to be a map. Found ${yaml.runtimeType} instead']);
862+
}
863+
864+
final Object? package = yaml['package'];
865+
if (package is! String || package.isEmpty) {
866+
return (null, <String>['Expected "package" to be a String. Found ${package.runtimeType} instead.']);
867+
}
868+
869+
final (List<String>? args, List<String> argsErrors) = _parseArgsSection(yaml['args']);
870+
if (argsErrors.isNotEmpty) {
871+
return (null, argsErrors.map((String e) => 'In args section of transformer using package "$package": $e').toList());
872+
}
873+
874+
return (
875+
AssetTransformerEntry(
876+
package: package,
877+
args: args,
878+
),
879+
<String>[],
880+
);
881+
}
882+
883+
static (List<String>? args, List<String> errors) _parseArgsSection(Object? yaml) {
884+
if (yaml == null) {
885+
return (null, <String>[]);
886+
}
887+
return _parseList(yaml, 'args', 'String');
888+
}
889+
890+
@override
891+
bool operator ==(Object other) {
892+
if (identical(this, other)) {
893+
return true;
894+
}
895+
if (other is! AssetTransformerEntry) {
896+
return false;
897+
}
898+
899+
final bool argsAreEqual = (() {
900+
if (args == null && other.args == null) {
901+
return true;
902+
}
903+
if (args?.length != other.args?.length) {
904+
return false;
905+
}
906+
907+
for (int index = 0; index < args!.length; index += 1) {
908+
if (args![index] != other.args![index]) {
909+
return false;
910+
}
911+
}
912+
return true;
913+
})();
914+
915+
return package == other.package && argsAreEqual;
916+
}
917+
918+
@override
919+
int get hashCode => Object.hashAll(
920+
<Object?>[
921+
package.hashCode,
922+
args?.map((String e) => e.hashCode),
923+
],
924+
);
925+
926+
@override
927+
String toString() {
928+
return 'AssetTransformerEntry(package: $package, args: $args)';
929+
}
930+
}

packages/flutter_tools/test/general.shard/flutter_manifest_assets_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,9 @@ flutter:
154154
''';
155155
FlutterManifest.createFromString(manifest, logger: logger);
156156
expect(logger.errorText, contains(
157-
'Asset manifest entry is malformed. '
158-
'Expected "flavors" entry to be a list of strings.',
157+
'Unable to parse assets section.\n'
158+
'In flavors section of asset "assets/vanilla/": Expected flavors '
159+
'to be a list of String, but element at index 0 was a YamlMap.\n'
159160
));
160161
});
161162
});

0 commit comments

Comments
 (0)