Flutter深入之flutter-build-bundle命令如何编译Dart?

Flutter深入之flutter-build-bundle命令如何编译Dart?

开篇

上一篇我们讲到,在flutterTask中会调用flutter build bundle命令来编译dart代码,生成dart资源。那么build bundle命令是如何编译dart代码的?编译后生成了哪些资源?这些资源都是些什么?

flutter-build-bundle-产物

  • app.dill : 这就是dart代码编译后的二级制文件

  • Frontend_server.d : 这里面放的是frontend_server.dart.snapshot的绝对路径,使用该snapshot来编译dart代码生成上面的app.dill

    ​ flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot

  • snapshot_blob.bin.d : 这里面放的是所有参与编译的dart文件的绝对路径的集合,包括项目的代码和flutterSdk的代码以及pub库中的三方代码。

  • snapshot_blob.bin.d.fingerprint : 这里面放的是snapshot_blob.bin.d中的所有文件的绝对路径以及每个文件所对应的md5值。使用这个md5来判断该文件是否有修改。在每次编译的时候会判断,如果没有文件修改,则直接跳过编译。

编译Dart资源

flutter build bundle命令的实现在BuildBundleCommand类中。

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
Future<void> build({
TargetPlatform platform,
BuildMode buildMode,
String mainPath = defaultMainPath,
String manifestPath = defaultManifestPath,
String snapshotPath,
String applicationKernelFilePath,
String depfilePath,
String privateKeyPath = defaultPrivateKeyPath,
String assetDirPath,
String packagesPath,
bool precompiledSnapshot = false,
bool reportLicensedPackages = false,
bool trackWidgetCreation = false,
String compilationTraceFilePath,
bool buildHotUpdate = false,
List<String> extraFrontEndOptions = const <String>[],
List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots,
String fileSystemScheme,
}) async {
snapshotPath ??= defaultSnapshotPath;
// 通过--depfile 参数可自定义,默认是build/snapshot_blob.bin.d文件
// 该文件就是用来记录项目中参与编译的文件集合
depfilePath ??= defaultDepfilePath;
// 通过--asset-dir 参数可自定义,默认在当前目录下
// 该目录中文件就是flutter的产物,最终合并到apk中的flutter_assets资源
assetDirPath ??= getAssetBuildDirectory();
// .packages目录
packagesPath ??= fs.path.absolute(PackageMap.globalPackagesPath);
// app.dill dart代码编译后的二级制文件
applicationKernelFilePath ??= defaultApplicationKernelPath;

DevFSContent kernelContent;
if (!precompiledSnapshot) {
// debug模式下,默认不开启预编译 precompiledSnapshot=false
if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty)
printTrace('Extra front-end options: $extraFrontEndOptions');
ensureDirectoryExists(applicationKernelFilePath);
// 编译dart代码,生成app.dill 和 snapshot_blob.bin.d 以及 snapshot_blob.bin.d.fingerprint
final CompilerOutput compilerOutput = await kernelCompiler.compile(
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
incrementalCompilerByteStorePath: compilationTraceFilePath != null ? null :
fs.path.absolute(getIncrementalCompilerByteStoreDirectory()),
mainPath: fs.file(mainPath).absolute.path,
outputFilePath: applicationKernelFilePath,
depFilePath: depfilePath,
trackWidgetCreation: trackWidgetCreation,
extraFrontEndOptions: extraFrontEndOptions,
fileSystemRoots: fileSystemRoots,
fileSystemScheme: fileSystemScheme,
packagesPath: packagesPath,
linkPlatformKernelIn: compilationTraceFilePath != null,
);
if (compilerOutput?.outputFilename == null) {
throwToolExit('Compiler failed on $mainPath');
}
kernelContent = DevFSFileContent(fs.file(compilerOutput.outputFilename));
// 生成 frontend_server.d文件,向文件中写入frontendServerSnapshotForEngineDartSdk的路径
await fs.directory(getBuildDirectory()).childFile('frontend_server.d')
.writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n');

if (compilationTraceFilePath != null) {
//
final JITSnapshotter snapshotter = JITSnapshotter();
final int snapshotExitCode = await snapshotter.build(
platform: platform,
buildMode: buildMode,
mainPath: applicationKernelFilePath,
outputPath: getBuildDirectory(),
packagesPath: packagesPath,
compilationTraceFilePath: compilationTraceFilePath,
extraGenSnapshotOptions: extraGenSnapshotOptions,
buildHotUpdate: buildHotUpdate,
);
if (snapshotExitCode != 0) {
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
}
}
}
// 生成 flutter_assets
final AssetBundle assets = await buildAssets(
manifestPath: manifestPath,
assetDirPath: assetDirPath,
packagesPath: packagesPath,
reportLicensedPackages: reportLicensedPackages,
);
if (assets == null)
throwToolExit('Error building assets', exitCode: 1);

