Skip to content

Commit

Permalink
Feat: dSYM debug info for iOS & macOS builds (#101586)
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind committed Sep 5, 2022
1 parent 96345a4 commit 723b82e
Show file tree
Hide file tree
Showing 15 changed files with 602 additions and 82 deletions.
79 changes: 79 additions & 0 deletions dev/devicelab/bin/tasks/build_ios_framework_module_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
'vm_snapshot_data',
));

final String appFrameworkDsymPath = path.join(
outputPath,
mode,
'App.xcframework',
'ios-arm64',
'dSYMs',
'App.framework.dSYM'
);
checkDirectoryExists(appFrameworkDsymPath);
await _checkDsym(path.join(
appFrameworkDsymPath,
'Contents',
'Resources',
'DWARF',
'App',
));

checkFileExists(path.join(
outputPath,
mode,
Expand Down Expand Up @@ -404,6 +421,25 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
'App',
));

if (mode != 'Debug') {
final String appFrameworkDsymPath = path.join(
cocoapodsOutputPath,
mode,
'App.xcframework',
'ios-arm64',
'dSYMs',
'App.framework.dSYM'
);
checkDirectoryExists(appFrameworkDsymPath);
await _checkDsym(path.join(
appFrameworkDsymPath,
'Contents',
'Resources',
'DWARF',
'App',
));
}

if (Directory(path.join(
cocoapodsOutputPath,
mode,
Expand Down Expand Up @@ -582,6 +618,23 @@ Future<void> _testBuildMacOSFramework(Directory projectDir) async {
'Resources',
'Info.plist',
));

final String appFrameworkDsymPath = path.join(
outputPath,
mode,
'App.xcframework',
'macos-arm64_x86_64',
'dSYMs',
'App.framework.dSYM'
);
checkDirectoryExists(appFrameworkDsymPath);
await _checkDsym(path.join(
appFrameworkDsymPath,
'Contents',
'Resources',
'DWARF',
'App',
));
}

section("Check all modes' engine dylib");
Expand Down Expand Up @@ -712,6 +765,25 @@ Future<void> _testBuildMacOSFramework(Directory projectDir) async {
'App',
));

if (mode != 'Debug') {
final String appFrameworkDsymPath = path.join(
cocoapodsOutputPath,
mode,
'App.xcframework',
'macos-arm64_x86_64',
'dSYMs',
'App.framework.dSYM'
);
checkDirectoryExists(appFrameworkDsymPath);
await _checkDsym(path.join(
appFrameworkDsymPath,
'Contents',
'Resources',
'DWARF',
'App',
));
}

await _checkStatic(path.join(
cocoapodsOutputPath,
mode,
Expand Down Expand Up @@ -750,6 +822,13 @@ Future<void> _checkDylib(String pathToLibrary) async {
}
}

Future<void> _checkDsym(String pathToSymbolFile) async {
final String binaryFileType = await fileType(pathToSymbolFile);
if (!binaryFileType.contains('dSYM companion file')) {
throw TaskResult.failure('$pathToSymbolFile is not a dSYM, found: $binaryFileType');
}
}

Future<void> _checkStatic(String pathToLibrary) async {
final String binaryFileType = await fileType(pathToLibrary);
if (!binaryFileType.contains('current ar archive random library')) {
Expand Down
10 changes: 10 additions & 0 deletions dev/devicelab/bin/tasks/module_test_ios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,16 @@ end
'Frameworks',
'url_launcher_ios.framework',
));

checkFileExists(path.join(
'${objectiveCBuildArchiveDirectory.path}.xcarchive',
'dSYMs',
'App.framework.dSYM',
'Contents',
'Resources',
'DWARF',
'App'
));
});

section('Run platform unit tests');
Expand Down
71 changes: 57 additions & 14 deletions packages/flutter_tools/lib/src/base/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,17 @@ class AOTSnapshotter {
'--deterministic',
];

final bool targetingApplePlatform =
platform == TargetPlatform.ios || platform == TargetPlatform.darwin;
_logger.printTrace('targetingApplePlatform = $targetingApplePlatform');

final bool extractAppleDebugSymbols =
buildMode == BuildMode.profile || buildMode == BuildMode.release;
_logger.printTrace('extractAppleDebugSymbols = $extractAppleDebugSymbols');

// We strip snapshot by default, but allow to suppress this behavior
// by supplying --no-strip in extraGenSnapshotOptions.
bool shouldStrip = true;

if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
_logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
for (final String option in extraGenSnapshotOptions) {
Expand All @@ -168,8 +175,20 @@ class AOTSnapshotter {
]);
}

