1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// 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 '../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 loading 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;
}
}