bundle.dart 8.69 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

7 8
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
9
import 'package:meta/meta.dart';
10
import 'package:pool/pool.dart';
11

12
import 'asset.dart';
13
import 'base/common.dart';
14
import 'base/file_system.dart';
15
import 'base/logger.dart';
16
import 'build_info.dart';
17
import 'build_system/build_system.dart';
18
import 'build_system/depfile.dart';
19
import 'build_system/targets/common.dart';
20
import 'build_system/targets/icon_tree_shaker.dart';
21
import 'cache.dart';
22
import 'convert.dart';
23
import 'devfs.dart';
24
import 'globals.dart' as globals;
25
import 'project.dart';
26

27
String get defaultMainPath => globals.fs.path.join('lib', 'main.dart');
28
const String defaultAssetBasePath = '.';
29
const String defaultManifestPath = 'pubspec.yaml';
30
String get defaultDepfilePath => globals.fs.path.join(getBuildDirectory(), 'snapshot_blob.bin.d');
31

32
String getDefaultApplicationKernelPath({ @required bool trackWidgetCreation }) {
33
  return getKernelPathForTransformerOptions(
34
    globals.fs.path.join(getBuildDirectory(), 'app.dill'),
35 36 37 38
    trackWidgetCreation: trackWidgetCreation,
  );
}

39 40 41
String getDefaultCachedKernelPath({
  @required bool trackWidgetCreation,
  @required List<String> dartDefines,
42
  @required List<String> extraFrontEndOptions,
43 44 45
}) {
  final StringBuffer buffer = StringBuffer();
  buffer.writeAll(dartDefines);
46
  buffer.writeAll(extraFrontEndOptions ?? <String>[]);
47 48 49 50 51 52
  String buildPrefix = '';
  if (buffer.isNotEmpty) {
    final String output = buffer.toString();
    final Digest digest = md5.convert(utf8.encode(output));
    buildPrefix = '${hex.encode(digest.bytes)}.';
  }
53
  return getKernelPathForTransformerOptions(
54
    globals.fs.path.join(getBuildDirectory(), '${buildPrefix}cache.dill'),
55 56 57 58
    trackWidgetCreation: trackWidgetCreation,
  );
}

59 60 61 62 63 64 65 66 67 68
String getKernelPathForTransformerOptions(
  String path, {
  @required bool trackWidgetCreation,
}) {
  if (trackWidgetCreation) {
    path += '.track.dill';
  }
  return path;
}

69 70
const String defaultPrivateKeyPath = 'privatekey.der';

71 72 73 74 75 76 77
/// Provides a `build` method that builds the bundle.
class BundleBuilder {
  /// Builds the bundle for the given target platform.
  ///
  /// The default `mainPath` is `lib/main.dart`.
  /// The default  `manifestPath` is `pubspec.yaml`
  Future<void> build({
78
    @required TargetPlatform platform,
79
    BuildInfo buildInfo,
80
    String mainPath,
81 82 83 84 85 86 87 88 89 90 91 92 93
    String manifestPath = defaultManifestPath,
    String applicationKernelFilePath,
    String depfilePath,
    String privateKeyPath = defaultPrivateKeyPath,
    String assetDirPath,
    String packagesPath,
    bool precompiledSnapshot = false,
    bool reportLicensedPackages = false,
    bool trackWidgetCreation = false,
    List<String> extraFrontEndOptions = const <String>[],
    List<String> extraGenSnapshotOptions = const <String>[],
    List<String> fileSystemRoots,
    String fileSystemScheme,
94
    @required bool treeShakeIcons,
95
  }) async {
96
    mainPath ??= defaultMainPath;
97 98
    depfilePath ??= defaultDepfilePath;
    assetDirPath ??= getAssetBuildDirectory();
99
    packagesPath ??= globals.fs.path.absolute('.packages');
100
    final FlutterProject flutterProject = FlutterProject.current();
101
    await buildWithAssemble(
102
      buildMode: buildInfo.mode,
103 104 105 106 107 108
      targetPlatform: platform,
      mainPath: mainPath,
      flutterProject: flutterProject,
      outputDir: assetDirPath,
      depfilePath: depfilePath,
      precompiled: precompiledSnapshot,
109
      trackWidgetCreation: trackWidgetCreation,
110
      treeShakeIcons: treeShakeIcons,
111
      dartDefines: buildInfo.dartDefines,
112
    );
113 114
    // Work around for flutter_tester placing kernel artifacts in odd places.
    if (applicationKernelFilePath != null) {
115
      final File outputDill = globals.fs.directory(assetDirPath).childFile('kernel_blob.bin');
116 117 118
      if (outputDill.existsSync()) {
        outputDill.copySync(applicationKernelFilePath);
      }
119
    }
120
    return;
121
  }
122 123
}