if (shouldStrip) {
genSnapshotArgs.add('--strip');
// When buiding for iOS and splitting out debug info, we want to strip
// manually after the dSYM export, instead of in the `gen_snapshot`.
final bool stripAfterBuild;
if (targetingApplePlatform) {
stripAfterBuild = shouldStrip;
if (stripAfterBuild) {
_logger.printTrace('Will strip AOT snapshot manual after build and dSYM generation.');
}
} else {
stripAfterBuild = false;
if (shouldStrip) {
genSnapshotArgs.add('--strip');
_logger.printTrace('Will strip AOT snapshot during build.');
}
}

if (platform == TargetPlatform.android_arm) {
Expand Down Expand Up @@ -218,33 +237,35 @@ 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) {
final RunResult result = await _buildFramework(
if (targetingApplePlatform) {
return _buildFramework(
appleArch: darwinArch!,
isIOS: platform == TargetPlatform.ios,
sdkRoot: sdkRoot,
assemblyPath: assembly,
outputPath: outputDir.path,
bitcode: bitcode,
quiet: quiet,
stripAfterBuild: stripAfterBuild,
extractAppleDebugSymbols: extractAppleDebugSymbols
);
if (result.exitCode != 0) {
return result.exitCode;
}
} else {
return 0;
}
return 0;
}

/// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly
/// source at [assemblyPath].
Future<RunResult> _buildFramework({
Future<int> _buildFramework({
required DarwinArch appleArch,
required bool isIOS,
String? sdkRoot,
required String assemblyPath,
required String outputPath,
required bool bitcode,
required bool quiet
required bool quiet,
required bool stripAfterBuild,
required bool extractAppleDebugSymbols
}) async {
final String targetArch = getNameForDarwinArch(appleArch);
if (!quiet) {
Expand Down Expand Up @@ -278,7 +299,7 @@ class AOTSnapshotter {
]);
if (compileResult.exitCode != 0) {
_logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
return compileResult;
return compileResult.exitCode;
}

final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework');
Expand All @@ -294,11 +315,33 @@ class AOTSnapshotter {
'-o', appLib,
assemblyO,
];

final RunResult linkResult = await _xcode.clang(linkArgs);
if (linkResult.exitCode != 0) {
_logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
_logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${linkResult.exitCode}');
return linkResult.exitCode;
}

if (extractAppleDebugSymbols) {
final RunResult dsymResult = await _xcode.dsymutil(<String>['-o', '$frameworkDir.dSYM', appLib]);
if (dsymResult.exitCode != 0) {
_logger.printError('Failed to generate dSYM - dsymutil terminated with exit code ${dsymResult.exitCode}');
return dsymResult.exitCode;
}

if (stripAfterBuild) {
// See https://www.unix.com/man-page/osx/1/strip/ for arguments
final RunResult stripResult = await _xcode.strip(<String>['-S', appLib, '-o', appLib]);
if (stripResult.exitCode != 0) {
_logger.printError('Failed to strip debugging symbols from the generated AOT snapshot - strip terminated with exit code ${stripResult.exitCode}');
return stripResult.exitCode;
}
}
} else {
assert(stripAfterBuild == false);
}
return linkResult;

return 0;
}

bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
Expand Down
46 changes: 46 additions & 0 deletions packages/flutter_tools/lib/src/build_system/targets/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:package_config/package_config.dart';
import '../../artifacts.dart';
import '../../base/build.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../build_info.dart';
import '../../compile.dart';
import '../../dart/package_map.dart';
Expand Down Expand Up @@ -394,3 +395,48 @@ abstract class CopyFlutterAotBundle extends Target {
environment.buildDir.childFile('app.so').copySync(outputFile.path);
}
}

/// Lipo CLI tool wrapper shared by iOS and macOS builds.
class Lipo {
/// Static only.
Lipo._();

/// Create a "fat" binary by combining multiple architecture-specific ones.
/// `skipMissingInputs` can be changed to `true` to first check whether
/// the expected input paths exist and ignore the command if they don't.
/// Otherwise, `lipo` would fail if the given paths didn't exist.
static Future<void> create(
Environment environment,
List<DarwinArch> darwinArchs, {
required String relativePath,
required String inputDir,
bool skipMissingInputs = false,
}) async {

final String resultPath = environment.fileSystem.path.join(environment.buildDir.path, relativePath);
environment.fileSystem.directory(resultPath).parent.createSync(recursive: true);

Iterable<String> inputPaths = darwinArchs.map(
(DarwinArch iosArch) => environment.fileSystem.path.join(inputDir, getNameForDarwinArch(iosArch), relativePath)
);
if (skipMissingInputs) {
inputPaths = inputPaths.where(environment.fileSystem.isFileSync);
if (inputPaths.isEmpty) {
return;
}
}

final List<String> lipoArgs = <String>[
'lipo',
...inputPaths,
'-create',
'-output',
resultPath,
];

final ProcessResult result = await environment.processManager.run(lipoArgs);
if (result.exitCode != 0) {
throw Exception('lipo exited with code ${result.exitCode}.\n${result.stderr}');
}
}
}
Loading

0 comments on commit 723b82e

Please sign in to comment.