From 9391e480b2cbf0905dc2630f383e562e1f05ef67 Mon Sep 17 00:00:00 2001 From: Jonah Williams <jonahwilliams@google.com> Date: Thu, 19 Mar 2020 10:16:20 -0700 Subject: [PATCH] Revert "[flutter_tools] refactor GenSnapshot and AotBuilder (#52091)" (#52893) This reverts commit f65421aaca381f55bee97dd0e0292b553aeaffb7. --- packages/flutter_tools/lib/src/aot.dart | 171 ++- .../flutter_tools/lib/src/base/build.dart | 181 ++- .../flutter_tools/lib/src/base/logger.dart | 6 +- .../lib/src/build_system/targets/android.dart | 9 +- .../lib/src/build_system/targets/dart.dart | 13 +- .../lib/src/build_system/targets/ios.dart | 12 +- .../lib/src/build_system/targets/macos.dart | 10 +- .../lib/src/commands/build_aot.dart | 6 + .../lib/src/commands/build_ios_framework.dart | 1 + .../flutter_tools/lib/src/context_runner.dart | 2 + .../flutter_tools/lib/src/ios/xcodeproj.dart | 4 +- .../test/general.shard/base/build_test.dart | 1184 ++++++++++------- .../build_system/targets/macos_test.dart | 32 + 13 files changed, 960 insertions(+), 671 deletions(-) diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart index 83ee0e935b..77796f9b6f 100644 --- a/packages/flutter_tools/lib/src/aot.dart +++ b/packages/flutter_tools/lib/src/aot.dart @@ -6,18 +6,15 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'base/build.dart'; import 'base/common.dart'; +import 'base/io.dart'; import 'base/logger.dart'; +import 'base/process.dart'; import 'build_info.dart'; -import 'build_system/build_system.dart'; -import 'build_system/targets/dart.dart'; -import 'build_system/targets/icon_tree_shaker.dart'; -import 'build_system/targets/ios.dart'; -import 'cache.dart'; -import 'convert.dart'; +import 'dart/package_map.dart'; import 'globals.dart' as globals; import 'ios/bitcode.dart'; -import 'project.dart'; /// Builds AOT snapshots given a platform, build mode and a path to a Dart /// library. @@ -29,41 +26,18 @@ class AotBuilder { @required String mainDartFile, bool bitcode = kBitcodeEnabledDefault, bool quiet = true, + bool reportTimings = false, Iterable<DarwinArch> iosBuildArchs = defaultIOSArchs, }) async { if (platform == null) { throwToolExit('No AOT build platform specified'); } - Target target; - bool expectSo = false; - switch (platform) { - case TargetPlatform.android: - case TargetPlatform.darwin_x64: - case TargetPlatform.linux_x64: - case TargetPlatform.windows_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.android_x86: - throwToolExit('$platform is not supported in AOT.'); - break; - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - throwToolExit( - "To build release for fuchsia, use 'flutter build fuchsia --release'" - ); - break; - case TargetPlatform.ios: - target = buildInfo.isRelease - ? const AotAssemblyRelease() - : const AotAssemblyProfile(); - break; - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - expectSo = true; - target = buildInfo.isRelease - ? const AotElfRelease() - : const AotElfProfile(); + + if (bitcode) { + if (platform != TargetPlatform.ios) { + throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $platform).'); + } + await validateBitcode(buildInfo.mode, platform); } Status status; @@ -74,45 +48,102 @@ class AotBuilder { timeout: timeoutConfiguration.slowOperation, ); } + try { + final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: reportTimings); - final Environment environment = Environment( - projectDir: globals.fs.currentDirectory, - outputDir: globals.fs.directory(outputPath), - buildDir: FlutterProject.current().dartTool.childDirectory('flutter_build'), - cacheDir: null, - flutterRootDir: globals.fs.directory(Cache.flutterRoot), - defines: <String, String>{ - kTargetFile: mainDartFile ?? globals.fs.path.join('lib', 'main.dart'), - kBuildMode: getNameForBuildMode(buildInfo.mode), - kTargetPlatform: getNameForTargetPlatform(platform), - kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), - kDartDefines: jsonEncode(buildInfo.dartDefines), - if (buildInfo?.extraGenSnapshotOptions?.isNotEmpty ?? false) - kExtraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions.join(','), - if (buildInfo?.extraFrontEndOptions?.isNotEmpty ?? false) - kExtraFrontEndOptions: buildInfo.extraFrontEndOptions.join(','), - if (platform == TargetPlatform.ios) - kIosArchs: iosBuildArchs.map(getNameForDarwinArch).join(' ') + // Compile to kernel. + final String kernelOut = await snapshotter.compileKernel( + platform: platform, + buildMode: buildInfo.mode, + mainPath: mainDartFile, + packagesPath: PackageMap.globalPackagesPath, + trackWidgetCreation: buildInfo.trackWidgetCreation, + outputPath: outputPath, + extraFrontEndOptions: buildInfo.extraFrontEndOptions, + dartDefines: buildInfo.dartDefines + ); + if (kernelOut == null) { + throwToolExit('Compiler terminated unexpectedly.'); + return; } - ); - final BuildResult result = await buildSystem.build(target, environment); - status?.stop(); - if (!result.success) { - for (final ExceptionMeasurement measurement in result.exceptions.values) { - globals.printError(measurement.exception.toString()); + // Build AOT snapshot. + if (platform == TargetPlatform.ios) { + // Determine which iOS architectures to build for. + final Map<DarwinArch, String> iosBuilds = <DarwinArch, String>{}; + for (final DarwinArch arch in iosBuildArchs) { + iosBuilds[arch] = globals.fs.path.join(outputPath, getNameForDarwinArch(arch)); + } + + // Generate AOT snapshot and compile to arch-specific App.framework. + final Map<DarwinArch, Future<int>> exitCodes = <DarwinArch, Future<int>>{}; + iosBuilds.forEach((DarwinArch iosArch, String outputPath) { + exitCodes[iosArch] = snapshotter.build( + platform: platform, + darwinArch: iosArch, + buildMode: buildInfo.mode, + mainPath: kernelOut, + packagesPath: PackageMap.globalPackagesPath, + outputPath: outputPath, + extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions, + bitcode: bitcode, + quiet: quiet, + splitDebugInfo: null, + dartObfuscation: false, + ).then<int>((int buildExitCode) { + return buildExitCode; + }); + }); + + // Merge arch-specific App.frameworks into a multi-arch App.framework. + if ((await Future.wait<int>(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) { + final Iterable<String> dylibs = iosBuilds.values.map<String>( + (String outputDir) => globals.fs.path.join(outputDir, 'App.framework', 'App')); + globals.fs.directory(globals.fs.path.join(outputPath, 'App.framework')).createSync(); + await processUtils.run( + <String>[ + 'lipo', + ...dylibs, + '-create', + '-output', globals.fs.path.join(outputPath, 'App.framework', 'App'), + ], + throwOnError: true, + ); + } else { + status?.cancel(); + exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async { + final int buildExitCode = await exitCodeFuture; + globals.printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode'); + }); + } + } else { + // Android AOT snapshot. + final int snapshotExitCode = await snapshotter.build( + platform: platform, + buildMode: buildInfo.mode, + mainPath: kernelOut, + packagesPath: PackageMap.globalPackagesPath, + outputPath: outputPath, + extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions, + bitcode: false, + splitDebugInfo: null, + dartObfuscation: false, + ); + if (snapshotExitCode != 0) { + status?.cancel(); + throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode'); + } } - throwToolExit('The aot build failed.'); + } on ProcessException catch (error) { + // Catch the String exceptions thrown from the `runSync` methods below. + status?.cancel(); + globals.printError(error.toString()); + return; } + status?.stop(); - if (expectSo) { - environment.buildDir.childFile('app.so') - .copySync(globals.fs.path.join(outputPath, 'app.so')); - } else { - globals.fs.directory(globals.fs.path.join(outputPath, 'App.framework')) - .createSync(recursive: true); - environment.buildDir.childDirectory('App.framework').childFile('App') - .copySync(globals.fs.path.join(outputPath, 'App.framework', 'App')); + if (outputPath == null) { + throwToolExit(null); } final String builtMessage = 'Built to $outputPath${globals.fs.path.separator}.'; diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 235765a1e3..a1f05391fd 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -3,17 +3,23 @@ // found in the LICENSE file. import 'dart:async'; + import 'package:meta/meta.dart'; -import 'package:process/process.dart'; import '../artifacts.dart'; import '../build_info.dart'; +import '../bundle.dart'; +import '../compile.dart'; +import '../globals.dart' as globals; import '../macos/xcode.dart'; +import '../project.dart'; +import 'context.dart'; import 'file_system.dart'; -import 'logger.dart'; import 'process.dart'; +GenSnapshot get genSnapshot => context.get<GenSnapshot>(); + /// A snapshot build configuration. class SnapshotType { SnapshotType(this.platform, this.mode) @@ -28,18 +34,10 @@ class SnapshotType { /// Interface to the gen_snapshot command-line tool. class GenSnapshot { - GenSnapshot({ - @required Artifacts artifacts, - @required ProcessManager processManager, - @required Logger logger, - }) : _artifacts = artifacts, - _processUtils = ProcessUtils(logger: logger, processManager: processManager); - - final Artifacts _artifacts; - final ProcessUtils _processUtils; - - String getSnapshotterPath(SnapshotType snapshotType) { - return _artifacts.getArtifactPath( + const GenSnapshot(); + + static String getSnapshotterPath(SnapshotType snapshotType) { + return globals.artifacts.getArtifactPath( Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode); } @@ -53,7 +51,6 @@ class GenSnapshot { 'Warning: This VM has been configured to obfuscate symbol information which violates the Dart standard.', ' See dartbug.com/30524 for more information.', }; - Future<int> run({ @required SnapshotType snapshotType, DarwinArch darwinArch, @@ -71,7 +68,7 @@ class GenSnapshot { snapshotterPath += '_' + getNameForDarwinArch(darwinArch); } - return _processUtils.stream( + return processUtils.stream( <String>[snapshotterPath, ...args], mapFunction: (String line) => kIgnoredWarnings.contains(line) ? null : line, ); @@ -79,26 +76,7 @@ class GenSnapshot { } class AOTSnapshotter { - AOTSnapshotter({ - this.reportTimings = false, - @required Logger logger, - @required FileSystem fileSystem, - @required Xcode xcode, - @required ProcessManager processManager, - @required Artifacts artifacts, - }) : _logger = logger, - _fileSystem = fileSystem, - _xcode = xcode, - _genSnapshot = GenSnapshot( - artifacts: artifacts, - processManager: processManager, - logger: logger, - ); - - final Logger _logger; - final FileSystem _fileSystem; - final Xcode _xcode; - final GenSnapshot _genSnapshot; + AOTSnapshotter({this.reportTimings = false}); /// If true then AOTSnapshotter would report timings for individual building /// steps (Dart front-end parsing and snapshot generation) in a stable @@ -119,43 +97,40 @@ class AOTSnapshotter { @required bool dartObfuscation, bool quiet = false, }) async { - // TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}. - assert(platform != TargetPlatform.ios || darwinArch != null); if (bitcode && platform != TargetPlatform.ios) { - _logger.printError('Bitcode is only supported for iOS.'); + globals.printError('Bitcode is only supported for iOS.'); return 1; } if (!_isValidAotPlatform(platform, buildMode)) { - _logger.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.'); + globals.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.'); return 1; } + // TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}. + assert(platform != TargetPlatform.ios || darwinArch != null); - final Directory outputDir = _fileSystem.directory(outputPath); + final Directory outputDir = globals.fs.directory(outputPath); outputDir.createSync(recursive: true); final List<String> genSnapshotArgs = <String>[ '--deterministic', ]; if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) { - _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions'); + globals.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions'); genSnapshotArgs.addAll(extraGenSnapshotOptions); } - final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S'); + final String assembly = globals.fs.path.join(outputDir.path, 'snapshot_assembly.S'); if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) { - genSnapshotArgs.addAll(<String>[ - '--snapshot_kind=app-aot-assembly', - '--assembly=$assembly', - '--strip' - ]); + // Assembly AOT snapshot. + genSnapshotArgs.add('--snapshot_kind=app-aot-assembly'); + genSnapshotArgs.add('--assembly=$assembly'); + genSnapshotArgs.add('--strip'); } else { - final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so'); - genSnapshotArgs.addAll(<String>[ - '--snapshot_kind=app-aot-elf', - '--elf=$aotSharedLibrary', - '--strip' - ]); + final String aotSharedLibrary = globals.fs.path.join(outputDir.path, 'app.so'); + genSnapshotArgs.add('--snapshot_kind=app-aot-elf'); + genSnapshotArgs.add('--elf=$aotSharedLibrary'); + genSnapshotArgs.add('--strip'); } if (platform == TargetPlatform.android_arm || darwinArch == DarwinArch.armv7) { @@ -175,7 +150,7 @@ class AOTSnapshotter { final String debugFilename = 'app.$archName.symbols'; final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false; if (shouldSplitDebugInfo) { - _fileSystem.directory(splitDebugInfo) + globals.fs.directory(splitDebugInfo) .createSync(recursive: true); } @@ -186,7 +161,7 @@ class AOTSnapshotter { '--lazy-async-stacks', if (shouldSplitDebugInfo) ...<String>[ '--dwarf-stack-traces', - '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}' + '--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}' ], if (dartObfuscation) '--obfuscate', @@ -195,13 +170,15 @@ class AOTSnapshotter { genSnapshotArgs.add(mainPath); final SnapshotType snapshotType = SnapshotType(platform, buildMode); - final int genSnapshotExitCode = await _genSnapshot.run( + final int genSnapshotExitCode = + await _timedStep('snapshot(CompileTime)', 'aot-snapshot', + () => genSnapshot.run( snapshotType: snapshotType, additionalArgs: genSnapshotArgs, darwinArch: darwinArch, - ); + )); if (genSnapshotExitCode != 0) { - _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); + globals.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); return genSnapshotExitCode; } @@ -235,7 +212,7 @@ class AOTSnapshotter { }) async { final String targetArch = getNameForDarwinArch(appleArch); if (!quiet) { - _logger.printStatus('Building App.framework for $targetArch...'); + globals.printStatus('Building App.framework for $targetArch...'); } final List<String> commonBuildOptions = <String>[ @@ -245,15 +222,15 @@ class AOTSnapshotter { ]; const String embedBitcodeArg = '-fembed-bitcode'; - final String assemblyO = _fileSystem.path.join(outputPath, 'snapshot_assembly.o'); + final String assemblyO = globals.fs.path.join(outputPath, 'snapshot_assembly.o'); List<String> isysrootArgs; if (isIOS) { - final String iPhoneSDKLocation = await _xcode.sdkLocation(SdkType.iPhone); + final String iPhoneSDKLocation = await globals.xcode.sdkLocation(SdkType.iPhone); if (iPhoneSDKLocation != null) { isysrootArgs = <String>['-isysroot', iPhoneSDKLocation]; } } - final RunResult compileResult = await _xcode.cc(<String>[ + final RunResult compileResult = await globals.xcode.cc(<String>[ '-arch', targetArch, if (isysrootArgs != null) ...isysrootArgs, if (bitcode) embedBitcodeArg, @@ -263,13 +240,13 @@ class AOTSnapshotter { assemblyO, ]); if (compileResult.exitCode != 0) { - _logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}'); + globals.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}'); return compileResult; } - final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework'); - _fileSystem.directory(frameworkDir).createSync(recursive: true); - final String appLib = _fileSystem.path.join(frameworkDir, 'App'); + final String frameworkDir = globals.fs.path.join(outputPath, 'App.framework'); + globals.fs.directory(frameworkDir).createSync(recursive: true); + final String appLib = globals.fs.path.join(frameworkDir, 'App'); final List<String> linkArgs = <String>[ ...commonBuildOptions, '-dynamiclib', @@ -281,13 +258,64 @@ class AOTSnapshotter { '-o', appLib, assemblyO, ]; - final RunResult linkResult = await _xcode.clang(linkArgs); + final RunResult linkResult = await globals.xcode.clang(linkArgs); if (linkResult.exitCode != 0) { - _logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}'); + globals.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}'); } return linkResult; } + /// Compiles a Dart file to kernel. + /// + /// Returns the output kernel file path, or null on failure. + Future<String> compileKernel({ + @required TargetPlatform platform, + @required BuildMode buildMode, + @required String mainPath, + @required String packagesPath, + @required String outputPath, + @required bool trackWidgetCreation, + @required List<String> dartDefines, + List<String> extraFrontEndOptions = const <String>[], + }) async { + final FlutterProject flutterProject = FlutterProject.current(); + final Directory outputDir = globals.fs.directory(outputPath); + outputDir.createSync(recursive: true); + + globals.printTrace('Compiling Dart to kernel: $mainPath'); + + if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) { + globals.printTrace('Extra front-end options: $extraFrontEndOptions'); + } + + final String depfilePath = globals.fs.path.join(outputPath, 'kernel_compile.d'); + final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(flutterProject); + final CompilerOutput compilerOutput = + await _timedStep('frontend(CompileTime)', 'aot-kernel', + () => kernelCompiler.compile( + sdkRoot: globals.artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode), + mainPath: mainPath, + packagesPath: packagesPath, + outputFilePath: getKernelPathForTransformerOptions( + globals.fs.path.join(outputPath, 'app.dill'), + trackWidgetCreation: trackWidgetCreation, + ), + depFilePath: depfilePath, + extraFrontEndOptions: extraFrontEndOptions, + linkPlatformKernelIn: true, + aot: true, + buildMode: buildMode, + trackWidgetCreation: trackWidgetCreation, + dartDefines: dartDefines, + )); + + // Write path to frontend_server, since things need to be re-generated when that changes. + final String frontendPath = globals.artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk); + globals.fs.directory(outputPath).childFile('frontend_server.d').writeAsStringSync('frontend_server.d: $frontendPath\n'); + + return compilerOutput?.outputFilename; + } + bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) { if (buildMode == BuildMode.debug) { return false; @@ -300,4 +328,19 @@ class AOTSnapshotter { TargetPlatform.darwin_x64, ].contains(platform); } + + /// This method is used to measure duration of an action and emit it into + /// verbose output from flutter_tool for other tools (e.g. benchmark runner) + /// to find. + /// Important: external performance tracking tools expect format of this + /// output to be stable. + Future<T> _timedStep<T>(String marker, String analyticsVar, FutureOr<T> Function() action) async { + final Stopwatch sw = Stopwatch()..start(); + final T value = await action(); + if (reportTimings) { + globals.printStatus('$marker: ${sw.elapsedMilliseconds} ms.'); + } + globals.flutterUsage.sendTiming('build', analyticsVar, Duration(milliseconds: sw.elapsedMilliseconds)); + return value; + } } diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart index d946e22c8c..0d9283311f 100644 --- a/packages/flutter_tools/lib/src/base/logger.dart +++ b/packages/flutter_tools/lib/src/base/logger.dart @@ -697,7 +697,7 @@ abstract class Status { @required Duration timeout, @required TimeoutConfiguration timeoutConfiguration, @required Stopwatch stopwatch, - @required Terminal terminal, + @required AnsiTerminal terminal, VoidCallback onFinish, SlowWarningCallback slowWarningCallback, }) { @@ -877,7 +877,7 @@ class AnsiSpinner extends Status { @required Duration timeout, @required TimeoutConfiguration timeoutConfiguration, @required Stopwatch stopwatch, - @required Terminal terminal, + @required AnsiTerminal terminal, VoidCallback onFinish, this.slowWarningCallback, Stdio stdio, @@ -893,7 +893,7 @@ class AnsiSpinner extends Status { final String _backspaceChar = '\b'; final String _clearChar = ' '; final Stdio _stdio; - final Terminal _terminal; + final AnsiTerminal _terminal; bool timedOut = false; diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index d832c50f58..ed677b2589 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -210,14 +210,7 @@ class AndroidAot extends AotElfBase { @override Future<void> build(Environment environment) async { - final AOTSnapshotter snapshotter = AOTSnapshotter( - reportTimings: false, - fileSystem: globals.fs, - logger: globals.logger, - xcode: globals.xcode, - processManager: globals.processManager, - artifacts: globals.artifacts, - ); + final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false); final Directory output = environment.buildDir.childDirectory(_androidAbiName); final String splitDebugInfo = environment.defines[kSplitDebugInfo]; if (environment.defines[kBuildMode] == null) { diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart index a67e834293..afa08b77ff 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart @@ -204,7 +204,9 @@ class KernelSnapshot extends Target { final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); // This configuration is all optional. - final List<String> extraFrontEndOptions = environment.defines[kExtraFrontEndOptions]?.split(','); + final List<String> extraFrontEndOptions = <String>[ + ...?environment.defines[kExtraFrontEndOptions]?.split(',') + ]; final List<String> fileSystemRoots = environment.defines[kFileSystemRoots]?.split(','); final String fileSystemScheme = environment.defines[kFileSystemScheme]; @@ -259,14 +261,7 @@ abstract class AotElfBase extends Target { @override Future<void> build(Environment environment) async { - final AOTSnapshotter snapshotter = AOTSnapshotter( - reportTimings: false, - fileSystem: globals.fs, - logger: globals.logger, - xcode: globals.xcode, - processManager: globals.processManager, - artifacts: globals.artifacts, - ); + final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false); final String outputPath = environment.buildDir.path; if (environment.defines[kBuildMode] == null) { throw MissingDefineException(kBuildMode, 'aot_elf'); diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 20e685ab0d..990716cf8a 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -28,14 +28,7 @@ abstract class AotAssemblyBase extends Target { @override Future<void> build(Environment environment) async { - final AOTSnapshotter snapshotter = AOTSnapshotter( - reportTimings: false, - fileSystem: globals.fs, - logger: globals.logger, - xcode: globals.xcode, - artifacts: globals.artifacts, - processManager: globals.processManager, - ); + final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false); final String buildOutputPath = environment.buildDir.path; if (environment.defines[kBuildMode] == null) { throw MissingDefineException(kBuildMode, 'aot_assembly'); @@ -43,8 +36,6 @@ abstract class AotAssemblyBase extends Target { if (environment.defines[kTargetPlatform] == null) { throw MissingDefineException(kTargetPlatform, 'aot_assembly'); } - final List<String> extraGenSnapshotOptions = environment - .defines[kExtraGenSnapshotOptions]?.split(',') ?? const <String>[]; final bool bitcode = environment.defines[kBitcodeFlag] == 'true'; final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); @@ -74,7 +65,6 @@ abstract class AotAssemblyBase extends Target { quiet: true, splitDebugInfo: splitDebugInfo, dartObfuscation: dartObfuscation, - extraGenSnapshotOptions: extraGenSnapshotOptions, )); } final List<int> results = await Future.wait(pending); diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart index 5104d99901..a4d5c9dfe3 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -202,15 +202,7 @@ class CompileMacOSFramework extends Target { } final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; - final AOTSnapshotter snapshotter = AOTSnapshotter( - reportTimings: false, - fileSystem: globals.fs, - logger: globals.logger, - xcode: globals.xcode, - artifacts: globals.artifacts, - processManager: globals.processManager - ); - final int result = await snapshotter.build( + final int result = await AOTSnapshotter(reportTimings: false).build( bitcode: false, buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index f54ec5c51e..fb5694255d 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -27,6 +27,11 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen allowed: <String>['android-arm', 'android-arm64', 'ios', 'android-x64'], ) ..addFlag('quiet', defaultsTo: false) + ..addFlag('report-timings', + negatable: false, + defaultsTo: false, + help: 'Report timing information about build steps in machine readable form,', + ) ..addMultiOption('ios-arch', splitCommas: true, defaultsTo: defaultIOSArchs.map<String>(getNameForDarwinArch), @@ -75,6 +80,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen mainDartFile: findMainDartFile(targetFile), bitcode: boolArg('bitcode'), quiet: boolArg('quiet'), + reportTimings: boolArg('report-timings'), iosBuildArchs: stringsArg('ios-arch').map<DarwinArch>(getIOSArchForName), ); return FlutterCommandResult.success(); diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index 1784a906d3..dd0943776d 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -436,6 +436,7 @@ end mainDartFile: globals.fs.path.absolute(targetFile), quiet: true, bitcode: true, + reportTimings: false, iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64], ); } finally { diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index beb2100248..07ad8c8a03 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -11,6 +11,7 @@ import 'android/gradle_utils.dart'; import 'application_package.dart'; import 'artifacts.dart'; import 'asset.dart'; +import 'base/build.dart'; import 'base/config.dart'; import 'base/context.dart'; import 'base/io.dart'; @@ -124,6 +125,7 @@ Future<T> runInContext<T>( FuchsiaDeviceTools: () => FuchsiaDeviceTools(), FuchsiaSdk: () => FuchsiaSdk(), FuchsiaWorkflow: () => FuchsiaWorkflow(), + GenSnapshot: () => const GenSnapshot(), GradleUtils: () => GradleUtils(), HotRunnerConfig: () => HotRunnerConfig(), IMobileDevice: () => IMobileDevice(), diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 09081ecf28..3bd514163a 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -249,7 +249,7 @@ class XcodeProjectInterpreter { @required ProcessManager processManager, @required Logger logger, @required FileSystem fileSystem, - @required Terminal terminal, + @required AnsiTerminal terminal, }) : _platform = platform, _fileSystem = fileSystem, _terminal = terminal, @@ -259,7 +259,7 @@ class XcodeProjectInterpreter { final Platform _platform; final FileSystem _fileSystem; final ProcessUtils _processUtils; - final Terminal _terminal; + final AnsiTerminal _terminal; final Logger _logger; static const String _executable = '/usr/bin/xcodebuild'; diff --git a/packages/flutter_tools/test/general.shard/base/build_test.dart b/packages/flutter_tools/test/general.shard/base/build_test.dart index 00a4625a0a..6ea4d69114 100644 --- a/packages/flutter_tools/test/general.shard/base/build_test.dart +++ b/packages/flutter_tools/test/general.shard/base/build_test.dart @@ -2,70 +2,75 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:file/memory.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/base/build.dart'; -import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/macos/xcode.dart'; +import 'package:flutter_tools/src/version.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; -const FakeCommand kSdkPathCommand = FakeCommand( - command: <String>[ - 'xcrun', - '--sdk', - 'iphoneos', - '--show-sdk-path' - ] -); - -const List<String> kDefaultClang = <String>[ - '-miphoneos-version-min=8.0', - '-dynamiclib', - '-Xlinker', - '-rpath', - '-Xlinker', - '@executable_path/Frameworks', - '-Xlinker', - '-rpath', - '-Xlinker', - '@loader_path/Frameworks', - '-install_name', - '@rpath/App.framework/App', - '-isysroot', - '', - '-o', - 'build/foo/App.framework/App', - 'build/foo/snapshot_assembly.o', -]; - -const List<String> kBitcodeClang = <String>[ - '-miphoneos-version-min=8.0', - '-dynamiclib', - '-Xlinker', - '-rpath', - '-Xlinker', - '@executable_path/Frameworks', - '-Xlinker', - '-rpath', - '-Xlinker', - '@loader_path/Frameworks', - '-install_name', - '@rpath/App.framework/App', - '-fembed-bitcode', - '-isysroot', - '', - '-o', - 'build/foo/App.framework/App', - 'build/foo/snapshot_assembly.o', -]; +class MockFlutterVersion extends Mock implements FlutterVersion {} +class MockAndroidSdk extends Mock implements AndroidSdk {} +class MockArtifacts extends Mock implements Artifacts {} +class MockXcode extends Mock implements Xcode {} +class MockProcessManager extends Mock implements ProcessManager {} +class MockProcess extends Mock implements Process {} + +class _FakeGenSnapshot implements GenSnapshot { + _FakeGenSnapshot({ + this.succeed = true, + }); + + final bool succeed; + Map<String, String> outputs = <String, String>{}; + int _callCount = 0; + SnapshotType _snapshotType; + String _depfilePath; + List<String> _additionalArgs; + + int get callCount => _callCount; + + SnapshotType get snapshotType => _snapshotType; + + String get depfilePath => _depfilePath; + + List<String> get additionalArgs => _additionalArgs; + + @override + Future<int> run({ + SnapshotType snapshotType, + String depfilePath, + DarwinArch darwinArch, + Iterable<String> additionalArgs = const <String>[], + }) async { + _callCount += 1; + _snapshotType = snapshotType; + _depfilePath = depfilePath; + _additionalArgs = additionalArgs.toList(); + + if (!succeed) { + return 1; + } + outputs.forEach((String filePath, String fileContent) { + globals.fs.file(filePath).writeAsString(fileContent); + }); + return 0; + } +} void main() { group('SnapshotType', () { @@ -76,137 +81,181 @@ void main() { ); }); test('does not throw, if target platform is null', () { - expect(() => SnapshotType(null, BuildMode.release), returnsNormally); + expect(SnapshotType(null, BuildMode.release), isNotNull); }); }); group('GenSnapshot', () { GenSnapshot genSnapshot; MockArtifacts mockArtifacts; - FakeProcessManager processManager; - BufferLogger logger; + MockProcessManager mockProcessManager; + MockProcess mockProc; setUp(() async { + genSnapshot = const GenSnapshot(); mockArtifacts = MockArtifacts(); - logger = BufferLogger.test(); - processManager = FakeProcessManager.list(< FakeCommand>[]); - genSnapshot = GenSnapshot( - artifacts: mockArtifacts, - logger: logger, - processManager: processManager, - ); - when(mockArtifacts.getArtifactPath( - any, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - )).thenReturn('gen_snapshot'); - }); - - testWithoutContext('android_x64', () async { - processManager.addCommand(const FakeCommand( - command: <String>['gen_snapshot', '--additional_arg'] - )); - - final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release), - darwinArch: null, - additionalArgs: <String>['--additional_arg'], - ); - expect(result, 0); + mockProcessManager = MockProcessManager(); + mockProc = MockProcess(); }); - testWithoutContext('iOS armv7', () async { - processManager.addCommand(const FakeCommand( - command: <String>['gen_snapshot_armv7', '--additional_arg'] - )); - - final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), - darwinArch: DarwinArch.armv7, - additionalArgs: <String>['--additional_arg'], - ); - expect(result, 0); - }); - - testWithoutContext('iOS arm64', () async { - processManager.addCommand(const FakeCommand( - command: <String>['gen_snapshot_arm64', '--additional_arg'] - )); - - final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), - darwinArch: DarwinArch.arm64, - additionalArgs: <String>['--additional_arg'], - ); - expect(result, 0); - }); - - testWithoutContext('--strip filters error output from gen_snapshot', () async { - processManager.addCommand(FakeCommand( - command: const <String>['gen_snapshot', '--strip'], - stderr: 'ABC\n${GenSnapshot.kIgnoredWarnings.join('\n')}\nXYZ\n' - )); - - final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release), - darwinArch: null, - additionalArgs: <String>['--strip'], - ); - - expect(result, 0); - expect(logger.errorText, contains('ABC')); - for (final String ignoredWarning in GenSnapshot.kIgnoredWarnings) { - expect(logger.errorText, isNot(contains(ignoredWarning))); - } - expect(logger.errorText, contains('XYZ')); - }); + final Map<Type, Generator> contextOverrides = <Type, Generator>{ + Artifacts: () => mockArtifacts, + ProcessManager: () => mockProcessManager, + }; + + testUsingContext('android_x64', () async { + when(mockArtifacts.getArtifactPath(Artifact.genSnapshot, + platform: TargetPlatform.android_x64, mode: BuildMode.release)) + .thenReturn('gen_snapshot'); + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) => Future<Process>.value(mockProc)); + when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty()); + when(mockProc.stderr).thenAnswer((_) => const Stream<List<int>>.empty()); + await genSnapshot.run( + snapshotType: + SnapshotType(TargetPlatform.android_x64, BuildMode.release), + darwinArch: null, + additionalArgs: <String>['--additional_arg']); + verify(mockProcessManager.start( + <String>[ + 'gen_snapshot', + '--additional_arg', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).called(1); + }, overrides: contextOverrides); + + testUsingContext('iOS armv7', () async { + when(mockArtifacts.getArtifactPath(Artifact.genSnapshot, + platform: TargetPlatform.ios, mode: BuildMode.release)) + .thenReturn('gen_snapshot'); + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) => Future<Process>.value(mockProc)); + when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty()); + when(mockProc.stderr).thenAnswer((_) => const Stream<List<int>>.empty()); + await genSnapshot.run( + snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), + darwinArch: DarwinArch.armv7, + additionalArgs: <String>['--additional_arg']); + verify(mockProcessManager.start( + <String>[ + 'gen_snapshot_armv7', + '--additional_arg', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment')), + ).called(1); + }, overrides: contextOverrides); + + testUsingContext('iOS arm64', () async { + when(mockArtifacts.getArtifactPath(Artifact.genSnapshot, + platform: TargetPlatform.ios, mode: BuildMode.release)) + .thenReturn('gen_snapshot'); + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) => Future<Process>.value(mockProc)); + when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty()); + when(mockProc.stderr).thenAnswer((_) => const Stream<List<int>>.empty()); + await genSnapshot.run( + snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), + darwinArch: DarwinArch.arm64, + additionalArgs: <String>['--additional_arg']); + verify(mockProcessManager.start( + <String>[ + 'gen_snapshot_arm64', + '--additional_arg', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).called(1); + }, overrides: contextOverrides); + + testUsingContext('--strip filters outputs', () async { + when(mockArtifacts.getArtifactPath(Artifact.genSnapshot, + platform: TargetPlatform.android_x64, mode: BuildMode.release)) + .thenReturn('gen_snapshot'); + when(mockProcessManager.start( + <String>['gen_snapshot', '--strip'], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) => Future<Process>.value(mockProc)); + when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty()); + when(mockProc.stderr) + .thenAnswer((_) => Stream<String>.fromIterable(<String>[ + '--ABC\n', + 'Warning: Generating ELF library without DWARF debugging information.\n', + '--XYZ\n', + ]) + .transform<List<int>>(utf8.encoder)); + await genSnapshot.run( + snapshotType: + SnapshotType(TargetPlatform.android_x64, BuildMode.release), + darwinArch: null, + additionalArgs: <String>['--strip']); + verify(mockProcessManager.start( + <String>['gen_snapshot', '--strip'], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .called(1); + expect(testLogger.errorText, contains('ABC')); + expect(testLogger.errorText, isNot(contains('ELF library'))); + expect(testLogger.errorText, contains('XYZ')); + }, overrides: contextOverrides); }); - group('AOTSnapshotter', () { - MemoryFileSystem fileSystem; + group('Snapshotter - AOT', () { + const String kSnapshotDart = 'snapshot.dart'; + const String kSDKPath = '/path/to/sdk'; + String skyEnginePath; + + _FakeGenSnapshot genSnapshot; + MemoryFileSystem fs; AOTSnapshotter snapshotter; + AOTSnapshotter snapshotterWithTimings; + MockAndroidSdk mockAndroidSdk; MockArtifacts mockArtifacts; - FakeProcessManager processManager; - Logger logger; + MockXcode mockXcode; setUp(() async { - final Platform platform = FakePlatform(operatingSystem: 'macos'); - logger = BufferLogger.test(); - fileSystem = MemoryFileSystem.test(); + fs = MemoryFileSystem(); + fs.file(kSnapshotDart).createSync(); + fs.file('.packages').writeAsStringSync('sky_engine:file:///flutter/bin/cache/pkg/sky_engine/lib/'); + + skyEnginePath = fs.path.fromUri(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 = _FakeGenSnapshot(); + snapshotter = AOTSnapshotter(); + snapshotterWithTimings = AOTSnapshotter(reportTimings: true); + mockAndroidSdk = MockAndroidSdk(); mockArtifacts = MockArtifacts(); - processManager = FakeProcessManager.list(<FakeCommand>[]); - snapshotter = AOTSnapshotter( - fileSystem: fileSystem, - logger: logger, - xcode: Xcode( - fileSystem: fileSystem, - logger: logger, - platform: FakePlatform(operatingSystem: 'macos'), - processManager: processManager, - xcodeProjectInterpreter: XcodeProjectInterpreter( - platform: platform, - processManager: processManager, - logger: logger, - fileSystem: fileSystem, - terminal: Terminal.test(), - ), - ), - artifacts: mockArtifacts, - processManager: processManager, - ); - when(mockArtifacts.getArtifactPath( - Artifact.genSnapshot, - platform: anyNamed('platform'), - mode: anyNamed('mode')), - ).thenReturn('gen_snapshot'); + mockXcode = MockXcode(); + when(mockXcode.sdkLocation(any)).thenAnswer((_) => Future<String>.value(kSDKPath)); }); - testWithoutContext('does not build iOS with debug build mode', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - + final Map<Type, Generator> contextOverrides = <Type, Generator>{ + AndroidSdk: () => mockAndroidSdk, + Artifacts: () => mockArtifacts, + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + GenSnapshot: () => genSnapshot, + Xcode: () => mockXcode, + }; + + testUsingContext('iOS debug AOT snapshot is invalid', () async { + final String outputPath = globals.fs.path.join('build', 'foo'); expect(await snapshotter.build( platform: TargetPlatform.ios, - darwinArch: DarwinArch.arm64, buildMode: BuildMode.debug, mainPath: 'main.dill', packagesPath: '.packages', @@ -215,11 +264,10 @@ void main() { splitDebugInfo: null, dartObfuscation: false, ), isNot(equals(0))); - }); - - testWithoutContext('does not build android-arm with debug build mode', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); + }, overrides: contextOverrides); + testUsingContext('Android arm debug AOT snapshot is invalid', () async { + final String outputPath = globals.fs.path.join('build', 'foo'); expect(await snapshotter.build( platform: TargetPlatform.android_arm, buildMode: BuildMode.debug, @@ -230,11 +278,10 @@ void main() { splitDebugInfo: null, dartObfuscation: false, ), isNot(0)); - }); - - testWithoutContext('does not build android-arm64 with debug build mode', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); + }, overrides: contextOverrides); + testUsingContext('Android arm64 debug AOT snapshot is invalid', () async { + final String outputPath = globals.fs.path.join('build', 'foo'); expect(await snapshotter.build( platform: TargetPlatform.android_arm64, buildMode: BuildMode.debug, @@ -245,50 +292,22 @@ void main() { splitDebugInfo: null, dartObfuscation: false, ), isNot(0)); - }); + }, overrides: contextOverrides); - testWithoutContext('builds iOS with bitcode', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S'); - processManager.addCommand(FakeCommand( - command: <String>[ - 'gen_snapshot_armv7', - '--deterministic', - '--snapshot_kind=app-aot-assembly', - '--assembly=$assembly', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - 'main.dill', - ] - )); - processManager.addCommand(kSdkPathCommand); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'cc', - '-arch', - 'armv7', - '-isysroot', - '', - '-fembed-bitcode', - '-c', - 'build/foo/snapshot_assembly.S', - '-o', - 'build/foo/snapshot_assembly.o', - ] - )); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'clang', - '-arch', - 'armv7', - ...kBitcodeClang, - ] - )); + testUsingContext('iOS profile AOT with bitcode uses right flags', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S'); + genSnapshot.outputs = <String, String>{ + assembly: 'blah blah\n.section __DWARF\nblah blah\n', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.ios, @@ -303,53 +322,115 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.profile); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=$assembly', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + + final VerificationResult toVerifyCC = verify(mockXcode.cc(captureAny)); + expect(toVerifyCC.callCount, 1); + final dynamic ccArgs = toVerifyCC.captured.first; + expect(ccArgs, contains('-fembed-bitcode')); + expect(ccArgs, contains('-isysroot')); + expect(ccArgs, contains(kSDKPath)); + + final VerificationResult toVerifyClang = verify(mockXcode.clang(captureAny)); + expect(toVerifyClang.callCount, 1); + final dynamic clangArgs = toVerifyClang.captured.first; + expect(clangArgs, contains('-fembed-bitcode')); + expect(clangArgs, contains('-isysroot')); + expect(clangArgs, contains(kSDKPath)); + + final File assemblyFile = globals.fs.file(assembly); + expect(assemblyFile.existsSync(), true); + expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true); + }, overrides: contextOverrides); + + testUsingContext('iOS release AOT with bitcode uses right flags', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S'); + genSnapshot.outputs = <String, String>{ + assembly: 'blah blah\n', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); - testWithoutContext('builds iOS armv7 snapshot with dwarStackTraces', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S'); - final String debugPath = fileSystem.path.join('foo', 'app.ios-armv7.symbols'); - processManager.addCommand(FakeCommand( - command: <String>[ - 'gen_snapshot_armv7', - '--deterministic', - '--snapshot_kind=app-aot-assembly', - '--assembly=$assembly', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - '--dwarf-stack-traces', - '--save-debugging-info=$debugPath', - 'main.dill', - ] - )); - processManager.addCommand(kSdkPathCommand); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'cc', - '-arch', - 'armv7', - '-isysroot', - '', - '-c', - 'build/foo/snapshot_assembly.S', - '-o', - 'build/foo/snapshot_assembly.o', - ] - )); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'clang', - '-arch', - 'armv7', - ...kDefaultClang, - ] - )); + final int genSnapshotExitCode = await snapshotter.build( + platform: TargetPlatform.ios, + buildMode: BuildMode.release, + mainPath: 'main.dill', + packagesPath: '.packages', + outputPath: outputPath, + darwinArch: DarwinArch.armv7, + bitcode: true, + splitDebugInfo: null, + dartObfuscation: false, + ); + + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=$assembly', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + + final VerificationResult toVerifyCC = verify(mockXcode.cc(captureAny)); + expect(toVerifyCC.callCount, 1); + final dynamic ccArgs = toVerifyCC.captured.first; + expect(ccArgs, contains('-fembed-bitcode')); + expect(ccArgs, contains('-isysroot')); + expect(ccArgs, contains(kSDKPath)); + + final VerificationResult toVerifyClang = verify(mockXcode.clang(captureAny)); + expect(toVerifyClang.callCount, 1); + final dynamic clangArgs = toVerifyClang.captured.first; + expect(clangArgs, contains('-fembed-bitcode')); + expect(clangArgs, contains('-isysroot')); + expect(clangArgs, contains(kSDKPath)); + + final File assemblyFile = globals.fs.file(assembly); + expect(assemblyFile.existsSync(), true); + }, overrides: contextOverrides); + + testUsingContext('builds iOS armv7 profile AOT snapshot', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S'); + genSnapshot.outputs = <String, String>{ + assembly: 'blah blah\n.section __DWARF\nblah blah\n', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.ios, @@ -359,56 +440,106 @@ void main() { outputPath: outputPath, darwinArch: DarwinArch.armv7, bitcode: false, - splitDebugInfo: 'foo', + splitDebugInfo: null, dartObfuscation: false, ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.profile); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=$assembly', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode')))); + verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode')))); + + verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1); + verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1); + + final File assemblyFile = globals.fs.file(assembly); + expect(assemblyFile.existsSync(), true); + expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true); + }, overrides: contextOverrides); + + testUsingContext('builds iOS armv7 profile AOT snapshot with dwarStackTraces', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S'); + genSnapshot.outputs = <String, String>{ + assembly: 'blah blah\n.section __DWARF\nblah blah\n', + }; + final String debugPath = globals.fs.path.join('foo', 'app.ios-armv7.symbols'); + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); - testWithoutContext('builds iOS armv7 snapshot with obfuscate', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S'); - processManager.addCommand(FakeCommand( - command: <String>[ - 'gen_snapshot_armv7', - '--deterministic', - '--snapshot_kind=app-aot-assembly', - '--assembly=$assembly', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - '--obfuscate', - 'main.dill', - ] - )); - processManager.addCommand(kSdkPathCommand); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'cc', - '-arch', - 'armv7', - '-isysroot', - '', - '-c', - 'build/foo/snapshot_assembly.S', - '-o', - 'build/foo/snapshot_assembly.o', - ] - )); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'clang', - '-arch', - 'armv7', - ...kDefaultClang, - ] - )); + final int genSnapshotExitCode = await snapshotter.build( + platform: TargetPlatform.ios, + buildMode: BuildMode.profile, + mainPath: 'main.dill', + packagesPath: '.packages', + outputPath: outputPath, + darwinArch: DarwinArch.armv7, + bitcode: false, + splitDebugInfo: 'foo', + dartObfuscation: false, + ); + + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.profile); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=$assembly', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + '--dwarf-stack-traces', + '--save-debugging-info=$debugPath', + 'main.dill', + ]); + verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode')))); + verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode')))); + + verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1); + verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1); + + final File assemblyFile = globals.fs.file(assembly); + expect(assemblyFile.existsSync(), true); + expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true); + }, overrides: contextOverrides); + + testUsingContext('builds iOS armv7 profile AOT snapshot with obfuscate', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S'); + genSnapshot.outputs = <String, String>{ + assembly: 'blah blah\n.section __DWARF\nblah blah\n', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.ios, @@ -423,50 +554,86 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.profile); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=$assembly', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + '--obfuscate', + 'main.dill', + ]); + verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode')))); + verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode')))); + + verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1); + verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1); + + final File assemblyFile = globals.fs.file(assembly); + expect(assemblyFile.existsSync(), true); + expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true); + }, overrides: contextOverrides); + + testUsingContext('builds iOS arm64 profile AOT snapshot', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + genSnapshot.outputs = <String, String>{ + globals.fs.path.join(outputPath, 'snapshot_assembly.S'): '', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + final int genSnapshotExitCode = await snapshotter.build( + platform: TargetPlatform.ios, + buildMode: BuildMode.profile, + mainPath: 'main.dill', + packagesPath: '.packages', + outputPath: outputPath, + darwinArch: DarwinArch.arm64, + bitcode: false, + splitDebugInfo: null, + dartObfuscation: false, + ); - testWithoutContext('builds iOS armv7 snapshot', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - processManager.addCommand(FakeCommand( - command: <String>[ - 'gen_snapshot_armv7', - '--deterministic', - '--snapshot_kind=app-aot-assembly', - '--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - 'main.dill', - ] - )); - processManager.addCommand(kSdkPathCommand); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'cc', - '-arch', - 'armv7', - '-isysroot', - '', - '-c', - 'build/foo/snapshot_assembly.S', - '-o', - 'build/foo/snapshot_assembly.o', - ] - )); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'clang', - '-arch', - 'armv7', - ...kDefaultClang, - ] - )); + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.profile); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=${globals.fs.path.join(outputPath, 'snapshot_assembly.S')}', + '--strip', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds iOS release armv7 AOT snapshot', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + genSnapshot.outputs = <String, String>{ + globals.fs.path.join(outputPath, 'snapshot_assembly.S'): '', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.ios, @@ -481,47 +648,35 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); - - testWithoutContext('builds iOS arm64 snapshot', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - processManager.addCommand(FakeCommand( - command: <String>[ - 'gen_snapshot_arm64', - '--deterministic', - '--snapshot_kind=app-aot-assembly', - '--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}', - '--strip', - '--no-causal-async-stacks', - '--lazy-async-stacks', - 'main.dill', - ] - )); - processManager.addCommand(kSdkPathCommand); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'cc', - '-arch', - 'arm64', - '-isysroot', - '', - '-c', - 'build/foo/snapshot_assembly.S', - '-o', - 'build/foo/snapshot_assembly.o', - ] - )); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'xcrun', - 'clang', - '-arch', - 'arm64', - ...kDefaultClang, - ] - )); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=${globals.fs.path.join(outputPath, 'snapshot_assembly.S')}', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds iOS release arm64 AOT snapshot', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + genSnapshot.outputs = <String, String>{ + globals.fs.path.join(outputPath, 'snapshot_assembly.S'): '', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.ios, @@ -536,25 +691,25 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); - - testWithoutContext('builds shared library for android-arm (32bit)', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'gen_snapshot', - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - 'main.dill', - ] - )); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.ios); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=${globals.fs.path.join(outputPath, 'snapshot_assembly.S')}', + '--strip', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds shared library for android-arm', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm, @@ -568,28 +723,28 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); - - testWithoutContext('builds shared library for android-arm with dwarf stack traces', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - final String debugPath = fileSystem.path.join('foo', 'app.android-arm.symbols'); - processManager.addCommand(FakeCommand( - command: <String>[ - 'gen_snapshot', - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - '--dwarf-stack-traces', - '--save-debugging-info=$debugPath', - 'main.dill', - ] - )); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-elf', + '--elf=build/foo/app.so', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds shared library for android-arm with dwarf stack traces', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + final String debugPath = globals.fs.path.join('foo', 'app.android-arm.symbols'); + globals.fs.directory(outputPath).createSync(recursive: true); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm, @@ -603,26 +758,29 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); - - testWithoutContext('builds shared library for android-arm with obfuscate', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'gen_snapshot', - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - '--obfuscate', - 'main.dill', - ] - )); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-elf', + '--elf=build/foo/app.so', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + '--dwarf-stack-traces', + '--save-debugging-info=$debugPath', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds shared library for android-arm with obfuscate', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm, @@ -636,25 +794,28 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); - - testWithoutContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'gen_snapshot', - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - '--strip', - '--no-sim-use-hardfp', - '--no-use-integer-division', - '--no-causal-async-stacks', - '--lazy-async-stacks', - 'main.dill', - ] - )); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-elf', + '--elf=build/foo/app.so', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + '--obfuscate', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm, @@ -668,23 +829,27 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); - - testWithoutContext('builds shared library for android-arm64', () async { - final String outputPath = fileSystem.path.join('build', 'foo'); - processManager.addCommand(const FakeCommand( - command: <String>[ - 'gen_snapshot', - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - '--strip', - '--no-causal-async-stacks', - '--lazy-async-stacks', - 'main.dill', - ] - )); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-elf', + '--elf=build/foo/app.so', + '--strip', + '--no-sim-use-hardfp', + '--no-use-integer-division', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('builds shared library for android-arm64', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm64, @@ -698,9 +863,48 @@ void main() { ); expect(genSnapshotExitCode, 0); - expect(processManager.hasRemainingExpectations, false); - }); + expect(genSnapshot.callCount, 1); + expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm64); + expect(genSnapshot.snapshotType.mode, BuildMode.release); + expect(genSnapshot.additionalArgs, <String>[ + '--deterministic', + '--snapshot_kind=app-aot-elf', + '--elf=build/foo/app.so', + '--strip', + '--no-causal-async-stacks', + '--lazy-async-stacks', + 'main.dill', + ]); + }, overrides: contextOverrides); + + testUsingContext('reports timing', () async { + globals.fs.file('main.dill').writeAsStringSync('binary magic'); + + final String outputPath = globals.fs.path.join('build', 'foo'); + globals.fs.directory(outputPath).createSync(recursive: true); + + genSnapshot.outputs = <String, String>{ + globals.fs.path.join(outputPath, 'app.so'): '', + }; + + final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); + when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult)); + + final int genSnapshotExitCode = await snapshotterWithTimings.build( + platform: TargetPlatform.android_arm, + buildMode: BuildMode.release, + mainPath: 'main.dill', + packagesPath: '.packages', + outputPath: outputPath, + bitcode: false, + splitDebugInfo: null, + dartObfuscation: false, + ); + + expect(genSnapshotExitCode, 0); + expect(genSnapshot.callCount, 1); + expect(testLogger.statusText, matches(RegExp(r'snapshot\(CompileTime\): \d+ ms.'))); + }, overrides: contextOverrides); }); } - -class MockArtifacts extends Mock implements Artifacts {} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart index e0ebb090fa..0cee898d37 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_tools/src/base/build.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/targets/dart.dart'; import 'package:flutter_tools/src/build_system/targets/macos.dart'; @@ -48,6 +49,7 @@ void main() { Testbed testbed; Environment environment; MockPlatform mockPlatform; + MockXcode mockXcode; setUpAll(() { Cache.disableLocking(); @@ -55,6 +57,7 @@ void main() { }); setUp(() { + mockXcode = MockXcode(); mockPlatform = MockPlatform(); when(mockPlatform.isWindows).thenReturn(false); when(mockPlatform.isMacOS).thenReturn(true); @@ -190,6 +193,35 @@ void main() { expect(outputFramework.readAsStringSync(), 'DEF'); })); + + test('release/profile macOS compilation uses correct gen_snapshot', () => testbed.run(() async { + when(genSnapshot.run( + snapshotType: anyNamed('snapshotType'), + additionalArgs: anyNamed('additionalArgs'), + darwinArch: anyNamed('darwinArch'), + )).thenAnswer((Invocation invocation) { + environment.buildDir.childFile('snapshot_assembly.o').createSync(); + environment.buildDir.childFile('snapshot_assembly.S').createSync(); + return Future<int>.value(0); + }); + when(mockXcode.cc(any)).thenAnswer((Invocation invocation) { + return Future<RunResult>.value(RunResult(FakeProcessResult()..exitCode = 0, <String>['test'])); + }); + when(mockXcode.clang(any)).thenAnswer((Invocation invocation) { + return Future<RunResult>.value(RunResult(FakeProcessResult()..exitCode = 0, <String>['test'])); + }); + environment.buildDir.childFile('app.dill').createSync(recursive: true); + globals.fs.file('.packages') + ..createSync() + ..writeAsStringSync(''' +# Generated +sky_engine:file:///bin/cache/pkg/sky_engine/lib/ +flutter_tools:lib/'''); + await const CompileMacOSFramework().build(environment..defines[kBuildMode] = 'release'); + }, overrides: <Type, Generator>{ + GenSnapshot: () => MockGenSnapshot(), + Xcode: () => mockXcode, + })); } class MockPlatform extends Mock implements Platform {} -- 2.21.0