bundle_builder.dart 5.79 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 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:meta/meta.dart';
import 'package:pool/pool.dart';

import 'asset.dart' hide defaultManifestPath;
import 'base/common.dart';
import 'base/file_system.dart';
import 'base/logger.dart';
import 'build_info.dart';
import 'build_system/build_system.dart';
import 'build_system/depfile.dart';
import 'build_system/targets/common.dart';
import 'bundle.dart';
import 'cache.dart';
import 'devfs.dart';
19
import 'globals.dart' as globals;
20 21 22 23 24 25 26 27 28 29
import 'project.dart';


/// 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({
30 31 32 33
    required TargetPlatform platform,
    required BuildInfo buildInfo,
    FlutterProject? project,
    String? mainPath,
34
    String manifestPath = defaultManifestPath,
35 36 37 38
    String? applicationKernelFilePath,
    String? depfilePath,
    String? assetDirPath,
    @visibleForTesting BuildSystem? buildSystem,
39 40 41 42 43 44 45 46 47 48 49 50 51 52
  }) async {
    project ??= FlutterProject.current();
    mainPath ??= defaultMainPath;
    depfilePath ??= defaultDepfilePath;
    assetDirPath ??= getAssetBuildDirectory();
    buildSystem ??= globals.buildSystem;

    // If the precompiled flag was not passed, force us into debug mode.
    final Environment environment = Environment(
      projectDir: project.directory,
      outputDir: globals.fs.directory(assetDirPath),
      buildDir: project.dartTool.childDirectory('flutter_build'),
      cacheDir: globals.cache.getRoot(),
      flutterRootDir: globals.fs.directory(Cache.flutterRoot),
53
      engineVersion: globals.artifacts!.isLocalEngine
54 55 56 57 58 59 60 61 62
          ? null
          : globals.flutterVersion.engineRevision,
      defines: <String, String>{
        // used by the KernelSnapshot target
        kTargetPlatform: getNameForTargetPlatform(platform),
        kTargetFile: mainPath,
        kDeferredComponents: 'false',
        ...buildInfo.toBuildSystemEnvironment(),
      },
63
      artifacts: globals.artifacts!,
64 65 66 67 68 69 70 71 72
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      platform: globals.platform,
      generateDartPluginRegistry: true,
    );
    final Target target = buildInfo.mode == BuildMode.debug
        ? const CopyFlutterBundle()
        : const ReleaseCopyFlutterBundle();
73
    final BuildResult result = await buildSystem.build(target, environment);
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

    if (!result.success) {
      for (final ExceptionMeasurement measurement in result.exceptions.values) {
        globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
          stackTrace: measurement.fatal
              ? measurement.stackTrace
              : null,
        );
      }
      throwToolExit('Failed to build bundle.');
    }
    if (depfilePath != null) {
      final Depfile depfile = Depfile(result.inputFiles, result.outputFiles);
      final File outputDepfile = globals.fs.file(depfilePath);
      if (!outputDepfile.parent.existsSync()) {
        outputDepfile.parent.createSync(recursive: true);
      }
      final DepfileService depfileService = DepfileService(
        fileSystem: globals.fs,
        logger: globals.logger,
      );
      depfileService.writeToFile(depfile, outputDepfile);
    }

    // Work around for flutter_tester placing kernel artifacts in odd places.
    if (applicationKernelFilePath != null) {
      final File outputDill = globals.fs.directory(assetDirPath).childFile('kernel_blob.bin');
      if (outputDill.existsSync()) {
        outputDill.copySync(applicationKernelFilePath);
      }
    }
    return;
  }
}

109 110 111 112 113
Future<AssetBundle?> buildAssets({
  required String manifestPath,
  String? assetDirPath,
  String? packagesPath,
  TargetPlatform? targetPlatform,
114 115
}) async {
  assetDirPath ??= getAssetBuildDirectory();
116
  packagesPath ??= globals.fs.path.absolute('.packages');
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

  // Build the asset bundle.
  final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
  final int result = await assetBundle.build(
    manifestPath: manifestPath,
    assetDirPath: assetDirPath,
    packagesPath: packagesPath,
    targetPlatform: targetPlatform,
  );
  if (result != 0) {
    return null;
  }

  return assetBundle;
}

Future<void> writeBundle(
  Directory bundleDir,
  Map<String, DevFSContent> assetEntries,
136
  { Logger? loggerOverride }
137 138 139 140 141 142
) async {
  loggerOverride ??= globals.logger;
  if (bundleDir.existsSync()) {
    try {
      bundleDir.deleteSync(recursive: true);
    } on FileSystemException catch (err) {
143
      loggerOverride.printWarning(
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
        'Failed to clean up asset directory ${bundleDir.path}: $err\n'
        'To clean build artifacts, use the command "flutter clean".'
      );
    }
  }
  bundleDir.createSync(recursive: true);

  // Limit number of open files to avoid running out of file descriptors.
  final Pool pool = Pool(64);
  await Future.wait<void>(
    assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
      final PoolResource resource = await pool.request();
      try {
        // 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.
        final File file = globals.fs.file(globals.fs.path.join(bundleDir.path, entry.key));
        file.parent.createSync(recursive: true);
        await file.writeAsBytes(await entry.value.contentsAsBytes());
      } finally {
        resource.release();
      }
    }));
}