1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'package:meta/meta.dart'; 8import 'package:pool/pool.dart'; 9 10import 'artifacts.dart'; 11import 'asset.dart'; 12import 'base/common.dart'; 13import 'base/file_system.dart'; 14import 'build_info.dart'; 15import 'compile.dart'; 16import 'dart/package_map.dart'; 17import 'devfs.dart'; 18import 'globals.dart'; 19import 'project.dart'; 20 21String get defaultMainPath => fs.path.join('lib', 'main.dart'); 22const String defaultAssetBasePath = '.'; 23const String defaultManifestPath = 'pubspec.yaml'; 24String get defaultDepfilePath => fs.path.join(getBuildDirectory(), 'snapshot_blob.bin.d'); 25 26String getDefaultApplicationKernelPath({ @required bool trackWidgetCreation }) { 27 return getKernelPathForTransformerOptions( 28 fs.path.join(getBuildDirectory(), 'app.dill'), 29 trackWidgetCreation: trackWidgetCreation, 30 ); 31} 32 33String getKernelPathForTransformerOptions( 34 String path, { 35 @required bool trackWidgetCreation, 36}) { 37 if (trackWidgetCreation) { 38 path += '.track.dill'; 39 } 40 return path; 41} 42 43const String defaultPrivateKeyPath = 'privatekey.der'; 44 45const String _kKernelKey = 'kernel_blob.bin'; 46const String _kVMSnapshotData = 'vm_snapshot_data'; 47const String _kIsolateSnapshotData = 'isolate_snapshot_data'; 48 49/// Provides a `build` method that builds the bundle. 50class BundleBuilder { 51 /// Builds the bundle for the given target platform. 52 /// 53 /// The default `mainPath` is `lib/main.dart`. 54 /// The default `manifestPath` is `pubspec.yaml` 55 Future<void> build({ 56 TargetPlatform platform, 57 BuildMode buildMode, 58 String mainPath, 59 String manifestPath = defaultManifestPath, 60 String applicationKernelFilePath, 61 String depfilePath, 62 String privateKeyPath = defaultPrivateKeyPath, 63 String assetDirPath, 64 String packagesPath, 65 bool precompiledSnapshot = false, 66 bool reportLicensedPackages = false, 67 bool trackWidgetCreation = false, 68 List<String> extraFrontEndOptions = const <String>[], 69 List<String> extraGenSnapshotOptions = const <String>[], 70 List<String> fileSystemRoots, 71 String fileSystemScheme, 72 }) async { 73 mainPath ??= defaultMainPath; 74 depfilePath ??= defaultDepfilePath; 75 assetDirPath ??= getAssetBuildDirectory(); 76 packagesPath ??= fs.path.absolute(PackageMap.globalPackagesPath); 77 applicationKernelFilePath ??= getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation); 78 final FlutterProject flutterProject = FlutterProject.current(); 79 80 DevFSContent kernelContent; 81 if (!precompiledSnapshot) { 82 if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) 83 printTrace('Extra front-end options: $extraFrontEndOptions'); 84 ensureDirectoryExists(applicationKernelFilePath); 85 final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(flutterProject); 86 final CompilerOutput compilerOutput = await kernelCompiler.compile( 87 sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode), 88 mainPath: fs.file(mainPath).absolute.path, 89 outputFilePath: applicationKernelFilePath, 90 depFilePath: depfilePath, 91 trackWidgetCreation: trackWidgetCreation, 92 extraFrontEndOptions: extraFrontEndOptions, 93 fileSystemRoots: fileSystemRoots, 94 fileSystemScheme: fileSystemScheme, 95 packagesPath: packagesPath, 96 ); 97 if (compilerOutput?.outputFilename == null) { 98 throwToolExit('Compiler failed on $mainPath'); 99 } 100 kernelContent = DevFSFileContent(fs.file(compilerOutput.outputFilename)); 101 102 await fs.directory(getBuildDirectory()).childFile('frontend_server.d') 103 .writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n'); 104 } 105 106 final AssetBundle assets = await buildAssets( 107 manifestPath: manifestPath, 108 assetDirPath: assetDirPath, 109 packagesPath: packagesPath, 110 reportLicensedPackages: reportLicensedPackages, 111 ); 112 if (assets == null) 113 throwToolExit('Error building assets', exitCode: 1); 114 115 await assemble( 116 buildMode: buildMode, 117 assetBundle: assets, 118 kernelContent: kernelContent, 119 privateKeyPath: privateKeyPath, 120 assetDirPath: assetDirPath, 121 ); 122 } 123} 124 125Future<AssetBundle> buildAssets({ 126 String manifestPath, 127 String assetDirPath, 128 String packagesPath, 129 bool includeDefaultFonts = true, 130 bool reportLicensedPackages = false, 131}) async { 132 assetDirPath ??= getAssetBuildDirectory(); 133 packagesPath ??= fs.path.absolute(PackageMap.globalPackagesPath); 134 135 // Build the asset bundle. 136 final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); 137 final int result = await assetBundle.build( 138 manifestPath: manifestPath, 139 assetDirPath: assetDirPath, 140 packagesPath: packagesPath, 141 includeDefaultFonts: includeDefaultFonts, 142 reportLicensedPackages: reportLicensedPackages, 143 ); 144 if (result != 0) 145 return null; 146 147 return assetBundle; 148} 149 150Future<void> assemble({ 151 BuildMode buildMode, 152 AssetBundle assetBundle, 153 DevFSContent kernelContent, 154 String privateKeyPath = defaultPrivateKeyPath, 155 String assetDirPath, 156}) async { 157 assetDirPath ??= getAssetBuildDirectory(); 158 printTrace('Building bundle'); 159 160 final Map<String, DevFSContent> assetEntries = Map<String, DevFSContent>.from(assetBundle.entries); 161 if (kernelContent != null) { 162 final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: buildMode); 163 final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: buildMode); 164 assetEntries[_kKernelKey] = kernelContent; 165 assetEntries[_kVMSnapshotData] = DevFSFileContent(fs.file(vmSnapshotData)); 166 assetEntries[_kIsolateSnapshotData] = DevFSFileContent(fs.file(isolateSnapshotData)); 167 } 168 169 printTrace('Writing asset files to $assetDirPath'); 170 ensureDirectoryExists(assetDirPath); 171 172 await writeBundle(fs.directory(assetDirPath), assetEntries); 173 printTrace('Wrote $assetDirPath'); 174} 175 176Future<void> writeBundle( 177 Directory bundleDir, 178 Map<String, DevFSContent> assetEntries, 179) async { 180 if (bundleDir.existsSync()) 181 bundleDir.deleteSync(recursive: true); 182 bundleDir.createSync(recursive: true); 183 184 // Limit number of open files to avoid running out of file descriptors. 185 final Pool pool = Pool(64); 186 await Future.wait<void>( 187 assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async { 188 final PoolResource resource = await pool.request(); 189 try { 190 final File file = fs.file(fs.path.join(bundleDir.path, entry.key)); 191 file.parent.createSync(recursive: true); 192 await file.writeAsBytes(await entry.value.contentsAsBytes()); 193 } finally { 194 resource.release(); 195 } 196 })); 197} 198