diff --git a/dev/devicelab/bin/tasks/module_test_ios.dart b/dev/devicelab/bin/tasks/module_test_ios.dart index 04503efe49..53413cc7b5 100644 --- a/dev/devicelab/bin/tasks/module_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_test_ios.dart @@ -270,10 +270,10 @@ Future main() async { final String objectiveCAnalyticsOutput = objectiveCAnalyticsOutputFile.readAsStringSync(); if (!objectiveCAnalyticsOutput.contains('cd24: ios') || !objectiveCAnalyticsOutput.contains('cd25: true') - || !objectiveCAnalyticsOutput.contains('viewName: build/bundle')) { + || !objectiveCAnalyticsOutput.contains('viewName: assemble')) { return TaskResult.failure( 'Building outer Objective-C app produced the following analytics: "$objectiveCAnalyticsOutput" ' - 'but not the expected strings: "cd24: ios", "cd25: true", "viewName: build/bundle"' + 'but not the expected strings: "cd24: ios", "cd25: true", "viewName: assemble"' ); } @@ -359,10 +359,10 @@ Future main() async { final String swiftAnalyticsOutput = swiftAnalyticsOutputFile.readAsStringSync(); if (!swiftAnalyticsOutput.contains('cd24: ios') || !swiftAnalyticsOutput.contains('cd25: true') - || !swiftAnalyticsOutput.contains('viewName: build/bundle')) { + || !swiftAnalyticsOutput.contains('viewName: assemble')) { return TaskResult.failure( 'Building outer Swift app produced the following analytics: "$swiftAnalyticsOutput" ' - 'but not the expected strings: "cd24: ios", "cd25: true", "viewName: build/bundle"' + 'but not the expected strings: "cd24: ios", "cd25: true", "viewName: assemble"' ); } diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index 00229927b1..aa5c224811 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -51,6 +51,10 @@ BuildApp() { derived_dir="${project_path}/.ios/Flutter" fi + RunCommand mkdir -p -- "$derived_dir" + AssertExists "$derived_dir" + RunCommand rm -rf -- "${derived_dir}/App.framework" + # Default value of assets_path is flutter_assets local assets_path="flutter_assets" # The value of assets_path can set by add FLTAssetsPath to AppFrameworkInfo.plist @@ -96,15 +100,6 @@ BuildApp() { fi local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}" - - AssertExists "${framework_path}" - AssertExists "${project_path}" - - RunCommand mkdir -p -- "$derived_dir" - AssertExists "$derived_dir" - - RunCommand rm -rf -- "${derived_dir}/App.framework" - local flutter_engine_flag="" local local_engine_flag="" local flutter_framework="${framework_path}/Flutter.framework" @@ -134,9 +129,10 @@ BuildApp() { local bitcode_flag="" if [[ $ENABLE_BITCODE == "YES" ]]; then - bitcode_flag="--bitcode" + bitcode_flag="true" fi + # TODO(jonahwilliams): move engine copying to build system. if [[ -e "${project_path}/.ios" ]]; then RunCommand rm -rf -- "${derived_dir}/engine" mkdir "${derived_dir}/engine" @@ -150,102 +146,29 @@ BuildApp() { RunCommand pushd "${project_path}" > /dev/null - AssertExists "${target_path}" - local verbose_flag="" if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then verbose_flag="--verbose" fi - local build_dir="${FLUTTER_BUILD_DIR:-build}" - local track_widget_creation_flag="" if [[ -n "$TRACK_WIDGET_CREATION" ]]; then - track_widget_creation_flag="--track-widget-creation" + track_widget_creation_flag="true" fi - if [[ "${build_mode}" != "debug" ]]; then - StreamOutput " ├─Building Dart code..." - # Transform ARCHS to comma-separated list of target architectures. - local archs="${ARCHS// /,}" - if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then - EchoError "========================================================================" - EchoError "ERROR: Flutter does not support running in profile or release mode on" - EchoError "the Simulator (this build was: '$build_mode')." - EchoError "You can ensure Flutter runs in Debug mode with your host app in release" - EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated" - EchoError "with the ${CONFIGURATION} build configuration." - EchoError "========================================================================" - exit -1 - fi - - RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \ - ${verbose_flag} \ - build aot \ - --output-dir="${build_dir}/aot" \ - --target-platform=ios \ - --target="${target_path}" \ - --${build_mode} \ - --ios-arch="${archs}" \ - ${flutter_engine_flag} \ - ${local_engine_flag} \ - ${bitcode_flag} - - if [[ $? -ne 0 ]]; then - EchoError "Failed to build ${project_path}." - exit -1 - fi - StreamOutput "done" - - local app_framework="${build_dir}/aot/App.framework" - - RunCommand cp -r -- "${app_framework}" "${derived_dir}" - - else - RunCommand mkdir -p -- "${derived_dir}/App.framework" - - # Build stub for all requested architectures. - local arch_flags="" - read -r -a archs <<< "$ARCHS" - for arch in "${archs[@]}"; do - arch_flags="${arch_flags}-arch $arch " - done - - RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \ - ${arch_flags} \ - -fembed-bitcode-marker \ - -dynamiclib \ - -Xlinker -rpath -Xlinker '@executable_path/Frameworks' \ - -Xlinker -rpath -Xlinker '@loader_path/Frameworks' \ - -install_name '@rpath/App.framework/App' \ - -o "${derived_dir}/App.framework/App" -)" - fi - - local plistPath="${project_path}/ios/Flutter/AppFrameworkInfo.plist" - if [[ -e "${project_path}/.ios" ]]; then - plistPath="${project_path}/.ios/Flutter/AppFrameworkInfo.plist" - fi - - RunCommand cp -- "$plistPath" "${derived_dir}/App.framework/Info.plist" - - local precompilation_flag="" - if [[ "$CURRENT_ARCH" != "x86_64" ]] && [[ "$build_mode" != "debug" ]]; then - precompilation_flag="--precompiled" - fi - - StreamOutput " ├─Assembling Flutter resources..." - RunCommand "${FLUTTER_ROOT}/bin/flutter" \ - ${verbose_flag} \ - build bundle \ - --target-platform=ios \ - --target="${target_path}" \ - --${build_mode} \ - --depfile="${build_dir}/snapshot_blob.bin.d" \ - --asset-dir="${derived_dir}/App.framework/${assets_path}" \ - ${precompilation_flag} \ - ${flutter_engine_flag} \ - ${local_engine_flag} \ - ${track_widget_creation_flag} + RunCommand "${FLUTTER_ROOT}/bin/flutter" \ + ${verbose_flag} \ + ${flutter_engine_flag} \ + ${local_engine_flag} \ + assemble \ + --output="${derived_dir}/" \ + -dTargetPlatform=ios \ + -dTargetFile="${target_path}" \ + -dBuildMode=${build_mode} \ + -dIosArchs="${ARCHS}" \ + -dTrackWidgetCreation="${track_widget_creation_flag}" \ + -dEnableBitcode="${bitcode_flag}" \ + "${build_mode}_ios_bundle_flutter_assets" if [[ $? -ne 0 ]]; then EchoError "Failed to package ${project_path}." diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart index 7465654f8d..24d3b84bc6 100644 --- a/packages/flutter_tools/lib/src/aot.dart +++ b/packages/flutter_tools/lib/src/aot.dart @@ -12,14 +12,9 @@ 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/ios.dart'; -import 'cache.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. @@ -42,21 +37,6 @@ class AotBuilder { throwToolExit('No AOT build platform specified'); } - if (_canUseAssemble(platform) - && extraGenSnapshotOptions?.isEmpty != false - && extraFrontEndOptions?.isEmpty != false) { - await _buildWithAssemble( - targetFile: mainDartFile, - outputDir: outputPath, - targetPlatform: platform, - buildMode: buildMode, - quiet: quiet, - iosArchs: iosBuildArchs ?? defaultIOSArchs, - bitcode: bitcode ?? kBitcodeEnabledDefault, - ); - return; - } - if (bitcode) { if (platform != TargetPlatform.ios) { throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $platform).'); @@ -174,81 +154,4 @@ class AotBuilder { } return; } - - bool _canUseAssemble(TargetPlatform targetPlatform) { - switch (targetPlatform) { - case TargetPlatform.ios: - return true; - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x86: - case TargetPlatform.darwin_x64: - case TargetPlatform.android_x64: - case TargetPlatform.linux_x64: - case TargetPlatform.windows_x64: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - default: - return false; - } - } - - Future _buildWithAssemble({ - TargetPlatform targetPlatform, - BuildMode buildMode, - String targetFile, - String outputDir, - bool quiet, - Iterable iosArchs, - bool bitcode, - }) async { - Status status; - if (!quiet) { - final String typeName = globals.artifacts.getEngineType(targetPlatform, buildMode); - status = globals.logger.startProgress( - 'Building AOT snapshot in ${getFriendlyModeName(buildMode)} mode ($typeName)...', - timeout: timeoutConfiguration.slowOperation, - ); - } - final FlutterProject flutterProject = FlutterProject.current(); - final Target target = buildMode == BuildMode.profile - ? const AotAssemblyProfile() - : const AotAssemblyRelease(); - - final BuildResult result = await buildSystem.build(target, Environment( - projectDir: flutterProject.directory, - cacheDir: globals.cache.getRoot(), - flutterRootDir: globals.fs.directory(Cache.flutterRoot), - outputDir: globals.fs.directory(outputDir), - buildDir: flutterProject.directory - .childDirectory('.dart_tool') - .childDirectory('flutter_build'), - defines: { - kBuildMode: getNameForBuildMode(buildMode), - kTargetPlatform: getNameForTargetPlatform(targetPlatform), - kTargetFile: targetFile, - kIosArchs: iosArchs.map(getNameForDarwinArch).join(','), - kBitcodeFlag: bitcode.toString() - } - )); - status?.stop(); - if (!result.success) { - for (final ExceptionMeasurement measurement in result.exceptions.values) { - globals.printError('Target ${measurement.target} failed: ${measurement.exception}', - stackTrace: measurement.fatal - ? measurement.stackTrace - : null, - ); - } - throwToolExit('Failed to build aot.'); - } - final String builtMessage = 'Built to $outputDir${globals.fs.path.separator}.'; - if (quiet) { - globals.printTrace(builtMessage); - } else { - globals.printStatus(builtMessage); - } - } } diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 545a8690b7..38889540bb 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -385,6 +385,8 @@ DarwinArch getIOSArchForName(String arch) { return DarwinArch.armv7; case 'arm64': return DarwinArch.arm64; + case 'x86_64': + return DarwinArch.x86_64; } assert(false); return null; 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 9b1ede5207..a25b1ef423 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -11,8 +11,11 @@ import '../../base/process.dart'; import '../../build_info.dart'; import '../../globals.dart' as globals; import '../../macos/xcode.dart'; +import '../../project.dart'; import '../build_system.dart'; +import '../depfile.dart'; import '../exceptions.dart'; +import 'assets.dart'; import 'dart.dart'; /// Supports compiling a dart kernel file to an assembly file. @@ -25,7 +28,7 @@ abstract class AotAssemblyBase extends Target { @override Future build(Environment environment) async { final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false); - final String buildOutputPath = environment.outputDir.path; + final String buildOutputPath = environment.buildDir.path; if (environment.defines[kBuildMode] == null) { throw MissingDefineException(kBuildMode, 'aot_assembly'); } @@ -35,8 +38,11 @@ abstract class AotAssemblyBase extends Target { final bool bitcode = environment.defines[kBitcodeFlag] == 'true'; final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); - final List iosArchs = environment.defines[kIosArchs]?.split(',')?.map(getIOSArchForName)?.toList() - ?? [DarwinArch.arm64]; + final List iosArchs = environment.defines[kIosArchs] + ?.split(' ') + ?.map(getIOSArchForName) + ?.toList() + ?? [DarwinArch.arm64]; if (targetPlatform != TargetPlatform.ios) { throw Exception('aot_assembly is only supported for iOS applications'); } @@ -60,7 +66,7 @@ abstract class AotAssemblyBase extends Target { if (results.any((int result) => result != 0)) { throw Exception('AOT snapshotter exited with code ${results.join()}'); } - final String resultPath = globals.fs.path.join(environment.outputDir.path, 'App.framework', 'App'); + final String resultPath = globals.fs.path.join(environment.buildDir.path, 'App.framework', 'App'); globals.fs.directory(resultPath).parent.createSync(recursive: true); final ProcessResult result = await globals.processManager.run([ 'lipo', @@ -145,12 +151,215 @@ class AotAssemblyProfile extends AotAssemblyBase { ]; } +/// Create a trivial App.framework file for debug iOS builds. +class DebugUniveralFramework extends Target { + const DebugUniveralFramework(); + + @override + String get name => 'debug_universal_framework'; + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; + + @override + List get inputs => const [ + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'), + ]; + + @override + List get outputs => const [ + Source.pattern('{BUILD_DIR}/App') + ]; + + @override + Future build(Environment environment) async { + // Generate a trivial App.framework. + final Set iosArchs = environment.defines[kIosArchs] + ?.split(' ') + ?.map(getIOSArchForName) + ?.toSet() + ?? {DarwinArch.arm64}; + final File iphoneFile = environment.buildDir.childFile('iphone_framework'); + final File simulatorFile = environment.buildDir.childFile('simulator_framework'); + final File lipoOutputFile = environment.buildDir.childFile('App'); + final RunResult iphoneResult = await createStubAppFramework( + iphoneFile, + SdkType.iPhone, + // Only include 32bit if it is contained in the active architectures. + include32Bit: iosArchs.contains(DarwinArch.armv7) + ); + final RunResult simulatorResult = await createStubAppFramework( + simulatorFile, + SdkType.iPhoneSimulator, + ); + if (iphoneResult.exitCode != 0 || simulatorResult.exitCode != 0) { + throw Exception('Failed to create App.framework.'); + } + final List lipoCommand = [ + 'xcrun', + 'lipo', + '-create', + iphoneFile.path, + simulatorFile.path, + '-output', + lipoOutputFile.path + ]; + final RunResult lipoResult = await processUtils.run( + lipoCommand, + ); + + if (lipoResult.exitCode != 0) { + throw Exception('Failed to create App.framework.'); + } + } +} + +/// The base class for all iOS bundle targets. +/// +/// This is responsible for setting up the basic App.framework structure, including: +/// * Copying the app.dill/kernel_blob.bin from the build directory to assets (debug) +/// * Copying the precompiled isolate/vm data from the engine (debug) +/// * Copying the flutter assets to App.framework/flutter_assets +/// * Copying either the stub or real App assembly file to App.framework/App +abstract class IosAssetBundle extends Target { + const IosAssetBundle(); + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; + + @override + List get inputs => const [ + Source.pattern('{BUILD_DIR}/App'), + Source.pattern('{PROJECT_DIR}/pubspec.yaml'), + ]; + + @override + List get outputs => const [ + Source.pattern('{OUTPUT_DIR}/App.framework/App'), + Source.pattern('{OUTPUT_DIR}/App.framework/Info.plist') + ]; + + @override + List get depfiles => [ + 'flutter_assets.d', + ]; + + @override + Future build(Environment environment) async { + if (environment.defines[kBuildMode] == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); + final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework'); + final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets'); + frameworkDirectory.createSync(recursive: true); + assetDirectory.createSync(); + + // Only copy the prebuilt runtimes and kernel blob in debug mode. + if (buildMode == BuildMode.debug) { + // Copy the App.framework to the output directory. + environment.buildDir.childFile('App') + .copySync(frameworkDirectory.childFile('App').path); + + final String vmSnapshotData = globals.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug); + final String isolateSnapshotData = globals.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug); + environment.buildDir.childFile('app.dill') + .copySync(assetDirectory.childFile('kernel_blob.bin').path); + globals.fs.file(vmSnapshotData) + .copySync(assetDirectory.childFile('vm_snapshot_data').path); + globals.fs.file(isolateSnapshotData) + .copySync(assetDirectory.childFile('isolate_snapshot_data').path); + } else { + environment.buildDir.childDirectory('App.framework').childFile('App') + .copySync(frameworkDirectory.childFile('App').path); + } + + // Copy the assets. + final Depfile assetDepfile = await copyAssets(environment, assetDirectory); + assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d')); + + + // Copy the plist from either the project or module. + // TODO(jonahwilliams): add plist to inputs + final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir); + final Directory plistRoot = flutterProject.isModule + ? flutterProject.ios.ephemeralDirectory + : environment.projectDir.childDirectory('ios'); + plistRoot + .childDirectory('Flutter') + .childFile('AppFrameworkInfo.plist') + .copySync(environment.outputDir + .childDirectory('App.framework') + .childFile('Info.plist').path); + } +} + +/// Build a debug iOS application bundle. +class DebugIosApplicationBundle extends IosAssetBundle { + const DebugIosApplicationBundle(); + + @override + String get name => 'debug_ios_bundle_flutter_assets'; + + @override + List get inputs => [ + const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug), + const Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug), + const Source.pattern('{BUILD_DIR}/app.dill'), + ...super.inputs, + ]; + + @override + List get outputs => [ + const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/vm_snapshot_data'), + const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/isolate_snapshot_data'), + const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/kernel_blob.bin'), + ...super.outputs, + ]; + + @override + List get dependencies => [ + const DebugUniveralFramework(), + ...super.dependencies, + ]; +} + +/// Build a profile iOS application bundle. +class ProfileIosApplicationBundle extends IosAssetBundle { + const ProfileIosApplicationBundle(); + + @override + String get name => 'profile_ios_bundle_flutter_assets'; + + @override + List get dependencies => const [ + AotAssemblyProfile(), + ]; +} + +/// Build a release iOS application bundle. +class ReleaseIosApplicationBundle extends IosAssetBundle { + const ReleaseIosApplicationBundle(); + + @override + String get name => 'release_ios_bundle_flutter_assets'; + + @override + List get dependencies => const [ + AotAssemblyRelease(), + ]; +} + /// Create an App.framework for debug iOS targets. /// /// This framework needs to exist for the Xcode project to link/bundle, /// but it isn't actually executed. To generate something valid, we compile a trivial /// constant. -Future createStubAppFramework(File outputFile, SdkType sdk) async { +Future createStubAppFramework(File outputFile, SdkType sdk, { bool include32Bit = true }) async { try { outputFile.createSync(recursive: true); } catch (e) { @@ -167,8 +376,8 @@ Future createStubAppFramework(File outputFile, SdkType sdk) async { List archFlags; if (sdk == SdkType.iPhone) { archFlags = [ - '-arch', - getNameForDarwinArch(DarwinArch.armv7), + if (include32Bit) + ...['-arch', getNameForDarwinArch(DarwinArch.armv7)], '-arch', getNameForDarwinArch(DarwinArch.arm64), ]; diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 4d89483cdd..6a8ca21a5d 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -50,6 +50,9 @@ const List _kDefaultTargets = [ androidArmReleaseBundle, androidArm64ReleaseBundle, androidx64ReleaseBundle, + DebugIosApplicationBundle(), + ProfileIosApplicationBundle(), + ReleaseIosApplicationBundle(), ]; /// Assemble provides a low level API to interact with the flutter tool build diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart index baa0699cef..590887d3ae 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart @@ -315,7 +315,7 @@ flutter_tools:lib/'''); })); test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async { - iosEnvironment.defines[kIosArchs] ='armv7,arm64'; + iosEnvironment.defines[kIosArchs] ='armv7 arm64'; when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) .createSync(recursive: true); @@ -360,7 +360,7 @@ flutter_tools:lib/'''); })); test('aot_assembly_profile with bitcode sends correct argument to snapshotter (mutli arch)', () => testbed.run(() async { - iosEnvironment.defines[kIosArchs] = 'armv7,arm64'; + iosEnvironment.defines[kIosArchs] = 'armv7 arm64'; iosEnvironment.defines[kBitcodeFlag] = 'true'; final FakeProcessResult fakeProcessResult = FakeProcessResult( @@ -377,9 +377,8 @@ flutter_tools:lib/'''); when(mockXcode.cc(any)).thenAnswer((_) => Future.value(fakeRunResult)); when(mockXcode.clang(any)).thenAnswer((_) => Future.value(fakeRunResult)); - final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment); + await const AotAssemblyProfile().build(iosEnvironment); - expect(result.success, true); verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2); verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2); }, overrides: { @@ -388,7 +387,7 @@ flutter_tools:lib/'''); })); test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async { - iosEnvironment.defines[kIosArchs] = 'armv7,arm64'; + iosEnvironment.defines[kIosArchs] = 'armv7 arm64'; when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) .createSync(recursive: true); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart new file mode 100644 index 0000000000..3a610c237e --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart @@ -0,0 +1,165 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/build_info.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/ios.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:mockito/mockito.dart'; + +import '../../../src/common.dart'; +import '../../../src/fake_process_manager.dart'; +import '../../../src/testbed.dart'; + +const List _kSharedConfig = [ + '-dynamiclib', + '-fembed-bitcode-marker', + '-Xlinker', + '-rpath', + '-Xlinker', + '@executable_path/Frameworks', + '-Xlinker', + '-rpath', + '-Xlinker', + '@loader_path/Frameworks', + '-install_name', + '@rpath/App.framework/App', + '-isysroot', +]; + +void main() { + Testbed testbed; + Environment environment; + ProcessManager processManager; + + setUp(() { + testbed = Testbed(setup: () { + environment = Environment.test(globals.fs.currentDirectory, defines: { + kTargetPlatform: 'ios', + }); + }); + }); + + test('DebugUniveralFramework creates expected binary with arm64 only arch', () => testbed.run(() async { + environment.defines[kIosArchs] = 'arm64'; + processManager = FakeProcessManager.list([ + // Create iphone stub. + const FakeCommand(command: ['xcrun', '--sdk', 'iphoneos', '--show-sdk-path']), + FakeCommand(command: [ + 'xcrun', + 'clang', + '-x', + 'c', + // iphone only gets 64 bit arch based on kIosArchs + '-arch', + 'arm64', + globals.fs.path.absolute(globals.fs.path.join('.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')), + ..._kSharedConfig, + '', + '-o', + environment.buildDir.childFile('iphone_framework').path + ]), + // Create simulator stub. + const FakeCommand(command: ['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-path']), + FakeCommand(command: [ + 'xcrun', + 'clang', + '-x', + 'c', + // Simulator only as x86_64 arch + '-arch', + 'x86_64', + globals.fs.path.absolute(globals.fs.path.join('.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')), + ..._kSharedConfig, + '', + '-o', + environment.buildDir.childFile('simulator_framework').path + ]), + // Lipo stubs together. + FakeCommand(command: [ + 'xcrun', + 'lipo', + '-create', + environment.buildDir.childFile('iphone_framework').path, + environment.buildDir.childFile('simulator_framework').path, + '-output', + environment.buildDir.childFile('App').path, + ]), + ]); + + await const DebugUniveralFramework().build(environment); + }, overrides: { + ProcessManager: () => processManager, + })); + + test('DebugIosApplicationBundle', () => testbed.run(() async { + environment.defines[kBuildMode] = 'debug'; + // Precompiled dart data + when(globals.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug)) + .thenReturn('vm_snapshot_data'); + when(globals.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug)) + .thenReturn('isolate_snapshot_data'); + globals.fs.file('vm_snapshot_data').createSync(); + globals.fs.file('isolate_snapshot_data').createSync(); + // Project info + globals.fs.file('pubspec.yaml').writeAsStringSync('name: hello'); + globals.fs.file('.packages').writeAsStringSync('\n'); + // Plist file + globals.fs.file(globals.fs.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist')) + ..createSync(recursive: true); + // App kernel + environment.buildDir.childFile('app.dill').createSync(recursive: true); + // Stub framework + environment.buildDir.childFile('App').createSync(); + + await const DebugIosApplicationBundle().build(environment); + + final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework'); + expect(frameworkDirectory.childFile('App'), exists); + expect(frameworkDirectory.childFile('Info.plist'), exists); + + final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets'); + expect(assetDirectory.childFile('kernel_blob.bin'), exists); + expect(assetDirectory.childFile('AssetManifest.json'), exists); + expect(assetDirectory.childFile('vm_snapshot_data'), exists); + expect(assetDirectory.childFile('isolate_snapshot_data'), exists); + }, overrides: { + Artifacts: () => MockArtifacts(), + })); + + test('ReleaseIosApplicationBundle', () => testbed.run(() async { + environment.defines[kBuildMode] = 'release'; + + // Project info + globals.fs.file('pubspec.yaml').writeAsStringSync('name: hello'); + globals.fs.file('.packages').writeAsStringSync('\n'); + // Plist file + globals.fs.file(globals.fs.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist')) + ..createSync(recursive: true); + + // Real framework + environment.buildDir + .childDirectory('App.framework') + .childFile('App') + .createSync(recursive: true); + + await const ReleaseIosApplicationBundle().build(environment); + + final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework'); + expect(frameworkDirectory.childFile('App'), exists); + expect(frameworkDirectory.childFile('Info.plist'), exists); + + final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets'); + expect(assetDirectory.childFile('kernel_blob.bin'), isNot(exists)); + expect(assetDirectory.childFile('AssetManifest.json'), exists); + expect(assetDirectory.childFile('vm_snapshot_data'), isNot(exists)); + expect(assetDirectory.childFile('isolate_snapshot_data'), isNot(exists)); + })); +} + +class MockArtifacts extends Mock implements Artifacts {}