diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 34fb6d6c2a1..7a39727f88a 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -9,13 +9,18 @@ import 'package:crypto/crypto.dart' show md5; import 'package:meta/meta.dart'; import 'package:quiver/core.dart' show hash2; +import '../android/android_sdk.dart'; import '../artifacts.dart'; import '../build_info.dart'; +import '../compile.dart'; +import '../dart/package_map.dart'; import '../globals.dart'; +import '../ios/mac.dart'; import '../version.dart'; import 'context.dart'; import 'file_system.dart'; import 'process.dart'; +import 'utils.dart' show toTitleCase; GenSnapshot get genSnapshot => context[GenSnapshot]; @@ -203,8 +208,349 @@ class Snapshotter { } /// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script. - Future buildAotSnapshot() async { - throw new UnimplementedError('AOT snapshotting not yet implemented'); + Future buildAotSnapshot({ + @required TargetPlatform platform, + @required BuildMode buildMode, + @required String mainPath, + @required String depfilePath, + @required String packagesPath, + @required String outputPath, + @required bool interpreter, + @required bool previewDart2, + @required bool preferSharedLibrary, + List extraFrontEndOptions: const [], + List extraGenSnapshotOptions: const [], + }) async { + if (!isAotBuildMode(buildMode) && !interpreter) { + printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.'); + return -1; + } + + if (!(platform == TargetPlatform.android_arm || + platform == TargetPlatform.android_arm64 || + platform == TargetPlatform.ios)) { + printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.'); + return -2; + } + + final Directory outputDir = fs.directory(outputPath); + outputDir.createSync(recursive: true); + final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data'); + final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr'); + final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data'); + final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr'); + final String dependencies = fs.path.join(outputDir.path, 'snapshot.d'); + final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S'); + final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o'); + final String assemblySo = fs.path.join(outputDir.path, 'app.so'); + final bool compileToSharedLibrary = + preferSharedLibrary && androidSdk.ndkCompiler != null; + + if (preferSharedLibrary && !compileToSharedLibrary) { + printStatus( + 'Could not find NDK compiler. Not building in shared library mode'); + } + + final String vmEntryPoints = artifacts.getArtifactPath(Artifact.dartVmEntryPointsTxt, platform, buildMode); + assert(vmEntryPoints != null); + + final String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode); + assert(ioEntryPoints != null); + + final List entryPointsJsonFiles = []; + if (previewDart2 && !interpreter) { + entryPointsJsonFiles.addAll([ + artifacts.getArtifactPath(Artifact.entryPointsJson, platform, buildMode), + artifacts.getArtifactPath(Artifact.entryPointsExtraJson, platform, buildMode), + ]); + } + + final PackageMap packageMap = new PackageMap(packagesPath); + final String packageMapError = packageMap.checkValid(); + if (packageMapError != null) { + printError(packageMapError); + return -3; + } + + final String skyEnginePkg = _getPackagePath(packageMap, 'sky_engine'); + final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart'); + final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart'); + + final List inputPaths = [ + vmEntryPoints, + ioEntryPoints, + uiPath, + vmServicePath, + mainPath, + ]; + + inputPaths.addAll(entryPointsJsonFiles); + + final Set outputPaths = new Set(); + + // These paths are used only on iOS. + String snapshotDartIOS; + + switch (platform) { + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.android_x86: + if (compileToSharedLibrary) { + outputPaths.add(assemblySo); + } else { + outputPaths.addAll([ + vmSnapshotData, + isolateSnapshotData, + ]); + } + break; + case TargetPlatform.ios: + snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode); + inputPaths.add(snapshotDartIOS); + break; + case TargetPlatform.darwin_x64: + case TargetPlatform.linux_x64: + case TargetPlatform.windows_x64: + case TargetPlatform.fuchsia: + case TargetPlatform.tester: + assert(false); + } + + final Iterable missingInputs = inputPaths.where((String p) => !fs.isFileSync(p)); + if (missingInputs.isNotEmpty) { + printError('Missing input files: $missingInputs'); + return -4; + } + + final List genSnapshotArgs = [ + '--vm_snapshot_data=$vmSnapshotData', + '--isolate_snapshot_data=$isolateSnapshotData', + '--url_mapping=dart:ui,$uiPath', + '--url_mapping=dart:vmservice_io,$vmServicePath', + '--dependencies=$dependencies', + ]; + + if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) + printTrace('Extra front-end options: $extraFrontEndOptions'); + + if ((extraGenSnapshotOptions != null) && extraGenSnapshotOptions.isNotEmpty) { + printTrace('Extra gen-snapshot options: $extraGenSnapshotOptions'); + genSnapshotArgs.addAll(extraGenSnapshotOptions); + } + + if (!interpreter) { + genSnapshotArgs.add('--embedder_entry_points_manifest=$vmEntryPoints'); + genSnapshotArgs.add('--embedder_entry_points_manifest=$ioEntryPoints'); + } + + // iOS symbols used to load snapshot data in the engine. + const String kVmSnapshotData = 'kDartVmSnapshotData'; + const String kIsolateSnapshotData = 'kDartIsolateSnapshotData'; + + // iOS snapshot generated files, compiled object files. + final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c'); + final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c'); + final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o'); + final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o'); + final String kApplicationKernelPath = fs.path.join(getBuildDirectory(), 'app.dill'); + + switch (platform) { + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.android_x86: + if (compileToSharedLibrary) { + genSnapshotArgs.add('--snapshot_kind=app-aot-assembly'); + genSnapshotArgs.add('--assembly=$assembly'); + outputPaths.add(assemblySo); + } else { + genSnapshotArgs.addAll([ + '--snapshot_kind=app-aot-blobs', + '--vm_snapshot_instructions=$vmSnapshotInstructions', + '--isolate_snapshot_instructions=$isolateSnapshotInstructions', + ]); + } + if (platform == TargetPlatform.android_arm) { + genSnapshotArgs.addAll([ + '--no-sim-use-hardfp', // Android uses the softfloat ABI. + '--no-use-integer-division', // Not supported by the Pixel in 32-bit mode. + ]); + } + break; + case TargetPlatform.ios: + if (interpreter) { + genSnapshotArgs.add('--snapshot_kind=core'); + genSnapshotArgs.add(snapshotDartIOS); + outputPaths.addAll([ + kVmSnapshotDataO, + kIsolateSnapshotDataO, + ]); + } else { + genSnapshotArgs.add('--snapshot_kind=app-aot-assembly'); + genSnapshotArgs.add('--assembly=$assembly'); + outputPaths.add(assemblyO); + } + break; + case TargetPlatform.darwin_x64: + case TargetPlatform.linux_x64: + case TargetPlatform.windows_x64: + case TargetPlatform.fuchsia: + case TargetPlatform.tester: + assert(false); + } + + if (buildMode != BuildMode.release) { + genSnapshotArgs.addAll([ + '--no-checked', + '--conditional_directives', + ]); + } + + final String entryPoint = mainPath; + final SnapshotType snapshotType = new SnapshotType(platform, buildMode); + Future makeFingerprint() async { + final Set snapshotInputPaths = await readDepfile(dependencies) + ..add(entryPoint) + ..addAll(outputPaths); + return Snapshotter.createFingerprint(snapshotType, entryPoint, snapshotInputPaths); + } + + final File fingerprintFile = fs.file('$dependencies.fingerprint'); + final List fingerprintFiles = [fingerprintFile, fs.file(dependencies)] + ..addAll(inputPaths.map(fs.file)) + ..addAll(outputPaths.map(fs.file)); + if (fingerprintFiles.every((File file) => file.existsSync())) { + try { + final String json = await fingerprintFile.readAsString(); + final Fingerprint oldFingerprint = new Fingerprint.fromJson(json); + if (oldFingerprint == await makeFingerprint()) { + printStatus('Skipping AOT snapshot build. Fingerprint match.'); + return 0; + } + } catch (e) { + // Log exception and continue, this step is a performance improvement only. + printTrace('Rebuilding snapshot due to fingerprint check error: $e'); + } + } + + if (previewDart2) { + final CompilerOutput compilerOutput = await kernelCompiler.compile( + sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath), + mainPath: mainPath, + outputFilePath: kApplicationKernelPath, + depFilePath: dependencies, + extraFrontEndOptions: extraFrontEndOptions, + linkPlatformKernelIn: true, + aot: !interpreter, + entryPointsJsonFiles: entryPointsJsonFiles, + trackWidgetCreation: false, + ); + mainPath = compilerOutput?.outputFilename; + if (mainPath == null) { + printError('Compiler terminated unexpectedly.'); + return -5; + } + // Write path to frontend_server, since things need to be re-generated when + // that changes. + await outputDir.childFile('frontend_server.d') + .writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n'); + + genSnapshotArgs.addAll([ + '--reify-generic-functions', + '--strong', + ]); + } + + genSnapshotArgs.add(mainPath); + + final int genSnapshotExitCode = await genSnapshot.run( + snapshotType: new SnapshotType(platform, buildMode), + packagesPath: packageMap.packagesPath, + depfilePath: dependencies, + additionalArgs: genSnapshotArgs, + ); + if (genSnapshotExitCode != 0) { + printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); + return -6; + } + + // Write path to gen_snapshot, since snapshots have to be re-generated when we roll + // the Dart SDK. + await outputDir.childFile('gen_snapshot.d').writeAsString('snapshot.d: $genSnapshot\n'); + + // On iOS, we use Xcode to compile the snapshot into a dynamic library that the + // end-developer can link into their app. + if (platform == TargetPlatform.ios) { + printStatus('Building App.framework...'); + + const List commonBuildOptions = const ['-arch', 'arm64', '-miphoneos-version-min=8.0']; + + if (interpreter) { + await fs.file(vmSnapshotData).rename(fs.path.join(outputDir.path, kVmSnapshotData)); + await fs.file(isolateSnapshotData).rename(fs.path.join(outputDir.path, kIsolateSnapshotData)); + + await xxd.run( + ['--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)], + workingDirectory: outputDir.path, + ); + await xxd.run( + ['--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)], + workingDirectory: outputDir.path, + ); + + await xcode.cc(commonBuildOptions.toList()..addAll(['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO])); + await xcode.cc(commonBuildOptions.toList()..addAll(['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO])); + } else { + await xcode.cc(commonBuildOptions.toList()..addAll(['-c', assembly, '-o', assemblyO])); + } + + final String frameworkDir = fs.path.join(outputDir.path, 'App.framework'); + fs.directory(frameworkDir).createSync(recursive: true); + final String appLib = fs.path.join(frameworkDir, 'App'); + final List linkArgs = commonBuildOptions.toList()..addAll([ + '-dynamiclib', + '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', + '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', + '-install_name', '@rpath/App.framework/App', + '-o', appLib, + ]); + if (interpreter) { + linkArgs.add(kVmSnapshotDataO); + linkArgs.add(kIsolateSnapshotDataO); + } else { + linkArgs.add(assemblyO); + } + await xcode.clang(linkArgs); + } else { + if (compileToSharedLibrary) { + // A word of warning: Instead of compiling via two steps, to a .o file and + // then to a .so file we use only one command. When using two commands + // gcc will end up putting a .eh_frame and a .debug_frame into the shared + // library. Without stripping .debug_frame afterwards, unwinding tools + // based upon libunwind use just one and ignore the contents of the other + // (which causes it to not look into the other section and therefore not + // find the correct unwinding information). + await runCheckedAsync([androidSdk.ndkCompiler] + ..addAll(androidSdk.ndkCompilerArgs) + ..addAll([ '-shared', '-nostdlib', '-o', assemblySo, assembly ])); + } + } + + // Compute and record build fingerprint. + try { + final Fingerprint fingerprint = await makeFingerprint(); + await fingerprintFile.writeAsString(fingerprint.toJson()); + } catch (e, s) { + // Log exception and continue, this step is a performance improvement only. + printStatus('Error during AOT snapshot fingerprinting: $e\n$s'); + } + + return 0; + } + + String _getPackagePath(PackageMap packageMap, String package) { + return fs.path.dirname(fs.path.fromUri(packageMap.map[package])); } Future _isBuildRequired(SnapshotType type, String outputSnapshotPath, String depfilePath, String mainPath, String fingerprintPath) async { diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index 04ff0a9436f..af3068e8ffd 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -4,28 +4,17 @@ import 'dart:async'; -import '../android/android_sdk.dart'; -import '../artifacts.dart'; import '../base/build.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; -import '../base/process.dart'; -import '../base/utils.dart'; import '../build_info.dart'; -import '../compile.dart'; import '../dart/package_map.dart'; import '../globals.dart'; -import '../ios/mac.dart' show xcode, xxd; import '../resident_runner.dart'; import '../runner/flutter_command.dart'; import 'build.dart'; -// Files generated by the ahead-of-time snapshot builder. -const List kAotSnapshotFiles = const [ - 'vm_snapshot_data', 'vm_snapshot_instr', 'isolate_snapshot_data', 'isolate_snapshot_instr', -]; - class BuildAotCommand extends BuildSubCommand { BuildAotCommand({bool verboseHelp: false}) { usesTargetOption(); @@ -102,10 +91,6 @@ class BuildAotCommand extends BuildSubCommand { } } -String _getPackagePath(PackageMap packageMap, String package) { - return fs.path.dirname(packageMap.map[package].toFilePath()); -} - /// Build an AOT snapshot. Return null (and log to `printError`) if the method /// fails. Future buildAotSnapshot( @@ -121,362 +106,28 @@ Future buildAotSnapshot( }) async { outputPath ??= getAotBuildDirectory(); try { - return _buildAotSnapshot( - mainPath, - platform, - buildMode, + final Snapshotter snapshotter = new Snapshotter(); + final int snapshotExitCode = await snapshotter.buildAotSnapshot( + platform: platform, + buildMode: buildMode, + mainPath: mainPath, + depfilePath: 'depFilePathGoesHere', + packagesPath: PackageMap.globalPackagesPath, outputPath: outputPath, interpreter: interpreter, previewDart2: previewDart2, + preferSharedLibrary: preferSharedLibrary, extraFrontEndOptions: extraFrontEndOptions, extraGenSnapshotOptions: extraGenSnapshotOptions, - preferSharedLibrary: preferSharedLibrary, ); + if (snapshotExitCode != 0) { + printError('Snapshotting exited with non-zero exit code: $snapshotExitCode'); + return null; + } + return outputPath; } on String catch (error) { // Catch the String exceptions thrown from the `runCheckedSync` methods below. printError(error); return null; } } - -// TODO(cbracken): split AOT and Assembly AOT snapshotting logic and migrate to Snapshotter class. -Future _buildAotSnapshot( - String mainPath, - TargetPlatform platform, - BuildMode buildMode, { - String outputPath, - bool interpreter: false, - bool previewDart2: false, - List extraFrontEndOptions, - List extraGenSnapshotOptions, - bool preferSharedLibrary: false, -}) async { - outputPath ??= getAotBuildDirectory(); - if (!isAotBuildMode(buildMode) && !interpreter) { - printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.'); - return null; - } - - if (!(platform == TargetPlatform.android_arm || - platform == TargetPlatform.android_arm64 || - platform == TargetPlatform.ios)) { - printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.'); - return null; - } - - final Directory outputDir = fs.directory(outputPath); - outputDir.createSync(recursive: true); - final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data'); - final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr'); - final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data'); - final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr'); - final String dependencies = fs.path.join(outputDir.path, 'snapshot.d'); - final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S'); - final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o'); - final String assemblySo = fs.path.join(outputDir.path, 'app.so'); - final bool compileToSharedLibrary = - preferSharedLibrary && androidSdk.ndkCompiler != null; - - if (preferSharedLibrary && !compileToSharedLibrary) { - printStatus( - 'Could not find NDK compiler. Not building in shared library mode'); - } - - final String vmEntryPoints = artifacts.getArtifactPath( - Artifact.dartVmEntryPointsTxt, - platform, - buildMode, - ); - final String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode); - - final List entryPointsJsonFiles = []; - if (previewDart2 && !interpreter) { - entryPointsJsonFiles.addAll([ - artifacts.getArtifactPath(Artifact.entryPointsJson, platform, buildMode), - artifacts.getArtifactPath(Artifact.entryPointsExtraJson, platform, buildMode), - ]); - } - - final PackageMap packageMap = new PackageMap(PackageMap.globalPackagesPath); - final String packageMapError = packageMap.checkValid(); - if (packageMapError != null) { - printError(packageMapError); - return null; - } - - final String skyEnginePkg = _getPackagePath(packageMap, 'sky_engine'); - final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart'); - final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart'); - - final List inputPaths = [ - vmEntryPoints, - ioEntryPoints, - uiPath, - vmServicePath, - mainPath, - ]; - - inputPaths.addAll(entryPointsJsonFiles); - - final Set outputPaths = new Set(); - - // These paths are used only on iOS. - String snapshotDartIOS; - - switch (platform) { - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.android_x86: - if (compileToSharedLibrary) { - outputPaths.add(assemblySo); - } else { - outputPaths.addAll([ - vmSnapshotData, - isolateSnapshotData, - ]); - } - break; - case TargetPlatform.ios: - snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode); - inputPaths.add(snapshotDartIOS); - break; - case TargetPlatform.darwin_x64: - case TargetPlatform.linux_x64: - case TargetPlatform.windows_x64: - case TargetPlatform.fuchsia: - case TargetPlatform.tester: - assert(false); - } - - final Iterable missingInputs = inputPaths.where((String p) => !fs.isFileSync(p)); - if (missingInputs.isNotEmpty) { - printError('Missing input files: $missingInputs'); - return null; - } - - final List genSnapshotArgs = [ - '--vm_snapshot_data=$vmSnapshotData', - '--isolate_snapshot_data=$isolateSnapshotData', - '--url_mapping=dart:ui,$uiPath', - '--url_mapping=dart:vmservice_io,$vmServicePath', - '--dependencies=$dependencies', - ]; - - if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) - printTrace('Extra front-end options: $extraFrontEndOptions'); - - if ((extraGenSnapshotOptions != null) && extraGenSnapshotOptions.isNotEmpty) { - printTrace('Extra gen-snapshot options: $extraGenSnapshotOptions'); - genSnapshotArgs.addAll(extraGenSnapshotOptions); - } - - if (!interpreter) { - genSnapshotArgs.add('--embedder_entry_points_manifest=$vmEntryPoints'); - genSnapshotArgs.add('--embedder_entry_points_manifest=$ioEntryPoints'); - } - - // iOS symbols used to load snapshot data in the engine. - const String kVmSnapshotData = 'kDartVmSnapshotData'; - const String kIsolateSnapshotData = 'kDartIsolateSnapshotData'; - - // iOS snapshot generated files, compiled object files. - final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c'); - final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c'); - final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o'); - final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o'); - final String kApplicationKernelPath = fs.path.join(getBuildDirectory(), 'app.dill'); - - switch (platform) { - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.android_x86: - if (compileToSharedLibrary) { - genSnapshotArgs.add('--snapshot_kind=app-aot-assembly'); - genSnapshotArgs.add('--assembly=$assembly'); - outputPaths.add(assemblySo); - } else { - genSnapshotArgs.addAll([ - '--snapshot_kind=app-aot-blobs', - '--vm_snapshot_instructions=$vmSnapshotInstructions', - '--isolate_snapshot_instructions=$isolateSnapshotInstructions', - ]); - } - if (platform == TargetPlatform.android_arm) { - genSnapshotArgs.addAll([ - '--no-sim-use-hardfp', // Android uses the softfloat ABI. - '--no-use-integer-division', // Not supported by the Pixel in 32-bit mode. - ]); - } - break; - case TargetPlatform.ios: - if (interpreter) { - genSnapshotArgs.add('--snapshot_kind=core'); - genSnapshotArgs.add(snapshotDartIOS); - outputPaths.addAll([ - kVmSnapshotDataO, - kIsolateSnapshotDataO, - ]); - } else { - genSnapshotArgs.add('--snapshot_kind=app-aot-assembly'); - genSnapshotArgs.add('--assembly=$assembly'); - outputPaths.add(assemblyO); - } - break; - case TargetPlatform.darwin_x64: - case TargetPlatform.linux_x64: - case TargetPlatform.windows_x64: - case TargetPlatform.fuchsia: - case TargetPlatform.tester: - assert(false); - } - - if (buildMode != BuildMode.release) { - genSnapshotArgs.addAll([ - '--no-checked', - '--conditional_directives', - ]); - } - - final String entryPoint = mainPath; - final SnapshotType snapshotType = new SnapshotType(platform, buildMode); - Future makeFingerprint() async { - final Set snapshotInputPaths = await readDepfile(dependencies) - ..add(entryPoint) - ..addAll(outputPaths); - return Snapshotter.createFingerprint(snapshotType, entryPoint, snapshotInputPaths); - } - - final File fingerprintFile = fs.file('$dependencies.fingerprint'); - final List fingerprintFiles = [fingerprintFile, fs.file(dependencies)] - ..addAll(inputPaths.map(fs.file)) - ..addAll(outputPaths.map(fs.file)); - if (fingerprintFiles.every((File file) => file.existsSync())) { - try { - final String json = await fingerprintFile.readAsString(); - final Fingerprint oldFingerprint = new Fingerprint.fromJson(json); - if (oldFingerprint == await makeFingerprint()) { - printStatus('Skipping AOT snapshot build. Fingerprint match.'); - return outputPath; - } - } catch (e) { - // Log exception and continue, this step is a performance improvement only. - printTrace('Rebuilding snapshot due to fingerprint check error: $e'); - } - } - - if (previewDart2) { - final CompilerOutput compilerOutput = await kernelCompiler.compile( - sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath), - mainPath: mainPath, - outputFilePath: kApplicationKernelPath, - depFilePath: dependencies, - extraFrontEndOptions: extraFrontEndOptions, - linkPlatformKernelIn : true, - aot : !interpreter, - entryPointsJsonFiles: entryPointsJsonFiles, - trackWidgetCreation: false, - ); - mainPath = compilerOutput?.outputFilename; - if (mainPath == null) { - printError('Compiler terminated unexpectedly.'); - return null; - } - // Write path to frontend_server, since things need to be re-generated when - // that changes. - await outputDir.childFile('frontend_server.d') - .writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n'); - - genSnapshotArgs.addAll([ - '--reify-generic-functions', - '--strong', - ]); - } - - genSnapshotArgs.add(mainPath); - - final int genSnapshotExitCode = await genSnapshot.run( - snapshotType: new SnapshotType(platform, buildMode), - packagesPath: packageMap.packagesPath, - depfilePath: dependencies, - additionalArgs: genSnapshotArgs, - ); - if (genSnapshotExitCode != 0) { - printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); - return null; - } - - // Write path to gen_snapshot, since snapshots have to be re-generated when we roll - // the Dart SDK. - await outputDir.childFile('gen_snapshot.d').writeAsString('snapshot.d: $genSnapshot\n'); - - // On iOS, we use Xcode to compile the snapshot into a dynamic library that the - // end-developer can link into their app. - if (platform == TargetPlatform.ios) { - printStatus('Building App.framework...'); - - const List commonBuildOptions = const ['-arch', 'arm64', '-miphoneos-version-min=8.0']; - - if (interpreter) { - await fs.file(vmSnapshotData).rename(fs.path.join(outputDir.path, kVmSnapshotData)); - await fs.file(isolateSnapshotData).rename(fs.path.join(outputDir.path, kIsolateSnapshotData)); - - await xxd.run( - ['--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)], - workingDirectory: outputDir.path, - ); - await xxd.run( - ['--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)], - workingDirectory: outputDir.path, - ); - - await xcode.cc(commonBuildOptions.toList()..addAll(['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO])); - await xcode.cc(commonBuildOptions.toList()..addAll(['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO])); - } else { - await xcode.cc(commonBuildOptions.toList()..addAll(['-c', assembly, '-o', assemblyO])); - } - - final String frameworkDir = fs.path.join(outputDir.path, 'App.framework'); - fs.directory(frameworkDir).createSync(recursive: true); - final String appLib = fs.path.join(frameworkDir, 'App'); - final List linkArgs = commonBuildOptions.toList()..addAll([ - '-dynamiclib', - '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', - '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', - '-install_name', '@rpath/App.framework/App', - '-o', appLib, - ]); - if (interpreter) { - linkArgs.add(kVmSnapshotDataO); - linkArgs.add(kIsolateSnapshotDataO); - } else { - linkArgs.add(assemblyO); - } - await xcode.clang(linkArgs); - } else { - if (compileToSharedLibrary) { - // A word of warning: Instead of compiling via two steps, to a .o file and - // then to a .so file we use only one command. When using two commands - // gcc will end up putting a .eh_frame and a .debug_frame into the shared - // library. Without stripping .debug_frame afterwards, unwinding tools - // based upon libunwind use just one and ignore the contents of the other - // (which causes it to not look into the other section and therefore not - // find the correct unwinding information). - await runCheckedAsync([androidSdk.ndkCompiler] - ..addAll(androidSdk.ndkCompilerArgs) - ..addAll([ '-shared', '-nostdlib', '-o', assemblySo, assembly ])); - } - } - - // Compute and record build fingerprint. - try { - final Fingerprint fingerprint = await makeFingerprint(); - await fingerprintFile.writeAsString(fingerprint.toJson()); - } catch (e, s) { - // Log exception and continue, this step is a performance improvement only. - printStatus('Error during AOT snapshot fingerprinting: $e\n$s'); - } - - return outputPath; -} diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart index dd80b5787b8..9592e529b16 100644 --- a/packages/flutter_tools/test/base/build_test.dart +++ b/packages/flutter_tools/test/base/build_test.dart @@ -9,9 +9,11 @@ import 'dart:convert' show json; import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/base/build.dart'; import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; @@ -20,6 +22,8 @@ import '../src/context.dart'; class MockFlutterVersion extends Mock implements FlutterVersion {} class MockArtifacts extends Mock implements Artifacts {} +class MockXcode extends Mock implements Xcode {} +class MockXxd extends Mock implements Xxd {} class _FakeGenSnapshot implements GenSnapshot { _FakeGenSnapshot({ @@ -66,6 +70,29 @@ class _FakeGenSnapshot implements GenSnapshot { } } +class _FakeKernelCompiler implements KernelCompiler { + CompilerOutput output; + + @override + Future compile({ + String sdkRoot, + String mainPath, + String outputFilePath, + String depFilePath, + bool linkPlatformKernelIn: false, + bool aot: false, + List entryPointsJsonFiles, + bool trackWidgetCreation: false, + List extraFrontEndOptions, + String incrementalCompilerByteStorePath, + String packagesPath, + List fileSystemRoots, + String fileSystemScheme, + }) async { + return output; + } +} + void main() { group('SnapshotType', () { test('throws, if build mode is null', () { @@ -325,7 +352,7 @@ void main() { }, overrides: contextOverrides); }); - group('Snapshotter', () { + group('Snapshotter - Script Snapshots', () { const String kVersion = '123456abcdef'; const String kIsolateSnapshotData = 'isolate_snapshot.bin'; const String kVmSnapshotData = 'vm_isolate_snapshot.bin'; @@ -382,8 +409,8 @@ void main() { final Map jsonObject = json.decode(fs.file('output.snapshot.d.fingerprint').readAsStringSync()); expect(jsonObject['properties']['entryPoint'], entryPoint); expect(jsonObject['files'], hasLength(checksums.length + 2)); - checksums.forEach((String path, String checksum) { - expect(jsonObject['files'][path], checksum); + checksums.forEach((String filePath, String checksum) { + expect(jsonObject['files'][filePath], checksum); }); expect(jsonObject['files'][kVmSnapshotData], '2ec34912477a46c03ddef07e8b909b46'); expect(jsonObject['files'][kIsolateSnapshotData], '621b3844bb7d4d17d2cfc5edf9a91c4c'); @@ -569,4 +596,205 @@ void main() { }, overrides: contextOverrides); }); }); + + group('Snapshotter - iOS AOT', () { + const String kVmEntrypoints = 'dart_vm_entry_points.txt'; + const String kIoEntries = 'dart_io_entries.txt'; + const String kSnapshotDart = 'snapshot.dart'; + const String kEntrypointsJson = 'entry_points.json'; + const String kEntrypointsExtraJson = 'entry_points_extra.json'; + String skyEnginePath; + + _FakeGenSnapshot genSnapshot; + _FakeKernelCompiler kernelCompiler; + MemoryFileSystem fs; + Snapshotter snapshotter; + MockArtifacts mockArtifacts; + MockXcode mockXcode; + MockXxd mockXxd; + + setUp(() async { + fs = new MemoryFileSystem(); + fs.file(kVmEntrypoints).createSync(); + fs.file(kIoEntries).createSync(); + fs.file(kSnapshotDart).createSync(); + fs.file(kEntrypointsJson).createSync(); + fs.file(kEntrypointsExtraJson).createSync(); + fs.file('.packages').writeAsStringSync('sky_engine:file:///flutter/bin/cache/pkg/sky_engine/lib/'); + + skyEnginePath = fs.path.fromUri(new Uri.file('/flutter/bin/cache/pkg/sky_engine')); + fs.directory(fs.path.join(skyEnginePath, 'lib', 'ui')).createSync(recursive: true); + fs.directory(fs.path.join(skyEnginePath, 'sdk_ext')).createSync(recursive: true); + fs.file(fs.path.join(skyEnginePath, '.packages')).createSync(); + fs.file(fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')).createSync(); + fs.file(fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')).createSync(); + + genSnapshot = new _FakeGenSnapshot(); + kernelCompiler = new _FakeKernelCompiler(); + snapshotter = new Snapshotter(); + mockArtifacts = new MockArtifacts(); + mockXcode = new MockXcode(); + mockXxd = new MockXxd(); + for (BuildMode mode in BuildMode.values) { + when(mockArtifacts.getArtifactPath(Artifact.dartVmEntryPointsTxt, TargetPlatform.ios, mode)).thenReturn(kVmEntrypoints); + when(mockArtifacts.getArtifactPath(Artifact.dartIoEntriesTxt, TargetPlatform.ios, mode)).thenReturn(kIoEntries); + when(mockArtifacts.getArtifactPath(Artifact.snapshotDart, TargetPlatform.ios, mode)).thenReturn(kSnapshotDart); + when(mockArtifacts.getArtifactPath(Artifact.entryPointsJson, TargetPlatform.ios, mode)).thenReturn(kEntrypointsJson); + when(mockArtifacts.getArtifactPath(Artifact.entryPointsExtraJson, TargetPlatform.ios, mode)).thenReturn(kEntrypointsExtraJson); + } + }); + + final Map contextOverrides = { + Artifacts: () => mockArtifacts, + FileSystem: () => fs, + GenSnapshot: () => genSnapshot, + KernelCompiler: () => kernelCompiler, + Xcode: () => mockXcode, + Xxd: () => mockXxd, + }; + + testUsingContext('builds iOS debug AOT snapshot', () async { + fs.file('main.dart').writeAsStringSync('void main() {}'); + + final String outputPath = fs.path.join('build', 'foo'); + fs.directory(outputPath).createSync(recursive: true); + + kernelCompiler.output = const CompilerOutput('main.dill', 0); + genSnapshot.outputs = { + fs.path.join(outputPath, 'vm_snapshot_data'): '', + fs.path.join(outputPath, 'vm_snapshot_instr'): '', + fs.path.join(outputPath, 'isolate_snapshot_data'): '', + fs.path.join(outputPath, 'isolate_snapshot_instr'): '', + fs.path.join(outputPath, 'snapshot.d'): '', + fs.path.join(outputPath, 'snapshot_assembly.S'): '', + }; + + final int genSnapshotExitCode = await snapshotter.buildAotSnapshot( + platform: TargetPlatform.ios, + buildMode: BuildMode.debug, + mainPath: 'main.dart', + depfilePath: fs.path.join(outputPath, 'snapshot.d'), + packagesPath: '.packages', + outputPath: outputPath, + interpreter: true, + preferSharedLibrary: false, + previewDart2: true, + ); + + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.debug); + expect(genSnapshot.packagesPath, '.packages'); + expect(genSnapshot.depfilePath, fs.path.join(outputPath, 'snapshot.d')); + expect(genSnapshot.additionalArgs, [ + '--vm_snapshot_data=${fs.path.join(outputPath, 'vm_snapshot_data')}', + '--isolate_snapshot_data=${fs.path.join(outputPath, 'isolate_snapshot_data')}', + '--url_mapping=dart:ui,${fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')}', + '--url_mapping=dart:vmservice_io,${fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')}', + '--dependencies=${fs.path.join(outputPath, 'snapshot.d')}', + '--snapshot_kind=core', + 'snapshot.dart', + '--no-checked', + '--conditional_directives', + '--reify-generic-functions', + '--strong', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds iOS profile AOT snapshot', () async { + fs.file('main.dart').writeAsStringSync('void main() {}'); + + final String outputPath = fs.path.join('build', 'foo'); + fs.directory(outputPath).createSync(recursive: true); + + kernelCompiler.output = const CompilerOutput('main.dill', 0); + genSnapshot.outputs = { + fs.path.join(outputPath, 'snapshot_assembly.S'): '', + fs.path.join(outputPath, 'snapshot.d'): '', + }; + + final int genSnapshotExitCode = await snapshotter.buildAotSnapshot( + platform: TargetPlatform.ios, + buildMode: BuildMode.profile, + mainPath: 'main.dart', + depfilePath: fs.path.join(outputPath, 'snapshot.d'), + packagesPath: '.packages', + outputPath: outputPath, + interpreter: false, + preferSharedLibrary: false, + previewDart2: true, + ); + + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.profile); + expect(genSnapshot.packagesPath, '.packages'); + expect(genSnapshot.depfilePath, fs.path.join(outputPath, 'snapshot.d')); + expect(genSnapshot.additionalArgs, [ + '--vm_snapshot_data=${fs.path.join(outputPath, 'vm_snapshot_data')}', + '--isolate_snapshot_data=${fs.path.join(outputPath, 'isolate_snapshot_data')}', + '--url_mapping=dart:ui,${fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')}', + '--url_mapping=dart:vmservice_io,${fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')}', + '--dependencies=${fs.path.join(outputPath, 'snapshot.d')}', + '--embedder_entry_points_manifest=$kVmEntrypoints', + '--embedder_entry_points_manifest=$kIoEntries', + '--snapshot_kind=app-aot-assembly', + '--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}', + '--no-checked', + '--conditional_directives', + '--reify-generic-functions', + '--strong', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds iOS release AOT snapshot', () async { + fs.file('main.dart').writeAsStringSync('void main() {}'); + + final String outputPath = fs.path.join('build', 'foo'); + fs.directory(outputPath).createSync(recursive: true); + + kernelCompiler.output = const CompilerOutput('main.dill', 0); + genSnapshot.outputs = { + fs.path.join(outputPath, 'snapshot_assembly.S'): '', + fs.path.join(outputPath, 'snapshot.d'): '', + }; + + final int genSnapshotExitCode = await snapshotter.buildAotSnapshot( + platform: TargetPlatform.ios, + buildMode: BuildMode.release, + mainPath: 'main.dart', + depfilePath: fs.path.join(outputPath, 'snapshot.d'), + packagesPath: '.packages', + outputPath: outputPath, + interpreter: false, + preferSharedLibrary: false, + previewDart2: true, + ); + + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.packagesPath, '.packages'); + expect(genSnapshot.depfilePath, fs.path.join(outputPath, 'snapshot.d')); + expect(genSnapshot.additionalArgs, [ + '--vm_snapshot_data=${fs.path.join(outputPath, 'vm_snapshot_data')}', + '--isolate_snapshot_data=${fs.path.join(outputPath, 'isolate_snapshot_data')}', + '--url_mapping=dart:ui,${fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')}', + '--url_mapping=dart:vmservice_io,${fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')}', + '--dependencies=${fs.path.join(outputPath, 'snapshot.d')}', + '--embedder_entry_points_manifest=$kVmEntrypoints', + '--embedder_entry_points_manifest=$kIoEntries', + '--snapshot_kind=app-aot-assembly', + '--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}', + '--reify-generic-functions', + '--strong', + 'main.dill', + ]); + }, overrides: contextOverrides); + }); }