fix: dump debug info when linking ios-framework patches (#2833)
Co-authored-by: Felix Angelov <felix@shorebird.dev>
This commit is contained in:
@@ -39,15 +39,32 @@ class IosFrameworkPatcher extends Patcher {
|
||||
required super.target,
|
||||
});
|
||||
|
||||
String get _patchClassTableLinkInfoFile =>
|
||||
p.join(buildDirectory.path, 'ios', 'shorebird', 'App.ct.link');
|
||||
String get _aotOutputPath =>
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.aot');
|
||||
|
||||
String get _patchClassTableLinkDebugInfoPath =>
|
||||
p.join(buildDirectory.path, 'ios', 'shorebird', 'App.class_table.json');
|
||||
String get _patchClassTableLinkInfoFile => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'ios',
|
||||
'shorebird',
|
||||
'App.ct.link',
|
||||
);
|
||||
|
||||
String get _vmcodeOutputPath => p.join(buildDirectory.path, 'out.vmcode');
|
||||
String get _patchClassTableLinkDebugInfoPath => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'ios',
|
||||
'shorebird',
|
||||
'App.class_table.json',
|
||||
);
|
||||
|
||||
String get _appDillCopyPath => p.join(buildDirectory.path, 'app.dill');
|
||||
String get _vmcodeOutputPath => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'out.vmcode',
|
||||
);
|
||||
|
||||
String get _appDillCopyPath => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'app.dill',
|
||||
);
|
||||
|
||||
@override
|
||||
String get primaryReleaseArtifactArch => 'xcframework';
|
||||
@@ -123,11 +140,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
}
|
||||
await artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: buildResult.kernelFile.path,
|
||||
outFilePath: p.join(
|
||||
shorebirdEnv.getShorebirdProjectRoot()!.path,
|
||||
'build',
|
||||
'out.aot',
|
||||
),
|
||||
outFilePath: _aotOutputPath,
|
||||
genSnapshotArtifact: ShorebirdArtifact.genSnapshotIos,
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
@@ -228,16 +241,19 @@ class IosFrameworkPatcher extends Patcher {
|
||||
// Copy the patch's class table link info file to the build directory
|
||||
// so that it can be used to generate a patch.
|
||||
File(_patchClassTableLinkInfoFile).copySync(
|
||||
p.join(buildDirectory.path, 'out.ct.link'),
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'),
|
||||
);
|
||||
File(_patchClassTableLinkDebugInfoPath).copySync(
|
||||
p.join(buildDirectory.path, 'out.class_table.json'),
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.class_table.json'),
|
||||
);
|
||||
}
|
||||
|
||||
await _runLinker(
|
||||
aotSnapshot: aotSnapshotFile,
|
||||
await apple.runLinker(
|
||||
kernelFile: File(_appDillCopyPath),
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
splitDebugInfoArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
aotOutputFile: File(_aotOutputPath),
|
||||
vmCodeFile: File(_vmcodeOutputPath),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -302,48 +318,4 @@ class IosFrameworkPatcher extends Patcher {
|
||||
xcodeVersion: await xcodeBuild.version(),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _runLinker({
|
||||
required File aotSnapshot,
|
||||
required File releaseArtifact,
|
||||
}) async {
|
||||
if (!aotSnapshot.existsSync()) {
|
||||
logger.err('Unable to find patch AOT file at ${aotSnapshot.path}');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
final analyzeSnapshot = File(
|
||||
shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
);
|
||||
|
||||
if (!analyzeSnapshot.existsSync()) {
|
||||
logger.err('Unable to find analyze_snapshot at ${analyzeSnapshot.path}');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
final genSnapshot = shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.genSnapshotIos,
|
||||
);
|
||||
|
||||
final linkProgress = logger.progress('Linking AOT files');
|
||||
try {
|
||||
lastBuildLinkPercentage = await aotTools.link(
|
||||
base: releaseArtifact.path,
|
||||
patch: aotSnapshot.path,
|
||||
analyzeSnapshot: analyzeSnapshot.path,
|
||||
genSnapshot: genSnapshot,
|
||||
kernel: _appDillCopyPath,
|
||||
outputPath: _vmcodeOutputPath,
|
||||
workingDirectory: buildDirectory.path,
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} on Exception catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
linkProgress.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
|
||||
typedef _LinkResult = ({int exitCode, double? linkPercentage});
|
||||
|
||||
/// {@template ios_patcher}
|
||||
/// Functions to create an iOS patch.
|
||||
/// {@endtemplate}
|
||||
@@ -45,17 +43,28 @@ class IosPatcher extends Patcher {
|
||||
required super.target,
|
||||
});
|
||||
|
||||
String get _patchClassTableLinkInfoPath =>
|
||||
p.join(buildDirectory.path, 'ios', 'shorebird', 'App.ct.link');
|
||||
String get _patchClassTableLinkInfoPath => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'ios',
|
||||
'shorebird',
|
||||
'App.ct.link',
|
||||
);
|
||||
|
||||
String get _patchClassTableLinkDebugInfoPath =>
|
||||
p.join(buildDirectory.path, 'ios', 'shorebird', 'App.class_table.json');
|
||||
String get _patchClassTableLinkDebugInfoPath => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'ios',
|
||||
'shorebird',
|
||||
'App.class_table.json',
|
||||
);
|
||||
|
||||
String get _aotOutputPath => p.join(buildDirectory.path, 'out.aot');
|
||||
String get _aotOutputPath =>
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.aot');
|
||||
|
||||
String get _vmcodeOutputPath => p.join(buildDirectory.path, 'out.vmcode');
|
||||
String get _vmcodeOutputPath =>
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.vmcode');
|
||||
|
||||
String get _appDillCopyPath => p.join(buildDirectory.path, 'app.dill');
|
||||
String get _appDillCopyPath =>
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'app.dill');
|
||||
|
||||
/// The name of the split debug info file when the target is iOS.
|
||||
static const splitDebugInfoFileName = 'app.ios-arm64.symbols';
|
||||
@@ -316,16 +325,19 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
// Copy the patch's class table link info file to the build directory
|
||||
// so that it can be used to generate a patch.
|
||||
File(_patchClassTableLinkInfoPath).copySync(
|
||||
p.join(buildDirectory.path, 'out.ct.link'),
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'),
|
||||
);
|
||||
File(_patchClassTableLinkDebugInfoPath).copySync(
|
||||
p.join(buildDirectory.path, 'out.class_table.json'),
|
||||
p.join(shorebirdEnv.buildDirectory.path, 'out.class_table.json'),
|
||||
);
|
||||
}
|
||||
|
||||
final (:exitCode, :linkPercentage) = await _runLinker(
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
final (:exitCode, :linkPercentage) = await apple.runLinker(
|
||||
kernelFile: File(_appDillCopyPath),
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
splitDebugInfoArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
aotOutputFile: File(_aotOutputPath),
|
||||
vmCodeFile: File(_vmcodeOutputPath),
|
||||
);
|
||||
if (exitCode != ExitCode.success.code) throw ProcessExit(exitCode);
|
||||
if (linkPercentage != null &&
|
||||
@@ -424,66 +436,4 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
xcodeVersion: await xcodeBuild.version(),
|
||||
),
|
||||
);
|
||||
|
||||
Future<_LinkResult> _runLinker({
|
||||
required File releaseArtifact,
|
||||
required File kernelFile,
|
||||
}) async {
|
||||
final patch = File(_aotOutputPath);
|
||||
|
||||
if (!patch.existsSync()) {
|
||||
logger.err('Unable to find patch AOT file at ${patch.path}');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
}
|
||||
|
||||
final analyzeSnapshot = File(
|
||||
shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
);
|
||||
|
||||
if (!analyzeSnapshot.existsSync()) {
|
||||
logger.err('Unable to find analyze_snapshot at ${analyzeSnapshot.path}');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
}
|
||||
|
||||
final genSnapshot = shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.genSnapshotIos,
|
||||
);
|
||||
|
||||
final linkProgress = logger.progress('Linking AOT files');
|
||||
double? linkPercentage;
|
||||
final dumpDebugInfoDir = await aotTools.isLinkDebugInfoSupported()
|
||||
? Directory.systemTemp.createTempSync()
|
||||
: null;
|
||||
|
||||
Future<void> dumpDebugInfo() async {
|
||||
if (dumpDebugInfoDir == null) return;
|
||||
|
||||
final debugInfoZip = await dumpDebugInfoDir.zipToTempFile();
|
||||
debugInfoZip.copySync(p.join('build', debugInfoFile.path));
|
||||
logger.detail('Link debug info saved to ${debugInfoFile.path}');
|
||||
}
|
||||
|
||||
try {
|
||||
linkPercentage = await aotTools.link(
|
||||
base: releaseArtifact.path,
|
||||
patch: patch.path,
|
||||
analyzeSnapshot: analyzeSnapshot.path,
|
||||
genSnapshot: genSnapshot,
|
||||
outputPath: _vmcodeOutputPath,
|
||||
workingDirectory: buildDirectory.path,
|
||||
kernel: kernelFile.path,
|
||||
dumpDebugInfoPath: dumpDebugInfoDir?.path,
|
||||
additionalArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} on Exception catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
} finally {
|
||||
await dumpDebugInfo();
|
||||
}
|
||||
linkProgress.complete();
|
||||
return (exitCode: ExitCode.success.code, linkPercentage: linkPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,17 +43,20 @@ class MacosPatcher extends Patcher {
|
||||
|
||||
// The elf snapshot built for Apple Silicon macs.
|
||||
String get _arm64AotOutputPath => p.join(
|
||||
buildDirectory.path,
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'out.arm64.aot',
|
||||
);
|
||||
|
||||
// The elf snapshot built for Intel macs.
|
||||
String get _x64AotOutputPath => p.join(
|
||||
buildDirectory.path,
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'out.x64.aot',
|
||||
);
|
||||
|
||||
String get _appDillCopyPath => p.join(buildDirectory.path, 'app.dill');
|
||||
String get _appDillCopyPath => p.join(
|
||||
shorebirdEnv.buildDirectory.path,
|
||||
'app.dill',
|
||||
);
|
||||
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.macos;
|
||||
|
||||
@@ -552,7 +552,7 @@ Please re-run the release command for this version or create a new release.''');
|
||||
trackSummary,
|
||||
if (patcher.linkPercentage != null &&
|
||||
patcher.linkPercentage! < Patcher.minLinkPercentage)
|
||||
'''🔍 Debug Info: ${lightCyan.wrap(patcher.debugInfoFile.path)}''',
|
||||
'''🔍 Debug Info: ${lightCyan.wrap(Patcher.debugInfoFile.path)}''',
|
||||
];
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -140,16 +140,9 @@ More info: ${troubleshootingUrl.toLink()}.
|
||||
);
|
||||
}
|
||||
|
||||
/// The build directory of the respective shorebird project.
|
||||
Directory get buildDirectory {
|
||||
return Directory(
|
||||
p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'build'),
|
||||
);
|
||||
}
|
||||
|
||||
/// The path to the output file for the debug info.
|
||||
File get debugInfoFile {
|
||||
return File(p.join(buildDirectory.path, 'patch-debug.zip'));
|
||||
static File get debugInfoFile {
|
||||
return File(p.join(shorebirdEnv.buildDirectory.path, 'patch-debug.zip'));
|
||||
}
|
||||
|
||||
/// Extracts the --build-name and --build-number from the --release-version
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:io/io.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive/directory_archive.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch/patcher.dart';
|
||||
import 'package:shorebird_cli/src/executables/aot_tools.dart';
|
||||
import 'package:shorebird_cli/src/logging/shorebird_logger.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_artifacts.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
@@ -17,6 +23,10 @@ enum ApplePlatform {
|
||||
macos,
|
||||
}
|
||||
|
||||
/// A record containing the exit code and optionally link percentage
|
||||
/// returned by `runLinker`.
|
||||
typedef LinkResult = ({int exitCode, double? linkPercentage});
|
||||
|
||||
/// {@template missing_xcode_project_exception}
|
||||
/// Thrown when the Flutter project does not have iOS configured as a platform.
|
||||
/// {@endtemplate}
|
||||
@@ -170,6 +180,74 @@ class Apple {
|
||||
.toSet();
|
||||
}
|
||||
|
||||
/// Runs the linking step to minimize differences between patch and release
|
||||
/// and maximize code that can be executed on the CPU.
|
||||
Future<LinkResult> runLinker({
|
||||
required File kernelFile,
|
||||
required File releaseArtifact,
|
||||
required List<String> splitDebugInfoArgs,
|
||||
required File aotOutputFile,
|
||||
required File vmCodeFile,
|
||||
}) async {
|
||||
final patch = aotOutputFile;
|
||||
final buildDirectory = shorebirdEnv.buildDirectory;
|
||||
|
||||
if (!patch.existsSync()) {
|
||||
logger.err('Unable to find patch AOT file at ${patch.path}');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
}
|
||||
|
||||
final analyzeSnapshot = File(
|
||||
shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
);
|
||||
|
||||
if (!analyzeSnapshot.existsSync()) {
|
||||
logger.err('Unable to find analyze_snapshot at ${analyzeSnapshot.path}');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
}
|
||||
|
||||
final genSnapshot = shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.genSnapshotIos,
|
||||
);
|
||||
|
||||
final linkProgress = logger.progress('Linking AOT files');
|
||||
double? linkPercentage;
|
||||
final dumpDebugInfoDir = await aotTools.isLinkDebugInfoSupported()
|
||||
? Directory.systemTemp.createTempSync()
|
||||
: null;
|
||||
|
||||
Future<void> dumpDebugInfo() async {
|
||||
if (dumpDebugInfoDir == null) return;
|
||||
|
||||
final debugInfoZip = await dumpDebugInfoDir.zipToTempFile();
|
||||
debugInfoZip.copySync(p.join('build', Patcher.debugInfoFile.path));
|
||||
logger.detail('Link debug info saved to ${Patcher.debugInfoFile.path}');
|
||||
}
|
||||
|
||||
try {
|
||||
linkPercentage = await aotTools.link(
|
||||
base: releaseArtifact.path,
|
||||
patch: patch.path,
|
||||
analyzeSnapshot: analyzeSnapshot.path,
|
||||
genSnapshot: genSnapshot,
|
||||
outputPath: vmCodeFile.path,
|
||||
workingDirectory: buildDirectory.path,
|
||||
kernel: kernelFile.path,
|
||||
dumpDebugInfoPath: dumpDebugInfoDir?.path,
|
||||
additionalArgs: splitDebugInfoArgs,
|
||||
);
|
||||
} on Exception catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
} finally {
|
||||
await dumpDebugInfo();
|
||||
}
|
||||
linkProgress.complete();
|
||||
return (exitCode: ExitCode.success.code, linkPercentage: linkPercentage);
|
||||
}
|
||||
|
||||
/// Parses the .xcscheme file to determine if it was created for an app
|
||||
/// extension. We don't want to include these schemes as app flavors.
|
||||
///
|
||||
|
||||
@@ -114,6 +114,11 @@ class ShorebirdEnv {
|
||||
return File(p.join(getFlutterProjectRoot()!.path, 'macos', 'Podfile.lock'));
|
||||
}
|
||||
|
||||
/// The build directory of the current shorebird project.
|
||||
Directory get buildDirectory {
|
||||
return Directory(p.join(getFlutterProjectRoot()!.path, 'build'));
|
||||
}
|
||||
|
||||
/// The `shorebird.yaml` file for this project.
|
||||
File getShorebirdYamlFile({required Directory cwd}) {
|
||||
return File(p.join(cwd.path, 'shorebird.yaml'));
|
||||
|
||||
@@ -76,5 +76,32 @@ void main() {
|
||||
expect(fileSetDiff.isEmpty, isFalse);
|
||||
expect(fileSetDiff.isNotEmpty, isTrue);
|
||||
});
|
||||
|
||||
test('supports value based equality comparisons', () {
|
||||
// For the sake of testing equality comparisons avoid using const.
|
||||
// ignore: prefer_const_constructors
|
||||
final fileSetDiffA = FileSetDiff(
|
||||
addedPaths: const {'a'},
|
||||
changedPaths: const {'b'},
|
||||
removedPaths: const {'c'},
|
||||
);
|
||||
// For the sake of testing equality comparisons avoid using const.
|
||||
// ignore: prefer_const_constructors
|
||||
final fileSetDiffB = FileSetDiff(
|
||||
addedPaths: const {'a'},
|
||||
changedPaths: const {'b'},
|
||||
removedPaths: const {'c'},
|
||||
);
|
||||
// For the sake of testing equality comparisons avoid using const.
|
||||
// ignore: prefer_const_constructors
|
||||
final fileSetDiffC = FileSetDiff(
|
||||
addedPaths: const {'c'},
|
||||
changedPaths: const {'b'},
|
||||
removedPaths: const {'a'},
|
||||
);
|
||||
expect(fileSetDiffA, equals(fileSetDiffB));
|
||||
expect(fileSetDiffA, isNot(equals(fileSetDiffC)));
|
||||
expect(fileSetDiffB, isNot(equals(fileSetDiffC)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ void main() {
|
||||
IosFrameworkPatcher,
|
||||
() {
|
||||
late AotTools aotTools;
|
||||
late Apple apple;
|
||||
late ArgParser argParser;
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
@@ -69,6 +70,7 @@ void main() {
|
||||
body,
|
||||
values: {
|
||||
aotToolsRef.overrideWith(() => aotTools),
|
||||
appleRef.overrideWith(() => apple),
|
||||
artifactBuilderRef.overrideWith(() => artifactBuilder),
|
||||
artifactManagerRef.overrideWith(() => artifactManager),
|
||||
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
|
||||
@@ -97,6 +99,7 @@ void main() {
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
apple = MockApple();
|
||||
aotTools = MockAotTools();
|
||||
argParser = MockArgParser();
|
||||
argResults = MockArgResults();
|
||||
@@ -126,6 +129,9 @@ void main() {
|
||||
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
|
||||
when(() => shorebirdEnv.buildDirectory).thenReturn(
|
||||
Directory(p.join(projectRoot.path, 'build')),
|
||||
);
|
||||
when(
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
@@ -634,17 +640,19 @@ void main() {
|
||||
)..createSync(recursive: true);
|
||||
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
() => apple.runLinker(
|
||||
kernelFile: any(named: 'kernelFile'),
|
||||
aotOutputFile: any(named: 'aotOutputFile'),
|
||||
releaseArtifact: any(named: 'releaseArtifact'),
|
||||
splitDebugInfoArgs: any(named: 'splitDebugInfoArgs'),
|
||||
vmCodeFile: any(named: 'vmCodeFile'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
).thenAnswer(
|
||||
(_) async => (
|
||||
exitCode: ExitCode.success.code,
|
||||
linkPercentage: linkPercentage
|
||||
),
|
||||
);
|
||||
when(
|
||||
aotTools.isGeneratePatchDiffBaseSupported,
|
||||
).thenAnswer((_) async => false);
|
||||
@@ -663,144 +671,6 @@ void main() {
|
||||
).thenReturn(genSnapshotFile.path);
|
||||
});
|
||||
|
||||
group('when linking fails', () {
|
||||
group('when aot snapshot does not exist', () {
|
||||
test('logs error and exits with code 70', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(that: startsWith('Unable to find patch AOT file at')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when analyzeSnapshot binary does not exist', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
).thenReturn('');
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('logs error and exits with code 70', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err('Unable to find analyze_snapshot at '),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --split-debug-info is provided', () {
|
||||
final tempDirectory = Directory.systemTemp.createTempSync();
|
||||
final splitDebugInfoPath = p.join(tempDirectory.path, 'symbols');
|
||||
final splitDebugInfoFile = File(
|
||||
p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'),
|
||||
);
|
||||
setUp(() {
|
||||
when(
|
||||
() => argResults.wasParsed(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
),
|
||||
).thenReturn(true);
|
||||
when(
|
||||
() => argResults[CommonArguments.splitDebugInfoArg.name],
|
||||
).thenReturn(splitDebugInfoPath);
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('forwards correct args to linker', () async {
|
||||
try {
|
||||
await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: analyzeSnapshotFile.path,
|
||||
genSnapshot: genSnapshotFile.path,
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: [
|
||||
'--dwarf-stack-traces',
|
||||
'--resolve-dwarf-paths',
|
||||
'--save-debugging-info=${splitDebugInfoFile.path}',
|
||||
],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when call to aotTools.link fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('logs error and exits with code 70', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => progress.fail(
|
||||
'Failed to link AOT files: Exception: oops',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when generate patch diff base is supported', () {
|
||||
setUp(() {
|
||||
when(
|
||||
@@ -855,6 +725,56 @@ void main() {
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('calls runLinker with correct arguments', () async {
|
||||
await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => apple.runLinker(
|
||||
kernelFile: any(
|
||||
named: 'kernelFile',
|
||||
that: isA<File>().having(
|
||||
(f) => f.path,
|
||||
'path',
|
||||
p.join(projectRoot.path, 'build', 'app.dill'),
|
||||
),
|
||||
),
|
||||
aotOutputFile: any(
|
||||
named: 'aotOutputFile',
|
||||
that: isA<File>().having(
|
||||
(f) => f.path,
|
||||
'path',
|
||||
p.join(projectRoot.path, 'build', 'out.aot'),
|
||||
),
|
||||
),
|
||||
releaseArtifact: any(
|
||||
named: 'releaseArtifact',
|
||||
that: isA<File>().having(
|
||||
(f) => f.path,
|
||||
'path',
|
||||
endsWith(
|
||||
p.join('ios-arm64', 'App.framework', 'App'),
|
||||
),
|
||||
),
|
||||
),
|
||||
vmCodeFile: any(
|
||||
named: 'vmCodeFile',
|
||||
that: isA<File>().having(
|
||||
(f) => f.path,
|
||||
'path',
|
||||
p.join(projectRoot.path, 'build', 'out.vmcode'),
|
||||
),
|
||||
),
|
||||
splitDebugInfoArgs: [],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('returns linked patch artifact in patch bundle', () async {
|
||||
final patchBundle = await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
|
||||
@@ -142,6 +142,9 @@ void main() {
|
||||
when(
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
when(() => shorebirdEnv.buildDirectory).thenReturn(
|
||||
Directory(p.join(projectRoot.path, 'build')),
|
||||
);
|
||||
|
||||
when(aotTools.isLinkDebugInfoSupported).thenAnswer((_) async => false);
|
||||
|
||||
@@ -1001,18 +1004,19 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
)..createSync(recursive: true);
|
||||
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
() => apple.runLinker(
|
||||
kernelFile: any(named: 'kernelFile'),
|
||||
aotOutputFile: any(named: 'aotOutputFile'),
|
||||
releaseArtifact: any(named: 'releaseArtifact'),
|
||||
splitDebugInfoArgs: any(named: 'splitDebugInfoArgs'),
|
||||
vmCodeFile: any(named: 'vmCodeFile'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
).thenAnswer(
|
||||
(_) async => (
|
||||
exitCode: ExitCode.success.code,
|
||||
linkPercentage: linkPercentage
|
||||
),
|
||||
);
|
||||
when(
|
||||
aotTools.isGeneratePatchDiffBaseSupported,
|
||||
).thenAnswer((_) async => false);
|
||||
@@ -1095,144 +1099,6 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when aot snapshot does not exist', () {
|
||||
test('logs error and exits with code 70', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(that: startsWith('Unable to find patch AOT file at')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when analyzeSnapshot binary does not exist', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
).thenReturn('');
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('logs error and exits with code 70', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err('Unable to find analyze_snapshot at '),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --split-debug-info is provided', () {
|
||||
final tempDirectory = Directory.systemTemp.createTempSync();
|
||||
final splitDebugInfoPath = p.join(tempDirectory.path, 'symbols');
|
||||
final splitDebugInfoFile = File(
|
||||
p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'),
|
||||
);
|
||||
setUp(() {
|
||||
when(
|
||||
() => argResults.wasParsed(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
),
|
||||
).thenReturn(true);
|
||||
when(
|
||||
() => argResults[CommonArguments.splitDebugInfoArg.name],
|
||||
).thenReturn(splitDebugInfoPath);
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('forwards correct args to linker', () async {
|
||||
try {
|
||||
await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: analyzeSnapshotFile.path,
|
||||
genSnapshot: genSnapshotFile.path,
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: [
|
||||
'--dwarf-stack-traces',
|
||||
'--resolve-dwarf-paths',
|
||||
'--save-debugging-info=${splitDebugInfoFile.path}',
|
||||
],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when call to aotTools.link fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
|
||||
setUpProjectRootArtifacts();
|
||||
});
|
||||
|
||||
test('logs error and exits with code 70', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => progress.fail(
|
||||
'Failed to link AOT files: Exception: oops',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when generate patch diff base is supported', () {
|
||||
@@ -1418,119 +1284,6 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
});
|
||||
});
|
||||
|
||||
group('when isLinkDebugInfoSupported is true', () {
|
||||
setUp(() {
|
||||
when(
|
||||
aotTools.isLinkDebugInfoSupported,
|
||||
).thenAnswer((_) async => true);
|
||||
});
|
||||
|
||||
test('dumps debug info', () async {
|
||||
await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(
|
||||
named: 'dumpDebugInfoPath',
|
||||
that: isNotNull,
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(
|
||||
that: contains(
|
||||
'Link debug info saved to',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when aot_tools link fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(
|
||||
named: 'dumpDebugInfoPath',
|
||||
that: isNotNull,
|
||||
),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
});
|
||||
|
||||
test('dumps debug info and logs', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(
|
||||
that: contains(
|
||||
'Link debug info saved to',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when isLinkDebugInfoSupported is false', () {
|
||||
setUp(() {
|
||||
when(aotTools.isLinkDebugInfoSupported)
|
||||
.thenAnswer((_) async => false);
|
||||
});
|
||||
|
||||
test('does not pass dumpDebugInfoPath to aotTools.link',
|
||||
() async {
|
||||
await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when code signing the patch', () {
|
||||
setUp(() {
|
||||
final privateKey = File(
|
||||
|
||||
@@ -150,6 +150,9 @@ void main() {
|
||||
when(
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
when(
|
||||
() => shorebirdEnv.buildDirectory,
|
||||
).thenReturn(Directory(p.join(projectRoot.path, 'build')));
|
||||
when(
|
||||
() => shorebirdEnv.flutterRevision,
|
||||
).thenReturn('5c1dcc19ebcee3565c65262dd95970186e4d81cc');
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
@@ -710,20 +711,24 @@ void main() {
|
||||
|
||||
group('when has link percentage', () {
|
||||
const linkPercentage = 42.1337;
|
||||
final debugInfoFile = File('debug-info.txt');
|
||||
late Directory buildDirectory;
|
||||
|
||||
setUp(() {
|
||||
buildDirectory = Directory.systemTemp.createTempSync();
|
||||
when(() => shorebirdEnv.buildDirectory).thenReturn(buildDirectory);
|
||||
when(() => patcher.linkPercentage).thenReturn(linkPercentage);
|
||||
when(() => patcher.debugInfoFile).thenReturn(debugInfoFile);
|
||||
});
|
||||
|
||||
test('logs correct summary', () async {
|
||||
final debugInfoFile = File(
|
||||
p.join(buildDirectory.path, 'patch-debug.zip'),
|
||||
);
|
||||
final expectedSummary = [
|
||||
'''📱 App: ${lightCyan.wrap(appDisplayName)} ${lightCyan.wrap('($appId)')}''',
|
||||
'📦 Release Version: ${lightCyan.wrap(releaseVersion)}',
|
||||
'''🕹️ Platform: ${lightCyan.wrap(patcher.releaseType.releasePlatform.displayName)} ${lightCyan.wrap('[arm32 (42 B)]')}''',
|
||||
'🟢 Track: ${lightCyan.wrap('Stable')}',
|
||||
'''🔍 Debug Info: ${lightCyan.wrap(patcher.debugInfoFile.path)}''',
|
||||
'''🔍 Debug Info: ${lightCyan.wrap(debugInfoFile.path)}''',
|
||||
];
|
||||
await expectLater(
|
||||
runWithOverrides(
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:io/io.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/executables/executables.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_artifacts.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -18,21 +23,34 @@ void main() {
|
||||
});
|
||||
|
||||
group(Apple, () {
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
late AotTools aotTools;
|
||||
late Apple apple;
|
||||
late Progress progress;
|
||||
late ShorebirdArtifacts shorebirdArtifacts;
|
||||
late ShorebirdLogger logger;
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
|
||||
R runWithOverrides<R>(R Function() body) {
|
||||
return runScoped(
|
||||
body,
|
||||
values: {
|
||||
aotToolsRef.overrideWith(() => aotTools),
|
||||
loggerRef.overrideWith(() => logger),
|
||||
shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts),
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
aotTools = MockAotTools();
|
||||
apple = Apple();
|
||||
progress = MockProgress();
|
||||
logger = MockShorebirdLogger();
|
||||
shorebirdArtifacts = MockShorebirdArtifacts();
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
});
|
||||
|
||||
group(MissingXcodeProjectException, () {
|
||||
@@ -280,5 +298,238 @@ To add macOS, run "flutter create . --platforms macos"''',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('runLinker', () {
|
||||
const linkPercentage = 42.0;
|
||||
|
||||
late Directory buildDirectory;
|
||||
late File aotOutputFile;
|
||||
late File analyzeSnapshotFile;
|
||||
late File genSnapshotFile;
|
||||
|
||||
setUp(() {
|
||||
buildDirectory = Directory.systemTemp.createTempSync();
|
||||
aotOutputFile = File(p.join(buildDirectory.path, 'out.aot'))
|
||||
..createSync();
|
||||
analyzeSnapshotFile = File(
|
||||
p.join(buildDirectory.path, 'analyze_snapshot'),
|
||||
)..createSync();
|
||||
genSnapshotFile = File(
|
||||
p.join(buildDirectory.path, 'gen_snapshot'),
|
||||
)..createSync();
|
||||
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
).thenReturn(analyzeSnapshotFile.path);
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.genSnapshotIos,
|
||||
),
|
||||
).thenReturn(genSnapshotFile.path);
|
||||
|
||||
when(() => shorebirdEnv.buildDirectory).thenReturn(buildDirectory);
|
||||
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
when(
|
||||
() => aotTools.isLinkDebugInfoSupported(),
|
||||
).thenAnswer((_) async => false);
|
||||
});
|
||||
|
||||
group('when aot snapshot does not exist', () {
|
||||
test('logs error and exits with code 70', () async {
|
||||
final result = await runWithOverrides(
|
||||
() => apple.runLinker(
|
||||
aotOutputFile: File('missing'),
|
||||
kernelFile: File('missing'),
|
||||
releaseArtifact: File('missing'),
|
||||
vmCodeFile: File('missing'),
|
||||
splitDebugInfoArgs: [],
|
||||
),
|
||||
);
|
||||
expect(result.exitCode, equals(ExitCode.software.code));
|
||||
expect(result.linkPercentage, isNull);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(that: startsWith('Unable to find patch AOT file at')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when analyzeSnapshot binary does not exist', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshotIos,
|
||||
),
|
||||
).thenReturn('');
|
||||
});
|
||||
|
||||
test('logs error and exits with code 70', () async {
|
||||
final result = await runWithOverrides(
|
||||
() => apple.runLinker(
|
||||
aotOutputFile: aotOutputFile,
|
||||
kernelFile: File('missing'),
|
||||
releaseArtifact: File('missing'),
|
||||
vmCodeFile: File('missing'),
|
||||
splitDebugInfoArgs: [],
|
||||
),
|
||||
);
|
||||
expect(result.exitCode, equals(ExitCode.software.code));
|
||||
expect(result.linkPercentage, isNull);
|
||||
|
||||
verify(
|
||||
() => logger.err('Unable to find analyze_snapshot at '),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --split-debug-info is provided', () {
|
||||
final tempDirectory = Directory.systemTemp.createTempSync();
|
||||
final splitDebugInfoPath = p.join(tempDirectory.path, 'symbols');
|
||||
final splitDebugInfoFile = File(
|
||||
p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'),
|
||||
);
|
||||
final splitDebugInfoArgs = [
|
||||
'--dwarf-stack-traces',
|
||||
'--resolve-dwarf-paths',
|
||||
'--save-debugging-info=${splitDebugInfoFile.path}',
|
||||
];
|
||||
|
||||
test('forwards correct args to linker', () async {
|
||||
try {
|
||||
await runWithOverrides(
|
||||
() => apple.runLinker(
|
||||
aotOutputFile: aotOutputFile,
|
||||
kernelFile: File('missing'),
|
||||
releaseArtifact: File('missing'),
|
||||
vmCodeFile: File('missing'),
|
||||
splitDebugInfoArgs: splitDebugInfoArgs,
|
||||
),
|
||||
);
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: analyzeSnapshotFile.path,
|
||||
genSnapshot: genSnapshotFile.path,
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: splitDebugInfoArgs,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when isLinkDebugInfoSupported is true', () {
|
||||
setUp(() {
|
||||
when(
|
||||
aotTools.isLinkDebugInfoSupported,
|
||||
).thenAnswer((_) async => true);
|
||||
});
|
||||
|
||||
test('dumps debug info', () async {
|
||||
await runWithOverrides(
|
||||
() => apple.runLinker(
|
||||
aotOutputFile: aotOutputFile,
|
||||
kernelFile: File('missing'),
|
||||
releaseArtifact: File('missing'),
|
||||
vmCodeFile: File('missing'),
|
||||
splitDebugInfoArgs: [],
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(
|
||||
named: 'dumpDebugInfoPath',
|
||||
that: isNotNull,
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: contains('Link debug info saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when call to aotTools.link fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
patch: any(named: 'patch'),
|
||||
analyzeSnapshot: any(named: 'analyzeSnapshot'),
|
||||
genSnapshot: any(named: 'genSnapshot'),
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
});
|
||||
|
||||
test('logs error and exits with code 70', () async {
|
||||
await runWithOverrides(
|
||||
() => apple.runLinker(
|
||||
aotOutputFile: aotOutputFile,
|
||||
kernelFile: File('missing'),
|
||||
releaseArtifact: File('missing'),
|
||||
vmCodeFile: File('missing'),
|
||||
splitDebugInfoArgs: [],
|
||||
),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => progress.fail(
|
||||
'Failed to link AOT files: Exception: oops',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when call to aotTools.link succeeds', () {
|
||||
test('completes and exits with code 0', () async {
|
||||
final result = await runWithOverrides(
|
||||
() => apple.runLinker(
|
||||
aotOutputFile: aotOutputFile,
|
||||
kernelFile: File('missing'),
|
||||
releaseArtifact: File('missing'),
|
||||
vmCodeFile: File('missing'),
|
||||
splitDebugInfoArgs: [],
|
||||
),
|
||||
);
|
||||
expect(result.exitCode, equals(ExitCode.success.code));
|
||||
expect(result.linkPercentage, equals(linkPercentage));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,6 +216,24 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('buildDirectory', () {
|
||||
test('returns correct path', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
File(p.join(tempDir.path, 'pubspec.yaml')).createSync(recursive: true);
|
||||
Directory(p.join(tempDir.path, 'build')).createSync(recursive: true);
|
||||
final buildDirectory = IOOverrides.runZoned(
|
||||
() => runWithOverrides(
|
||||
() => shorebirdEnv.buildDirectory,
|
||||
),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
expect(
|
||||
buildDirectory.path,
|
||||
equals(p.join(tempDir.path, 'build')),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('macosPodfileLockFile', () {
|
||||
test('returns correct path', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
|
||||
Reference in New Issue
Block a user