Unverified Commit 81530744 authored by Gary Qian's avatar Gary Qian Committed by GitHub

flutter_tool: DeferredComponent and LoadingUnit classes, Manifest...

flutter_tool: DeferredComponent and LoadingUnit classes, Manifest deferred-components parsing (#75394)
parent 859aec59
// 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.
// @dart = 2.8
import 'package:meta/meta.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../convert.dart';
/// Represents a configured deferred component as defined in
/// the app's pubspec.yaml.
class DeferredComponent {
DeferredComponent({
@required this.name,
this.libraries = const <String>[],
this.assets = const <Uri>[],
}) : _assigned = false;
/// The name of the deferred component. There should be a matching
/// android dynamic feature module with the same name.
final String name;
/// The dart libraries this component includes as listed in pubspec.yaml.
///
/// This list is only of dart libraries manually configured to be in this component.
/// Valid libraries that are listed here will always be guaranteed to be
/// packaged in this component. However, libraries that are not listed here
/// may also be included if the loading units that are needed also contain
/// libraries that are not listed here.
final List<String> libraries;
/// Assets that are part of this component as a Uri relative to the project directory.
final List<Uri> assets;
/// The minimal set of [LoadingUnit]s needed that contain all of the dart libraries in
/// [libraries].
///
/// Each [LoadingUnit] contains the compiled code for a set of dart libraries. Each
/// [DeferredComponent] contains a list of dart libraries that must be included in the
/// component. The set [loadingUnits] is all of the [LoadingUnit]s needed such that
/// all required dart libs in [libraries] are in the union of the [LoadingUnit.libraries]
/// included by the loading units in [loadingUnits].
///
/// When [loadingUnits] is non-null, then the component is considered [assigned] and the
/// field [assigned] will be true. When [loadingUnits] is null, then the component is
/// unassigned and should not be used for any tasks that require loading unit information.
/// When using [loadingUnits], [assigned] should be checked first. Loading units can be
/// assigned with [assignLoadingUnits].
Set<LoadingUnit> get loadingUnits => _loadingUnits;
Set<LoadingUnit> _loadingUnits;
/// Indicates if the component has loading units assigned.
///
/// Unassigned components simply reflect the pubspec.yaml configuration directly,
/// contain no loading unit data, and [loadingUnits] is null. Once assigned, the component
/// will contain a set of [loadingUnits] which contains the [LoadingUnit]s that the
/// component needs to include. Loading units can be assigned with the [assignLoadingUnits]
/// call.
bool get assigned => _assigned;
bool _assigned;
/// Selects the [LoadingUnit]s that contain this component's dart libraries.
///
/// After calling this method, this [DeferredComponent] will be considered [assigned],
/// and [loadingUnits] will return a non-null result.
///
/// [LoadingUnit]s in `allLoadingUnits` that contain libraries that are in [libraries]
/// are added to the set [loadingUnits].
///
/// Providing null or empty list of `allLoadingUnits` will still change the assigned
/// status, but will result in [loadingUnits] returning an empty set.
void assignLoadingUnits(List<LoadingUnit> allLoadingUnits) {
_assigned = true;
_loadingUnits = <LoadingUnit>{};
if (allLoadingUnits == null) {
return;
}
for (final String lib in libraries) {
for (final LoadingUnit loadingUnit in allLoadingUnits) {
if (loadingUnit.libraries.contains(lib)) {
_loadingUnits.add(loadingUnit);
}
}
}
}
/// Provides a human readable string representation of the
/// configuration.
@override
String toString() {
final StringBuffer out = StringBuffer('\nDeferredComponent: $name\n Libraries:');
for (final String lib in libraries) {
out.write('\n - $lib');
}
if (loadingUnits != null && _assigned) {
out.write('\n LoadingUnits:');
for (final LoadingUnit loadingUnit in loadingUnits) {
out.write('\n - ${loadingUnit.id}');
}
}
out.write('\n Assets:');
for (final Uri asset in assets) {
out.write('\n - ${asset.path}');
}
return out.toString();
}
}
/// Represents a single loading unit and holds information regarding it's id,
/// shared library path, and dart libraries in it.
class LoadingUnit {
/// Constructs a [LoadingUnit].
///
/// Loading units must include an [id] and [libraries]. The [path] is only present when
/// parsing the loading unit from a laoding unit manifest produced by gen_snapshot.
LoadingUnit({
@required this.id,
@required this.libraries,
this.path,
});
/// The unique loading unit id that is used to identify the loading unit within dart.
final int id;
/// A list of dart libraries that the loading unit contains.
final List<String> libraries;
/// The output path of the shared library .so file created by gen_snapshot.
///
/// This value may be null when the loading unit is parsed from a
/// `deferred_components_golden.yaml` file, which does not store the path.
final String path;
/// Returns a human readable string representation of this LoadingUnit, ignoring
/// the [path] field. The [path] is not included as it is not relevant when the
@override
String toString() {
final StringBuffer out = StringBuffer('\nLoadingUnit $id\n Libraries:');
for (final String lib in libraries) {
out.write('\n - $lib');
}
return out.toString();
}
/// Returns true if the other loading unit has the same [id] and the same set of [libraries],
/// ignoring order.
bool equalsIgnoringPath(LoadingUnit other) {
return other.id == id && other.libraries.toSet().containsAll(libraries);
}
/// Parses the loading unit manifests from the [outputDir] of the latest
/// gen_snapshot/assemble run.
///
/// This will read all existing loading units for every provided abi. If no abis are
/// provided, loading units for all abis will be parsed.
static List<LoadingUnit> parseGeneratedLoadingUnits(Directory outputDir, Logger logger, {List<String> abis}) {
final List<LoadingUnit> loadingUnits = <LoadingUnit>[];
final List<FileSystemEntity> files = outputDir.listSync(recursive: true);
for (final FileSystemEntity fileEntity in files) {
if (fileEntity is File) {
final File file = fileEntity;
// Determine if the abi is one we build.
bool matchingAbi = abis == null;
if (abis != null) {
for (final String abi in abis) {
if (file.parent.path.endsWith(abi)) {
matchingAbi = true;
break;
}
}
}
if (!file.path.endsWith('manifest.json') || !matchingAbi) {
continue;
}
loadingUnits.addAll(parseLoadingUnitManifest(file, logger));
}
}
return loadingUnits;
}
/// Parses loading units from a single loading unit manifest json file.
///
/// Returns an empty list if the manifestFile does not exist or is invalid.
static List<LoadingUnit> parseLoadingUnitManifest(File manifestFile, Logger logger) {
if (!manifestFile.existsSync()) {
return <LoadingUnit>[];
}
// Read gen_snapshot manifest
final String fileString = manifestFile.readAsStringSync();
Map<String, dynamic> manifest;
try {
manifest = jsonDecode(fileString) as Map<String, dynamic>;
} on FormatException catch (e) {
logger.printError('Loading unit manifest at `${manifestFile.path}` was invalid JSON:\n$e');
}
final List<LoadingUnit> loadingUnits = <LoadingUnit>[];
// Setup android source directory
if (manifest != null) {
for (final dynamic loadingUnitMetadata in manifest['loadingUnits']) {
final Map<String, dynamic> loadingUnitMap = loadingUnitMetadata as Map<String, dynamic>;
if (loadingUnitMap['id'] == 1) {
continue; // Skip base unit
}
loadingUnits.add(LoadingUnit(
id: loadingUnitMap['id'] as int,
path: loadingUnitMap['path'] as String,
libraries: List<String>.from(loadingUnitMap['libraries'] as List<dynamic>)),
);
}
}
return loadingUnits;
}
}
......@@ -6,6 +6,7 @@
import '../../artifacts.dart';
import '../../base/build.dart';
import '../../base/deferred_component.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../globals.dart' as globals hide fs, artifacts, logger, processManager;
......@@ -311,3 +312,79 @@ const Target androidx64ProfileBundle = AndroidAotBundle(androidx64Profile);
const Target androidArmReleaseBundle = AndroidAotBundle(androidArmRelease);
const Target androidArm64ReleaseBundle = AndroidAotBundle(androidArm64Release);
const Target androidx64ReleaseBundle = AndroidAotBundle(androidx64Release);
/// Utility method to copy and rename the required .so shared libs from the build output
/// to the correct component intermediate directory.
///
/// The [DeferredComponent]s passed to this method must have had loading units assigned.
/// Assigned components are components that have determined which loading units contains
/// the dart libraries it has via the DeferredComponent.assignLoadingUnits method.
Depfile copyDeferredComponentSoFiles(
Environment env,
List<DeferredComponent> components,
List<LoadingUnit> loadingUnits,
Directory buildDir, // generally `<projectDir>/build`
List<String> abis,
BuildMode buildMode,) {
final List<File> inputs = <File>[];
final List<File> outputs = <File>[];
final Set<int> usedLoadingUnits = <int>{};
// Copy all .so files for loading units that are paired with a deferred component.
for (final String abi in abis) {
for (final DeferredComponent component in components) {
if (!component.assigned) {
globals.printError('Deferred component require loading units to be assigned.');
return Depfile(inputs, outputs);
}
for (final LoadingUnit unit in component.loadingUnits) {
// ensure the abi for the unit is one of the abis we build for.
final List<String> splitPath = unit.path.split(env.fileSystem.path.separator);
if (splitPath[splitPath.length - 2] != abi) {
continue;
}
usedLoadingUnits.add(unit.id);
// the deferred_libs directory is added as a source set for the component.
final File destination = buildDir
.childDirectory(component.name)
.childDirectory('intermediates')
.childDirectory('flutter')
.childDirectory(buildMode.name)
.childDirectory('deferred_libs')
.childDirectory(abi)
.childFile('libapp.so-${unit.id}.part.so');
if (!destination.existsSync()) {
destination.createSync(recursive: true);
}
final File source = env.fileSystem.file(unit.path);
source.copySync(destination.path);
inputs.add(source);
outputs.add(destination);
}
}
}
// Copy unused loading units, which are included in the base module.
for (final String abi in abis) {
for (final LoadingUnit unit in loadingUnits) {
if (usedLoadingUnits.contains(unit.id)) {
continue;
}
// ensure the abi for the unit is one of the abis we build for.
final List<String> splitPath = unit.path.split(env.fileSystem.path.separator);
if (splitPath[splitPath.length - 2] != abi) {
continue;
}
final File destination = env.outputDir
.childDirectory(abi)
// Omit 'lib' prefix here as it is added by the gradle task that adds 'lib' to 'app.so'.
.childFile('app.so-${unit.id}.part.so');
if (!destination.existsSync()) {
destination.createSync(recursive: true);
}
final File source = env.fileSystem.file(unit.path);
source.copySync(destination.path);
inputs.add(source);
outputs.add(destination);
}
}
return Depfile(inputs, outputs);
}
......@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
import 'base/deferred_component.dart';
import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/user_messages.dart';
......@@ -198,6 +199,50 @@ class FlutterManifest {
return null;
}
/// Returns the deferred components configuration if declared. Returns
/// null if no deferred components are declared.
List<DeferredComponent> get deferredComponents => _deferredComponents ??= computeDeferredComponents();
List<DeferredComponent> _deferredComponents;
List<DeferredComponent> computeDeferredComponents() {
if (!_flutterDescriptor.containsKey('deferred-components')) {
return null;
}
final List<DeferredComponent> components = <DeferredComponent>[];
if (_flutterDescriptor['deferred-components'] == null) {
return components;
}
for (final dynamic componentData in _flutterDescriptor['deferred-components']) {
final YamlMap component = componentData as YamlMap;
List<Uri> assetsUri = <Uri>[];
final List<dynamic> assets = component['assets'] as List<dynamic>;
if (assets == null) {
assetsUri = const <Uri>[];
} else {
for (final Object asset in assets) {
if (asset is! String || asset == null || asset == '') {
_logger.printError('Deferred component asset manifest contains a null or empty uri.');
continue;
}
final String stringAsset = asset as String;
try {
assetsUri.add(Uri.parse(stringAsset));
} on FormatException {
_logger.printError('Asset manifest contains invalid uri: $asset.');
}
}
}
components.add(
DeferredComponent(
name: component['name'] as String,
libraries: component['libraries'] == null ?
<String>[] : component['libraries'].cast<String>() as List<String>,
assets: assetsUri,
)
);
}
return components;
}
/// Returns the iOS bundle identifier declared by this manifest in its
/// module descriptor. Returns null if there is no such declaration.
String get iosBundleIdentifier {
......@@ -455,7 +500,7 @@ void _validateFlutter(YamlMap yaml, List<String> errors) {
case 'licenses':
final dynamic value = kvp.value;
if (value is YamlList) {
_validateListType<String>(value, '${kvp.key}', errors);
_validateListType<String>(value, errors, '"${kvp.key}"', 'files');
} else {
errors.add('Expected "${kvp.key}" to be a list of files, but got $value (${value.runtimeType})');
}
......@@ -485,6 +530,9 @@ void _validateFlutter(YamlMap yaml, List<String> errors) {
break;
case 'generate':
break;
case 'deferred-components':
_validateDeferredComponents(kvp, errors);
break;
default:
errors.add('Unexpected child "${kvp.key}" found under "flutter".');
break;
......@@ -492,10 +540,40 @@ void _validateFlutter(YamlMap yaml, List<String> errors) {
}
}
void _validateListType<T>(YamlList yamlList, String context, List<String> errors) {
void _validateListType<T>(YamlList yamlList, List<String> errors, String context, String typeAlias) {
for (int i = 0; i < yamlList.length; i++) {
if (yamlList[i] is! T) {
errors.add('Expected "$context" to be a list of files, but element $i was a ${yamlList[i].runtimeType}');
errors.add('Expected $context to be a list of $typeAlias, but element $i was a ${yamlList[i].runtimeType}');
}
}
}
void _validateDeferredComponents(MapEntry<dynamic, dynamic> kvp, List<String> errors) {
if (kvp.value != null && (kvp.value is! YamlList || kvp.value[0] is! YamlMap)) {
errors.add('Expected "${kvp.key}" to be a list, but got ${kvp.value} (${kvp.value.runtimeType}).');
} else if (kvp.value != null) {
for (int i = 0; i < (kvp.value as YamlList).length; i++) {
if (kvp.value[i] is! YamlMap) {
errors.add('Expected the $i element in "${kvp.key}" to be a map, but got ${kvp.value[i]} (${kvp.value[i].runtimeType}).');
continue;
}
if (!(kvp.value[i] as YamlMap).containsKey('name') || kvp.value[i]['name'] is! String) {
errors.add('Expected the $i element in "${kvp.key}" to have required key "name" of type String');
}
if ((kvp.value[i] as YamlMap).containsKey('libraries')) {
if (kvp.value[i]['libraries'] is! YamlList) {
errors.add('Expected "libraries" key in the $i element of "${kvp.key}" to be a list, but got ${kvp.value[i]['libraries']} (${kvp.value[i]['libraries'].runtimeType}).');
} else {
_validateListType<String>(kvp.value[i]['libraries'] as YamlList, errors, '"libraries" key in the $i element of "${kvp.key}"', 'dart library Strings');
}
}
if ((kvp.value[i] as YamlMap).containsKey('assets')) {
if (kvp.value[i]['assets'] is! YamlList) {
errors.add('Expected "assets" key in the $i element of "${kvp.key}" to be a list, but got ${kvp.value[i]['assets']} (${kvp.value[i]['assets'].runtimeType}).');
} else {
_validateListType<String>(kvp.value[i]['assets'] as YamlList, errors, '"assets" key in the $i element of "${kvp.key}"', 'file paths');
}
}
}
}
}
......
......@@ -7,11 +7,13 @@
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/deferred_component.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/depfile.dart';
import 'package:flutter_tools/src/build_system/targets/android.dart';
import 'package:flutter_tools/src/build_system/targets/assets.dart';
import 'package:flutter_tools/src/build_system/targets/common.dart';
......@@ -362,4 +364,168 @@ void main() {
.childDirectory('arm64-v8a')
.childFile('app.so').existsSync(), true);
});
test('copyDeferredComponentSoFiles copies all files to correct locations', () {
final Environment environment = Environment.test(
fileSystem.currentDirectory,
outputDir: fileSystem.directory('/out')..createSync(),
defines: <String, String>{
kBuildMode: 'release',
},
processManager: processManager,
artifacts: artifacts,
fileSystem: fileSystem,
logger: logger,
);
final File so1 = fileSystem.file('/unit2/abi1/part.so');
so1.createSync(recursive: true);
so1.writeAsStringSync('lib1');
final File so2 = fileSystem.file('/unit3/abi1/part.so');
so2.createSync(recursive: true);
so2.writeAsStringSync('lib2');
final File so3 = fileSystem.file('/unit4/abi1/part.so');
so3.createSync(recursive: true);
so3.writeAsStringSync('lib3');
final File so4 = fileSystem.file('/unit2/abi2/part.so');
so4.createSync(recursive: true);
so4.writeAsStringSync('lib1');
final File so5 = fileSystem.file('/unit3/abi2/part.so');
so5.createSync(recursive: true);
so5.writeAsStringSync('lib2');
final File so6 = fileSystem.file('/unit4/abi2/part.so');
so6.createSync(recursive: true);
so6.writeAsStringSync('lib3');
final List<DeferredComponent> components = <DeferredComponent>[
DeferredComponent(name: 'component2', libraries: <String>['lib1']),
DeferredComponent(name: 'component3', libraries: <String>['lib2']),
];
final List<LoadingUnit> loadingUnits = <LoadingUnit>[
LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi1/part.so'),
LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi1/part.so'),
LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi1/part.so'),
LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi2/part.so'),
LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi2/part.so'),
LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi2/part.so'),
];
for (final DeferredComponent component in components) {
component.assignLoadingUnits(loadingUnits);
}
final Directory buildDir = fileSystem.directory('/build');
if (!buildDir.existsSync()) {
buildDir.createSync(recursive: true);
}
final Depfile depfile = copyDeferredComponentSoFiles(
environment,
components,
loadingUnits,
buildDir,
<String>['abi1', 'abi2'],
BuildMode.release
);
expect(depfile.inputs.length, 6);
expect(depfile.outputs.length, 6);
expect(depfile.inputs[0].path, so1.path);
expect(depfile.inputs[1].path, so2.path);
expect(depfile.inputs[2].path, so4.path);
expect(depfile.inputs[3].path, so5.path);
expect(depfile.inputs[4].path, so3.path);
expect(depfile.inputs[5].path, so6.path);
expect(depfile.outputs[0].readAsStringSync(), so1.readAsStringSync());
expect(depfile.outputs[1].readAsStringSync(), so2.readAsStringSync());
expect(depfile.outputs[2].readAsStringSync(), so4.readAsStringSync());
expect(depfile.outputs[3].readAsStringSync(), so5.readAsStringSync());
expect(depfile.outputs[4].readAsStringSync(), so3.readAsStringSync());
expect(depfile.outputs[5].readAsStringSync(), so6.readAsStringSync());
expect(depfile.outputs[0].path, '/build/component2/intermediates/flutter/release/deferred_libs/abi1/libapp.so-2.part.so');
expect(depfile.outputs[1].path, '/build/component3/intermediates/flutter/release/deferred_libs/abi1/libapp.so-3.part.so');
expect(depfile.outputs[2].path, '/build/component2/intermediates/flutter/release/deferred_libs/abi2/libapp.so-2.part.so');
expect(depfile.outputs[3].path, '/build/component3/intermediates/flutter/release/deferred_libs/abi2/libapp.so-3.part.so');
expect(depfile.outputs[4].path, '/out/abi1/app.so-4.part.so');
expect(depfile.outputs[5].path, '/out/abi2/app.so-4.part.so');
});
test('copyDeferredComponentSoFiles copies files for only listed abis', () {
final Environment environment = Environment.test(
fileSystem.currentDirectory,
outputDir: fileSystem.directory('/out')..createSync(),
defines: <String, String>{
kBuildMode: 'release',
},
processManager: processManager,
artifacts: artifacts,
fileSystem: fileSystem,
logger: logger,
);
final File so1 = fileSystem.file('/unit2/abi1/part.so');
so1.createSync(recursive: true);
so1.writeAsStringSync('lib1');
final File so2 = fileSystem.file('/unit3/abi1/part.so');
so2.createSync(recursive: true);
so2.writeAsStringSync('lib2');
final File so3 = fileSystem.file('/unit4/abi1/part.so');
so3.createSync(recursive: true);
so3.writeAsStringSync('lib3');
final File so4 = fileSystem.file('/unit2/abi2/part.so');
so4.createSync(recursive: true);
so4.writeAsStringSync('lib1');
final File so5 = fileSystem.file('/unit3/abi2/part.so');
so5.createSync(recursive: true);
so5.writeAsStringSync('lib2');
final File so6 = fileSystem.file('/unit4/abi2/part.so');
so6.createSync(recursive: true);
so6.writeAsStringSync('lib3');
final List<DeferredComponent> components = <DeferredComponent>[
DeferredComponent(name: 'component2', libraries: <String>['lib1']),
DeferredComponent(name: 'component3', libraries: <String>['lib2']),
];
final List<LoadingUnit> loadingUnits = <LoadingUnit>[
LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi1/part.so'),
LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi1/part.so'),
LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi1/part.so'),
LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi2/part.so'),
LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi2/part.so'),
LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi2/part.so'),
];
for (final DeferredComponent component in components) {
component.assignLoadingUnits(loadingUnits);
}
final Directory buildDir = fileSystem.directory('/build');
if (!buildDir.existsSync()) {
buildDir.createSync(recursive: true);
}
final Depfile depfile = copyDeferredComponentSoFiles(
environment,
components,
loadingUnits,
buildDir,
<String>['abi1'],
BuildMode.release
);
expect(depfile.inputs.length, 3);
expect(depfile.outputs.length, 3);
expect(depfile.inputs[0].path, so1.path);
expect(depfile.inputs[1].path, so2.path);
expect(depfile.inputs[2].path, so3.path);
expect(depfile.outputs[0].readAsStringSync(), so1.readAsStringSync());
expect(depfile.outputs[1].readAsStringSync(), so2.readAsStringSync());
expect(depfile.outputs[2].readAsStringSync(), so3.readAsStringSync());
expect(depfile.outputs[0].path, '/build/component2/intermediates/flutter/release/deferred_libs/abi1/libapp.so-2.part.so');
expect(depfile.outputs[1].path, '/build/component3/intermediates/flutter/release/deferred_libs/abi1/libapp.so-3.part.so');
expect(depfile.outputs[2].path, '/out/abi1/app.so-4.part.so');
});
}
......@@ -1144,6 +1144,303 @@ flutter:
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "licenses" to be a list of files, but element 1 was a YamlMap\n');
});
testWithoutContext('FlutterManifest parses single deferred components', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: component1
libraries:
- lib1
assets:
- path/to/asset.jpg
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, isNotNull);
expect(flutterManifest.deferredComponents.length, 1);
expect(flutterManifest.deferredComponents[0].name, 'component1');
expect(flutterManifest.deferredComponents[0].libraries.length, 1);
expect(flutterManifest.deferredComponents[0].libraries[0], 'lib1');
expect(flutterManifest.deferredComponents[0].assets.length, 1);
expect(flutterManifest.deferredComponents[0].assets[0].path, 'path/to/asset.jpg');
});
testWithoutContext('FlutterManifest parses multiple deferred components', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: component1
libraries:
- lib1
assets:
- path/to/asset.jpg
- name: component2
libraries:
- lib2
- lib3
assets:
- path/to/asset2.jpg
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, isNotNull);
expect(flutterManifest.deferredComponents.length, 2);
expect(flutterManifest.deferredComponents[0].name, 'component1');
expect(flutterManifest.deferredComponents[0].libraries.length, 1);
expect(flutterManifest.deferredComponents[0].libraries[0], 'lib1');
expect(flutterManifest.deferredComponents[0].assets.length, 1);
expect(flutterManifest.deferredComponents[0].assets[0].path, 'path/to/asset.jpg');
expect(flutterManifest.deferredComponents[1].name, 'component2');
expect(flutterManifest.deferredComponents[1].libraries.length, 2);
expect(flutterManifest.deferredComponents[1].libraries[0], 'lib2');
expect(flutterManifest.deferredComponents[1].libraries[1], 'lib3');
expect(flutterManifest.deferredComponents[1].assets.length, 1);
expect(flutterManifest.deferredComponents[1].assets[0].path, 'path/to/asset2.jpg');
});
testWithoutContext('FlutterManifest parses empty deferred components', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, isNotNull);
expect(flutterManifest.deferredComponents.length, 0);
});
testWithoutContext('FlutterManifest deferred component requires name', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- libraries:
- lib1
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected the 0 element in "deferred-components" to have required key "name" of type String\n');
});
testWithoutContext('FlutterManifest deferred component is list', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components: blah
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "deferred-components" to be a list, but got blah (String).\n');
});
testWithoutContext('FlutterManifest deferred component libraries is list', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: blah
libraries: blah
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "libraries" key in the 0 element of "deferred-components" to be a list, but got blah (String).\n');
});
testWithoutContext('FlutterManifest deferred component libraries is string', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: blah
libraries:
- not-a-string:
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "libraries" key in the 0 element of "deferred-components" to be a list of dart library Strings, but element 0 was a YamlMap\n');
});
testWithoutContext('FlutterManifest deferred component assets is string', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: blah
assets:
- not-a-string:
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "assets" key in the 0 element of "deferred-components" to be a list of file paths, but element 0 was a YamlMap\n');
});
testWithoutContext('FlutterManifest deferred component multiple assets is string', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: blah
assets:
- path/to/file.so
- also-not-a-string:
- woo
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "assets" key in the 0 element of "deferred-components" to be a list of file paths, but element 1 was a YamlMap\n');
});
testWithoutContext('FlutterManifest multiple deferred components assets is string', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: blah
assets:
- path/to/file.so
- name: blah2
assets:
- path/to/other/file.so
- not-a-string:
- woo
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "assets" key in the 1 element of "deferred-components" to be a list of file paths, but element 1 was a YamlMap\n');
});
testWithoutContext('FlutterManifest deferred component assets is list', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: blah
assets: blah
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null);
expect(logger.errorText, 'Expected "assets" key in the 0 element of "deferred-components" to be a list, but got blah (String).\n');
});
testWithoutContext('FlutterManifest parses asset-only deferred components', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
deferred-components:
- name: component1
assets:
- path/to/asset1.jpg
- path/to/asset2.jpg
- path/to/asset3.jpg
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, isNotNull);
expect(flutterManifest.deferredComponents.length, 1);
expect(flutterManifest.deferredComponents[0].name, 'component1');
expect(flutterManifest.deferredComponents[0].libraries.length, 0);
expect(flutterManifest.deferredComponents[0].assets.length, 3);
expect(flutterManifest.deferredComponents[0].assets[0].path, 'path/to/asset1.jpg');
expect(flutterManifest.deferredComponents[0].assets[1].path, 'path/to/asset2.jpg');
expect(flutterManifest.deferredComponents[0].assets[2].path, 'path/to/asset3.jpg');
});
}
Matcher matchesManifest({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment