fix(shorebird_cli): support --split-debug-info (#2570)
This commit is contained in:
@@ -406,11 +406,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
Future<File> buildElfAotSnapshot({
|
||||
required String appDillPath,
|
||||
required String outFilePath,
|
||||
List<String> additionalArgs = const [],
|
||||
}) async {
|
||||
final arguments = [
|
||||
'--deterministic',
|
||||
'--snapshot-kind=app-aot-elf',
|
||||
'--elf=$outFilePath',
|
||||
...additionalArgs,
|
||||
appDillPath,
|
||||
];
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class AarPatcher extends Patcher {
|
||||
/// {@macro aar_patcher}
|
||||
AarPatcher({
|
||||
required super.argResults,
|
||||
required super.argParser,
|
||||
required super.flavor,
|
||||
required super.target,
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ class AndroidPatcher extends Patcher {
|
||||
/// {@macro android_patcher}
|
||||
AndroidPatcher({
|
||||
required super.argResults,
|
||||
required super.argParser,
|
||||
required super.flavor,
|
||||
required super.target,
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
/// {@macro ios_framework_patcher}
|
||||
IosFrameworkPatcher({
|
||||
required super.argResults,
|
||||
required super.argParser,
|
||||
required super.flavor,
|
||||
required super.target,
|
||||
});
|
||||
@@ -108,6 +109,9 @@ class IosFrameworkPatcher extends Patcher {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
try {
|
||||
if (splitDebugInfoPath != null) {
|
||||
Directory(splitDebugInfoPath!).createSync(recursive: true);
|
||||
}
|
||||
await artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: buildResult.kernelFile.path,
|
||||
outFilePath: p.join(
|
||||
@@ -115,6 +119,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
'build',
|
||||
'out.aot',
|
||||
),
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
buildProgress.fail('$error');
|
||||
@@ -271,6 +276,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
kernel: _appDillCopyPath,
|
||||
outputPath: _vmcodeOutputPath,
|
||||
workingDirectory: buildDirectory.path,
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
|
||||
@@ -42,6 +42,7 @@ class IosPatcher extends Patcher {
|
||||
/// {@macro ios_patcher}
|
||||
IosPatcher({
|
||||
required super.argResults,
|
||||
required super.argParser,
|
||||
required super.flavor,
|
||||
required super.target,
|
||||
});
|
||||
@@ -52,6 +53,26 @@ class IosPatcher extends Patcher {
|
||||
|
||||
String get _appDillCopyPath => p.join(buildDirectory.path, 'app.dill');
|
||||
|
||||
/// The name of the split debug info file when the target is iOS.
|
||||
static const splitDebugInfoFileName = 'app.ios-arm64.symbols';
|
||||
|
||||
/// The additional gen_snapshot arguments to use when building the patch with
|
||||
/// `--split-debug-info`.
|
||||
static List<String> splitDebugInfoArgs(String? splitDebugInfoPath) {
|
||||
return splitDebugInfoPath != null
|
||||
? [
|
||||
'--dwarf-stack-traces',
|
||||
'--resolve-dwarf-paths',
|
||||
'''--save-debugging-info=${saveDebuggingInfoPath(splitDebugInfoPath)}''',
|
||||
]
|
||||
: <String>[];
|
||||
}
|
||||
|
||||
/// The path to save the split debug info file.
|
||||
static String saveDebuggingInfoPath(String directory) {
|
||||
return p.join(p.absolute(directory), splitDebugInfoFileName);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
double? lastBuildLinkPercentage;
|
||||
|
||||
@@ -186,9 +207,13 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
}
|
||||
|
||||
try {
|
||||
if (splitDebugInfoPath != null) {
|
||||
Directory(splitDebugInfoPath!).createSync(recursive: true);
|
||||
}
|
||||
await artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: ipaBuildResult.kernelFile.path,
|
||||
outFilePath: _aotOutputPath,
|
||||
additionalArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
buildProgress.fail('$error');
|
||||
@@ -398,6 +423,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
workingDirectory: buildDirectory.path,
|
||||
kernel: kernelFile.path,
|
||||
dumpDebugInfoPath: dumpDebugInfoDir?.path,
|
||||
additionalArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
|
||||
@@ -123,6 +123,10 @@ of the iOS app that is using this module.''',
|
||||
..addOption(
|
||||
CommonArguments.publicKeyArg.name,
|
||||
help: CommonArguments.publicKeyArg.description,
|
||||
)
|
||||
..addOption(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
help: CommonArguments.splitDebugInfoArg.description,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,24 +182,28 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
case ReleaseType.android:
|
||||
return AndroidPatcher(
|
||||
argResults: results,
|
||||
argParser: argParser,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
);
|
||||
case ReleaseType.ios:
|
||||
return IosPatcher(
|
||||
argResults: results,
|
||||
argParser: argParser,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
);
|
||||
case ReleaseType.iosFramework:
|
||||
return IosFrameworkPatcher(
|
||||
argResults: results,
|
||||
argParser: argParser,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
);
|
||||
case ReleaseType.aar:
|
||||
return AarPatcher(
|
||||
argResults: results,
|
||||
argParser: argParser,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/common_arguments.dart';
|
||||
import 'package:shorebird_cli/src/deployment_track.dart';
|
||||
import 'package:shorebird_cli/src/extensions/arg_results.dart';
|
||||
import 'package:shorebird_cli/src/extensions/iterable.dart';
|
||||
import 'package:shorebird_cli/src/metadata/metadata.dart';
|
||||
import 'package:shorebird_cli/src/patch_diff_checker.dart';
|
||||
@@ -24,6 +25,7 @@ import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
abstract class Patcher {
|
||||
/// {@macro patcher}
|
||||
Patcher({
|
||||
required this.argParser,
|
||||
required this.argResults,
|
||||
required this.flavor,
|
||||
required this.target,
|
||||
@@ -41,6 +43,9 @@ ${iOSLinkPercentageUrl.toLink()}
|
||||
''';
|
||||
}
|
||||
|
||||
/// The parser for the arguments passed to the command.
|
||||
final ArgParser argParser;
|
||||
|
||||
/// The arguments passed to the command.
|
||||
final ArgResults argResults;
|
||||
|
||||
@@ -124,6 +129,14 @@ ${iOSLinkPercentageUrl.toLink()}
|
||||
/// step has not yet been run.
|
||||
double? get linkPercentage => null;
|
||||
|
||||
/// The value of `--split-debug-info-path` if specified.
|
||||
String? get splitDebugInfoPath {
|
||||
return argResults.findOption(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
argParser: argParser,
|
||||
);
|
||||
}
|
||||
|
||||
/// The build directory of the respective shorebird project.
|
||||
Directory get buildDirectory {
|
||||
return Directory(
|
||||
|
||||
@@ -132,6 +132,10 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
|
||||
..addOption(
|
||||
CommonArguments.publicKeyArg.name,
|
||||
help: CommonArguments.publicKeyArg.description,
|
||||
)
|
||||
..addOption(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
help: CommonArguments.splitDebugInfoArg.description,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,18 @@ The path for a public key .pem file that will be used to validate patch signatur
|
||||
name: 'private-key-path',
|
||||
description: '''
|
||||
The path for a private key .pem file that will be used to sign the patch artifact.
|
||||
''',
|
||||
);
|
||||
|
||||
/// An argument that allows the user to specify a directory where program
|
||||
/// symbols are stored.
|
||||
static const splitDebugInfoArg = ArgumentDescriber(
|
||||
name: 'split-debug-info',
|
||||
description: '''
|
||||
In a release build, this flag reduces application size by storing Dart program symbols in a separate file on the host rather than
|
||||
in the application. The value of the flag should be a directory where program symbol files can be stored for later use. These
|
||||
symbol files contain the information needed to symbolize Dart stack traces. For an app built with this flag, the "flutter
|
||||
symbolize" command with the right program symbol file is required to obtain a human readable stack trace.
|
||||
''',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,6 +243,7 @@ class AotTools {
|
||||
required String outputPath,
|
||||
String? workingDirectory,
|
||||
String? dumpDebugInfoPath,
|
||||
List<String> additionalArgs = const [],
|
||||
}) async {
|
||||
// We use the json lines format. https://jsonlines.org
|
||||
const linkJson = 'link.jsonl';
|
||||
@@ -263,6 +264,10 @@ class AotTools {
|
||||
'--redirect-to=${p.join(outputDir, linkJson)}',
|
||||
],
|
||||
if (dumpDebugInfoPath != null) '--dump-debug-info=$dumpDebugInfoPath',
|
||||
if (additionalArgs.isNotEmpty) ...[
|
||||
'--',
|
||||
...additionalArgs,
|
||||
]
|
||||
],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
@@ -137,6 +137,7 @@ extension ForwardedArgs on ArgResults {
|
||||
..._argsNamed(CommonArguments.dartDefineFromFileArg.name),
|
||||
..._argsNamed(CommonArguments.buildNameArg.name),
|
||||
..._argsNamed(CommonArguments.buildNumberArg.name),
|
||||
..._argsNamed(CommonArguments.splitDebugInfoArg.name),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -999,10 +999,35 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
|
||||
).thenReturn('gen_snapshot');
|
||||
});
|
||||
|
||||
test('passes additional args to gen_snapshot', () async {
|
||||
await runWithOverrides(
|
||||
() => builder.buildElfAotSnapshot(
|
||||
appDillPath: '/app/dill/path',
|
||||
outFilePath: '/path/to/out',
|
||||
additionalArgs: ['--foo', 'bar'],
|
||||
),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => shorebirdProcess.run(
|
||||
'gen_snapshot',
|
||||
[
|
||||
'--deterministic',
|
||||
'--snapshot-kind=app-aot-elf',
|
||||
'--elf=/path/to/out',
|
||||
'--foo',
|
||||
'bar',
|
||||
'/app/dill/path',
|
||||
],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when build fails', () {
|
||||
setUp(() {
|
||||
when(() => buildProcessResult.exitCode)
|
||||
.thenReturn(ExitCode.software.code);
|
||||
when(
|
||||
() => buildProcessResult.exitCode,
|
||||
).thenReturn(ExitCode.software.code);
|
||||
});
|
||||
|
||||
test('throws ArtifactBuildException', () {
|
||||
|
||||
@@ -284,6 +284,20 @@ void main() {
|
||||
|
||||
expect(download.progress, emitsInOrder([1 / 3, 2 / 3, 3 / 3]));
|
||||
});
|
||||
|
||||
test('uses outputPath when specified', () async {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final outputPath = p.join(tempDir.path, 'output-file.txt');
|
||||
final download = await runWithOverrides(
|
||||
() => artifactManager.startFileDownload(
|
||||
Uri.parse('https://example.com'),
|
||||
outputPath: outputPath,
|
||||
),
|
||||
);
|
||||
final file = await download.file;
|
||||
expect(file.path, equals(outputPath));
|
||||
expect(file.lengthSync(), equals(3));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ void main() {
|
||||
const packageName = 'com.example.my_flutter_module';
|
||||
const buildNumber = '1.0';
|
||||
|
||||
late ArgParser argParser;
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
late ArtifactManager artifactManager;
|
||||
@@ -93,6 +94,7 @@ void main() {
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
argParser = MockArgParser();
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
artifactManager = MockArtifactManager();
|
||||
@@ -117,7 +119,12 @@ void main() {
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
|
||||
patcher = AarPatcher(argResults: argResults, flavor: null, target: null);
|
||||
patcher = AarPatcher(
|
||||
argParser: argParser,
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
);
|
||||
});
|
||||
|
||||
group('buildNumber', () {
|
||||
|
||||
@@ -37,6 +37,7 @@ import '../../mocks.dart';
|
||||
|
||||
void main() {
|
||||
group(AndroidPatcher, () {
|
||||
late ArgParser argParser;
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
late ArtifactManager artifactManager;
|
||||
@@ -116,6 +117,7 @@ void main() {
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
argParser = MockArgParser();
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
artifactManager = MockArtifactManager();
|
||||
@@ -144,6 +146,7 @@ void main() {
|
||||
).thenReturn(projectRoot);
|
||||
|
||||
patcher = AndroidPatcher(
|
||||
argParser: argParser,
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
|
||||
@@ -42,6 +42,7 @@ void main() {
|
||||
IosFrameworkPatcher,
|
||||
() {
|
||||
late AotTools aotTools;
|
||||
late ArgParser argParser;
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
late ArtifactManager artifactManager;
|
||||
@@ -96,6 +97,7 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
aotTools = MockAotTools();
|
||||
argParser = MockArgParser();
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
artifactManager = MockArtifactManager();
|
||||
@@ -115,13 +117,11 @@ void main() {
|
||||
shorebirdValidator = MockShorebirdValidator();
|
||||
xcodeBuild = MockXcodeBuild();
|
||||
|
||||
when(() => argParser.options).thenReturn({});
|
||||
|
||||
when(() => argResults['build-number']).thenReturn('1.0');
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => argResults.wasParsed(any())).thenReturn(false);
|
||||
when(() => argResults.wasParsed(CommonArguments.privateKeyArg.name))
|
||||
.thenReturn(false);
|
||||
when(() => argResults.wasParsed(CommonArguments.publicKeyArg.name))
|
||||
.thenReturn(false);
|
||||
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
|
||||
@@ -130,6 +130,7 @@ void main() {
|
||||
).thenReturn(projectRoot);
|
||||
|
||||
patcher = IosFrameworkPatcher(
|
||||
argParser: argParser,
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -356,6 +357,7 @@ void main() {
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
outFilePath: any(named: 'outFilePath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenThrow(const FileSystemException('error'));
|
||||
});
|
||||
@@ -390,6 +392,7 @@ void main() {
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
outFilePath: any(named: 'outFilePath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(invocation) async =>
|
||||
@@ -400,8 +403,44 @@ void main() {
|
||||
Directory(
|
||||
p.join(projectRoot.path, ArtifactManager.appXcframeworkName),
|
||||
).createSync(recursive: true);
|
||||
when(() => artifactManager.getAppXcframeworkDirectory())
|
||||
.thenReturn(projectRoot);
|
||||
when(
|
||||
() => artifactManager.getAppXcframeworkDirectory(),
|
||||
).thenReturn(projectRoot);
|
||||
});
|
||||
|
||||
group('when --split-debug-info is provided', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final splitDebugInfoPath = p.join(tempDir.path, 'symbols');
|
||||
final splitDebugInfoFile = File(
|
||||
p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'),
|
||||
);
|
||||
setUp(() {
|
||||
when(
|
||||
() => argResults.wasParsed(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
),
|
||||
).thenReturn(true);
|
||||
when(
|
||||
() => argResults['split-debug-info'],
|
||||
).thenReturn(splitDebugInfoPath);
|
||||
});
|
||||
|
||||
test('forwards --split-debug-info to builder', () async {
|
||||
try {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
} catch (_) {}
|
||||
verify(
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
outFilePath: any(named: 'outFilePath'),
|
||||
additionalArgs: [
|
||||
'--dwarf-stack-traces',
|
||||
'--resolve-dwarf-paths',
|
||||
'--save-debugging-info=${splitDebugInfoFile.path}',
|
||||
],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when platform was specified via arg results rest', () {
|
||||
@@ -562,10 +601,12 @@ void main() {
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
when(() => shorebirdEnv.flutterRevision)
|
||||
.thenReturn(postLinkerFlutterRevision);
|
||||
when(
|
||||
() => shorebirdEnv.flutterRevision,
|
||||
).thenReturn(postLinkerFlutterRevision);
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshot,
|
||||
@@ -628,6 +669,54 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
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(
|
||||
|
||||
@@ -48,6 +48,7 @@ void main() {
|
||||
IosPatcher,
|
||||
() {
|
||||
late AotTools aotTools;
|
||||
late ArgParser argParser;
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
late ArtifactManager artifactManager;
|
||||
@@ -107,6 +108,7 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
aotTools = MockAotTools();
|
||||
argParser = MockArgParser();
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
artifactManager = MockArtifactManager();
|
||||
@@ -128,6 +130,8 @@ void main() {
|
||||
shorebirdValidator = MockShorebirdValidator();
|
||||
xcodeBuild = MockXcodeBuild();
|
||||
|
||||
when(() => argParser.options).thenReturn({});
|
||||
|
||||
when(() => argResults.options).thenReturn([]);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => argResults.wasParsed(any())).thenReturn(false);
|
||||
@@ -143,6 +147,7 @@ void main() {
|
||||
when(aotTools.isLinkDebugInfoSupported).thenAnswer((_) async => false);
|
||||
|
||||
patcher = IosPatcher(
|
||||
argParser: argParser,
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -665,6 +670,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
outFilePath: any(named: 'outFilePath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(invocation) async =>
|
||||
@@ -673,6 +679,41 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
);
|
||||
});
|
||||
|
||||
group('when --split-debug-info is provided', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final splitDebugInfoPath = p.join(tempDir.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);
|
||||
});
|
||||
|
||||
test('forwards --split-debug-info to builder', () async {
|
||||
try {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
} catch (_) {}
|
||||
verify(
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
outFilePath: any(named: 'outFilePath'),
|
||||
additionalArgs: [
|
||||
'--dwarf-stack-traces',
|
||||
'--resolve-dwarf-paths',
|
||||
'--save-debugging-info=${splitDebugInfoFile.path}',
|
||||
],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when releaseVersion is provided', () {
|
||||
test('forwards --build-name and --build-number to builder',
|
||||
() async {
|
||||
@@ -718,8 +759,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
|
||||
group('when the key pair is provided', () {
|
||||
setUp(() {
|
||||
when(() => codeSigner.base64PublicKey(any()))
|
||||
.thenReturn('public_key_encoded');
|
||||
when(
|
||||
() => codeSigner.base64PublicKey(any()),
|
||||
).thenReturn('public_key_encoded');
|
||||
});
|
||||
|
||||
test('calls the buildIpa passing the key', () async {
|
||||
@@ -730,13 +772,13 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
final key = createTempFile('public.pem')
|
||||
..writeAsStringSync('public_key');
|
||||
|
||||
when(() => argResults[CommonArguments.publicKeyArg.name])
|
||||
.thenReturn(key.path);
|
||||
when(() => argResults[CommonArguments.publicKeyArg.name])
|
||||
.thenReturn(key.path);
|
||||
await runWithOverrides(
|
||||
patcher.buildPatchArtifact,
|
||||
);
|
||||
when(
|
||||
() => argResults[CommonArguments.publicKeyArg.name],
|
||||
).thenReturn(key.path);
|
||||
when(
|
||||
() => argResults[CommonArguments.publicKeyArg.name],
|
||||
).thenReturn(key.path);
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
|
||||
verify(
|
||||
() => artifactBuilder.buildIpa(
|
||||
@@ -950,6 +992,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
when(
|
||||
@@ -983,8 +1026,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
),
|
||||
),
|
||||
);
|
||||
when(() => shorebirdEnv.flutterRevision)
|
||||
.thenReturn(postLinkerFlutterRevision);
|
||||
when(
|
||||
() => shorebirdEnv.flutterRevision,
|
||||
).thenReturn(postLinkerFlutterRevision);
|
||||
when(
|
||||
() => shorebirdArtifacts.getArtifactPath(
|
||||
artifact: ShorebirdArtifact.analyzeSnapshot,
|
||||
@@ -1080,6 +1124,54 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
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(
|
||||
@@ -1092,6 +1184,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
dumpDebugInfoPath: any(named: 'dumpDebugInfoPath'),
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
|
||||
|
||||
@@ -319,8 +319,9 @@ void main() {
|
||||
test('validates successfully', () async {
|
||||
await runWithOverrides(() => command.createPatch(patcher));
|
||||
|
||||
verify(() => shorebirdValidator.validateFlavors(flavorArg: null))
|
||||
.called(1);
|
||||
verify(
|
||||
() => shorebirdValidator.validateFlavors(flavorArg: null),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -333,8 +334,9 @@ void main() {
|
||||
test('validates successfully', () async {
|
||||
await runWithOverrides(() => command.createPatch(patcher));
|
||||
|
||||
verify(() => shorebirdValidator.validateFlavors(flavorArg: flavor))
|
||||
.called(1);
|
||||
verify(
|
||||
() => shorebirdValidator.validateFlavors(flavorArg: flavor),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -382,8 +384,9 @@ void main() {
|
||||
when(
|
||||
() => argResults.wasParsed(CommonArguments.publicKeyArg.name),
|
||||
).thenReturn(false);
|
||||
when(() => argResults[CommonArguments.privateKeyArg.name])
|
||||
.thenReturn(createTempFile('private.pem').path);
|
||||
when(
|
||||
() => argResults[CommonArguments.privateKeyArg.name],
|
||||
).thenReturn(createTempFile('private.pem').path);
|
||||
|
||||
await expectLater(
|
||||
runWithOverrides(() => command.createPatch(patcher)),
|
||||
@@ -408,8 +411,9 @@ void main() {
|
||||
when(
|
||||
() => argResults.wasParsed(CommonArguments.publicKeyArg.name),
|
||||
).thenReturn(true);
|
||||
when(() => argResults[CommonArguments.publicKeyArg.name])
|
||||
.thenReturn(createTempFile('public.pem').path);
|
||||
when(
|
||||
() => argResults[CommonArguments.publicKeyArg.name],
|
||||
).thenReturn(createTempFile('public.pem').path);
|
||||
|
||||
await expectLater(
|
||||
runWithOverrides(() => command.createPatch(patcher)),
|
||||
|
||||
@@ -26,6 +26,7 @@ void main() {
|
||||
test('defaults to null', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: MockArgResults(),
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -39,6 +40,7 @@ void main() {
|
||||
test('has no validations by default', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: MockArgResults(),
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -59,6 +61,7 @@ void main() {
|
||||
test('returns an empty list', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: MockArgResults(),
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -72,6 +75,7 @@ void main() {
|
||||
test('returns an empty list', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -90,6 +94,7 @@ void main() {
|
||||
test('returns an empty list', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -107,6 +112,7 @@ void main() {
|
||||
test('returns an empty list', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -122,6 +128,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -150,6 +157,7 @@ void main() {
|
||||
test('returns an empty list', () {
|
||||
expect(
|
||||
_TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: argResults,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -167,6 +175,7 @@ void main() {
|
||||
'with correct args', () async {
|
||||
final args = MockArgResults();
|
||||
final patcher = _TestPatcher(
|
||||
argParser: MockArgParser(),
|
||||
argResults: args,
|
||||
flavor: null,
|
||||
target: null,
|
||||
@@ -219,6 +228,7 @@ void main() {
|
||||
|
||||
class _TestPatcher extends Patcher {
|
||||
_TestPatcher({
|
||||
required super.argParser,
|
||||
required super.argResults,
|
||||
required super.flavor,
|
||||
required super.target,
|
||||
|
||||
@@ -205,6 +205,74 @@ stderr: error''',
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('passes additional args to underlying process', () async {
|
||||
when(
|
||||
() => process.run(
|
||||
aotToolsPath,
|
||||
any(),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
return const ShorebirdProcessResult(
|
||||
exitCode: 0,
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
);
|
||||
});
|
||||
when(
|
||||
() => process.start(
|
||||
aotToolsPath,
|
||||
any(),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async {
|
||||
final mockProcess = MockProcess();
|
||||
when(() => mockProcess.exitCode).thenAnswer((_) async => 0);
|
||||
when(
|
||||
() => mockProcess.stdout,
|
||||
).thenAnswer((_) => const Stream.empty());
|
||||
when(
|
||||
() => mockProcess.stderr,
|
||||
).thenAnswer((_) => const Stream.empty());
|
||||
|
||||
return mockProcess;
|
||||
},
|
||||
);
|
||||
await expectLater(
|
||||
runWithOverrides(
|
||||
() => aotTools.link(
|
||||
base: base,
|
||||
patch: patch,
|
||||
analyzeSnapshot: analyzeSnapshot,
|
||||
genSnapshot: genSnapshot,
|
||||
kernel: kernel,
|
||||
workingDirectory: workingDirectory.path,
|
||||
outputPath: outputPath,
|
||||
additionalArgs: ['--foo', 'bar'],
|
||||
),
|
||||
),
|
||||
completes,
|
||||
);
|
||||
verify(
|
||||
() => process.start(
|
||||
aotToolsPath,
|
||||
[
|
||||
'link',
|
||||
'--base=$base',
|
||||
'--patch=$patch',
|
||||
'--analyze-snapshot=$analyzeSnapshot',
|
||||
'--output=$outputPath',
|
||||
'--verbose',
|
||||
'--',
|
||||
'--foo',
|
||||
'bar',
|
||||
],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('forwards stdout from aot_tools link to the logger', () async {
|
||||
when(
|
||||
() => process.start(
|
||||
|
||||
@@ -107,6 +107,10 @@ void main() {
|
||||
CommonArguments.buildNumberArg.name,
|
||||
help: CommonArguments.buildNumberArg.description,
|
||||
)
|
||||
..addOption(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
help: CommonArguments.splitDebugInfoArg.description,
|
||||
)
|
||||
..addMultiOption(
|
||||
'platforms',
|
||||
allowed: ReleaseType.values.map((e) => e.cliName),
|
||||
@@ -230,5 +234,36 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when split-debug-info is provided before the --', () {
|
||||
test('forwards it', () {
|
||||
final args = [
|
||||
'--verbose',
|
||||
'--split-debug-info=build/symbols',
|
||||
];
|
||||
final result = parser.parse(args);
|
||||
expect(result.forwardedArgs, hasLength(1));
|
||||
expect(
|
||||
result.forwardedArgs,
|
||||
contains('--split-debug-info=build/symbols'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when split-debug-info is provided after the --', () {
|
||||
test('forwards it', () {
|
||||
final args = [
|
||||
'--verbose',
|
||||
'--',
|
||||
'--split-debug-info=build/symbols',
|
||||
];
|
||||
final result = parser.parse(args);
|
||||
expect(result.forwardedArgs, hasLength(1));
|
||||
expect(
|
||||
result.forwardedArgs,
|
||||
contains('--split-debug-info=build/symbols'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ class MockAppleDevice extends Mock implements AppleDevice {}
|
||||
|
||||
class MockArchiveDiffer extends Mock implements ArchiveDiffer {}
|
||||
|
||||
class MockArgParser extends Mock implements ArgParser {}
|
||||
|
||||
class MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class MockArtifactBuilder extends Mock implements ArtifactBuilder {}
|
||||
|
||||
@@ -37,7 +37,7 @@ echo "base_url: https://api-dev.shorebird.dev" >> shorebird.yaml
|
||||
APP_ID=$(cat shorebird.yaml | grep 'app_id:' | awk '{print $2}')
|
||||
|
||||
# Create a new release on Android
|
||||
shorebird release android --flutter-version=$FLUTTER_VERSION -v
|
||||
shorebird release android --flutter-version=$FLUTTER_VERSION --split-debug-info=./build/symbols -v
|
||||
|
||||
# Run the app on Android and ensure that the print statement is printed.
|
||||
while IFS= read line; do
|
||||
@@ -55,7 +55,7 @@ echo "lib/main.dart is now:"
|
||||
cat lib/main.dart
|
||||
|
||||
# Create a patch
|
||||
shorebird patch android -v
|
||||
shorebird patch android --split-debug-info=./build/symbols -v
|
||||
|
||||
# Run the app on Android and ensure that the original print statement is printed.
|
||||
while IFS= read line; do
|
||||
|
||||
Reference in New Issue
Block a user