await assemble(
buildMode: buildMode,
assetBundle: assets,
kernelContent: kernelContent,
privateKeyPath: privateKeyPath,
assetDirPath: assetDirPath,
compilationTraceFilePath: compilationTraceFilePath,
);
}
编译Dart代码
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

class KernelCompiler {
const KernelCompiler();

Future<CompilerOutput> compile({
String sdkRoot,
String mainPath,
String outputFilePath,
String depFilePath,
bool linkPlatformKernelIn = false,
bool aot = false,
bool trackWidgetCreation = false,
List<String> extraFrontEndOptions,
String incrementalCompilerByteStorePath,
String packagesPath,
List<String> fileSystemRoots,
String fileSystemScheme,
bool targetProductVm = false,
}) async {
// flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot
// 获取上面的snapshot
final String frontendServer = artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk
);

Fingerprinter fingerprinter;
// 如果snapshot_blob.bin.d文件不为空,则说明有编译缓存
if (depFilePath != null) {
// 判断与上次编译对比,是否有文件的md5改变
fingerprinter = Fingerprinter(
// snapshot_blob.bin.d.fingerprint文件
fingerprintPath: '$depFilePath.fingerprint',
// main.dart
paths: <String>[mainPath],
properties: <String, String>{
'entryPoint': mainPath,
'trackWidgetCreation': trackWidgetCreation.toString(),
'linkPlatformKernelIn': linkPlatformKernelIn.toString(),
},
// snapshot_blob.bin.d中的文件集合
depfilePaths: <String>[depFilePath],
pathFilter: (String path) => !path.startsWith('/b/build/slave/'),
);
// 判断是否有文件改动,如果没有,则直接返回。
if (await fingerprinter.doesFingerprintMatch()) {
printTrace('Skipping kernel compilation. Fingerprint match.');
return CompilerOutput(outputFilePath, 0);
}
}
// 如果没有上次编译缓存,或者文件有改变,Fingerprinter不匹配,则使用dart重新编译
...
// dart frontend_server.dart.snapshot --sdk-root flutter_patched_sdk/ --strong --target=flutter --no-link-platform --incremental --packages .packages --output-dill app.dill --depfile snapshot_blob.bin.d --filesystem-scheme --debug main.dart
final List<String> command = <String>[
engineDartPath,
frontendServer,
'--sdk-root',
sdkRoot,
'--strong',
'--target=flutter',
];
// 参数拼装
...
final Process server = await processManager
.start(command)
.catchError((dynamic error, StackTrace stack) {
printError('Failed to start frontend server $error, $stack');
});

final _StdoutHandler _stdoutHandler = _StdoutHandler();

server.stderr
.transform(utf8.decoder)
.listen((String message) { printError(message); });
server.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_stdoutHandler.handler);
final int exitCode = await server.exitCode;
if (exitCode == 0) {
if (fingerprinter != null) {
// 更新snapshot_blob.bin.d.fingerprint文件
await fingerprinter.writeFingerprint();
}
return _stdoutHandler.compilerOutput.future;
}
return null;
}
}
Fingerprint对比
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
Future<bool> doesFingerprintMatch() async {
try {
// 获取到当前的 snapshot_blob.bin.d.fingerprint文件
final File fingerprintFile = fs.file(fingerprintPath);
if (!fingerprintFile.existsSync())
return false;

if (!_depfilePaths.every(fs.isFileSync))
return false;

final List<String> paths = await _getPaths();
if (!paths.every(fs.isFileSync))
return false;
// 读取上一次的snapshot_blob.bin.d.fingerprint文件,构建一个老的Fingerprint对象
final Fingerprint oldFingerprint = Fingerprint.fromJson(await fingerprintFile.readAsString());
// 使用上一次的snapshot_blob.bin.d文件中的文件集合,构建一个新的Fingerprint对象,就是重新计算这些文件的md5值,此处并没有重新去收集更新snapshot_blob.bin.d文件中的文件集合,依旧用的是上一次的。如果本次有dart文件新建,但是并没有修改其它已经存在的文件,则该新建的dart文件不会被编译。
final Fingerprint newFingerprint = await buildFingerprint();
// 对比两次的文件集合中的每个文件的md5是否一样
return oldFingerprint == newFingerprint;
} catch (e) {
// Log exception and continue, fingerprinting is only a performance improvement.
printTrace('Fingerprint check error: $e');
}
return false;
}
生成FlutteerAsset
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
 @override
