Skip to content

Commit

Permalink
macOS: Copy macOS framwork dSYM into build outputs (flutter#153975)
Browse files Browse the repository at this point in the history
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 FlutterMacOS.xcframework shipped in the `darwin-x64-release` tools archive as of engine patches:
* flutter/engine#54696

This copies the FlutterMacOS.framework.dSYM bundle from the tools cache to the build outputs produced by `flutter build macOS`.

Fixes: flutter#153879
  • Loading branch information
cbracken authored and Buchimi committed Sep 2, 2024
1 parent 50d69db commit d418798
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 70 deletions.
106 changes: 90 additions & 16 deletions packages/flutter_tools/lib/src/artifacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum Artifact {
flutterXcframework,
/// The framework directory of the macOS desktop.
flutterMacOSFramework,
flutterMacOSFrameworkDsym,
flutterMacOSXcframework,
vmSnapshotData,
isolateSnapshotData,
Expand Down Expand Up @@ -182,12 +183,14 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
return 'flutter_tester$exe';
case Artifact.flutterFramework:
return 'Flutter.framework';
case Artifact.flutterFrameworkDsym:
return 'Flutter.framework.dSYM';
case Artifact.flutterFrameworkDsym:
return 'Flutter.framework.dSYM';
case Artifact.flutterXcframework:
return 'Flutter.xcframework';
case Artifact.flutterMacOSFramework:
return 'FlutterMacOS.framework';
case Artifact.flutterMacOSFrameworkDsym:
return 'FlutterMacOS.framework.dSYM';
case Artifact.flutterMacOSXcframework:
return 'FlutterMacOS.xcframework';
case Artifact.vmSnapshotData:
Expand Down Expand Up @@ -600,15 +603,44 @@ class CachedArtifacts implements Artifacts {
String _getDesktopArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode? mode) {
// When platform is null, a generic host platform artifact is being requested
// and not the gen_snapshot for darwin as a target platform.
if (artifact == Artifact.genSnapshot) {
final String engineDir = _getEngineArtifactsPath(platform, mode)!;
return _fileSystem.path.join(engineDir, _artifactToFileName(artifact, _platform));
}
if (artifact == Artifact.flutterMacOSFramework) {
final String engineDir = _getEngineArtifactsPath(platform, mode)!;
return _getMacOSEngineArtifactPath(engineDir, _fileSystem, _platform);
final String engineDir = _getEngineArtifactsPath(platform, mode)!;
switch (artifact) {
case Artifact.genSnapshot:
return _fileSystem.path.join(engineDir, _artifactToFileName(artifact, _platform));
case Artifact.engineDartSdkPath:
case Artifact.engineDartBinary:
case Artifact.engineDartAotRuntime:
case Artifact.dart2jsSnapshot:
case Artifact.dart2wasmSnapshot:
case Artifact.frontendServerSnapshotForEngineDartSdk:
case Artifact.constFinder:
case Artifact.flutterFramework:
case Artifact.flutterFrameworkDsym:
case Artifact.flutterMacOSFramework:
return _getMacOSFrameworkPath(engineDir, _fileSystem, _platform);
case Artifact.flutterMacOSFrameworkDsym:
return _getMacOSFrameworkDsymPath(engineDir, _fileSystem, _platform);
case Artifact.flutterMacOSXcframework:
case Artifact.flutterPatchedSdkPath:
case Artifact.flutterTester:
case Artifact.flutterXcframework:
case Artifact.fontSubset:
case Artifact.fuchsiaFlutterRunner:
case Artifact.fuchsiaKernelCompiler:
case Artifact.icuData:
case Artifact.isolateSnapshotData:
case Artifact.linuxDesktopPath:
case Artifact.linuxHeaders:
case Artifact.platformKernelDill:
case Artifact.platformLibrariesJson:
case Artifact.skyEnginePath:
case Artifact.vmSnapshotData:
case Artifact.windowsCppClientWrapper:
case Artifact.windowsDesktopPath:
case Artifact.flutterToolsFileGenerators:
case Artifact.flutterPreviewDevice:
return _getHostArtifactPath(artifact, platform, mode);
}
return _getHostArtifactPath(artifact, platform, mode);
}

String _getAndroidArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode mode) {
Expand All @@ -628,6 +660,7 @@ class CachedArtifacts implements Artifacts {
case Artifact.flutterFramework:
case Artifact.flutterFrameworkDsym:
case Artifact.flutterMacOSFramework:
case Artifact.flutterMacOSFrameworkDsym:
case Artifact.flutterMacOSXcframework:
case Artifact.flutterPatchedSdkPath:
case Artifact.flutterTester:
Expand Down Expand Up @@ -672,6 +705,7 @@ class CachedArtifacts implements Artifacts {
case Artifact.frontendServerSnapshotForEngineDartSdk:
case Artifact.constFinder:
case Artifact.flutterMacOSFramework:
case Artifact.flutterMacOSFrameworkDsym:
case Artifact.flutterMacOSXcframework:
case Artifact.flutterPatchedSdkPath:
case Artifact.flutterTester:
Expand Down Expand Up @@ -722,6 +756,7 @@ class CachedArtifacts implements Artifacts {
case Artifact.flutterFramework:
case Artifact.flutterFrameworkDsym:
case Artifact.flutterMacOSFramework:
case Artifact.flutterMacOSFrameworkDsym:
case Artifact.flutterMacOSXcframework:
case Artifact.flutterTester:
case Artifact.flutterXcframework:
Expand Down Expand Up @@ -794,7 +829,14 @@ class CachedArtifacts implements Artifacts {
platformDirName = '$platformDirName-${mode!.cliName}';
}
final String engineArtifactsPath = _cache.getArtifactDirectory('engine').path;
return _getMacOSEngineArtifactPath(_fileSystem.path.join(engineArtifactsPath, platformDirName), _fileSystem, _platform);
return _getMacOSFrameworkPath(_fileSystem.path.join(engineArtifactsPath, platformDirName), _fileSystem, _platform);
case Artifact.flutterMacOSFrameworkDsym:
String platformDirName = _enginePlatformDirectoryName(platform);
if (mode == BuildMode.profile || mode == BuildMode.release) {
platformDirName = '$platformDirName-${mode!.cliName}';
}
final String engineArtifactsPath = _cache.getArtifactDirectory('engine').path;
return _getMacOSFrameworkDsymPath(_fileSystem.path.join(engineArtifactsPath, platformDirName), _fileSystem, _platform);
case Artifact.flutterMacOSXcframework:
case Artifact.linuxDesktopPath:
case Artifact.windowsDesktopPath:
Expand Down Expand Up @@ -957,7 +999,13 @@ String _getIosFrameworkDsymPath(
.path;
}

String _getMacOSEngineArtifactPath(
/// Returns the Flutter.xcframework platform directory for the specified environment type.
///
/// `FlutterMacOS.xcframework` contains target environment/architecture-specific
/// subdirectories containing the appropriate `FlutterMacOS.framework` and
/// `FlutterMacOS.framework.dSYM` bundles for that target architecture. At present,
/// there is only one such directory: `macos-arm64_x86_64`.
Directory _getMacOSFrameworkPlatformDirectory(
String engineDirectory,
FileSystem fileSystem,
Platform hostPlatform,
Expand All @@ -969,21 +1017,43 @@ String _getMacOSEngineArtifactPath(
if (!xcframeworkDirectory.existsSync()) {
throwToolExit('No xcframework found at ${xcframeworkDirectory.path}. Try running "flutter precache --macos".');
}
final Directory? flutterFrameworkSource = xcframeworkDirectory
final Directory? platformDirectory = xcframeworkDirectory
.listSync()
.whereType<Directory>()
.where((Directory platformDirectory) =>
platformDirectory.basename.startsWith('macos-'))
.firstOrNull;
if (flutterFrameworkSource == null) {
if (platformDirectory == null) {
throwToolExit('No macOS frameworks found in ${xcframeworkDirectory.path}');
}
return platformDirectory;
}

return flutterFrameworkSource
/// Returns the path to `FlutterMacOS.framework`.
String _getMacOSFrameworkPath(
String engineDirectory,
FileSystem fileSystem,
Platform hostPlatform,
) {
final Directory platformDirectory = _getMacOSFrameworkPlatformDirectory(engineDirectory, fileSystem, hostPlatform);
return platformDirectory
.childDirectory(_artifactToFileName(Artifact.flutterMacOSFramework, hostPlatform)!)
.path;
}

/// Returns the path to `FlutterMacOS.framework`.
String _getMacOSFrameworkDsymPath(
String engineDirectory,
FileSystem fileSystem,
Platform hostPlatform,
) {
final Directory platformDirectory = _getMacOSFrameworkPlatformDirectory(engineDirectory, fileSystem, hostPlatform);
return platformDirectory
.childDirectory('dSYMs')
.childDirectory(_artifactToFileName(Artifact.flutterMacOSFrameworkDsym, hostPlatform)!)
.path;
}

/// Manages the artifacts of a locally built engine.
class CachedLocalEngineArtifacts implements Artifacts {
CachedLocalEngineArtifacts(
Expand Down Expand Up @@ -1158,7 +1228,10 @@ class CachedLocalEngineArtifacts implements Artifacts {
return _getIosFrameworkDsymPath(
localEngineInfo.targetOutPath, environmentType, _fileSystem, _platform);
case Artifact.flutterMacOSFramework:
return _getMacOSEngineArtifactPath(
return _getMacOSFrameworkPath(
localEngineInfo.targetOutPath, _fileSystem, _platform);
case Artifact.flutterMacOSFrameworkDsym:
return _getMacOSFrameworkDsymPath(
localEngineInfo.targetOutPath, _fileSystem, _platform);
case Artifact.flutterPatchedSdkPath:
// When using local engine always use [BuildMode.debug] regardless of
Expand Down Expand Up @@ -1341,6 +1414,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
case Artifact.flutterFrameworkDsym:
case Artifact.flutterXcframework:
case Artifact.flutterMacOSFramework:
case Artifact.flutterMacOSFrameworkDsym:
case Artifact.flutterMacOSXcframework:
case Artifact.vmSnapshotData:
case Artifact.isolateSnapshotData:
Expand Down
51 changes: 47 additions & 4 deletions packages/flutter_tools/lib/src/build_system/targets/macos.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ abstract class UnpackMacOS extends Target {
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, 'unpack_macos');
}

// Copy Flutter framework.
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final String basePath = environment.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode);

final ProcessResult result = environment.processManager.runSync(<String>[
'rsync',
'-av',
Expand Down Expand Up @@ -82,6 +83,7 @@ abstract class UnpackMacOS extends Target {
if (!frameworkBinary.existsSync()) {
throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
}

await _thinFramework(environment, frameworkBinaryPath);
}

Expand Down Expand Up @@ -156,10 +158,51 @@ class ReleaseUnpackMacOS extends UnpackMacOS {
String get name => 'release_unpack_macos';

@override
List<Source> get inputs => <Source>[
...super.inputs,
const Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.release),
List<Source> get outputs => super.outputs + const <Source>[
Source.pattern('{OUTPUT_DIR}/FlutterMacOS.framework.dSYM/Contents/Resources/DWARF/FlutterMacOS'),
];

@override
List<Source> get inputs => super.inputs + const <Source>[
Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.release),
];

@override
Future<void> build(Environment environment) async {
await super.build(environment);

// Copy Flutter framework dSYM (debug symbol) bundle, if present.
final String? buildModeEnvironment = environment.defines[kBuildMode];
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, 'unpack_macos');
}
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final Directory frameworkDsym = environment.fileSystem.directory(
environment.artifacts.getArtifactPath(
Artifact.flutterMacOSFrameworkDsym,
platform: TargetPlatform.darwin,
mode: buildMode,
)
);
if (frameworkDsym.existsSync()) {
final ProcessResult result = await environment.processManager.run(<String>[
'rsync',
'-av',
'--delete',
'--filter',
'- .DS_Store/',
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
frameworkDsym.path,
environment.outputDir.path,
]);
if (result.exitCode != 0) {
throw Exception(
'Failed to copy framework dSYM (exit ${result.exitCode}:\n'
'${result.stdout}\n---\n${result.stderr}',
);
}
}
}
}

/// Unpack the profile prebuilt engine framework.
Expand Down
Loading

0 comments on commit d418798

Please sign in to comment.