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',
|
'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(
|
checkFileExists(path.join(
|
||||||
outputPath,
|
outputPath,
|
||||||
mode,
|
mode,
|
||||||
@ -404,6 +421,25 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
|
|||||||
'App',
|
'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(
|
if (Directory(path.join(
|
||||||
cocoapodsOutputPath,
|
cocoapodsOutputPath,
|
||||||
mode,
|
mode,
|
||||||
@ -582,6 +618,23 @@ Future<void> _testBuildMacOSFramework(Directory projectDir) async {
|
|||||||
'Resources',
|
'Resources',
|
||||||
'Info.plist',
|
'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");
|
section("Check all modes' engine dylib");
|
||||||
@ -712,6 +765,25 @@ Future<void> _testBuildMacOSFramework(Directory projectDir) async {
|
|||||||
'App',
|
'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(
|
await _checkStatic(path.join(
|
||||||
cocoapodsOutputPath,
|
cocoapodsOutputPath,
|
||||||
mode,
|
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 {
|
Future<void> _checkStatic(String pathToLibrary) async {
|
||||||
final String binaryFileType = await fileType(pathToLibrary);
|
final String binaryFileType = await fileType(pathToLibrary);
|
||||||
if (!binaryFileType.contains('current ar archive random library')) {
|
if (!binaryFileType.contains('current ar archive random library')) {
|
||||||
|
@ -440,6 +440,16 @@ end
|
|||||||
'Frameworks',
|
'Frameworks',
|
||||||
'url_launcher_ios.framework',
|
'url_launcher_ios.framework',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
checkFileExists(path.join(
|
||||||
|
'${objectiveCBuildArchiveDirectory.path}.xcarchive',
|
||||||
|
'dSYMs',
|
||||||
|
'App.framework.dSYM',
|
||||||
|
'Contents',
|
||||||
|
'Resources',
|
||||||
|
'DWARF',
|
||||||
|
'App'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
section('Run platform unit tests');
|
section('Run platform unit tests');
|
||||||
|
@ -139,10 +139,17 @@ class AOTSnapshotter {
|
|||||||
'--deterministic',
|
'--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
|
// We strip snapshot by default, but allow to suppress this behavior
|
||||||
// by supplying --no-strip in extraGenSnapshotOptions.
|
// by supplying --no-strip in extraGenSnapshotOptions.
|
||||||
bool shouldStrip = true;
|
bool shouldStrip = true;
|
||||||
|
|
||||||
if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
|
if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
|
||||||
_logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
|
_logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
|
||||||
for (final String option in extraGenSnapshotOptions) {
|
for (final String option in extraGenSnapshotOptions) {
|
||||||
@ -168,8 +175,20 @@ class AOTSnapshotter {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldStrip) {
|
// When buiding for iOS and splitting out debug info, we want to strip
|
||||||
genSnapshotArgs.add('--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) {
|
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
|
// On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the
|
||||||
// end-developer can link into their app.
|
// end-developer can link into their app.
|
||||||
if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin) {
|
if (targetingApplePlatform) {
|
||||||
final RunResult result = await _buildFramework(
|
return _buildFramework(
|
||||||
appleArch: darwinArch!,
|
appleArch: darwinArch!,
|
||||||
isIOS: platform == TargetPlatform.ios,
|
isIOS: platform == TargetPlatform.ios,
|
||||||
sdkRoot: sdkRoot,
|
sdkRoot: sdkRoot,
|
||||||
@ -227,24 +246,26 @@ class AOTSnapshotter {
|
|||||||
outputPath: outputDir.path,
|
outputPath: outputDir.path,
|
||||||
bitcode: bitcode,
|
bitcode: bitcode,
|
||||||
quiet: quiet,
|
quiet: quiet,
|
||||||
|
stripAfterBuild: stripAfterBuild,
|
||||||
|
extractAppleDebugSymbols: extractAppleDebugSymbols
|
||||||
);
|
);
|
||||||
if (result.exitCode != 0) {
|
} else {
|
||||||
return result.exitCode;
|
return 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly
|
/// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly
|
||||||
/// source at [assemblyPath].
|
/// source at [assemblyPath].
|
||||||
Future<RunResult> _buildFramework({
|
Future<int> _buildFramework({
|
||||||
required DarwinArch appleArch,
|
required DarwinArch appleArch,
|
||||||
required bool isIOS,
|
required bool isIOS,
|
||||||
String? sdkRoot,
|
String? sdkRoot,
|
||||||
required String assemblyPath,
|
required String assemblyPath,
|
||||||
required String outputPath,
|
required String outputPath,
|
||||||
required bool bitcode,
|
required bool bitcode,
|
||||||
required bool quiet
|
required bool quiet,
|
||||||
|
required bool stripAfterBuild,
|
||||||
|
required bool extractAppleDebugSymbols
|
||||||
}) async {
|
}) async {
|
||||||
final String targetArch = getNameForDarwinArch(appleArch);
|
final String targetArch = getNameForDarwinArch(appleArch);
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
@ -278,7 +299,7 @@ class AOTSnapshotter {
|
|||||||
]);
|
]);
|
||||||
if (compileResult.exitCode != 0) {
|
if (compileResult.exitCode != 0) {
|
||||||
_logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
|
_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');
|
final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework');
|
||||||
@ -294,11 +315,33 @@ class AOTSnapshotter {
|
|||||||
'-o', appLib,
|
'-o', appLib,
|
||||||
assemblyO,
|
assemblyO,
|
||||||
];
|
];
|
||||||
|
|
||||||
final RunResult linkResult = await _xcode.clang(linkArgs);
|
final RunResult linkResult = await _xcode.clang(linkArgs);
|
||||||
if (linkResult.exitCode != 0) {
|
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) {
|
bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
|
||||||
|
@ -7,6 +7,7 @@ import 'package:package_config/package_config.dart';
|
|||||||
import '../../artifacts.dart';
|
import '../../artifacts.dart';
|
||||||
import '../../base/build.dart';
|
import '../../base/build.dart';
|
||||||
import '../../base/file_system.dart';
|
import '../../base/file_system.dart';
|
||||||
|
import '../../base/io.dart';
|
||||||
import '../../build_info.dart';
|
import '../../build_info.dart';
|
||||||
import '../../compile.dart';
|
import '../../compile.dart';
|
||||||
import '../../dart/package_map.dart';
|
import '../../dart/package_map.dart';
|
||||||
@ -394,3 +395,48 @@ abstract class CopyFlutterAotBundle extends Target {
|
|||||||
environment.buildDir.childFile('app.so').copySync(outputFile.path);
|
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)) {
|
if (results.any((int result) => result != 0)) {
|
||||||
throw Exception('AOT snapshotter exited with code ${results.join()}');
|
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);
|
// Combine the app lib into a fat framework.
|
||||||
final ProcessResult result = await environment.processManager.run(<String>[
|
await Lipo.create(
|
||||||
'lipo',
|
environment,
|
||||||
...darwinArchs.map((DarwinArch iosArch) =>
|
darwinArchs,
|
||||||
environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
|
relativePath: 'App.framework/App',
|
||||||
'-create',
|
inputDir: buildOutputPath,
|
||||||
'-output',
|
);
|
||||||
resultPath,
|
|
||||||
]);
|
// And combine the dSYM for each architecture too, if it was created.
|
||||||
if (result.exitCode != 0) {
|
await Lipo.create(
|
||||||
throw Exception('lipo exited with code ${result.exitCode}.\n${result.stderr}');
|
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);
|
.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.
|
// Copy the assets.
|
||||||
final Depfile assetDepfile = await copyAssets(
|
final Depfile assetDepfile = await copyAssets(
|
||||||
environment,
|
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.
|
/// Build a profile iOS application bundle.
|
||||||
class ProfileIosApplicationBundle extends IosAssetBundle {
|
class ProfileIosApplicationBundle extends _IosAssetBundleWithDSYM {
|
||||||
const ProfileIosApplicationBundle();
|
const ProfileIosApplicationBundle();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -561,7 +603,7 @@ class ProfileIosApplicationBundle extends IosAssetBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build a release iOS application bundle.
|
/// Build a release iOS application bundle.
|
||||||
class ReleaseIosApplicationBundle extends IosAssetBundle {
|
class ReleaseIosApplicationBundle extends _IosAssetBundleWithDSYM {
|
||||||
const ReleaseIosApplicationBundle();
|
const ReleaseIosApplicationBundle();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -302,19 +302,23 @@ class CompileMacOSFramework extends Target {
|
|||||||
throw Exception('AOT snapshotter exited with code ${results.join()}');
|
throw Exception('AOT snapshotter exited with code ${results.join()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
final String resultPath = environment.fileSystem.path.join(environment.buildDir.path, 'App.framework', 'App');
|
// Combine the app lib into a fat framework.
|
||||||
environment.fileSystem.directory(resultPath).parent.createSync(recursive: true);
|
await Lipo.create(
|
||||||
final ProcessResult result = await environment.processManager.run(<String>[
|
environment,
|
||||||
'lipo',
|
darwinArchs,
|
||||||
...darwinArchs.map((DarwinArch iosArch) =>
|
relativePath: 'App.framework/App',
|
||||||
environment.fileSystem.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
|
inputDir: buildOutputPath,
|
||||||
'-create',
|
);
|
||||||
'-output',
|
|
||||||
resultPath,
|
// And combine the dSYM for each architecture too, if it was created.
|
||||||
]);
|
await Lipo.create(
|
||||||
if (result.exitCode != 0) {
|
environment,
|
||||||
throw Exception('lipo exited with code ${result.exitCode}.\n${result.stderr}');
|
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
|
@override
|
||||||
@ -332,6 +336,7 @@ class CompileMacOSFramework extends Target {
|
|||||||
@override
|
@override
|
||||||
List<Source> get outputs => const <Source>[
|
List<Source> get outputs => const <Source>[
|
||||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
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')
|
.childFile('App')
|
||||||
.copySync(outputDirectory.childFile('App').path);
|
.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.
|
// Copy assets into asset directory.
|
||||||
final Directory assetDirectory = outputDirectory
|
final Directory assetDirectory = outputDirectory
|
||||||
.childDirectory('Resources')
|
.childDirectory('Resources')
|
||||||
@ -530,6 +555,18 @@ class ProfileMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
|
|||||||
CompileMacOSFramework(),
|
CompileMacOSFramework(),
|
||||||
ProfileUnpackMacOS(),
|
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(),
|
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
|
@override
|
||||||
Future<void> build(Environment environment) async {
|
Future<void> build(Environment environment) async {
|
||||||
bool buildSuccess = true;
|
bool buildSuccess = true;
|
||||||
|
@ -68,8 +68,11 @@ class SymbolizeCommand extends FlutterCommand {
|
|||||||
if (argResults?.wasParsed('debug-info') != true) {
|
if (argResults?.wasParsed('debug-info') != true) {
|
||||||
throwToolExit('"--debug-info" is required to symbolize stack traces.');
|
throwToolExit('"--debug-info" is required to symbolize stack traces.');
|
||||||
}
|
}
|
||||||
if (!_fileSystem.isFileSync(stringArgDeprecated('debug-info')!)) {
|
final String debugInfoPath = stringArgDeprecated('debug-info')!;
|
||||||
throwToolExit('${stringArgDeprecated('debug-info')} does not exist.');
|
if (debugInfoPath.endsWith('.dSYM')
|
||||||
|
? !_fileSystem.isDirectorySync(debugInfoPath)
|
||||||
|
: !_fileSystem.isFileSync(debugInfoPath)) {
|
||||||
|
throwToolExit('$debugInfoPath does not exist.');
|
||||||
}
|
}
|
||||||
if ((argResults?.wasParsed('input') ?? false) && !_fileSystem.isFileSync(stringArgDeprecated('input')!)) {
|
if ((argResults?.wasParsed('input') ?? false) && !_fileSystem.isFileSync(stringArgDeprecated('input')!)) {
|
||||||
throwToolExit('${stringArgDeprecated('input')} does not exist.');
|
throwToolExit('${stringArgDeprecated('input')} does not exist.');
|
||||||
@ -105,7 +108,25 @@ class SymbolizeCommand extends FlutterCommand {
|
|||||||
input = _stdio.stdin;
|
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(
|
await _dwarfSymbolizationService.decode(
|
||||||
input: input,
|
input: input,
|
||||||
output: output,
|
output: output,
|
||||||
|
@ -165,16 +165,17 @@ class Xcode {
|
|||||||
/// See [XcodeProjectInterpreter.xcrunCommand].
|
/// See [XcodeProjectInterpreter.xcrunCommand].
|
||||||
List<String> xcrunCommand() => _xcodeProjectInterpreter.xcrunCommand();
|
List<String> xcrunCommand() => _xcodeProjectInterpreter.xcrunCommand();
|
||||||
|
|
||||||
Future<RunResult> cc(List<String> args) {
|
Future<RunResult> cc(List<String> args) => _run('cc', args);
|
||||||
return _processUtils.run(
|
|
||||||
<String>[...xcrunCommand(), 'cc', ...args],
|
|
||||||
throwOnError: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
return _processUtils.run(
|
||||||
<String>[...xcrunCommand(), 'clang', ...args],
|
<String>[...xcrunCommand(), command, ...args],
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ void main() {
|
|||||||
OutputPreferences: () => OutputPreferences.test(),
|
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(
|
final SymbolizeCommand command = SymbolizeCommand(
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
@ -83,6 +83,20 @@ void main() {
|
|||||||
OutputPreferences: () => OutputPreferences.test(),
|
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 {
|
testUsingContext('symbolize exits when --input file is missing', () async {
|
||||||
final SymbolizeCommand command = SymbolizeCommand(
|
final SymbolizeCommand command = SymbolizeCommand(
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
|
@ -210,7 +210,6 @@ void main() {
|
|||||||
'--deterministic',
|
'--deterministic',
|
||||||
'--snapshot_kind=app-aot-assembly',
|
'--snapshot_kind=app-aot-assembly',
|
||||||
'--assembly=$assembly',
|
'--assembly=$assembly',
|
||||||
'--strip',
|
|
||||||
'main.dill',
|
'main.dill',
|
||||||
]),
|
]),
|
||||||
kWhichSysctlCommand,
|
kWhichSysctlCommand,
|
||||||
@ -253,6 +252,21 @@ void main() {
|
|||||||
'build/foo/App.framework/App',
|
'build/foo/App.framework/App',
|
||||||
'build/foo/snapshot_assembly.o',
|
'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(
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
@ -285,7 +299,6 @@ void main() {
|
|||||||
'--deterministic',
|
'--deterministic',
|
||||||
'--snapshot_kind=app-aot-assembly',
|
'--snapshot_kind=app-aot-assembly',
|
||||||
'--assembly=$assembly',
|
'--assembly=$assembly',
|
||||||
'--strip',
|
|
||||||
'--dwarf-stack-traces',
|
'--dwarf-stack-traces',
|
||||||
'--save-debugging-info=$debugPath',
|
'--save-debugging-info=$debugPath',
|
||||||
'main.dill',
|
'main.dill',
|
||||||
@ -312,6 +325,21 @@ void main() {
|
|||||||
'arm64',
|
'arm64',
|
||||||
...kDefaultClang,
|
...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(
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
@ -344,7 +372,6 @@ void main() {
|
|||||||
'--deterministic',
|
'--deterministic',
|
||||||
'--snapshot_kind=app-aot-assembly',
|
'--snapshot_kind=app-aot-assembly',
|
||||||
'--assembly=$assembly',
|
'--assembly=$assembly',
|
||||||
'--strip',
|
|
||||||
'--obfuscate',
|
'--obfuscate',
|
||||||
'main.dill',
|
'main.dill',
|
||||||
]),
|
]),
|
||||||
@ -370,6 +397,21 @@ void main() {
|
|||||||
'arm64',
|
'arm64',
|
||||||
...kDefaultClang,
|
...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(
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
@ -400,7 +442,6 @@ void main() {
|
|||||||
'--deterministic',
|
'--deterministic',
|
||||||
'--snapshot_kind=app-aot-assembly',
|
'--snapshot_kind=app-aot-assembly',
|
||||||
'--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}',
|
'--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}',
|
||||||
'--strip',
|
|
||||||
'main.dill',
|
'main.dill',
|
||||||
]),
|
]),
|
||||||
kWhichSysctlCommand,
|
kWhichSysctlCommand,
|
||||||
@ -425,6 +466,21 @@ void main() {
|
|||||||
'arm64',
|
'arm64',
|
||||||
...kDefaultClang,
|
...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(
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
|
@ -477,7 +477,6 @@ void main() {
|
|||||||
'--deterministic',
|
'--deterministic',
|
||||||
kAssemblyAot,
|
kAssemblyAot,
|
||||||
'--assembly=$build/arm64/snapshot_assembly.S',
|
'--assembly=$build/arm64/snapshot_assembly.S',
|
||||||
'--strip',
|
|
||||||
'$build/app.dill',
|
'$build/app.dill',
|
||||||
]),
|
]),
|
||||||
FakeCommand(command: <String>[
|
FakeCommand(command: <String>[
|
||||||
@ -520,6 +519,21 @@ void main() {
|
|||||||
'$build/arm64/App.framework/App',
|
'$build/arm64/App.framework/App',
|
||||||
'$build/arm64/snapshot_assembly.o',
|
'$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>[
|
FakeCommand(command: <String>[
|
||||||
'lipo',
|
'lipo',
|
||||||
'$build/arm64/App.framework/App',
|
'$build/arm64/App.framework/App',
|
||||||
@ -553,7 +567,6 @@ void main() {
|
|||||||
'--trace-precompiler-to=code_size_1/trace.arm64.json',
|
'--trace-precompiler-to=code_size_1/trace.arm64.json',
|
||||||
kAssemblyAot,
|
kAssemblyAot,
|
||||||
'--assembly=$build/arm64/snapshot_assembly.S',
|
'--assembly=$build/arm64/snapshot_assembly.S',
|
||||||
'--strip',
|
|
||||||
'$build/app.dill',
|
'$build/app.dill',
|
||||||
]),
|
]),
|
||||||
FakeCommand(command: <String>[
|
FakeCommand(command: <String>[
|
||||||
@ -596,6 +609,21 @@ void main() {
|
|||||||
'$build/arm64/App.framework/App',
|
'$build/arm64/App.framework/App',
|
||||||
'$build/arm64/snapshot_assembly.o',
|
'$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>[
|
FakeCommand(command: <String>[
|
||||||
'lipo',
|
'lipo',
|
||||||
'$build/arm64/App.framework/App',
|
'$build/arm64/App.framework/App',
|
||||||
|
@ -239,6 +239,14 @@ void main() {
|
|||||||
.childFile('App')
|
.childFile('App')
|
||||||
.createSync(recursive: true);
|
.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 Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
|
||||||
final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
|
final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
|
||||||
@ -257,6 +265,12 @@ void main() {
|
|||||||
|
|
||||||
expect(frameworkDirectoryBinary, exists);
|
expect(frameworkDirectoryBinary, exists);
|
||||||
expect(frameworkDirectory.childFile('Info.plist'), 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');
|
final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
|
||||||
expect(assetDirectory.childFile('kernel_blob.bin'), isNot(exists));
|
expect(assetDirectory.childFile('kernel_blob.bin'), isNot(exists));
|
||||||
|
@ -295,6 +295,28 @@ void main() {
|
|||||||
ProcessManager: () => processManager,
|
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 {
|
testUsingContext('release/profile macOS application updates when App.framework updates', () async {
|
||||||
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
@ -415,13 +437,20 @@ void main() {
|
|||||||
environment.defines[kDarwinArchs] = 'arm64 x86_64';
|
environment.defines[kDarwinArchs] = 'arm64 x86_64';
|
||||||
environment.defines[kBuildMode] = 'release';
|
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>[
|
processManager.addCommands(<FakeCommand>[
|
||||||
FakeCommand(command: <String>[
|
FakeCommand(command: <String>[
|
||||||
'Artifact.genSnapshot.TargetPlatform.darwin.release_arm64',
|
'Artifact.genSnapshot.TargetPlatform.darwin.release_arm64',
|
||||||
'--deterministic',
|
'--deterministic',
|
||||||
'--snapshot_kind=app-aot-assembly',
|
'--snapshot_kind=app-aot-assembly',
|
||||||
'--assembly=${environment.buildDir.childFile('arm64/snapshot_assembly.S').path}',
|
'--assembly=${environment.buildDir.childFile('arm64/snapshot_assembly.S').path}',
|
||||||
'--strip',
|
|
||||||
environment.buildDir.childFile('app.dill').path,
|
environment.buildDir.childFile('app.dill').path,
|
||||||
]),
|
]),
|
||||||
FakeCommand(command: <String>[
|
FakeCommand(command: <String>[
|
||||||
@ -429,7 +458,6 @@ void main() {
|
|||||||
'--deterministic',
|
'--deterministic',
|
||||||
'--snapshot_kind=app-aot-assembly',
|
'--snapshot_kind=app-aot-assembly',
|
||||||
'--assembly=${environment.buildDir.childFile('x86_64/snapshot_assembly.S').path}',
|
'--assembly=${environment.buildDir.childFile('x86_64/snapshot_assembly.S').path}',
|
||||||
'--strip',
|
|
||||||
environment.buildDir.childFile('app.dill').path,
|
environment.buildDir.childFile('app.dill').path,
|
||||||
]),
|
]),
|
||||||
FakeCommand(command: <String>[
|
FakeCommand(command: <String>[
|
||||||
@ -458,6 +486,36 @@ void main() {
|
|||||||
'-o', environment.buildDir.childFile('x86_64/App.framework/App').path,
|
'-o', environment.buildDir.childFile('x86_64/App.framework/App').path,
|
||||||
environment.buildDir.childFile('x86_64/snapshot_assembly.o').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>[
|
FakeCommand(command: <String>[
|
||||||
'lipo',
|
'lipo',
|
||||||
environment.buildDir.childFile('arm64/App.framework/App').path,
|
environment.buildDir.childFile('arm64/App.framework/App').path,
|
||||||
@ -466,6 +524,14 @@ void main() {
|
|||||||
'-output',
|
'-output',
|
||||||
environment.buildDir.childFile('App.framework/App').path,
|
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);
|
await const CompileMacOSFramework().build(environment);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:file_testing/file_testing.dart';
|
import 'package:file_testing/file_testing.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/io.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 'package:flutter_tools/src/build_info.dart';
|
||||||
|
|
||||||
import '../integration.shard/test_utils.dart';
|
import '../integration.shard/test_utils.dart';
|
||||||
@ -75,7 +76,7 @@ void main() {
|
|||||||
|
|
||||||
for (final BuildMode buildMode in <BuildMode>[BuildMode.debug, BuildMode.release]) {
|
for (final BuildMode buildMode in <BuildMode>[BuildMode.debug, BuildMode.release]) {
|
||||||
group('build in ${buildMode.name} mode', () {
|
group('build in ${buildMode.name} mode', () {
|
||||||
late Directory buildPath;
|
late Directory outputPath;
|
||||||
late Directory outputApp;
|
late Directory outputApp;
|
||||||
late Directory frameworkDirectory;
|
late Directory frameworkDirectory;
|
||||||
late Directory outputFlutterFramework;
|
late Directory outputFlutterFramework;
|
||||||
@ -83,6 +84,9 @@ void main() {
|
|||||||
late Directory outputAppFramework;
|
late Directory outputAppFramework;
|
||||||
late File outputAppFrameworkBinary;
|
late File outputAppFrameworkBinary;
|
||||||
late File outputPluginFrameworkBinary;
|
late File outputPluginFrameworkBinary;
|
||||||
|
late Directory buildPath;
|
||||||
|
late Directory buildAppFrameworkDsym;
|
||||||
|
late File buildAppFrameworkDsymBinary;
|
||||||
late ProcessResult buildResult;
|
late ProcessResult buildResult;
|
||||||
|
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
@ -98,14 +102,14 @@ void main() {
|
|||||||
'--split-debug-info=foo debug info/',
|
'--split-debug-info=foo debug info/',
|
||||||
], workingDirectory: projectRoot);
|
], workingDirectory: projectRoot);
|
||||||
|
|
||||||
buildPath = fileSystem.directory(fileSystem.path.join(
|
outputPath = fileSystem.directory(fileSystem.path.join(
|
||||||
projectRoot,
|
projectRoot,
|
||||||
'build',
|
'build',
|
||||||
'ios',
|
'ios',
|
||||||
'iphoneos',
|
'iphoneos',
|
||||||
));
|
));
|
||||||
|
|
||||||
outputApp = buildPath.childDirectory('Runner.app');
|
outputApp = outputPath.childDirectory('Runner.app');
|
||||||
|
|
||||||
frameworkDirectory = outputApp.childDirectory('Frameworks');
|
frameworkDirectory = outputApp.childDirectory('Frameworks');
|
||||||
outputFlutterFramework = frameworkDirectory.childDirectory('Flutter.framework');
|
outputFlutterFramework = frameworkDirectory.childDirectory('Flutter.framework');
|
||||||
@ -115,6 +119,16 @@ void main() {
|
|||||||
outputAppFrameworkBinary = outputAppFramework.childFile('App');
|
outputAppFrameworkBinary = outputAppFramework.childFile('App');
|
||||||
|
|
||||||
outputPluginFrameworkBinary = frameworkDirectory.childDirectory('hello.framework').childFile('hello');
|
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', () {
|
testWithoutContext('flutter build ios builds a valid app', () {
|
||||||
@ -128,6 +142,8 @@ void main() {
|
|||||||
expect(outputAppFrameworkBinary, exists);
|
expect(outputAppFrameworkBinary, exists);
|
||||||
expect(outputAppFramework.childFile('Info.plist'), exists);
|
expect(outputAppFramework.childFile('Info.plist'), exists);
|
||||||
|
|
||||||
|
expect(buildAppFrameworkDsymBinary.existsSync(), buildMode != BuildMode.debug);
|
||||||
|
|
||||||
final File vmSnapshot = fileSystem.file(fileSystem.path.join(
|
final File vmSnapshot = fileSystem.file(fileSystem.path.join(
|
||||||
outputAppFramework.path,
|
outputAppFramework.path,
|
||||||
'flutter_assets',
|
'flutter_assets',
|
||||||
@ -190,6 +206,25 @@ void main() {
|
|||||||
expect(aotSymbolsFound, buildMode != BuildMode.debug);
|
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', () {
|
testWithoutContext('xcode_backend embed_and_thin', () {
|
||||||
outputFlutterFramework.deleteSync(recursive: true);
|
outputFlutterFramework.deleteSync(recursive: true);
|
||||||
outputAppFramework.deleteSync(recursive: true);
|
outputAppFramework.deleteSync(recursive: true);
|
||||||
@ -219,7 +254,7 @@ void main() {
|
|||||||
'ios',
|
'ios',
|
||||||
'Release-iphoneos',
|
'Release-iphoneos',
|
||||||
),
|
),
|
||||||
'TARGET_BUILD_DIR': buildPath.path,
|
'TARGET_BUILD_DIR': outputPath.path,
|
||||||
'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks',
|
'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks',
|
||||||
'VERBOSE_SCRIPT_LOGGING': '1',
|
'VERBOSE_SCRIPT_LOGGING': '1',
|
||||||
'FLUTTER_BUILD_MODE': 'release',
|
'FLUTTER_BUILD_MODE': 'release',
|
||||||
|
@ -67,18 +67,18 @@ void main() {
|
|||||||
expect(result.exitCode, 0);
|
expect(result.exitCode, 0);
|
||||||
|
|
||||||
expect(result.stdout, contains('Running pod install'));
|
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,
|
workingDirectory,
|
||||||
'build',
|
'build',
|
||||||
'macos',
|
'macos',
|
||||||
'Build',
|
'Build',
|
||||||
'Products',
|
'Products',
|
||||||
buildMode,
|
buildMode,
|
||||||
'flutter_gallery.app',
|
|
||||||
));
|
));
|
||||||
expect(podfile.lastModifiedSync().isBefore(podfileLock.lastModifiedSync()), isTrue);
|
|
||||||
|
|
||||||
|
final Directory outputApp = buildPath.childDirectory('flutter_gallery.app');
|
||||||
final Directory outputAppFramework =
|
final Directory outputAppFramework =
|
||||||
fileSystem.directory(fileSystem.path.join(
|
fileSystem.directory(fileSystem.path.join(
|
||||||
outputApp.path,
|
outputApp.path,
|
||||||
@ -87,19 +87,19 @@ void main() {
|
|||||||
'App.framework',
|
'App.framework',
|
||||||
));
|
));
|
||||||
|
|
||||||
final File outputAppFrameworkBinary = outputAppFramework.childFile('App');
|
_checkFatBinary(
|
||||||
final String archs = processManager.runSync(
|
outputAppFramework.childFile('App'),
|
||||||
<String>['file', outputAppFrameworkBinary.path],
|
buildModeLower,
|
||||||
).stdout as String;
|
'dynamically linked shared library',
|
||||||
|
);
|
||||||
|
|
||||||
final bool containsX64 = archs.contains('Mach-O 64-bit dynamically linked shared library x86_64');
|
// dSYM is not created for a debug build so nothing to check.
|
||||||
final bool containsArm = archs.contains('Mach-O 64-bit dynamically linked shared library arm64');
|
if (buildMode != 'Debug') {
|
||||||
if (buildModeLower == 'debug') {
|
_checkFatBinary(
|
||||||
// Only build the architecture matching the machine running this test, not both.
|
buildPath.childFile('App.framework.dSYM/Contents/Resources/DWARF/App'),
|
||||||
expect(containsX64 ^ containsArm, isTrue, reason: 'Unexpected architecture $archs');
|
buildModeLower,
|
||||||
} else {
|
'dSYM companion file',
|
||||||
expect(containsX64, isTrue, reason: 'Unexpected architecture $archs');
|
);
|
||||||
expect(containsArm, isTrue, reason: 'Unexpected architecture $archs');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(outputAppFramework.childLink('Resources'), exists);
|
expect(outputAppFramework.childLink('Resources'), exists);
|
||||||
@ -172,3 +172,19 @@ void main() {
|
|||||||
}, skip: !platform.isMacOS); // [intended] only makes sense for macos platform.
|
}, 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