mirror of
https://github.com/flutter/flutter
synced 2024-08-24 18:36:03 +00:00
add parsing of assets transformer declarations in pubspec.yaml (#143557)
In service of https://github.com/flutter/flutter/issues/143348. This PR enables parsing of the pubspec yaml schemes for assets with transformations as described in #143348.
This commit is contained in:
parent
848aa5080b
commit
3a18473bd6
|
@ -519,7 +519,8 @@ void _validateFlutter(YamlMap? yaml, List<String> errors) {
|
|||
_validateFonts(yamlValue, errors);
|
||||
}
|
||||
case 'licenses':
|
||||
errors.addAll(_validateList<String>(yamlValue, '"$yamlKey"', 'files'));
|
||||
final (_, List<String> filesErrors) = _parseList<String>(yamlValue, '"$yamlKey"', 'files');
|
||||
errors.addAll(filesErrors);
|
||||
case 'module':
|
||||
if (yamlValue is! YamlMap) {
|
||||
errors.add('Expected "$yamlKey" to be an object, but got $yamlValue (${yamlValue.runtimeType}).');
|
||||
|
@ -553,11 +554,12 @@ void _validateFlutter(YamlMap? yaml, List<String> errors) {
|
|||
}
|
||||
}
|
||||
|
||||
List<String> _validateList<T>(Object? yamlList, String context, String typeAlias) {
|
||||
(List<T>? result, List<String> errors) _parseList<T>(Object? yamlList, String context, String typeAlias) {
|
||||
final List<String> errors = <String>[];
|
||||
|
||||
if (yamlList is! YamlList) {
|
||||
return <String>['Expected $context to be a list of $typeAlias, but got $yamlList (${yamlList.runtimeType}).'];
|
||||
final String message = 'Expected $context to be a list of $typeAlias, but got $yamlList (${yamlList.runtimeType}).';
|
||||
return (null, <String>[message]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < yamlList.length; i++) {
|
||||
|
@ -567,8 +569,9 @@ List<String> _validateList<T>(Object? yamlList, String context, String typeAlias
|
|||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
return errors.isEmpty ? (List<T>.from(yamlList), errors) : (null, errors);
|
||||
}
|
||||
|
||||
void _validateDeferredComponents(MapEntry<Object?, Object?> kvp, List<String> errors) {
|
||||
final Object? yamlList = kvp.value;
|
||||
if (yamlList != null && (yamlList is! YamlList || yamlList[0] is! YamlMap)) {
|
||||
|
@ -585,11 +588,12 @@ void _validateDeferredComponents(MapEntry<Object?, Object?> kvp, List<String> er
|
|||
errors.add('Expected the $i element in "${kvp.key}" to have required key "name" of type String');
|
||||
}
|
||||
if (valueMap.containsKey('libraries')) {
|
||||
errors.addAll(_validateList<String>(
|
||||
final (_, List<String> librariesErrors) = _parseList<String>(
|
||||
valueMap['libraries'],
|
||||
'"libraries" key in the element at index $i of "${kvp.key}"',
|
||||
'String',
|
||||
));
|
||||
);
|
||||
errors.addAll(librariesErrors);
|
||||
}
|
||||
if (valueMap.containsKey('assets')) {
|
||||
errors.addAll(_validateAssets(valueMap['assets']));
|
||||
|
@ -697,13 +701,16 @@ class AssetsEntry {
|
|||
const AssetsEntry({
|
||||
required this.uri,
|
||||
this.flavors = const <String>{},
|
||||
this.transformers = const <AssetTransformerEntry>[],
|
||||
});
|
||||
|
||||
final Uri uri;
|
||||
final Set<String> flavors;
|
||||
final List<AssetTransformerEntry> transformers;
|
||||
|
||||
static const String _pathKey = 'path';
|
||||
static const String _flavorKey = 'flavors';
|
||||
static const String _transformersKey = 'transformers';
|
||||
|
||||
static AssetsEntry? parseFromYaml(Object? yaml) {
|
||||
final (AssetsEntry? value, String? error) = parseFromYamlSafe(yaml);
|
||||
|
@ -738,7 +745,6 @@ class AssetsEntry {
|
|||
}
|
||||
|
||||
final Object? path = yaml[_pathKey];
|
||||
final Object? flavors = yaml[_flavorKey];
|
||||
|
||||
if (path == null || path is! String) {
|
||||
return (null, 'Asset manifest entry is malformed. '
|
||||
|
@ -746,41 +752,76 @@ class AssetsEntry {
|
|||
'containing a "$_pathKey" entry. Got ${path.runtimeType} instead.');
|
||||
}
|
||||
|
||||
final Uri uri = Uri(pathSegments: path.split('/'));
|
||||
final (List<String>? flavors, List<String> flavorsErrors) = _parseFlavorsSection(yaml[_flavorKey]);
|
||||
final (List<AssetTransformerEntry>? transformers, List<String> transformersErrors) = _parseTransformersSection(yaml[_transformersKey]);
|
||||
|
||||
if (flavors == null) {
|
||||
return (AssetsEntry(uri: uri), null);
|
||||
final List<String> errors = <String>[
|
||||
...flavorsErrors.map((String e) => 'In $_flavorKey section of asset "$path": $e'),
|
||||
...transformersErrors.map((String e) => 'In $_transformersKey section of asset "$path": $e'),
|
||||
];
|
||||
if (errors.isNotEmpty) {
|
||||
return (
|
||||
null,
|
||||
<String>[
|
||||
'Unable to parse assets section.',
|
||||
...errors
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
if (flavors is! YamlList) {
|
||||
return(null, 'Asset manifest entry is malformed. '
|
||||
'Expected "$_flavorKey" entry to be a list of strings. '
|
||||
'Got ${flavors.runtimeType} instead.');
|
||||
}
|
||||
|
||||
final List<String> flavorsListErrors = _validateList<String>(
|
||||
flavors,
|
||||
'flavors list of entry "$path"',
|
||||
'String',
|
||||
return (
|
||||
AssetsEntry(
|
||||
uri: Uri(pathSegments: path.split('/')),
|
||||
flavors: Set<String>.from(flavors ?? <String>[]),
|
||||
transformers: transformers ?? <AssetTransformerEntry>[],
|
||||
),
|
||||
null,
|
||||
);
|
||||
if (flavorsListErrors.isNotEmpty) {
|
||||
return (null, 'Asset manifest entry is malformed. '
|
||||
'Expected "$_flavorKey" entry to be a list of strings.\n'
|
||||
'${flavorsListErrors.join('\n')}');
|
||||
}
|
||||
|
||||
final AssetsEntry entry = AssetsEntry(
|
||||
uri: Uri(pathSegments: path.split('/')),
|
||||
flavors: Set<String>.from(flavors),
|
||||
);
|
||||
|
||||
return (entry, null);
|
||||
}
|
||||
|
||||
return (null, 'Assets entry had unexpected shape. '
|
||||
'Expected a string or an object. Got ${yaml.runtimeType} instead.');
|
||||
}
|
||||
|
||||
static (List<String>? flavors, List<String> errors) _parseFlavorsSection(Object? yaml) {
|
||||
if (yaml == null) {
|
||||
return (null, <String>[]);
|
||||
}
|
||||
|
||||
return _parseList<String>(yaml, _flavorKey, 'String');
|
||||
}
|
||||
|
||||
static (List<AssetTransformerEntry>?, List<String> errors) _parseTransformersSection(Object? yaml) {
|
||||
if (yaml == null) {
|
||||
return (null, <String>[]);
|
||||
}
|
||||
final (List<YamlMap>? yamlObjects, List<String> listErrors) = _parseList<YamlMap>(
|
||||
yaml,
|
||||
'$_transformersKey list',
|
||||
'Map',
|
||||
);
|
||||
|
||||
if (listErrors.isNotEmpty) {
|
||||
return (null, listErrors);
|
||||
}
|
||||
|
||||
final List<AssetTransformerEntry> transformers = <AssetTransformerEntry>[];
|
||||
final List<String> errors = <String>[];
|
||||
for (final YamlMap yaml in yamlObjects!) {
|
||||
final (AssetTransformerEntry? transformerEntry, List<String> transformerErrors) = AssetTransformerEntry.tryParse(yaml);
|
||||
if (transformerEntry != null) {
|
||||
transformers.add(transformerEntry);
|
||||
} else {
|
||||
errors.addAll(transformerErrors);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isEmpty) {
|
||||
return (transformers, errors);
|
||||
}
|
||||
return (null, errors);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! AssetsEntry) {
|
||||
|
@ -799,3 +840,91 @@ class AssetsEntry {
|
|||
@override
|
||||
String toString() => 'AssetsEntry(uri: $uri, flavors: $flavors)';
|
||||
}
|
||||
|
||||
|
||||
/// Represents an entry in the "transformers" section of an asset.
|
||||
@immutable
|
||||
final class AssetTransformerEntry {
|
||||
const AssetTransformerEntry({
|
||||
required this.package,
|
||||
required List<String>? args,
|
||||
}): args = args ?? const <String>[];
|
||||
|
||||
final String package;
|
||||
final List<String>? args;
|
||||
|
||||
static (AssetTransformerEntry? entry, List<String> errors) tryParse(Object? yaml) {
|
||||
if (yaml == null) {
|
||||
return (null, <String>['Transformer entry is null.']);
|
||||
}
|
||||
if (yaml is! YamlMap) {
|
||||
return (null, <String>['Expected entry to be a map. Found ${yaml.runtimeType} instead']);
|
||||
}
|
||||
|
||||
final Object? package = yaml['package'];
|
||||
if (package is! String || package.isEmpty) {
|
||||
return (null, <String>['Expected "package" to be a String. Found ${package.runtimeType} instead.']);
|
||||
}
|
||||
|
||||
final (List<String>? args, List<String> argsErrors) = _parseArgsSection(yaml['args']);
|
||||
if (argsErrors.isNotEmpty) {
|
||||
return (null, argsErrors.map((String e) => 'In args section of transformer using package "$package": $e').toList());
|
||||
}
|
||||
|
||||
return (
|
||||
AssetTransformerEntry(
|
||||
package: package,
|
||||
args: args,
|
||||
),
|
||||
<String>[],
|
||||
);
|
||||
}
|
||||
|
||||
static (List<String>? args, List<String> errors) _parseArgsSection(Object? yaml) {
|
||||
if (yaml == null) {
|
||||
return (null, <String>[]);
|
||||
}
|
||||
return _parseList(yaml, 'args', 'String');
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other is! AssetTransformerEntry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final bool argsAreEqual = (() {
|
||||
if (args == null && other.args == null) {
|
||||
return true;
|
||||
}
|
||||
if (args?.length != other.args?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < args!.length; index += 1) {
|
||||
if (args![index] != other.args![index]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
|
||||
return package == other.package && argsAreEqual;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll(
|
||||
<Object?>[
|
||||
package.hashCode,
|
||||
args?.map((String e) => e.hashCode),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AssetTransformerEntry(package: $package, args: $args)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,8 +154,9 @@ flutter:
|
|||
''';
|
||||
FlutterManifest.createFromString(manifest, logger: logger);
|
||||
expect(logger.errorText, contains(
|
||||
'Asset manifest entry is malformed. '
|
||||
'Expected "flavors" entry to be a list of strings.',
|
||||
'Unable to parse assets section.\n'
|
||||
'In flavors section of asset "assets/vanilla/": Expected flavors '
|
||||
'to be a list of String, but element at index 0 was a YamlMap.\n'
|
||||
));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
|
||||
void main() {
|
||||
group('parsing of assets section in flutter manifests with asset transformers', () {
|
||||
testWithoutContext('parses an asset with a simple transformation', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- path: asset/hello.txt
|
||||
transformers:
|
||||
- package: my_package
|
||||
''';
|
||||
final FlutterManifest? parsedManifest = FlutterManifest.createFromString(manifest, logger: logger);
|
||||
|
||||
expect(parsedManifest!.assets, <AssetsEntry>[
|
||||
AssetsEntry(
|
||||
uri: Uri.parse('asset/hello.txt'),
|
||||
transformers: const <AssetTransformerEntry>[
|
||||
AssetTransformerEntry(package: 'my_package', args: <String>[])
|
||||
],
|
||||
),
|
||||
]);
|
||||
|
||||
expect(logger.errorText, isEmpty);
|
||||
});
|
||||
|
||||
testWithoutContext('parses an asset with a transformation that has args', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- path: asset/hello.txt
|
||||
transformers:
|
||||
- package: my_package
|
||||
args: ["-e", "--color", "purple"]
|
||||
''';
|
||||
final FlutterManifest? parsedManifest = FlutterManifest.createFromString(manifest, logger: logger);
|
||||
|
||||
expect(parsedManifest!.assets, <AssetsEntry>[
|
||||
AssetsEntry(
|
||||
uri: Uri.parse('asset/hello.txt'),
|
||||
transformers: const <AssetTransformerEntry>[
|
||||
AssetTransformerEntry(
|
||||
package: 'my_package',
|
||||
args: <String>['-e', '--color', 'purple'],
|
||||
)
|
||||
],
|
||||
),
|
||||
]);
|
||||
expect(logger.errorText, isEmpty);
|
||||
});
|
||||
|
||||
testWithoutContext('fails when a transformers section is not a list', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- path: asset/hello.txt
|
||||
transformers:
|
||||
- my_transformer
|
||||
''';
|
||||
FlutterManifest.createFromString(manifest, logger: logger);
|
||||
expect(
|
||||
logger.errorText,
|
||||
'Unable to parse assets section.\n'
|
||||
'In transformers section of asset "asset/hello.txt": Expected '
|
||||
'transformers list to be a list of Map, but element at index 0 was a String.\n',
|
||||
);
|
||||
});
|
||||
testWithoutContext('fails when a transformers section package is not a string', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- path: asset/hello.txt
|
||||
transformers:
|
||||
- package:
|
||||
i am a key: i am a value
|
||||
''';
|
||||
FlutterManifest.createFromString(manifest, logger: logger);
|
||||
expect(
|
||||
logger.errorText,
|
||||
'Unable to parse assets section.\n'
|
||||
'In transformers section of asset "asset/hello.txt": '
|
||||
'Expected "package" to be a String. Found YamlMap instead.\n',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('fails when a transformer is missing the package field', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- path: asset/hello.txt
|
||||
transformers:
|
||||
- args: ["-e"]
|
||||
''';
|
||||
FlutterManifest.createFromString(manifest, logger: logger);
|
||||
expect(
|
||||
logger.errorText,
|
||||
'Unable to parse assets section.\n'
|
||||
'In transformers section of asset "asset/hello.txt": Expected "package" to be a '
|
||||
'String. Found Null instead.\n',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('fails when a transformer has args field that is not a list of strings', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- path: asset/hello.txt
|
||||
transformers:
|
||||
- package: my_transformer
|
||||
args: hello
|
||||
''';
|
||||
FlutterManifest.createFromString(manifest, logger: logger);
|
||||
expect(
|
||||
logger.errorText,
|
||||
'Unable to parse assets section.\n'
|
||||
'In transformers section of asset "asset/hello.txt": In args section '
|
||||
'of transformer using package "my_transformer": Expected args to be a '
|
||||
'list of String, but got hello (String).\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue