Skip to content

Commit b89524a

Browse files
cbrackenBuchimi
authored andcommitted
[iOS] Copy Flutter.framework.dSYM into app archive (flutter#153215)
As of Xcode 16, App Store validation now requires that apps uploaded to the App store bundle dSYM debug information bundles for each Framework they embed. dSYM bundles are packaged in the Flutter.xcframework shipped in the `ios-release` tools archive as of engine patches: * flutter/engine#54414 * flutter/engine#54458 This copies the Flutter.framework.dSYM bundle from the tools cache to the app archive produced by `flutter build ipa`. Issue: flutter#116493
1 parent 63547b9 commit b89524a

File tree

3 files changed

+159
-12
lines changed

3 files changed

+159
-12
lines changed

packages/flutter_tools/lib/src/artifacts.dart

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enum Artifact {
2222
/// The flutter tester binary.
2323
flutterTester,
2424
flutterFramework,
25+
flutterFrameworkDsym,
2526
flutterXcframework,
2627
/// The framework directory of the macOS desktop.
2728
flutterMacOSFramework,
@@ -181,6 +182,8 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
181182
return 'flutter_tester$exe';
182183
case Artifact.flutterFramework:
183184
return 'Flutter.framework';
185+
case Artifact.flutterFrameworkDsym:
186+
return 'Flutter.framework.dSYM';
184187
case Artifact.flutterXcframework:
185188
return 'Flutter.xcframework';
186189
case Artifact.flutterMacOSFramework:
@@ -623,6 +626,7 @@ class CachedArtifacts implements Artifacts {
623626
case Artifact.frontendServerSnapshotForEngineDartSdk:
624627
case Artifact.constFinder:
625628
case Artifact.flutterFramework:
629+
case Artifact.flutterFrameworkDsym:
626630
case Artifact.flutterMacOSFramework:
627631
case Artifact.flutterMacOSXcframework:
628632
case Artifact.flutterPatchedSdkPath:
@@ -656,7 +660,10 @@ class CachedArtifacts implements Artifacts {
656660
return _fileSystem.path.join(engineDir, artifactFileName);
657661
case Artifact.flutterFramework:
658662
final String engineDir = _getEngineArtifactsPath(platform, mode)!;
659-
return _getIosEngineArtifactPath(engineDir, environmentType, _fileSystem, _platform);
663+
return _getIosFrameworkPath(engineDir, environmentType, _fileSystem, _platform);
664+
case Artifact.flutterFrameworkDsym:
665+
final String engineDir = _getEngineArtifactsPath(platform, mode)!;
666+
return _getIosFrameworkDsymPath(engineDir, environmentType, _fileSystem, _platform);
660667
case Artifact.engineDartSdkPath:
661668
case Artifact.engineDartBinary:
662669
case Artifact.engineDartAotRuntime:
@@ -713,6 +720,7 @@ class CachedArtifacts implements Artifacts {
713720
return _fileSystem.path.join(root, runtime, artifactFileName);
714721
case Artifact.constFinder:
715722
case Artifact.flutterFramework:
723+
case Artifact.flutterFrameworkDsym:
716724
case Artifact.flutterMacOSFramework:
717725
case Artifact.flutterMacOSXcframework:
718726
case Artifact.flutterTester:
@@ -814,6 +822,7 @@ class CachedArtifacts implements Artifacts {
814822
.childFile(_artifactToFileName(artifact, _platform, mode)!)
815823
.path;
816824
case Artifact.flutterFramework:
825+
case Artifact.flutterFrameworkDsym:
817826
case Artifact.flutterXcframework:
818827
case Artifact.fuchsiaFlutterRunner:
819828
case Artifact.fuchsiaKernelCompiler:
@@ -882,37 +891,72 @@ TargetPlatform _currentHostPlatform(Platform platform, OperatingSystemUtils oper
882891
throw UnimplementedError('Host OS not supported.');
883892
}
884893

885-
String _getIosEngineArtifactPath(String engineDirectory,
886-
EnvironmentType? environmentType, FileSystem fileSystem, Platform hostPlatform) {
894+
/// Returns the Flutter.xcframework platform directory for the specified environment type.
895+
///
896+
/// `Flutter.xcframework` contains target environment/architecture-specific
897+
/// subdirectories containing the appropriate `Flutter.framework` and
898+
/// `dSYMs/Flutter.framework.dSYMs` bundles for that target architecture.
899+
Directory _getIosFlutterFrameworkPlatformDirectory(
900+
String engineDirectory,
901+
EnvironmentType? environmentType,
902+
FileSystem fileSystem,
903+
Platform hostPlatform) {
887904
final Directory xcframeworkDirectory = fileSystem
888905
.directory(engineDirectory)
889906
.childDirectory(_artifactToFileName(Artifact.flutterXcframework, hostPlatform)!);
890907

891908
if (!xcframeworkDirectory.existsSync()) {
892909
throwToolExit('No xcframework found at ${xcframeworkDirectory.path}. Try running "flutter precache --ios".');
893910
}
894-
Directory? flutterFrameworkSource;
895-
for (final Directory platformDirectory
896-
in xcframeworkDirectory.listSync().whereType<Directory>()) {
911+
for (final Directory platformDirectory in xcframeworkDirectory.listSync().whereType<Directory>()) {
897912
if (!platformDirectory.basename.startsWith('ios-')) {
898913
continue;
899914
}
900915
// ios-x86_64-simulator, ios-arm64_x86_64-simulator, or ios-arm64.
901916
final bool simulatorDirectory = platformDirectory.basename.endsWith('-simulator');
902917
if ((environmentType == EnvironmentType.simulator && simulatorDirectory) ||
903918
(environmentType == EnvironmentType.physical && !simulatorDirectory)) {
904-
flutterFrameworkSource = platformDirectory;
919+
return platformDirectory;
905920
}
906921
}
907-
if (flutterFrameworkSource == null) {
908-
throwToolExit('No iOS frameworks found in ${xcframeworkDirectory.path}');
909-
}
922+
throwToolExit('No iOS frameworks found in ${xcframeworkDirectory.path}');
923+
}
910924

911-
return flutterFrameworkSource
925+
/// Returns the path to Flutter.framework.
926+
String _getIosFrameworkPath(
927+
String engineDirectory,
928+
EnvironmentType? environmentType,
929+
FileSystem fileSystem,
930+
Platform hostPlatform) {
931+
final Directory platformDir = _getIosFlutterFrameworkPlatformDirectory(
932+
engineDirectory,
933+
environmentType,
934+
fileSystem,
935+
hostPlatform,
936+
);
937+
return platformDir
912938
.childDirectory(_artifactToFileName(Artifact.flutterFramework, hostPlatform)!)
913939
.path;
914940
}
915941

942+
/// Returns the path to Flutter.framework.dSYM.
943+
String _getIosFrameworkDsymPath(
944+
String engineDirectory,
945+
EnvironmentType? environmentType,
946+
FileSystem fileSystem,
947+
Platform hostPlatform) {
948+
final Directory platformDir = _getIosFlutterFrameworkPlatformDirectory(
949+
engineDirectory,
950+
environmentType,
951+
fileSystem,
952+
hostPlatform,
953+
);
954+
return platformDir
955+
.childDirectory('dSYMs')
956+
.childDirectory(_artifactToFileName(Artifact.flutterFrameworkDsym, hostPlatform)!)
957+
.path;
958+
}
959+
916960
String _getMacOSEngineArtifactPath(
917961
String engineDirectory,
918962
FileSystem fileSystem,
@@ -1108,7 +1152,10 @@ class CachedLocalEngineArtifacts implements Artifacts {
11081152
case Artifact.platformLibrariesJson:
11091153
return _fileSystem.path.join(_getFlutterPatchedSdkPath(mode), 'lib', artifactFileName);
11101154
case Artifact.flutterFramework:
1111-
return _getIosEngineArtifactPath(
1155+
return _getIosFrameworkPath(
1156+
localEngineInfo.targetOutPath, environmentType, _fileSystem, _platform);
1157+
case Artifact.flutterFrameworkDsym:
1158+
return _getIosFrameworkDsymPath(
11121159
localEngineInfo.targetOutPath, environmentType, _fileSystem, _platform);
11131160
case Artifact.flutterMacOSFramework:
11141161
return _getMacOSEngineArtifactPath(
@@ -1291,6 +1338,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
12911338
case Artifact.genSnapshot:
12921339
case Artifact.flutterTester:
12931340
case Artifact.flutterFramework:
1341+
case Artifact.flutterFrameworkDsym:
12941342
case Artifact.flutterXcframework:
12951343
case Artifact.flutterMacOSFramework:
12961344
case Artifact.flutterMacOSXcframework:

packages/flutter_tools/lib/src/build_system/targets/ios.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ abstract class UnpackIOS extends Target {
300300
}
301301

302302
Future<void> _copyFramework(Environment environment, String sdkRoot) async {
303+
// Copy Flutter framework.
303304
final EnvironmentType? environmentType = environmentTypeFromSdkroot(sdkRoot, environment.fileSystem);
304305
final String basePath = environment.artifacts.getArtifactPath(
305306
Artifact.flutterFramework,
@@ -324,6 +325,34 @@ abstract class UnpackIOS extends Target {
324325
'${result.stdout}\n---\n${result.stderr}',
325326
);
326327
}
328+
329+
// Copy Flutter framework dSYM (debug symbol) bundle, if present.
330+
final Directory frameworkDsym = environment.fileSystem.directory(
331+
environment.artifacts.getArtifactPath(
332+
Artifact.flutterFrameworkDsym,
333+
platform: TargetPlatform.ios,
334+
mode: buildMode,
335+
environmentType: environmentType,
336+
)
337+
);
338+
if (frameworkDsym.existsSync()) {
339+
final ProcessResult result = await environment.processManager.run(<String>[
340+
'rsync',
341+
'-av',
342+
'--delete',
343+
'--filter',
344+
'- .DS_Store/',
345+
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
346+
frameworkDsym.path,
347+
environment.outputDir.path,
348+
]);
349+
if (result.exitCode != 0) {
350+
throw Exception(
351+
'Failed to copy framework dSYM (exit ${result.exitCode}:\n'
352+
'${result.stdout}\n---\n${result.stderr}',
353+
);
354+
}
355+
}
327356
}
328357

329358
/// Destructively thin Flutter.framework to include only the specified architectures.

packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,8 @@ void main() {
526526
late Directory outputDir;
527527
late File binary;
528528
late FakeCommand copyPhysicalFrameworkCommand;
529+
late FakeCommand copyPhysicalFrameworkDsymCommand;
530+
late FakeCommand copyPhysicalFrameworkDsymCommandFailure;
529531
late FakeCommand lipoCommandNonFatResult;
530532
late FakeCommand lipoVerifyArm64Command;
531533
late FakeCommand xattrCommand;
@@ -535,6 +537,7 @@ void main() {
535537
final FileSystem fileSystem = MemoryFileSystem.test();
536538
outputDir = fileSystem.directory('output');
537539
binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter');
540+
538541
copyPhysicalFrameworkCommand = FakeCommand(command: <String>[
539542
'rsync',
540543
'-av',
@@ -546,6 +549,28 @@ void main() {
546549
outputDir.path,
547550
]);
548551

552+
copyPhysicalFrameworkDsymCommand = FakeCommand(command: <String>[
553+
'rsync',
554+
'-av',
555+
'--delete',
556+
'--filter',
557+
'- .DS_Store/',
558+
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
559+
'Artifact.flutterFrameworkDsym.TargetPlatform.ios.debug.EnvironmentType.physical',
560+
outputDir.path,
561+
]);
562+
563+
copyPhysicalFrameworkDsymCommandFailure = FakeCommand(command: <String>[
564+
'rsync',
565+
'-av',
566+
'--delete',
567+
'--filter',
568+
'- .DS_Store/',
569+
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
570+
'Artifact.flutterFrameworkDsym.TargetPlatform.ios.debug.EnvironmentType.physical',
571+
outputDir.path,
572+
], exitCode: 1);
573+
549574
lipoCommandNonFatResult = FakeCommand(command: <String>[
550575
'lipo',
551576
'-info',
@@ -643,6 +668,42 @@ void main() {
643668
)));
644669
});
645670

671+
testWithoutContext('fails when framework dSYM copy fails', () async {
672+
binary.createSync(recursive: true);
673+
final Directory dSYM = fileSystem.directory(
674+
artifacts.getArtifactPath(Artifact.flutterFrameworkDsym,
675+
platform: TargetPlatform.ios,
676+
mode: BuildMode.debug,
677+
environmentType: EnvironmentType.physical,
678+
),
679+
);
680+
dSYM.createSync(recursive: true);
681+
682+
final Environment environment = Environment.test(
683+
fileSystem.currentDirectory,
684+
processManager: processManager,
685+
artifacts: artifacts,
686+
logger: logger,
687+
fileSystem: fileSystem,
688+
outputDir: outputDir,
689+
defines: <String, String>{
690+
kIosArchs: 'arm64',
691+
kSdkRoot: 'path/to/iPhoneOS.sdk',
692+
},
693+
);
694+
processManager.addCommands(<FakeCommand>[
695+
copyPhysicalFrameworkCommand,
696+
copyPhysicalFrameworkDsymCommandFailure,
697+
]);
698+
await expectLater(
699+
const DebugUnpackIOS().build(environment),
700+
throwsA(isException.having(
701+
(Exception exception) => exception.toString(),
702+
'description',
703+
contains('Failed to copy framework dSYM'),
704+
)));
705+
});
706+
646707
testWithoutContext('fails when requested archs missing from framework', () async {
647708
binary.createSync(recursive: true);
648709

@@ -894,6 +955,14 @@ void main() {
894955

895956
testWithoutContext('codesigns framework', () async {
896957
binary.createSync(recursive: true);
958+
final Directory dSYM = fileSystem.directory(
959+
artifacts.getArtifactPath(Artifact.flutterFrameworkDsym,
960+
platform: TargetPlatform.ios,
961+
mode: BuildMode.debug,
962+
environmentType: EnvironmentType.physical,
963+
),
964+
);
965+
dSYM.createSync(recursive: true);
897966

898967
final Environment environment = Environment.test(
899968
fileSystem.currentDirectory,
@@ -911,6 +980,7 @@ void main() {
911980

912981
processManager.addCommands(<FakeCommand>[
913982
copyPhysicalFrameworkCommand,
983+
copyPhysicalFrameworkDsymCommand,
914984
lipoCommandNonFatResult,
915985
lipoVerifyArm64Command,
916986
xattrCommand,

0 commit comments

Comments
 (0)