Future<int> build({
String manifestPath = defaultManifestPath,
String assetDirPath,
String packagesPath,
bool includeDefaultFonts = true,
bool reportLicensedPackages = false
}) async {
// flutter_assets生成目录,通过--asset-dir参数来设置
assetDirPath ??= getAssetBuildDirectory();
// .package目录
packagesPath ??= fs.path.absolute(PackageMap.globalPackagesPath);
FlutterManifest flutterManifest;
try {
// manifestPath 就是 yaml文件
flutterManifest = await FlutterManifest.createFromPath(manifestPath);
} catch (e) {
printStatus('Error detected in pubspec.yaml:', emphasis: true);
printError('$e');
return 1;
}
...
// 处理yaml中配置的图片和字体资源
final Map<_Asset, List<_Asset>> assetVariants = _parseAssets(
packageMap,
flutterManifest,
assetBasePath,
excludeDirs: <String>[assetDirPath, getBuildDirectory()]
);

if (assetVariants == null)
return 1;

final List<Map<String, dynamic>> fonts = _parseFonts(
flutterManifest,
includeDefaultFonts,
packageMap,
);

// 添加所有package(SDK中的,三方的,项目中的)资源和字体
for (String packageName in packageMap.map.keys) {
final Uri package = packageMap.map[packageName];
if (package != null && package.scheme == 'file') {
// 得到该package的yaml文件
final String packageManifestPath = fs.path.fromUri(package.resolve('../pubspec.yaml'));
final FlutterManifest packageFlutterManifest = await FlutterManifest.createFromPath(packageManifestPath);
if (packageFlutterManifest == null)
continue;
// 跳过自身,因为在上面已经处理过自身的资源了
if (packageFlutterManifest.appName == flutterManifest.appName)
continue;
final String packageBasePath = fs.path.dirname(packageManifestPath);
// 处理yaml文件中定义的资源和字体
final Map<_Asset, List<_Asset>> packageAssets = _parseAssets(
packageMap,
packageFlutterManifest,
packageBasePath,
packageName: packageName,
);

if (packageAssets == null)
return 1;
assetVariants.addAll(packageAssets);

fonts.addAll(_parseFonts(
packageFlutterManifest,
includeDefaultFonts,
packageMap,
packageName: packageName,
));
}
}
...
final List<_Asset> materialAssets = <_Asset>[];
// 如果使用的是MaterialDesign,则添加MaterialDesign的默认字体
// 该字体定义在 packages/flutter_tools/schema/material_fonts.yaml
if (flutterManifest.usesMaterialDesign && includeDefaultFonts) {
materialAssets.addAll(_getMaterialAssets(_fontSetMaterial));
}
...
// AssetManifest.json
entries[_assetManifestJson] = _createAssetManifest(assetVariants);
// FontManifest.json
entries[_fontManifestJson] = DevFSStringContent(json.encode(fonts));
// LICENSE
entries[_license] = await _obtainLicenses(packageMap, assetBasePath, reportPackages: reportLicensedPackages);

return 0;
}

处理yaml中定义的资源和字体

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
Map<_Asset, List<_Asset>> _parseAssets(
PackageMap packageMap,
FlutterManifest flutterManifest,
String assetBase, {
List<String> excludeDirs = const <String>[],
String packageName
}) {
final Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};

final _AssetDirectoryCache cache = _AssetDirectoryCache(excludeDirs);
// 处理yaml中配置的assets(图片之类的资源)
for (Uri assetUri in flutterManifest.assets) {
if (assetUri.toString().endsWith('/')) {
_parseAssetsFromFolder(packageMap, flutterManifest, assetBase,
cache, result, assetUri,
excludeDirs: excludeDirs, packageName: packageName);
} else {
_parseAssetFromFile(packageMap, flutterManifest, assetBase,
cache, result, assetUri,
excludeDirs: excludeDirs, packageName: packageName);
}
}

