fix(shorebird_cli): support --split-debug-info (#2570)

This commit is contained in:
Felix Angelov
2024-10-23 14:44:13 -05:00
committed by GitHub
parent af8a8d18cb
commit 8b50e3b381
23 changed files with 461 additions and 32 deletions
@@ -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 {}
+2 -2
View File
@@ -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