Feat: dSYM debug info for iOS & macOS builds (#101586)
This commit is contained in:
parent
96345a4bb9
commit
723b82e4f0
@ -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,
|
||||
@ -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,
|
||||
@ -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");
|
||||
@ -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,
|
||||
@ -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')) {
|
||||
|
@ -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');
|
||||
|
@ -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) {
|
||||
@ -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) {
|
||||
@ -218,8 +237,8 @@ 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,
|
||||
@ -227,24 +246,26 @@ class AOTSnapshotter {
|
||||
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) {
|
||||
@ -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');
|
||||
@ -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;
|
||||
}
|
||||
return linkResult;
|
||||
|
||||
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 0;
|
||||
}
|
||||
|
||||
bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
|
||||
|
@ -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';
|
||||
@ -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}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,19 +112,24 @@ abstract class AotAssemblyBase extends Target {
|
||||
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(<String>[
|
||||
'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}');
|
||||
}
|
||||
|
||||
// Combine the app lib into a fat framework.
|
||||
await Lipo.create(
|
||||
environment,
|
||||
darwinArchs,
|
||||
relativePath: 'App.framework/App',
|
||||
inputDir: buildOutputPath,
|
||||
);
|
||||
|
||||
// And combine the dSYM for each architecture too, if it was created.
|
||||
await Lipo.create(
|
||||
environment,
|
||||
darwinArchs,
|
||||
relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App',
|
||||
inputDir: buildOutputPath,
|
||||
// Don't fail if the dSYM wasn't created (i.e. during a debug build).
|
||||
skipMissingInputs: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,6 +494,26 @@ abstract class IosAssetBundle extends Target {
|
||||
.copySync(frameworkBinaryPath);
|
||||
}
|
||||
|
||||
// Copy the dSYM
|
||||
if (environment.buildDir.childDirectory('App.framework.dSYM').existsSync()) {
|
||||
final File dsymOutputBinary = environment
|
||||
.outputDir
|
||||
.childDirectory('App.framework.dSYM')
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF')
|
||||
.childFile('App');
|
||||
dsymOutputBinary.parent.createSync(recursive: true);
|
||||
environment
|
||||
.buildDir
|
||||
.childDirectory('App.framework.dSYM')
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF')
|
||||
.childFile('App')
|
||||
.copySync(dsymOutputBinary.path);
|
||||
}
|
||||
|
||||
// Copy the assets.
|
||||
final Depfile assetDepfile = await copyAssets(
|
||||
environment,
|
||||
@ -547,8 +572,25 @@ class DebugIosApplicationBundle extends IosAssetBundle {
|
||||
];
|
||||
}
|
||||
|
||||
/// IosAssetBundle with debug symbols, used for Profile and Release builds.
|
||||
abstract class _IosAssetBundleWithDSYM extends IosAssetBundle {
|
||||
const _IosAssetBundleWithDSYM();
|
||||
|
||||
@override
|
||||
List<Source> get inputs => <Source>[
|
||||
...super.inputs,
|
||||
const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => <Source>[
|
||||
...super.outputs,
|
||||
const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
}
|
||||
|
||||
/// Build a profile iOS application bundle.
|
||||
class ProfileIosApplicationBundle extends IosAssetBundle {
|
||||
class ProfileIosApplicationBundle extends _IosAssetBundleWithDSYM {
|
||||
const ProfileIosApplicationBundle();
|
||||
|
||||
@override
|
||||
@ -561,7 +603,7 @@ class ProfileIosApplicationBundle extends IosAssetBundle {
|
||||
}
|
||||
|
||||
/// Build a release iOS application bundle.
|
||||
class ReleaseIosApplicationBundle extends IosAssetBundle {
|
||||
class ReleaseIosApplicationBundle extends _IosAssetBundleWithDSYM {
|
||||
const ReleaseIosApplicationBundle();
|
||||
|
||||
@override
|
||||
|
@ -302,19 +302,23 @@ class CompileMacOSFramework extends Target {
|
||||
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(<String>[
|
||||
'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}');
|
||||
}
|
||||
// Combine the app lib into a fat framework.
|
||||
await Lipo.create(
|
||||
environment,
|
||||
darwinArchs,
|
||||
relativePath: 'App.framework/App',
|
||||
inputDir: buildOutputPath,
|
||||
);
|
||||
|
||||
// And combine the dSYM for each architecture too, if it was created.
|
||||
await Lipo.create(
|
||||
environment,
|
||||
darwinArchs,
|
||||
relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App',
|
||||
inputDir: buildOutputPath,
|
||||
// Don't fail if the dSYM wasn't created (i.e. during a debug build).
|
||||
skipMissingInputs: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -332,6 +336,7 @@ class CompileMacOSFramework extends Target {
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
}
|
||||
|
||||
@ -382,6 +387,26 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
.childFile('App')
|
||||
.copySync(outputDirectory.childFile('App').path);
|
||||
|
||||
// Copy the dSYM
|
||||
if (environment.buildDir.childDirectory('App.framework.dSYM').existsSync()) {
|
||||
final File dsymOutputBinary = environment
|
||||
.outputDir
|
||||
.childDirectory('App.framework.dSYM')
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF')
|
||||
.childFile('App');
|
||||
dsymOutputBinary.parent.createSync(recursive: true);
|
||||
environment
|
||||
.buildDir
|
||||
.childDirectory('App.framework.dSYM')
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF')
|
||||
.childFile('App')
|
||||
.copySync(dsymOutputBinary.path);
|
||||
}
|
||||
|
||||
// Copy assets into asset directory.
|
||||
final Directory assetDirectory = outputDirectory
|
||||
.childDirectory('Resources')
|
||||
@ -530,6 +555,18 @@ class ProfileMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
|
||||
CompileMacOSFramework(),
|
||||
ProfileUnpackMacOS(),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get inputs => <Source>[
|
||||
...super.inputs,
|
||||
const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => <Source>[
|
||||
...super.outputs,
|
||||
const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -546,6 +583,18 @@ class ReleaseMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
|
||||
ReleaseUnpackMacOS(),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get inputs => <Source>[
|
||||
...super.inputs,
|
||||
const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => <Source>[
|
||||
...super.outputs,
|
||||
const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
bool buildSuccess = true;
|
||||
|
@ -68,8 +68,11 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
if (argResults?.wasParsed('debug-info') != true) {
|
||||
throwToolExit('"--debug-info" is required to symbolize stack traces.');
|
||||
}
|
||||
if (!_fileSystem.isFileSync(stringArgDeprecated('debug-info')!)) {
|
||||
throwToolExit('${stringArgDeprecated('debug-info')} does not exist.');
|
||||
final String debugInfoPath = stringArgDeprecated('debug-info')!;
|
||||
if (debugInfoPath.endsWith('.dSYM')
|
||||
? !_fileSystem.isDirectorySync(debugInfoPath)
|
||||
: !_fileSystem.isFileSync(debugInfoPath)) {
|
||||
throwToolExit('$debugInfoPath does not exist.');
|
||||
}
|
||||
if ((argResults?.wasParsed('input') ?? false) && !_fileSystem.isFileSync(stringArgDeprecated('input')!)) {
|
||||
throwToolExit('${stringArgDeprecated('input')} does not exist.');
|
||||
@ -105,7 +108,25 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
input = _stdio.stdin;
|
||||
}
|
||||
|
||||
final Uint8List symbols = _fileSystem.file(stringArgDeprecated('debug-info')).readAsBytesSync();
|
||||
String debugInfoPath = stringArgDeprecated('debug-info')!;
|
||||
|
||||
// If it's a dSYM container, expand the path to the actual DWARF.
|
||||
if (debugInfoPath.endsWith('.dSYM')) {
|
||||
final Directory debugInfoDir = _fileSystem
|
||||
.directory(debugInfoPath)
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF');
|
||||
|
||||
final List<FileSystemEntity> dwarfFiles = debugInfoDir.listSync().whereType<File>().toList();
|
||||
if (dwarfFiles.length == 1) {
|
||||
debugInfoPath = dwarfFiles.first.path;
|
||||
} else {
|
||||
throwToolExit('Expected a single DWARF file in a dSYM container.');
|
||||
}
|
||||
}
|
||||
|
||||
final Uint8List symbols = _fileSystem.file(debugInfoPath).readAsBytesSync();
|
||||
await _dwarfSymbolizationService.decode(
|
||||
input: input,
|
||||
output: output,
|
||||
|
@ -165,16 +165,17 @@ class Xcode {
|
||||
/// See [XcodeProjectInterpreter.xcrunCommand].
|
||||
List<String> xcrunCommand() => _xcodeProjectInterpreter.xcrunCommand();
|
||||
|
||||
Future<RunResult> cc(List<String> args) {
|
||||
return _processUtils.run(
|
||||
<String>[...xcrunCommand(), 'cc', ...args],
|
||||
throwOnError: true,
|
||||
);
|
||||
}
|
||||
Future<RunResult> cc(List<String> args) => _run('cc', args);
|
||||
|
||||
Future<RunResult> clang(List<String> args) {
|
||||
Future<RunResult> clang(List<String> args) => _run('clang', args);
|
||||
|
||||
Future<RunResult> dsymutil(List<String> args) => _run('dsymutil', args);
|
||||
|
||||
Future<RunResult> strip(List<String> args) => _run('strip', args);
|
||||
|
||||
Future<RunResult> _run(String command, List<String> args) {
|
||||
return _processUtils.run(
|
||||
<String>[...xcrunCommand(), 'clang', ...args],
|
||||
<String>[...xcrunCommand(), command, ...args],
|
||||
throwOnError: true,
|
||||
);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ void main() {
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --debug-info file is missing', () async {
|
||||
testUsingContext('symbolize exits when --debug-info dwarf file is missing', () async {
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
@ -83,6 +83,20 @@ void main() {
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --debug-info dSYM is missing', () async {
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=app.dSYM']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'app.dSYM does not exist.'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --input file is missing', () async {
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
|
@ -210,7 +210,6 @@ void main() {
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=$assembly',
|
||||
'--strip',
|
||||
'main.dill',
|
||||
]),
|
||||
kWhichSysctlCommand,
|
||||
@ -253,6 +252,21 @@ void main() {
|
||||
'build/foo/App.framework/App',
|
||||
'build/foo/snapshot_assembly.o',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
'build/foo/App.framework.dSYM',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
'build/foo/App.framework/App',
|
||||
'-o',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
]);
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
@ -285,7 +299,6 @@ void main() {
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=$assembly',
|
||||
'--strip',
|
||||
'--dwarf-stack-traces',
|
||||
'--save-debugging-info=$debugPath',
|
||||
'main.dill',
|
||||
@ -312,6 +325,21 @@ void main() {
|
||||
'arm64',
|
||||
...kDefaultClang,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
'build/foo/App.framework.dSYM',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
'build/foo/App.framework/App',
|
||||
'-o',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
]);
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
@ -344,7 +372,6 @@ void main() {
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=$assembly',
|
||||
'--strip',
|
||||
'--obfuscate',
|
||||
'main.dill',
|
||||
]),
|
||||
@ -370,6 +397,21 @@ void main() {
|
||||
'arm64',
|
||||
...kDefaultClang,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
'build/foo/App.framework.dSYM',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
'build/foo/App.framework/App',
|
||||
'-o',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
]);
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
@ -400,7 +442,6 @@ void main() {
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}',
|
||||
'--strip',
|
||||
'main.dill',
|
||||
]),
|
||||
kWhichSysctlCommand,
|
||||
@ -425,6 +466,21 @@ void main() {
|
||||
'arm64',
|
||||
...kDefaultClang,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
'build/foo/App.framework.dSYM',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
'build/foo/App.framework/App',
|
||||
'-o',
|
||||
'build/foo/App.framework/App',
|
||||
]),
|
||||
]);
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
|
@ -477,7 +477,6 @@ void main() {
|
||||
'--deterministic',
|
||||
kAssemblyAot,
|
||||
'--assembly=$build/arm64/snapshot_assembly.S',
|
||||
'--strip',
|
||||
'$build/app.dill',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
@ -520,6 +519,21 @@ void main() {
|
||||
'$build/arm64/App.framework/App',
|
||||
'$build/arm64/snapshot_assembly.o',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
'$build/arm64/App.framework.dSYM',
|
||||
'$build/arm64/App.framework/App',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
'$build/arm64/App.framework/App',
|
||||
'-o',
|
||||
'$build/arm64/App.framework/App',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'$build/arm64/App.framework/App',
|
||||
@ -553,7 +567,6 @@ void main() {
|
||||
'--trace-precompiler-to=code_size_1/trace.arm64.json',
|
||||
kAssemblyAot,
|
||||
'--assembly=$build/arm64/snapshot_assembly.S',
|
||||
'--strip',
|
||||
'$build/app.dill',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
@ -596,6 +609,21 @@ void main() {
|
||||
'$build/arm64/App.framework/App',
|
||||
'$build/arm64/snapshot_assembly.o',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
'$build/arm64/App.framework.dSYM',
|
||||
'$build/arm64/App.framework/App',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
'$build/arm64/App.framework/App',
|
||||
'-o',
|
||||
'$build/arm64/App.framework/App',
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'$build/arm64/App.framework/App',
|
||||
|
@ -239,6 +239,14 @@ void main() {
|
||||
.childFile('App')
|
||||
.createSync(recursive: true);
|
||||
|
||||
// Input dSYM
|
||||
environment.buildDir
|
||||
.childDirectory('App.framework.dSYM')
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF')
|
||||
.childFile('App')
|
||||
.createSync(recursive: true);
|
||||
|
||||
final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
|
||||
final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
|
||||
@ -257,6 +265,12 @@ void main() {
|
||||
|
||||
expect(frameworkDirectoryBinary, exists);
|
||||
expect(frameworkDirectory.childFile('Info.plist'), exists);
|
||||
expect(environment.outputDir
|
||||
.childDirectory('App.framework.dSYM')
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF')
|
||||
.childFile('App'), exists);
|
||||
|
||||
final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
|
||||
expect(assetDirectory.childFile('kernel_blob.bin'), isNot(exists));
|
||||
|
@ -295,6 +295,28 @@ void main() {
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testUsingContext('release macOS application creates App.framework.dSYM', () async {
|
||||
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
.createSync(recursive: true);
|
||||
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
|
||||
.createSync(recursive: true);
|
||||
fileSystem.file('${environment.buildDir.path}/App.framework/App')
|
||||
.createSync(recursive: true);
|
||||
fileSystem.file('${environment.buildDir.path}/App.framework.dSYM/Contents/Resources/DWARF/App')
|
||||
.createSync(recursive: true);
|
||||
|
||||
await const ReleaseMacOSBundleFlutterAssets()
|
||||
.build(environment..defines[kBuildMode] = 'release');
|
||||
|
||||
expect(fileSystem.file(
|
||||
'App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
exists,
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testUsingContext('release/profile macOS application updates when App.framework updates', () async {
|
||||
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||
.createSync(recursive: true);
|
||||
@ -415,13 +437,20 @@ void main() {
|
||||
environment.defines[kDarwinArchs] = 'arm64 x86_64';
|
||||
environment.defines[kBuildMode] = 'release';
|
||||
|
||||
// Input dSYMs need to exist for `lipo` to combine them
|
||||
environment.buildDir
|
||||
.childFile('arm64/App.framework.dSYM/Contents/Resources/DWARF/App')
|
||||
.createSync(recursive: true);
|
||||
environment.buildDir
|
||||
.childFile('x86_64/App.framework.dSYM/Contents/Resources/DWARF/App')
|
||||
.createSync(recursive: true);
|
||||
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
FakeCommand(command: <String>[
|
||||
'Artifact.genSnapshot.TargetPlatform.darwin.release_arm64',
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=${environment.buildDir.childFile('arm64/snapshot_assembly.S').path}',
|
||||
'--strip',
|
||||
environment.buildDir.childFile('app.dill').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
@ -429,7 +458,6 @@ void main() {
|
||||
'--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: <String>[
|
||||
@ -458,6 +486,36 @@ void main() {
|
||||
'-o', environment.buildDir.childFile('x86_64/App.framework/App').path,
|
||||
environment.buildDir.childFile('x86_64/snapshot_assembly.o').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
environment.buildDir.childFile('arm64/App.framework.dSYM').path,
|
||||
environment.buildDir.childFile('arm64/App.framework/App').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'dsymutil',
|
||||
'-o',
|
||||
environment.buildDir.childFile('x86_64/App.framework.dSYM').path,
|
||||
environment.buildDir.childFile('x86_64/App.framework/App').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
environment.buildDir.childFile('arm64/App.framework/App').path,
|
||||
'-o',
|
||||
environment.buildDir.childFile('arm64/App.framework/App').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'strip',
|
||||
'-S',
|
||||
environment.buildDir.childFile('x86_64/App.framework/App').path,
|
||||
'-o',
|
||||
environment.buildDir.childFile('x86_64/App.framework/App').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
environment.buildDir.childFile('arm64/App.framework/App').path,
|
||||
@ -466,6 +524,14 @@ void main() {
|
||||
'-output',
|
||||
environment.buildDir.childFile('App.framework/App').path,
|
||||
]),
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
environment.buildDir.childFile('arm64/App.framework.dSYM/Contents/Resources/DWARF/App').path,
|
||||
environment.buildDir.childFile('x86_64/App.framework.dSYM/Contents/Resources/DWARF/App').path,
|
||||
'-create',
|
||||
'-output',
|
||||
environment.buildDir.childFile('App.framework.dSYM/Contents/Resources/DWARF/App').path,
|
||||
]),
|
||||
]);
|
||||
|
||||
await const CompileMacOSFramework().build(environment);
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/utils.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
|
||||
import '../integration.shard/test_utils.dart';
|
||||
@ -75,7 +76,7 @@ void main() {
|
||||
|
||||
for (final BuildMode buildMode in <BuildMode>[BuildMode.debug, BuildMode.release]) {
|
||||
group('build in ${buildMode.name} mode', () {
|
||||
late Directory buildPath;
|
||||
late Directory outputPath;
|
||||
late Directory outputApp;
|
||||
late Directory frameworkDirectory;
|
||||
late Directory outputFlutterFramework;
|
||||
@ -83,6 +84,9 @@ void main() {
|
||||
late Directory outputAppFramework;
|
||||
late File outputAppFrameworkBinary;
|
||||
late File outputPluginFrameworkBinary;
|
||||
late Directory buildPath;
|
||||
late Directory buildAppFrameworkDsym;
|
||||
late File buildAppFrameworkDsymBinary;
|
||||
late ProcessResult buildResult;
|
||||
|
||||
setUpAll(() {
|
||||
@ -98,14 +102,14 @@ void main() {
|
||||
'--split-debug-info=foo debug info/',
|
||||
], workingDirectory: projectRoot);
|
||||
|
||||
buildPath = fileSystem.directory(fileSystem.path.join(
|
||||
outputPath = fileSystem.directory(fileSystem.path.join(
|
||||
projectRoot,
|
||||
'build',
|
||||
'ios',
|
||||
'iphoneos',
|
||||
));
|
||||
|
||||
outputApp = buildPath.childDirectory('Runner.app');
|
||||
outputApp = outputPath.childDirectory('Runner.app');
|
||||
|
||||
frameworkDirectory = outputApp.childDirectory('Frameworks');
|
||||
outputFlutterFramework = frameworkDirectory.childDirectory('Flutter.framework');
|
||||
@ -115,6 +119,16 @@ void main() {
|
||||
outputAppFrameworkBinary = outputAppFramework.childFile('App');
|
||||
|
||||
outputPluginFrameworkBinary = frameworkDirectory.childDirectory('hello.framework').childFile('hello');
|
||||
|
||||
buildPath = fileSystem.directory(fileSystem.path.join(
|
||||
projectRoot,
|
||||
'build',
|
||||
'ios',
|
||||
'${sentenceCase(buildMode.name)}-iphoneos',
|
||||
));
|
||||
|
||||
buildAppFrameworkDsym = buildPath.childDirectory('App.framework.dSYM');
|
||||
buildAppFrameworkDsymBinary = buildAppFrameworkDsym.childFile('Contents/Resources/DWARF/App');
|
||||
});
|
||||
|
||||
testWithoutContext('flutter build ios builds a valid app', () {
|
||||
@ -128,6 +142,8 @@ void main() {
|
||||
expect(outputAppFrameworkBinary, exists);
|
||||
expect(outputAppFramework.childFile('Info.plist'), exists);
|
||||
|
||||
expect(buildAppFrameworkDsymBinary.existsSync(), buildMode != BuildMode.debug);
|
||||
|
||||
final File vmSnapshot = fileSystem.file(fileSystem.path.join(
|
||||
outputAppFramework.path,
|
||||
'flutter_assets',
|
||||
@ -190,6 +206,25 @@ void main() {
|
||||
expect(aotSymbolsFound, buildMode != BuildMode.debug);
|
||||
});
|
||||
|
||||
// dSYM is not created for a debug build so nothing to check.
|
||||
if (buildMode != BuildMode.debug) {
|
||||
testWithoutContext('check symbols in dSYM', () {
|
||||
final ProcessResult nm = processManager.runSync(
|
||||
<String>[
|
||||
'nm',
|
||||
'--debug-syms',
|
||||
'--defined-only',
|
||||
'--just-symbol-name',
|
||||
buildAppFrameworkDsymBinary.path,
|
||||
'-arch',
|
||||
'arm64',
|
||||
],
|
||||
);
|
||||
final List<String> symbols = (nm.stdout as String).split('\n');
|
||||
expect(symbols, contains('_kDartVmSnapshotInstructions'));
|
||||
});
|
||||
}
|
||||
|
||||
testWithoutContext('xcode_backend embed_and_thin', () {
|
||||
outputFlutterFramework.deleteSync(recursive: true);
|
||||
outputAppFramework.deleteSync(recursive: true);
|
||||
@ -219,7 +254,7 @@ void main() {
|
||||
'ios',
|
||||
'Release-iphoneos',
|
||||
),
|
||||
'TARGET_BUILD_DIR': buildPath.path,
|
||||
'TARGET_BUILD_DIR': outputPath.path,
|
||||
'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks',
|
||||
'VERBOSE_SCRIPT_LOGGING': '1',
|
||||
'FLUTTER_BUILD_MODE': 'release',
|
||||
|
@ -67,18 +67,18 @@ void main() {
|
||||
expect(result.exitCode, 0);
|
||||
|
||||
expect(result.stdout, contains('Running pod install'));
|
||||
expect(podfile.lastModifiedSync().isBefore(podfileLock.lastModifiedSync()), isTrue);
|
||||
|
||||
final Directory outputApp = fileSystem.directory(fileSystem.path.join(
|
||||
final Directory buildPath = fileSystem.directory(fileSystem.path.join(
|
||||
workingDirectory,
|
||||
'build',
|
||||
'macos',
|
||||
'Build',
|
||||
'Products',
|
||||
buildMode,
|
||||
'flutter_gallery.app',
|
||||
));
|
||||
expect(podfile.lastModifiedSync().isBefore(podfileLock.lastModifiedSync()), isTrue);
|
||||
|
||||
final Directory outputApp = buildPath.childDirectory('flutter_gallery.app');
|
||||
final Directory outputAppFramework =
|
||||
fileSystem.directory(fileSystem.path.join(
|
||||
outputApp.path,
|
||||
@ -87,19 +87,19 @@ void main() {
|
||||
'App.framework',
|
||||
));
|
||||
|
||||
final File outputAppFrameworkBinary = outputAppFramework.childFile('App');
|
||||
final String archs = processManager.runSync(
|
||||
<String>['file', outputAppFrameworkBinary.path],
|
||||
).stdout as String;
|
||||
_checkFatBinary(
|
||||
outputAppFramework.childFile('App'),
|
||||
buildModeLower,
|
||||
'dynamically linked shared library',
|
||||
);
|
||||
|
||||
final bool containsX64 = archs.contains('Mach-O 64-bit dynamically linked shared library x86_64');
|
||||
final bool containsArm = archs.contains('Mach-O 64-bit dynamically linked shared library arm64');
|
||||
if (buildModeLower == 'debug') {
|
||||
// Only build the architecture matching the machine running this test, not both.
|
||||
expect(containsX64 ^ containsArm, isTrue, reason: 'Unexpected architecture $archs');
|
||||
} else {
|
||||
expect(containsX64, isTrue, reason: 'Unexpected architecture $archs');
|
||||
expect(containsArm, isTrue, reason: 'Unexpected architecture $archs');
|
||||
// dSYM is not created for a debug build so nothing to check.
|
||||
if (buildMode != 'Debug') {
|
||||
_checkFatBinary(
|
||||
buildPath.childFile('App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||
buildModeLower,
|
||||
'dSYM companion file',
|
||||
);
|
||||
}
|
||||
|
||||
expect(outputAppFramework.childLink('Resources'), exists);
|
||||
@ -172,3 +172,19 @@ void main() {
|
||||
}, skip: !platform.isMacOS); // [intended] only makes sense for macos platform.
|
||||
}
|
||||
}
|
||||
|
||||
void _checkFatBinary(File file, String buildModeLower, String expectedType) {
|
||||
final String archs = processManager.runSync(
|
||||
<String>['file', file.path],
|
||||
).stdout as String;
|
||||
|
||||
final bool containsX64 = archs.contains('Mach-O 64-bit $expectedType x86_64');
|
||||
final bool containsArm = archs.contains('Mach-O 64-bit $expectedType arm64');
|
||||
if (buildModeLower == 'debug') {
|
||||
// Only build the architecture matching the machine running this test, not both.
|
||||
expect(containsX64 ^ containsArm, isTrue, reason: 'Unexpected architecture $archs');
|
||||
} else {
|
||||
expect(containsX64, isTrue, reason: 'Unexpected architecture $archs');
|
||||
expect(containsArm, isTrue, reason: 'Unexpected architecture $archs');
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user