// 处理yaml中配置的字体
for (Font font in flutterManifest.fonts) {
for (FontAsset fontAsset in font.fontAssets) {
final _Asset baseAsset = _resolveAsset(
packageMap,
assetBase,
fontAsset.assetUri,
packageName,
);
if (!baseAsset.assetFileExists) {
printError('Error: unable to locate asset entry in pubspec.yaml: "${fontAsset.assetUri}".');
return null;
}

result[baseAsset] = <_Asset>[];
}
}

return result;
}
输出Assets
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
Future<void> assemble({
BuildMode buildMode,
AssetBundle assetBundle,
DevFSContent kernelContent,
File dylibFile,
String privateKeyPath = defaultPrivateKeyPath,
String assetDirPath,
String compilationTraceFilePath,
}) async {
assetDirPath ??= getAssetBuildDirectory();
printTrace('Building bundle');

final Map<String, DevFSContent> assetEntries = Map<String, DevFSContent>.from(assetBundle.entries);
if (kernelContent != null) {
if (compilationTraceFilePath != null) {
final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
final String isolateSnapshotData = fs.path.join(getBuildDirectory(), _kIsolateSnapshotData);
final String isolateSnapshotInstr = fs.path.join(getBuildDirectory(), _kIsolateSnapshotInstr);
// 拷贝 engine/darwin-x64/vm_isolate_snapshot.bin
// 复制 flutter-assets/vm_snapshot_data
assetEntries[_kVMSnapshotData] = DevFSFileContent(fs.file(vmSnapshotData));
// 拷贝 engine/darwin-x64/isolate_snapshot.bin
// 复制 flutter-assets/isolate_snapshot_data
assetEntries[_kIsolateSnapshotData] = DevFSFileContent(fs.file(isolateSnapshotData));
// 拷贝 build/isolate_snapshot_instr,项目编译生成的指令集
// 复制 flutter-assets/isolate_snapshot_instr,release模式下使用
assetEntries[_kIsolateSnapshotInstr] = DevFSFileContent(fs.file(isolateSnapshotInstr));
} else {

final String platformKernelDill = artifacts.getArtifactPath(Artifact.platformKernelDill);

final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData, null, buildMode);

final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData, null, buildMode);
// 拷贝 build/app.dill 项目编译生成的二进制文件,debug模式下使用
// 复制 flutter-assets/kernel_blob.bin
assetEntries[_kKernelKey] = kernelContent;
// 拷贝 engine/common/flutter_patched_sdk/platform_strong.dill
// 复制 flutter-assets/platform_strong.dill
assetEntries[_kPlatformKernelKey] = DevFSFileContent(fs.file(platformKernelDill));
// 拷贝 engine/darwin-x64/vm_isolate_snapshot.bin
// 复制 flutter-assets/vm_snapshot_data
assetEntries[_kVMSnapshotData] = DevFSFileContent(fs.file(vmSnapshotData));
// 拷贝 engine/darwin-x64/isolate_snapshot.bin
// 复制 flutter-assets/isolate_snapshot_data
assetEntries[_kIsolateSnapshotData] = DevFSFileContent(fs.file(isolateSnapshotData));
}
}
// 将上面的文件写入到指定目录
await writeBundle(fs.directory(assetDirPath), assetEntries);
printTrace('Wrote $assetDirPath');
}
1
2
3
4
5
6
build bundle --suppress-analytics --target /Volumes/zhenyu/zhenyu/flutter_2/app_1/lib/main.dart --filesystem-scheme org-dartlang-root --depfile /Volumes/zhenyu/zhenyu/flutter_2/app_1/build/app/intermediates/flutter/debug/snapshot_blob.bin.d --asset-dir /Volumes/zhenyu/zhenyu/flutter_2/app_1/build/app/intermediates/flutter/debug/flutter_assets --debug




dart /Volumes/zhenyu/zhenyu/project/flutter/flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot --sdk-root /Volumes/zhenyu/zhenyu/project/flutter/flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --strong --target=flutter --no-link-platform --incremental --packages /Volumes/zhenyu/zhenyu/flutter_2/app_1/.packages --output-dill /Volumes/zhenyu/zhenyu/flutter_2/app_1/build/app.dill --depfile /Volumes/zhenyu/zhenyu/flutter_2/app_1/build/snapshot_blob.bin.d --filesystem-scheme --debug /Volumes/zhenyu/zhenyu/flutter_2/app_1/lib/main.dart
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×