124 125 126 127 128 129 130 131 132 133
/// Build an application bundle using flutter assemble.
///
/// This is a temporary shim to migrate the build implementations.
Future<void> buildWithAssemble({
  @required FlutterProject flutterProject,
  @required BuildMode buildMode,
  @required TargetPlatform targetPlatform,
  @required String mainPath,
  @required String outputDir,
  @required String depfilePath,
134
  @required bool precompiled,
135
  bool trackWidgetCreation,
136
  @required bool treeShakeIcons,
137
  List<String> dartDefines,
138
}) async {
139 140
  // If the precompiled flag was not passed, force us into debug mode.
  buildMode = precompiled ? buildMode : BuildMode.debug;
141 142
  final Environment environment = Environment(
    projectDir: flutterProject.directory,
143
    outputDir: globals.fs.directory(outputDir),
144
    buildDir: flutterProject.dartTool.childDirectory('flutter_build'),
145 146
    cacheDir: globals.cache.getRoot(),
    flutterRootDir: globals.fs.directory(Cache.flutterRoot),
147 148 149
    engineVersion: globals.artifacts.isLocalEngine
      ? null
      : globals.flutterVersion.engineRevision,
150 151 152 153
    defines: <String, String>{
      kTargetFile: mainPath,
      kBuildMode: getNameForBuildMode(buildMode),
      kTargetPlatform: getNameForTargetPlatform(targetPlatform),
154
      kTrackWidgetCreation: trackWidgetCreation?.toString(),
155
      kIconTreeShakerFlag: treeShakeIcons ? 'true' : null,
156
      if (dartDefines != null && dartDefines.isNotEmpty)
157
        kDartDefines: encodeDartDefines(dartDefines),
158
    },
159 160 161 162
    artifacts: globals.artifacts,
    fileSystem: globals.fs,
    logger: globals.logger,
    processManager: globals.processManager,
163 164 165 166
  );
  final Target target = buildMode == BuildMode.debug
    ? const CopyFlutterBundle()
    : const ReleaseCopyFlutterBundle();
167
  final BuildResult result = await globals.buildSystem.build(target, environment);
168 169

  if (!result.success) {
170
    for (final ExceptionMeasurement measurement in result.exceptions.values) {
171
        globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
172 173 174 175
          stackTrace: measurement.fatal
            ? measurement.stackTrace
            : null,
        );
176 177 178
    }
    throwToolExit('Failed to build bundle.');
  }
179 180
  if (depfilePath != null) {
    final Depfile depfile = Depfile(result.inputFiles, result.outputFiles);
181
    final File outputDepfile = globals.fs.file(depfilePath);
182 183
    if (!outputDepfile.parent.existsSync()) {
      outputDepfile.parent.createSync(recursive: true);
184
    }
185 186 187 188 189
    final DepfileService depfileService = DepfileService(
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    depfileService.writeToFile(depfile, outputDepfile);
190 191 192
  }
}

193
Future<AssetBundle> buildAssets({
194
  String manifestPath,
195
  String assetDirPath,
196
  @required String packagesPath,
197
  bool includeDefaultFonts = true,
198
  bool reportLicensedPackages = false,
199
}) async {
200
  assetDirPath ??= getAssetBuildDirectory();
201
  packagesPath ??= globals.fs.path.absolute(packagesPath);
202

203
  // Build the asset bundle.
204
  final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
205
  final int result = await assetBundle.build(
206
    manifestPath: manifestPath,
207
    assetDirPath: assetDirPath,
208
    packagesPath: packagesPath,
209
    includeDefaultFonts: includeDefaultFonts,
210
    reportLicensedPackages: reportLicensedPackages,
211
  );
212
  if (result != 0) {
213
    return null;
214
  }
215 216 217 218

  return assetBundle;
}

219
Future<void> writeBundle(
220 221
  Directory bundleDir,
  Map<String, DevFSContent> assetEntries,
222
  { Logger loggerOverride }
223
) async {
224
  loggerOverride ??= globals.logger;
225
  if (bundleDir.existsSync()) {
226 227 228 229 230 231 232 233
    try {
      bundleDir.deleteSync(recursive: true);
    } on FileSystemException catch (err) {
      loggerOverride.printError(
        'Failed to clean up asset directory ${bundleDir.path}: $err\n'
        'To clean build artifacts, use the command "flutter clean".'
      );
    }
234
  }
235 236
  bundleDir.createSync(recursive: true);

237 238
  // Limit number of open files to avoid running out of file descriptors.
  final Pool pool = Pool(64);
239 240
  await Future.wait<void>(
    assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
241 242
      final PoolResource resource = await pool.request();
      try {
Dan Field's avatar
Dan Field committed
243 244 245 246 247
        // This will result in strange looking files, for example files with `/`
        // on Windows or files that end up getting URI encoded such as `#.ext`
        // to `%23.ext`.  However, we have to keep it this way since the
        // platform channels in the framework will URI encode these values,
        // and the native APIs will look for files this way.
248
        final File file = globals.fs.file(globals.fs.path.join(bundleDir.path, entry.key));
249 250 251 252 253
        file.parent.createSync(recursive: true);
        await file.writeAsBytes(await entry.value.contentsAsBytes());
      } finally {
        resource.release();
      }
254
    }));
255
}