From f6726b425df4b8fe75c94144258dcdc744f8fc02 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Wed, 28 Apr 2021 20:33:36 +0200 Subject: [PATCH] Add support for DarwinArchs when assembling macOS App.framework (#81243) --- packages/flutter_tools/bin/macos_assemble.sh | 3 +- packages/flutter_tools/lib/src/artifacts.dart | 24 +- .../flutter_tools/lib/src/base/build.dart | 6 +- .../flutter_tools/lib/src/build_info.dart | 32 +- .../lib/src/build_system/targets/common.dart | 12 +- .../lib/src/build_system/targets/macos.dart | 152 ++++++++-- .../lib/src/commands/build_bundle.dart | 4 +- .../lib/src/flutter_application_package.dart | 2 +- .../lib/src/ios/xcode_build_settings.dart | 2 +- .../lib/src/macos/macos_device.dart | 8 +- .../lib/src/runner/flutter_command.dart | 2 +- .../hermetic/assemble_test.dart | 2 +- .../permeable/build_bundle_test.dart | 4 +- .../test/general.shard/artifacts_test.dart | 4 +- .../build_system/targets/common_test.dart | 4 +- .../build_system/targets/macos_test.dart | 287 ++++++++++++++++-- .../macos/macos_device_test.dart | 2 +- 17 files changed, 447 insertions(+), 103 deletions(-) diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh index 4fdaf2664b..f9e821efc8 100755 --- a/packages/flutter_tools/bin/macos_assemble.sh +++ b/packages/flutter_tools/bin/macos_assemble.sh @@ -85,7 +85,8 @@ BuildApp() { assemble \ --no-version-check \ ${performance_measurement_option} \ - -dTargetPlatform=darwin-x64 \ + -dTargetPlatform=darwin \ + -dDarwinArchs=x86_64 \ -dTargetFile="${target_path}" \ -dBuildMode="${build_mode}" \ -dTreeShakeIcons="${TREE_SHAKE_ICONS}" \ diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 43adfbd6fb..f5d404daee 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -103,6 +103,14 @@ enum HostArtifact { pubExecutable, } +// TODO(knopp): Remove once darwin artifacts are universal and moved out of darwin-x64 +String _enginePlatformDirectoryName(TargetPlatform platform) { + if (platform == TargetPlatform.darwin) { + return 'darwin-x64'; + } + return getNameForTargetPlatform(platform); +} + String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMode mode ]) { final String exe = platform == TargetPlatform.windows_x64 ? '.exe' : ''; switch (artifact) { @@ -383,7 +391,7 @@ class CachedArtifacts implements Artifacts { return _getAndroidArtifactPath(artifact, platform, mode); case TargetPlatform.ios: return _getIosArtifactPath(artifact, platform, mode, environmentType); - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: case TargetPlatform.linux_x64: case TargetPlatform.linux_arm64: case TargetPlatform.windows_x64: @@ -498,7 +506,7 @@ class CachedArtifacts implements Artifacts { case Artifact.frontendServerSnapshotForEngineDartSdk: case Artifact.icuData: final String engineArtifactsPath = _cache.getArtifactDirectory('engine').path; - final String platformDirName = getNameForTargetPlatform(platform); + final String platformDirName = _enginePlatformDirectoryName(platform); return _fileSystem.path.join(engineArtifactsPath, platformDirName, _artifactToFileName(artifact, platform, mode)); case Artifact.platformKernelDill: return _fileSystem.path.join(_getFlutterPatchedSdkPath(mode), _artifactToFileName(artifact)); @@ -514,7 +522,7 @@ class CachedArtifacts implements Artifacts { // TODO(jonahwilliams): remove once debug desktop artifacts are uploaded // under a separate directory from the host artifacts. // https://github.com/flutter/flutter/issues/38935 - String platformDirName = getNameForTargetPlatform(platform); + String platformDirName = _enginePlatformDirectoryName(platform); if (mode == BuildMode.profile || mode == BuildMode.release) { platformDirName = '$platformDirName-${getNameForBuildMode(mode)}'; } @@ -532,7 +540,7 @@ class CachedArtifacts implements Artifacts { case Artifact.fontSubset: case Artifact.constFinder: return _cache.getArtifactDirectory('engine') - .childDirectory(getNameForTargetPlatform(platform)) + .childDirectory(_enginePlatformDirectoryName(platform)) .childFile(_artifactToFileName(artifact, platform, mode)) .path; default: @@ -543,11 +551,11 @@ class CachedArtifacts implements Artifacts { String _getEngineArtifactsPath(TargetPlatform platform, [ BuildMode mode ]) { final String engineDir = _cache.getArtifactDirectory('engine').path; - final String platformName = getNameForTargetPlatform(platform); + final String platformName = _enginePlatformDirectoryName(platform); switch (platform) { case TargetPlatform.linux_x64: case TargetPlatform.linux_arm64: - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: case TargetPlatform.windows_x64: // TODO(jonahwilliams): remove once debug desktop artifacts are uploaded // under a separate directory from the host artifacts. @@ -586,7 +594,7 @@ class CachedArtifacts implements Artifacts { TargetPlatform _currentHostPlatform(Platform platform, OperatingSystemUtils operatingSystemUtils) { if (platform.isMacOS) { - return TargetPlatform.darwin_x64; + return TargetPlatform.darwin; } if (platform.isLinux) { return operatingSystemUtils.hostPlatform == HostPlatform.linux_x64 ? @@ -836,7 +844,7 @@ class CachedLocalEngineArtifacts implements LocalEngineArtifacts { } String _genSnapshotPath() { - const List clangDirs = ['.', 'clang_x64', 'clang_x86', 'clang_i386']; + const List clangDirs = ['.', 'clang_x64', 'clang_x86', 'clang_i386', 'clang_arm64']; final String genSnapshotName = _artifactToFileName(Artifact.genSnapshot); for (final String clangDir in clangDirs) { final String genSnapshotPath = _fileSystem.path.join(engineOutPath, clangDir, genSnapshotName); diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 0fe01960f6..fa3d156c13 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -154,7 +154,7 @@ class AOTSnapshotter { } final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S'); - if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) { + if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin) { genSnapshotArgs.addAll([ '--snapshot_kind=app-aot-assembly', '--assembly=$assembly', @@ -218,7 +218,7 @@ class AOTSnapshotter { // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the // end-developer can link into their app. - if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) { + if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin) { final RunResult result = await _buildFramework( appleArch: darwinArch, isIOS: platform == TargetPlatform.ios, @@ -311,7 +311,7 @@ class AOTSnapshotter { TargetPlatform.android_arm64, TargetPlatform.android_x64, TargetPlatform.ios, - TargetPlatform.darwin_x64, + TargetPlatform.darwin, TargetPlatform.linux_x64, TargetPlatform.linux_arm64, TargetPlatform.windows_x64, diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 571546d8f2..904cae2e7c 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -360,7 +360,7 @@ String? validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String bu return null; } if (targetPlatform == TargetPlatform.ios || - targetPlatform == TargetPlatform.darwin_x64) { + targetPlatform == TargetPlatform.darwin) { // See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html final RegExp disallowed = RegExp(r'[^\d\.]'); String tmpBuildNumber = buildNumber.replaceAll(disallowed, ''); @@ -407,7 +407,7 @@ String? validatedBuildNameForPlatform(TargetPlatform targetPlatform, String buil return null; } if (targetPlatform == TargetPlatform.ios || - targetPlatform == TargetPlatform.darwin_x64) { + targetPlatform == TargetPlatform.darwin) { // See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html final RegExp disallowed = RegExp(r'[^\d\.]'); String tmpBuildName = buildName.replaceAll(disallowed, ''); @@ -458,8 +458,7 @@ bool isEmulatorBuildMode(BuildMode mode) { enum TargetPlatform { android, ios, - // darwin_arm64 not yet supported, macOS desktop targets run in Rosetta as x86. - darwin_x64, + darwin, linux_x64, linux_arm64, windows_x64, @@ -536,6 +535,16 @@ DarwinArch getIOSArchForName(String arch) { throw Exception('Unsupported iOS arch name "$arch"'); } +DarwinArch getDarwinArchForName(String arch) { + switch (arch) { + case 'arm64': + return DarwinArch.arm64; + case 'x86_64': + return DarwinArch.x86_64; + } + throw Exception('Unsupported MacOS arch name "$arch"'); +} + String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) { switch (platform) { case TargetPlatform.android_arm: @@ -551,8 +560,11 @@ String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch return 'ios-${getNameForDarwinArch(darwinArch)}'; } return 'ios'; - case TargetPlatform.darwin_x64: - return 'darwin-x64'; + case TargetPlatform.darwin: + if (darwinArch != null) { + return 'darwin-${getNameForDarwinArch(darwinArch)}'; + } + return 'darwin'; case TargetPlatform.linux_x64: return 'linux-x64'; case TargetPlatform.linux_arm64: @@ -592,8 +604,12 @@ TargetPlatform? getTargetPlatformForName(String platform) { return TargetPlatform.fuchsia_x64; case 'ios': return TargetPlatform.ios; + case 'darwin': + // For backward-compatibility and also for Tester, where it must match + // host platform name (HostPlatform.darwin_x64) case 'darwin-x64': - return TargetPlatform.darwin_x64; + case 'darwin-arm': + return TargetPlatform.darwin; case 'linux-x64': return TargetPlatform.linux_x64; case 'linux-arm64': @@ -814,7 +830,7 @@ String _getCurrentHostPlatformArchName() { String getNameForTargetPlatformArch(TargetPlatform platform) { switch (platform) { case TargetPlatform.linux_x64: - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: case TargetPlatform.windows_x64: return 'x64'; case TargetPlatform.linux_arm64: diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index ba55b4e444..f302131b87 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -66,12 +66,20 @@ const String kFileSystemRoots = 'FileSystemRoots'; /// The define to control what iOS architectures are built for. /// -/// This is expected to be a comma-separated list of architectures. If not +/// This is expected to be a space-delimited list of architectures. If not /// provided, defaults to arm64. /// /// The other supported value is armv7, the 32-bit iOS architecture. const String kIosArchs = 'IosArchs'; +/// The define to control what macOS architectures are built for. +/// +/// This is expected to be a space-delimited list of architectures. If not +/// provided, defautls to x86_64. +/// +/// Supported values are x86_64 and arm64. +const String kDarwinArchs = 'DarwinArchs'; + /// Path to the SDK root to be used as the isysroot. const String kSdkRoot = 'SdkRoot'; @@ -254,7 +262,7 @@ class KernelSnapshot extends Target { // See https://github.com/flutter/flutter/issues/44724 bool forceLinkPlatform; switch (targetPlatform) { - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: case TargetPlatform.windows_x64: case TargetPlatform.linux_x64: forceLinkPlatform = true; 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 a4957c16a3..a4790d7e70 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -67,6 +67,58 @@ abstract class UnpackMacOS extends Target { '${result.stdout}\n---\n${result.stderr}', ); } + + final File frameworkBinary = environment.outputDir.childDirectory('FlutterMacOS.framework').childFile('FlutterMacOS'); + final String frameworkBinaryPath = frameworkBinary.path; + if (!frameworkBinary.existsSync()) { + throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin'); + } + _thinFramework(environment, frameworkBinaryPath); + } + + void _thinFramework(Environment environment, String frameworkBinaryPath) { + final String archs = environment.defines[kDarwinArchs] ?? 'x86_64'; + final List archList = archs.split(' ').toList(); + final ProcessResult infoResult = environment.processManager.runSync([ + 'lipo', + '-info', + frameworkBinaryPath, + ]); + final String lipoInfo = infoResult.stdout as String; + + final ProcessResult verifyResult = environment.processManager.runSync([ + 'lipo', + frameworkBinaryPath, + '-verify_arch', + ...archList + ]); + + if (verifyResult.exitCode != 0) { + throw Exception('Binary $frameworkBinaryPath does not contain $archs. Running lipo -info:\n$lipoInfo'); + } + + // Skip thinning for non-fat executables. + if (lipoInfo.startsWith('Non-fat file:')) { + environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath'); + return; + } + + // Thin in-place. + final ProcessResult extractResult = environment.processManager.runSync([ + 'lipo', + '-output', + frameworkBinaryPath, + for (final String arch in archList) + ...[ + '-extract', + arch, + ], + ...[frameworkBinaryPath], + ]); + + if (extractResult.exitCode != 0) { + throw Exception('Failed to extract $archs for $frameworkBinaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo'); + } } } @@ -127,6 +179,15 @@ class DebugMacOSFramework extends Target { Future build(Environment environment) async { final File outputFile = environment.fileSystem.file(environment.fileSystem.path.join( environment.buildDir.path, 'App.framework', 'App')); + + final Iterable darwinArchs = environment.defines[kDarwinArchs] + ?.split(' ') + ?.map(getDarwinArchForName) + ?? [DarwinArch.x86_64]; + + final Iterable darwinArchArguments = + darwinArchs.expand((DarwinArch arch) => ['-arch', getNameForDarwinArch(arch)]); + outputFile.createSync(recursive: true); final File debugApp = environment.buildDir.childFile('debug_app.cc') ..writeAsStringSync(r''' @@ -136,7 +197,7 @@ static const int Moo = 88; '-x', 'c', debugApp.path, - '-arch', 'x86_64', + ...darwinArchArguments, '-dynamiclib', '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', @@ -177,21 +238,19 @@ class CompileMacOSFramework extends Target { if (buildMode == BuildMode.debug) { throw Exception('precompiled macOS framework only supported in release/profile builds.'); } + final String buildOutputPath = environment.buildDir.path; final String codeSizeDirectory = environment.defines[kCodeSizeDirectory]; final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; final List extraGenSnapshotOptions = decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions); - - if (codeSizeDirectory != null) { - final File codeSizeFile = environment.fileSystem - .directory(codeSizeDirectory) - .childFile('snapshot.${getNameForDarwinArch(DarwinArch.x86_64)}.json'); - extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}'); - final File precompilerTraceFile = environment.fileSystem - .directory(codeSizeDirectory) - .childFile('trace.${getNameForDarwinArch(DarwinArch.x86_64)}.json'); - extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}'); - extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}'); + final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); + final List darwinArchs = environment.defines[kDarwinArchs] + ?.split(' ') + ?.map(getDarwinArchForName) + ?.toList() + ?? [DarwinArch.x86_64]; + if (targetPlatform != TargetPlatform.darwin) { + throw Exception('compile_macos_framework is only supported for darwin TargetPlatform.'); } final AOTSnapshotter snapshotter = AOTSnapshotter( @@ -202,19 +261,50 @@ class CompileMacOSFramework extends Target { artifacts: environment.artifacts, processManager: environment.processManager ); - final int result = await snapshotter.build( - bitcode: false, - buildMode: buildMode, - mainPath: environment.buildDir.childFile('app.dill').path, - outputPath: environment.buildDir.path, - platform: TargetPlatform.darwin_x64, - darwinArch: DarwinArch.x86_64, - splitDebugInfo: splitDebugInfo, - dartObfuscation: dartObfuscation, - extraGenSnapshotOptions: extraGenSnapshotOptions, - ); - if (result != 0) { - throw Exception('gen shapshot failed.'); + + final List> pending = >[]; + for (final DarwinArch darwinArch in darwinArchs) { + if (codeSizeDirectory != null) { + final File codeSizeFile = environment.fileSystem + .directory(codeSizeDirectory) + .childFile('snapshot.${getNameForDarwinArch(darwinArch)}.json'); + final File precompilerTraceFile = environment.fileSystem + .directory(codeSizeDirectory) + .childFile('trace.${getNameForDarwinArch(darwinArch)}.json'); + extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}'); + extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}'); + } + + pending.add(snapshotter.build( + bitcode: false, + buildMode: buildMode, + mainPath: environment.buildDir.childFile('app.dill').path, + outputPath: environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(darwinArch)), + platform: TargetPlatform.darwin, + darwinArch: darwinArch, + splitDebugInfo: splitDebugInfo, + dartObfuscation: dartObfuscation, + extraGenSnapshotOptions: extraGenSnapshotOptions, + )); + } + + final List results = await Future.wait(pending); + if (results.any((int result) => result != 0)) { + throw Exception('AOT snapshotter exited with code ${results.join()}'); + } + + final String resultPath = environment.fileSystem.path.join(environment.buildDir.path, 'App.framework', 'App'); + environment.fileSystem.directory(resultPath).parent.createSync(recursive: true); + final ProcessResult result = await environment.processManager.run([ + 'lipo', + ...darwinArchs.map((DarwinArch iosArch) => + environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')), + '-create', + '-output', + resultPath, + ]); + if (result.exitCode != 0) { + throw Exception('lipo exited with code ${result.exitCode}.\n${result.stderr}'); } } @@ -227,7 +317,7 @@ class CompileMacOSFramework extends Target { List get inputs => const [ Source.pattern('{BUILD_DIR}/app.dill'), Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'), - Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin_x64), + Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin), ]; @override @@ -291,7 +381,7 @@ abstract class MacOSBundleFlutterAssets extends Target { final Depfile assetDepfile = await copyAssets( environment, assetDirectory, - targetPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.darwin, ); final DepfileService depfileService = DepfileService( fileSystem: environment.fileSystem, @@ -341,9 +431,9 @@ abstract class MacOSBundleFlutterAssets extends Target { // Copy precompiled runtimes. try { final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData, - platform: TargetPlatform.darwin_x64, mode: BuildMode.debug); + platform: TargetPlatform.darwin, mode: BuildMode.debug); final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData, - platform: TargetPlatform.darwin_x64, mode: BuildMode.debug); + platform: TargetPlatform.darwin, mode: BuildMode.debug); environment.fileSystem.file(vmSnapshotData).copySync( assetDirectory.childFile('vm_snapshot_data').path); environment.fileSystem.file(isolateSnapshotData).copySync( @@ -404,8 +494,8 @@ class DebugMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets { List get inputs => [ ...super.inputs, const Source.pattern('{BUILD_DIR}/app.dill'), - const Source.artifact(Artifact.isolateSnapshotData, platform: TargetPlatform.darwin_x64, mode: BuildMode.debug), - const Source.artifact(Artifact.vmSnapshotData, platform: TargetPlatform.darwin_x64, mode: BuildMode.debug), + const Source.artifact(Artifact.isolateSnapshotData, platform: TargetPlatform.darwin, mode: BuildMode.debug), + const Source.artifact(Artifact.vmSnapshotData, platform: TargetPlatform.darwin, mode: BuildMode.debug), ]; @override diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index 2f4ac87eba..f1a1690727 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -37,7 +37,7 @@ class BuildBundleCommand extends BuildSubCommand { 'android-x86', 'android-x64', 'ios', - 'darwin-x64', + 'darwin', 'linux-x64', 'linux-arm64', 'windows-x64', @@ -90,7 +90,7 @@ class BuildBundleCommand extends BuildSubCommand { } // Check for target platforms that are only allowed via feature flags. switch (platform) { - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: if (!featureFlags.isMacOSEnabled) { throwToolExit('macOS is not a supported target platform.'); } diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart index df7836769e..65beb69bfb 100644 --- a/packages/flutter_tools/lib/src/flutter_application_package.dart +++ b/packages/flutter_tools/lib/src/flutter_application_package.dart @@ -84,7 +84,7 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { : IOSApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.tester: return FlutterTesterApp.fromCurrentDirectory(globals.fs); - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: return applicationBinary == null ? MacOSApp.fromMacOSProject(FlutterProject.current().macos) : MacOSApp.fromPrebuiltApp(applicationBinary); diff --git a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart index da9ae373e2..bd5499bcc8 100644 --- a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart +++ b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart @@ -18,7 +18,7 @@ String flutterMacOSFrameworkDir(BuildMode mode, FileSystem fileSystem, Artifacts artifacts) { final String flutterMacOSFramework = artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin_x64, + platform: TargetPlatform.darwin, mode: mode, ); return fileSystem.path diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index 175255fc50..19715a2fed 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -50,18 +50,16 @@ class MacOSDevice extends DesktopDevice { @override String get name => 'macOS'; - /// Returns [TargetPlatform.darwin_x64] even on macOS ARM devices. - /// - /// Build system, artifacts rely on Rosetta to translate to x86_64 on ARM. @override - Future get targetPlatform async => TargetPlatform.darwin_x64; + Future get targetPlatform async => TargetPlatform.darwin; @override Future get targetPlatformDisplayName async { if (_operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm) { return 'darwin-arm64'; + } else { + return 'darwin-x64'; } - return super.targetPlatformDisplayName; } @override diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index ab94384193..f2c7997fc6 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1447,7 +1447,7 @@ DevelopmentArtifact artifactFromTargetPlatform(TargetPlatform targetPlatform) { return DevelopmentArtifact.web; case TargetPlatform.ios: return DevelopmentArtifact.iOS; - case TargetPlatform.darwin_x64: + case TargetPlatform.darwin: if (featureFlags.isMacOSEnabled) { return DevelopmentArtifact.macOS; } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart index 49f8ed325b..05d1ad69b8 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart @@ -76,7 +76,7 @@ void main() { final AssembleCommand command = AssembleCommand( buildSystem: TestBuildSystem.all(BuildResult(success: true))); final CommandRunner commandRunner = createTestCommandRunner(command); - await commandRunner.run(['assemble', '-o Output', '-dTargetPlatform=darwin-x64', 'debug_macos_bundle_flutter_assets']); + await commandRunner.run(['assemble', '-o Output', '-dTargetPlatform=darwin', '-dDarwinArchs=x86_64', 'debug_macos_bundle_flutter_assets']); expect(await command.requiredArtifacts, { DevelopmentArtifact.macOS, diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart index c7949d807f..13d43f245b 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart @@ -139,7 +139,7 @@ void main() { expect(() => runner.run([ 'bundle', '--no-pub', - '--target-platform=darwin-x64', + '--target-platform=darwin', ]), throwsToolExit()); }, overrides: { FileSystem: () => MemoryFileSystem.test(), @@ -193,7 +193,7 @@ void main() { await runner.run([ 'bundle', '--no-pub', - '--target-platform=darwin-x64', + '--target-platform=darwin', ]); }, overrides: { FileSystem: () => MemoryFileSystem.test(), diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart index a3ce055b5a..99c48fb167 100644 --- a/packages/flutter_tools/test/general.shard/artifacts_test.dart +++ b/packages/flutter_tools/test/general.shard/artifacts_test.dart @@ -178,7 +178,7 @@ void main() { 'ios-release', ); expect( - artifacts.getEngineType(TargetPlatform.darwin_x64), + artifacts.getEngineType(TargetPlatform.darwin), 'darwin-x64', ); }); @@ -308,7 +308,7 @@ void main() { 'android_debug_unopt', ); expect( - artifacts.getEngineType(TargetPlatform.darwin_x64), + artifacts.getEngineType(TargetPlatform.darwin), 'android_debug_unopt', ); }); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart index 3b0ca07e41..3690800ded 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart @@ -271,7 +271,7 @@ void main() { '--sdk-root', artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.darwin_x64, + platform: TargetPlatform.darwin, mode: BuildMode.debug, ) + '/', '--target=flutter', @@ -288,7 +288,7 @@ void main() { ]); await const KernelSnapshot().build(androidEnvironment - ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin_x64) + ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin) ..defines[kBuildMode] = getNameForBuildMode(BuildMode.debug) ..defines[kTrackWidgetCreation] = 'false' ); 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 d6490335cf..de13bbd42a 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 @@ -25,55 +25,76 @@ void main() { FileSystem fileSystem; Artifacts artifacts; FakeProcessManager processManager; + File binary; + BufferLogger logger; + FakeCommand copyFrameworkCommand; + FakeCommand lipoInfoNonFatCommand; + FakeCommand lipoInfoFatCommand; + FakeCommand lipoVerifyX86_64Command; setUp(() { - processManager = FakeProcessManager.any(); + processManager = FakeProcessManager.empty(); artifacts = Artifacts.test(); fileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); environment = Environment.test( fileSystem.currentDirectory, defines: { kBuildMode: 'debug', - kTargetPlatform: 'darwin-x64', + kTargetPlatform: 'darwin', + kDarwinArchs: 'x86_64', }, inputs: {}, artifacts: artifacts, processManager: processManager, - logger: BufferLogger.test(), + logger: logger, fileSystem: fileSystem, engineVersion: '2' ); + + binary = environment.outputDir + .childDirectory('FlutterMacOS.framework') + .childFile('FlutterMacOS'); + + copyFrameworkCommand = FakeCommand( + command: [ + 'rsync', + '-av', + '--delete', + '--filter', + '- .DS_Store/', + 'Artifact.flutterMacOSFramework.debug', + environment.outputDir.path, + ], + ); + + lipoInfoNonFatCommand = FakeCommand(command: [ + 'lipo', + '-info', + binary.path, + ], stdout: 'Non-fat file:'); + + lipoInfoFatCommand = FakeCommand(command: [ + 'lipo', + '-info', + binary.path, + ], stdout: 'Architectures in the fat file:'); + + lipoVerifyX86_64Command = FakeCommand(command: [ + 'lipo', + binary.path, + '-verify_arch', + 'x86_64', + ]); }); testUsingContext('Copies files to correct cache directory', () async { - final Directory outputDir = fileSystem.directory('output'); - final FakeProcessManager processManager = FakeProcessManager.list([ - FakeCommand( - command: [ - 'rsync', - '-av', - '--delete', - '--filter', - '- .DS_Store/', - 'Artifact.flutterMacOSFramework.debug', - outputDir.path, - ], - ), + binary.createSync(recursive: true); + processManager.addCommands([ + copyFrameworkCommand, + lipoInfoNonFatCommand, + lipoVerifyX86_64Command, ]); - environment = Environment.test( - fileSystem.currentDirectory, - defines: { - kBuildMode: 'debug', - kTargetPlatform: 'darwin-x64', - }, - inputs: {}, - artifacts: artifacts, - processManager: processManager, - logger: BufferLogger.test(), - fileSystem: fileSystem, - engineVersion: '2', - outputDir: outputDir, - ); await const DebugUnpackMacOS().build(environment); @@ -83,6 +104,81 @@ void main() { ProcessManager: () => processManager, }); + testUsingContext('thinning fails when framework missing', () async { + processManager.addCommand(copyFrameworkCommand); + await expectLater( + const DebugUnpackMacOS().build(environment), + throwsA(isA().having( + (Exception exception) => exception.toString(), + 'description', + contains('FlutterMacOS.framework/FlutterMacOS does not exist, cannot thin'), + ))); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); + + testUsingContext('lipo fails when arch missing from framework', () async { + environment.defines[kDarwinArchs] = 'arm64 x86_64'; + binary.createSync(recursive: true); + processManager.addCommands([ + copyFrameworkCommand, + lipoInfoFatCommand, + FakeCommand(command: [ + 'lipo', + binary.path, + '-verify_arch', + 'arm64', + 'x86_64', + ], exitCode: 1), + ]); + + await expectLater( + const DebugUnpackMacOS().build(environment), + throwsA(isA().having( + (Exception exception) => exception.toString(), + 'description', + contains('does not contain arm64 x86_64. Running lipo -info:\nArchitectures in the fat file:'), + ))); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); + + testUsingContext('skips thins framework', () async { + binary.createSync(recursive: true); + processManager.addCommands([ + copyFrameworkCommand, + lipoInfoNonFatCommand, + lipoVerifyX86_64Command, + ]); + + await const DebugUnpackMacOS().build(environment); + + expect(logger.traceText, contains('Skipping lipo for non-fat file /FlutterMacOS.framework/FlutterMacOS')); + }); + + testUsingContext('thins fat framework', () async { + binary.createSync(recursive: true); + processManager.addCommands([ + copyFrameworkCommand, + lipoInfoFatCommand, + lipoVerifyX86_64Command, + FakeCommand(command: [ + 'lipo', + '-output', + binary.path, + '-extract', + 'x86_64', + binary.path, + ]), + ]); + + await const DebugUnpackMacOS().build(environment); + + expect(processManager, hasNoRemainingExpectations); + }); + testUsingContext('debug macOS application fails if App.framework missing', () async { fileSystem.directory( artifacts.getArtifactPath( @@ -113,13 +209,13 @@ void main() { fileSystem.file( artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: TargetPlatform.darwin_x64, + platform: TargetPlatform.darwin, mode: BuildMode.debug, )).createSync(recursive: true); fileSystem.file( artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: TargetPlatform.darwin_x64, + platform: TargetPlatform.darwin, mode: BuildMode.debug, )).createSync(recursive: true); fileSystem.file('${environment.buildDir.path}/App.framework/App') @@ -217,4 +313,131 @@ void main() { FileSystem: () => fileSystem, ProcessManager: () => processManager, }); + + testUsingContext('DebugMacOSFramework creates expected binary with arm64 only arch', () async { + environment.defines[kDarwinArchs] = 'arm64'; + processManager.addCommand( + FakeCommand(command: [ + 'xcrun', + 'clang', + '-x', + 'c', + environment.buildDir.childFile('debug_app.cc').path, + '-arch', + 'arm64', + '-dynamiclib', + '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', + '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', + '-install_name', '@rpath/App.framework/App', + '-o', + environment.buildDir + .childDirectory('App.framework') + .childFile('App') + .path, + ]), + ); + + await const DebugMacOSFramework().build(environment); + expect(processManager.hasRemainingExpectations, isFalse); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); + + testUsingContext('DebugMacOSFramework creates universal binary', () async { + environment.defines[kDarwinArchs] = 'arm64 x86_64'; + processManager.addCommand( + FakeCommand(command: [ + 'xcrun', + 'clang', + '-x', + 'c', + environment.buildDir.childFile('debug_app.cc').path, + '-arch', + 'arm64', + '-arch', + 'x86_64', + '-dynamiclib', + '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', + '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', + '-install_name', '@rpath/App.framework/App', + '-o', + environment.buildDir + .childDirectory('App.framework') + .childFile('App') + .path, + ]), + ); + + await const DebugMacOSFramework().build(environment); + expect(processManager.hasRemainingExpectations, isFalse); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); + + testUsingContext('CompileMacOSFramework creates universal binary', () async { + environment.defines[kDarwinArchs] = 'arm64 x86_64'; + environment.defines[kBuildMode] = 'release'; + + processManager.addCommands([ + FakeCommand(command: [ + 'Artifact.genSnapshot.TargetPlatform.darwin.release', + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=${environment.buildDir.childFile('arm64/snapshot_assembly.S').path}', + '--strip', + environment.buildDir.childFile('app.dill').path + ]), + FakeCommand(command: [ + 'Artifact.genSnapshot.TargetPlatform.darwin.release', + '--deterministic', + '--snapshot_kind=app-aot-assembly', + '--assembly=${environment.buildDir.childFile('x86_64/snapshot_assembly.S').path}', + '--strip', + environment.buildDir.childFile('app.dill').path + ]), + FakeCommand(command: [ + 'xcrun', 'cc', '-arch', 'arm64', + '-c', environment.buildDir.childFile('arm64/snapshot_assembly.S').path, + '-o', environment.buildDir.childFile('arm64/snapshot_assembly.o').path + ]), + FakeCommand(command: [ + 'xcrun', 'cc', '-arch', 'x86_64', + '-c', environment.buildDir.childFile('x86_64/snapshot_assembly.S').path, + '-o', environment.buildDir.childFile('x86_64/snapshot_assembly.o').path + ]), + FakeCommand(command: [ + 'xcrun', 'clang', '-arch', 'arm64', '-dynamiclib', '-Xlinker', '-rpath', + '-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath', + '-Xlinker', '@loader_path/Frameworks', + '-install_name', '@rpath/App.framework/App', + '-o', environment.buildDir.childFile('arm64/App.framework/App').path, + environment.buildDir.childFile('arm64/snapshot_assembly.o').path + ]), + FakeCommand(command: [ + 'xcrun', 'clang', '-arch', 'x86_64', '-dynamiclib', '-Xlinker', '-rpath', + '-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath', + '-Xlinker', '@loader_path/Frameworks', + '-install_name', '@rpath/App.framework/App', + '-o', environment.buildDir.childFile('x86_64/App.framework/App').path, + environment.buildDir.childFile('x86_64/snapshot_assembly.o').path + ]), + FakeCommand(command: [ + 'lipo', + environment.buildDir.childFile('arm64/App.framework/App').path, + environment.buildDir.childFile('x86_64/App.framework/App').path, + '-create', + '-output', + environment.buildDir.childFile('App.framework/App').path, + ]), + ]); + + await const CompileMacOSFramework().build(environment); + expect(processManager.hasRemainingExpectations, isFalse); + + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); } diff --git a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart index 9dc8da07c7..2eef1fab0d 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart @@ -41,7 +41,7 @@ void main() { ); final MockMacOSApp mockMacOSApp = MockMacOSApp(); - expect(await device.targetPlatform, TargetPlatform.darwin_x64); + expect(await device.targetPlatform, TargetPlatform.darwin); expect(device.name, 'macOS'); expect(await device.installApp(mockMacOSApp), true); expect(await device.uninstallApp(mockMacOSApp), true);