deferred_component.dart 8.25 KB
Newer Older
1 2 3 4 5 6 7
// 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';
8
import '../flutter_manifest.dart';
9 10 11 12 13

/// Represents a configured deferred component as defined in
/// the app's pubspec.yaml.
class DeferredComponent {
  DeferredComponent({
14
    required this.name,
15
    this.libraries = const <String>[],
16
    this.assets = const <AssetsEntry>[],
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
  }) : _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;

32 33
  /// Assets that are part of this component.
  final List<AssetsEntry> assets;
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

  /// 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].
49 50
  Set<LoadingUnit>? get loadingUnits => _loadingUnits;
  Set<LoadingUnit>? _loadingUnits;
51 52 53

  /// Indicates if the component has loading units assigned.
  ///
54
  /// Unassigned components reflect the pubspec.yaml configuration directly,
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  /// 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>{};
    for (final String lib in libraries) {
      for (final LoadingUnit loadingUnit in allLoadingUnits) {
        if (loadingUnit.libraries.contains(lib)) {
78
          _loadingUnits!.add(loadingUnit);
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
        }
      }
    }
  }

  /// 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:');
94
      for (final LoadingUnit loadingUnit in loadingUnits!) {
95 96 97 98
        out.write('\n    - ${loadingUnit.id}');
      }
    }
    out.write('\n  Assets:');
99 100 101 102 103
    for (final AssetsEntry asset in assets) {
      out.write('\n    - ${asset.uri.path}');
      if (asset.flavors.isNotEmpty) {
        out.write(' (flavors: ${asset.flavors.join(', ')})');
      }
104 105 106 107 108 109 110 111 112 113 114
    }
    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
115
  /// parsing the loading unit from a loading unit manifest produced by gen_snapshot.
116
  LoadingUnit({
117 118
    required this.id,
    required this.libraries,
119 120 121 122 123 124 125 126 127 128 129 130 131
    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.
132
  final String? path;
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

  /// 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.
156
  static List<LoadingUnit> parseGeneratedLoadingUnits(Directory outputDir, Logger logger, {List<String>? abis}) {
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
    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();
190
    Map<String, dynamic>? manifest;
191 192 193 194 195 196 197 198
    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) {
199
      for (final dynamic loadingUnitMetadata in manifest['loadingUnits'] as List<dynamic>) {
200 201 202 203 204 205 206 207 208 209 210 211 212 213
        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;
  }
}