feat(shorebird_cli): make --obfuscate a first-class CLI flag (#3626)
This commit is contained in:
@@ -1 +1 @@
|
||||
22f15e658f317f11954ee1933083a8d6a1bd7aae
|
||||
14f2a1545b43ddf6ba5d825a4a79310c74806a90
|
||||
|
||||
@@ -17,6 +17,7 @@ words:
|
||||
- armeabi
|
||||
- Azul
|
||||
- backboardd
|
||||
- bdero
|
||||
- bintools
|
||||
- bitcode
|
||||
- bryanoltman
|
||||
@@ -105,10 +106,12 @@ words:
|
||||
- sideload
|
||||
- sideloadable
|
||||
- sideloaded
|
||||
- sideloading
|
||||
- signup
|
||||
- SIGSTOP
|
||||
- storepass # From .github/workflows/e2e.yaml
|
||||
- storyboardc
|
||||
- symbolication
|
||||
- syskeys # From adb.dart
|
||||
- subosito # From .github dir, doesn't show up in "**" check?
|
||||
- swiftshader # From .github dir, doesn't show up in "**" check?
|
||||
@@ -119,6 +122,7 @@ words:
|
||||
- udid # Unique Device Identifier
|
||||
- unawaited
|
||||
- unmockable
|
||||
- unobfuscated
|
||||
- unpadded
|
||||
- Unpatchable
|
||||
- unsets
|
||||
|
||||
@@ -368,24 +368,41 @@ class ArtifactManager {
|
||||
return ipaFiles.single;
|
||||
}
|
||||
|
||||
/// Returns the shorebird release supplement directory for the given
|
||||
/// [platformSubdir] (e.g. 'ios', 'macos', 'android').
|
||||
///
|
||||
/// If [create] is true, the directory is created if it doesn't exist.
|
||||
/// Returns null if the directory doesn't exist and [create] is false.
|
||||
Directory? getReleaseSupplementDirectory({
|
||||
required String platformSubdir,
|
||||
bool create = false,
|
||||
}) {
|
||||
final projectRoot = shorebirdEnv.getShorebirdProjectRoot();
|
||||
if (projectRoot == null) return null;
|
||||
final releaseSupplementDir = Directory(
|
||||
p.join(projectRoot.path, 'build', platformSubdir, 'shorebird'),
|
||||
);
|
||||
|
||||
if (!releaseSupplementDir.existsSync()) {
|
||||
if (create) {
|
||||
releaseSupplementDir.createSync(recursive: true);
|
||||
} else {
|
||||
logger.detail(
|
||||
'No release supplements found at ${releaseSupplementDir.path}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return releaseSupplementDir;
|
||||
}
|
||||
|
||||
/// Returns the path to the shorebird release supplement directory for iOS.
|
||||
///
|
||||
/// Returns null if there is no supplement directory
|
||||
/// (e.g. when using older Flutter revisions).
|
||||
Directory? getIosReleaseSupplementDirectory() {
|
||||
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
|
||||
final releaseSupplementDir = Directory(
|
||||
p.join(projectRoot.path, 'build', 'ios', 'shorebird'),
|
||||
);
|
||||
|
||||
if (!releaseSupplementDir.existsSync()) {
|
||||
logger.detail(
|
||||
'No iOS release supplements found at ${releaseSupplementDir.path}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return releaseSupplementDir;
|
||||
return getReleaseSupplementDirectory(platformSubdir: 'ios');
|
||||
}
|
||||
|
||||
/// Returns the path to the shorebird release supplement directory for macOS.
|
||||
@@ -393,19 +410,7 @@ class ArtifactManager {
|
||||
/// Returns null if there is no supplement directory
|
||||
/// (e.g. when using older Flutter revisions).
|
||||
Directory? getMacosReleaseSupplementDirectory() {
|
||||
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
|
||||
final releaseSupplementDir = Directory(
|
||||
p.join(projectRoot.path, 'build', 'macos', 'shorebird'),
|
||||
);
|
||||
|
||||
if (!releaseSupplementDir.existsSync()) {
|
||||
logger.detail(
|
||||
'No macOS release supplements found at ${releaseSupplementDir.path}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return releaseSupplementDir;
|
||||
return getReleaseSupplementDirectory(platformSubdir: 'macos');
|
||||
}
|
||||
|
||||
/// Name of the App.xcframework generated by `shorebird release ios-framework`
|
||||
|
||||
@@ -744,7 +744,6 @@ aar artifact already exists, continuing...''');
|
||||
required String runnerPath,
|
||||
required bool isCodesigned,
|
||||
required String? podfileLockHash,
|
||||
required String? supplementPath,
|
||||
}) async {
|
||||
final createArtifactProgress = logger.progress('Uploading artifacts');
|
||||
final thinnedArchiveDirectory = await _thinXcarchive(
|
||||
@@ -791,30 +790,6 @@ aar artifact already exists, continuing...''');
|
||||
);
|
||||
}
|
||||
|
||||
if (supplementPath != null) {
|
||||
final zippedSupplement = await Directory(
|
||||
supplementPath,
|
||||
).zipToTempFile(name: 'ios_supplement');
|
||||
try {
|
||||
await codePushClient.createReleaseArtifact(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
artifactPath: zippedSupplement.path,
|
||||
arch: 'ios_supplement',
|
||||
platform: ReleasePlatform.ios,
|
||||
hash: sha256.convert(await zippedSupplement.readAsBytes()).toString(),
|
||||
canSideload: false,
|
||||
podfileLockHash: podfileLockHash,
|
||||
);
|
||||
} catch (error) {
|
||||
_handleErrorAndExit(
|
||||
error,
|
||||
progress: createArtifactProgress,
|
||||
message: 'Error uploading release supplements: $error',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
createArtifactProgress.complete();
|
||||
}
|
||||
|
||||
@@ -824,7 +799,6 @@ aar artifact already exists, continuing...''');
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required String appFrameworkPath,
|
||||
required String? supplementPath,
|
||||
}) async {
|
||||
final createArtifactProgress = logger.progress('Uploading artifacts');
|
||||
final appFrameworkDirectory = Directory(appFrameworkPath);
|
||||
@@ -850,33 +824,50 @@ aar artifact already exists, continuing...''');
|
||||
);
|
||||
}
|
||||
|
||||
if (supplementPath != null) {
|
||||
final zippedSupplement = await Directory(
|
||||
supplementPath,
|
||||
).zipToTempFile(name: 'ios_framework_supplement');
|
||||
try {
|
||||
await codePushClient.createReleaseArtifact(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
artifactPath: zippedSupplement.path,
|
||||
arch: 'ios_framework_supplement',
|
||||
platform: ReleasePlatform.ios,
|
||||
hash: sha256.convert(await zippedSupplement.readAsBytes()).toString(),
|
||||
canSideload: false,
|
||||
podfileLockHash: null,
|
||||
);
|
||||
} catch (error) {
|
||||
_handleErrorAndExit(
|
||||
error,
|
||||
progress: createArtifactProgress,
|
||||
message: 'Error uploading release supplements: $error',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
createArtifactProgress.complete();
|
||||
}
|
||||
|
||||
/// Zips and uploads a supplement directory as a release artifact.
|
||||
Future<void> createSupplementReleaseArtifact({
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required ReleasePlatform platform,
|
||||
required String supplementDirectoryPath,
|
||||
required String arch,
|
||||
}) async {
|
||||
final createSupplementProgress = logger.progress(
|
||||
'Uploading supplement artifacts',
|
||||
);
|
||||
final zippedSupplement = await Directory(
|
||||
supplementDirectoryPath,
|
||||
).zipToTempFile(name: arch);
|
||||
try {
|
||||
await codePushClient.createReleaseArtifact(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
artifactPath: zippedSupplement.path,
|
||||
arch: arch,
|
||||
platform: platform,
|
||||
hash: sha256.convert(await zippedSupplement.readAsBytes()).toString(),
|
||||
// Supplements are auxiliary snapshot metadata used during patching and
|
||||
// can't produce a working app on their own, so sideloading isn't
|
||||
// applicable.
|
||||
canSideload: false,
|
||||
// Supplement artifacts contain only Dart snapshot metadata (e.g. class
|
||||
// tables, dispatch tables) and have no dependency on native pods, so
|
||||
// the podfile lock hash is not applicable here.
|
||||
podfileLockHash: null,
|
||||
);
|
||||
} catch (error) {
|
||||
_handleErrorAndExit(
|
||||
error,
|
||||
progress: createSupplementProgress,
|
||||
message: 'Error uploading supplement artifacts: $error',
|
||||
);
|
||||
}
|
||||
createSupplementProgress.complete();
|
||||
}
|
||||
|
||||
/// Creates a patch for the given [appId], [releaseId], and [metadata].
|
||||
@visibleForTesting
|
||||
Future<Patch> createPatch({
|
||||
|
||||
@@ -40,6 +40,9 @@ class AarPatcher extends Patcher {
|
||||
@override
|
||||
String get primaryReleaseArtifactArch => 'aar';
|
||||
|
||||
@override
|
||||
String? get supplementaryReleaseArtifactArch => 'aar_supplement';
|
||||
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.aar;
|
||||
|
||||
@@ -75,9 +78,10 @@ class AarPatcher extends Patcher {
|
||||
|
||||
@override
|
||||
Future<File> buildPatchArtifact({String? releaseVersion}) async {
|
||||
final buildArgs = [...argResults.forwardedArgs, ...extraBuildArgs];
|
||||
await artifactBuilder.buildAar(
|
||||
buildNumber: buildNumber,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
);
|
||||
|
||||
@@ -94,7 +98,7 @@ class AarPatcher extends Patcher {
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) async {
|
||||
final releaseArtifacts = await codePushClientWrapper.getReleaseArtifacts(
|
||||
appId: appId,
|
||||
|
||||
@@ -51,6 +51,9 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir
|
||||
@override
|
||||
String get primaryReleaseArtifactArch => 'aab';
|
||||
|
||||
@override
|
||||
String? get supplementaryReleaseArtifactArch => 'android_supplement';
|
||||
|
||||
@override
|
||||
Future<DiffStatus> assertUnpatchableDiffs({
|
||||
required ReleaseArtifact releaseArtifact,
|
||||
@@ -87,12 +90,15 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir
|
||||
logger.warn(updaterPatchErrorWarning);
|
||||
}
|
||||
|
||||
final buildArgs = [
|
||||
...argResults.forwardedArgs,
|
||||
...extraBuildArgs,
|
||||
...buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
|
||||
];
|
||||
final aabFile = await artifactBuilder.buildAppBundle(
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
args:
|
||||
argResults.forwardedArgs +
|
||||
buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
|
||||
args: buildArgs,
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
);
|
||||
|
||||
@@ -123,7 +129,7 @@ Looked in:
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
Duration downloadMessageTimeout = const Duration(minutes: 1),
|
||||
}) async {
|
||||
final releaseArtifacts = await codePushClientWrapper.getReleaseArtifacts(
|
||||
|
||||
@@ -104,8 +104,9 @@ class IosFrameworkPatcher extends Patcher {
|
||||
|
||||
@override
|
||||
Future<File> buildPatchArtifact({String? releaseVersion}) async {
|
||||
final buildArgs = [...argResults.forwardedArgs, ...extraBuildArgs];
|
||||
final buildResult = await artifactBuilder.buildIosFramework(
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
);
|
||||
|
||||
@@ -116,7 +117,10 @@ class IosFrameworkPatcher extends Patcher {
|
||||
appDillPath: buildResult.kernelFile.path,
|
||||
outFilePath: _aotOutputPath,
|
||||
genSnapshotArtifact: ShorebirdArtifact.genSnapshotIos,
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
additionalArgs: [
|
||||
...IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
...obfuscationGenSnapshotArgs,
|
||||
],
|
||||
);
|
||||
|
||||
// Copy the kernel file to the build directory so that it can be used
|
||||
@@ -136,7 +140,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) async {
|
||||
final unzipProgress = logger.progress('Extracting release artifact');
|
||||
late final String releaseXcframeworkPath;
|
||||
@@ -149,13 +153,8 @@ class IosFrameworkPatcher extends Patcher {
|
||||
releaseXcframeworkPath = tempDir.path;
|
||||
}
|
||||
|
||||
final releaseSupplementDir = Directory.systemTemp.createTempSync();
|
||||
if (supplementArtifact != null) {
|
||||
await artifactManager.extractZip(
|
||||
zipFile: supplementArtifact,
|
||||
outputDirectory: releaseSupplementDir,
|
||||
);
|
||||
}
|
||||
final releaseSupplementDir =
|
||||
supplementDirectory ?? Directory.systemTemp.createTempSync();
|
||||
|
||||
unzipProgress.complete(
|
||||
'Extracted release artifact to $releaseXcframeworkPath',
|
||||
@@ -180,7 +179,10 @@ class IosFrameworkPatcher extends Patcher {
|
||||
final result = await apple.runLinker(
|
||||
kernelFile: File(_appDillCopyPath),
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
splitDebugInfoArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
splitDebugInfoArgs: [
|
||||
...IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
...obfuscationGenSnapshotArgs,
|
||||
],
|
||||
aotOutputFile: File(_aotOutputPath),
|
||||
vmCodeFile: File(_vmcodeOutputPath),
|
||||
);
|
||||
|
||||
@@ -167,15 +167,19 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
final buildArgs = [
|
||||
...argResults.forwardedArgs,
|
||||
...extraBuildArgs,
|
||||
...buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
|
||||
];
|
||||
|
||||
// If buildIpa is called with a different codesign value than the
|
||||
// release was, we will erroneously report native diffs.
|
||||
final ipaBuildResult = await artifactBuilder.buildIpa(
|
||||
codesign: shouldCodesign,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
args:
|
||||
argResults.forwardedArgs +
|
||||
buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
|
||||
args: buildArgs,
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
);
|
||||
|
||||
@@ -186,7 +190,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
appDillPath: ipaBuildResult.kernelFile.path,
|
||||
outFilePath: _aotOutputPath,
|
||||
genSnapshotArtifact: ShorebirdArtifact.genSnapshotIos,
|
||||
additionalArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
additionalArgs: [
|
||||
...splitDebugInfoArgs(splitDebugInfoPath),
|
||||
...obfuscationGenSnapshotArgs,
|
||||
],
|
||||
);
|
||||
|
||||
// Copy the kernel file to the build directory so that it can be used
|
||||
@@ -201,7 +208,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) async {
|
||||
// Verify that we have built a patch .xcarchive
|
||||
if (artifactManager.getXcarchiveDirectory()?.path == null) {
|
||||
@@ -221,13 +228,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
releaseXcarchivePath = tempDir.path;
|
||||
}
|
||||
|
||||
final releaseSupplementDir = Directory.systemTemp.createTempSync();
|
||||
if (supplementArtifact != null) {
|
||||
await artifactManager.extractZip(
|
||||
zipFile: supplementArtifact,
|
||||
outputDirectory: releaseSupplementDir,
|
||||
);
|
||||
}
|
||||
final releaseSupplementDir =
|
||||
supplementDirectory ?? Directory.systemTemp.createTempSync();
|
||||
|
||||
unzipProgress.complete();
|
||||
final appDirectory = artifactManager.getIosAppDirectory(
|
||||
@@ -253,7 +255,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
final result = await apple.runLinker(
|
||||
kernelFile: File(_appDillCopyPath),
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
splitDebugInfoArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
splitDebugInfoArgs: [
|
||||
...splitDebugInfoArgs(splitDebugInfoPath),
|
||||
...obfuscationGenSnapshotArgs,
|
||||
],
|
||||
aotOutputFile: File(_aotOutputPath),
|
||||
vmCodeFile: File(_vmcodeOutputPath),
|
||||
);
|
||||
|
||||
@@ -51,6 +51,7 @@ class LinuxPatcher extends Patcher {
|
||||
Future<File> buildPatchArtifact({String? releaseVersion}) async {
|
||||
await artifactBuilder.buildLinuxApp(
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
args: extraBuildArgs,
|
||||
);
|
||||
return artifactManager.linuxBundleDirectory.zipToTempFile();
|
||||
}
|
||||
@@ -60,7 +61,7 @@ class LinuxPatcher extends Patcher {
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) async {
|
||||
final createDiffProgress = logger.progress('Creating patch artifacts');
|
||||
final patchArtifactPath = p.join(
|
||||
@@ -122,6 +123,9 @@ class LinuxPatcher extends Patcher {
|
||||
@override
|
||||
String get primaryReleaseArtifactArch => 'bundle';
|
||||
|
||||
@override
|
||||
String? get supplementaryReleaseArtifactArch => 'linux_supplement';
|
||||
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.linux;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ class MacosPatcher extends Patcher {
|
||||
@override
|
||||
String get primaryReleaseArtifactArch => 'app';
|
||||
|
||||
@override
|
||||
String? get supplementaryReleaseArtifactArch => 'macos_supplement';
|
||||
|
||||
@override
|
||||
Future<void> assertPreconditions() async {
|
||||
try {
|
||||
@@ -135,15 +138,19 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
final buildArgs = [
|
||||
...argResults.forwardedArgs,
|
||||
...extraBuildArgs,
|
||||
...buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
|
||||
];
|
||||
|
||||
// If buildMacos is called with a different codesign value than the
|
||||
// release was, we will erroneously report native diffs.
|
||||
final macosBuildResult = await artifactBuilder.buildMacos(
|
||||
codesign: codesign,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
args:
|
||||
argResults.forwardedArgs +
|
||||
buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
|
||||
args: buildArgs,
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
);
|
||||
|
||||
@@ -154,6 +161,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
appDillPath: macosBuildResult.kernelFile.path,
|
||||
outFilePath: _arm64AotOutputPath,
|
||||
genSnapshotArtifact: ShorebirdArtifact.genSnapshotMacosArm64,
|
||||
additionalArgs: [
|
||||
...IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
...obfuscationGenSnapshotArgs,
|
||||
],
|
||||
);
|
||||
|
||||
if (!File(_arm64AotOutputPath).existsSync()) {
|
||||
@@ -164,6 +175,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
appDillPath: macosBuildResult.kernelFile.path,
|
||||
outFilePath: _x64AotOutputPath,
|
||||
genSnapshotArtifact: ShorebirdArtifact.genSnapshotMacosX64,
|
||||
additionalArgs: [
|
||||
...IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
...obfuscationGenSnapshotArgs,
|
||||
],
|
||||
);
|
||||
|
||||
if (!File(_x64AotOutputPath).existsSync()) {
|
||||
@@ -217,7 +232,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) async {
|
||||
final unzipProgress = logger.progress('Extracting release artifact');
|
||||
final releaseAppDirectory = Directory.systemTemp.createTempSync();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
@@ -153,6 +154,12 @@ To target the latest release (e.g. the release that was most recently updated) u
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
help: CommonArguments.splitDebugInfoArg.description,
|
||||
)
|
||||
..addFlag(
|
||||
CommonArguments.obfuscateArg.name,
|
||||
help: CommonArguments.obfuscateArg.description,
|
||||
negatable: false,
|
||||
hide: true,
|
||||
)
|
||||
..addOption(
|
||||
CommonArguments.minLinkPercentage.name,
|
||||
help: CommonArguments.minLinkPercentage.description,
|
||||
@@ -401,6 +408,83 @@ Building with Flutter $flutterVersionString to determine the release version...
|
||||
? await downloadReleaseArtifact(releaseArtifact: supplementalArtifact)
|
||||
: null;
|
||||
|
||||
// Download and extract the supplement archive (if present).
|
||||
Directory? supplementDirectory;
|
||||
File? obfuscationMapFile;
|
||||
if (supplementArchive != null) {
|
||||
supplementDirectory = Directory.systemTemp.createTempSync();
|
||||
await artifactManager.extractZip(
|
||||
zipFile: supplementArchive,
|
||||
outputDirectory: supplementDirectory,
|
||||
);
|
||||
final candidateMapFile = File(
|
||||
p.join(supplementDirectory.path, 'obfuscation_map.json'),
|
||||
);
|
||||
if (candidateMapFile.existsSync()) {
|
||||
obfuscationMapFile = candidateMapFile;
|
||||
logger.info(
|
||||
'Release was built with obfuscation. '
|
||||
'Applying obfuscation map to patch build.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If the user explicitly passed --obfuscate but the release has no
|
||||
// obfuscation map, the patch would be obfuscated against a non-obfuscated
|
||||
// release, producing a broken patch.
|
||||
// Also check rest for `-- --obfuscate`, which bypasses the parser but
|
||||
// still flows through forwardedArgs to the Flutter build command.
|
||||
final userPassedObfuscate =
|
||||
(results.wasParsed('obfuscate') && results['obfuscate'] == true) ||
|
||||
results.rest.any((a) => a == '--obfuscate');
|
||||
if (userPassedObfuscate && obfuscationMapFile == null) {
|
||||
logger.err(
|
||||
'--obfuscate was passed, but the release was not built with '
|
||||
'obfuscation. A patch cannot change the obfuscation mode of a '
|
||||
'release.',
|
||||
);
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
if (userPassedObfuscate && obfuscationMapFile != null) {
|
||||
logger.info(
|
||||
'--obfuscate is not needed for patching. Obfuscation is applied '
|
||||
'automatically when the release was built with --obfuscate.',
|
||||
);
|
||||
}
|
||||
|
||||
patcher.obfuscationMapPath = obfuscationMapFile?.path;
|
||||
|
||||
// Build extra args to inject into the Flutter build command. These use
|
||||
// --extra-gen-snapshot-options= because they're passed through Flutter's
|
||||
// build system, which forwards them to gen_snapshot. This is distinct from
|
||||
// patcher.obfuscationGenSnapshotArgs, which produces bare gen_snapshot
|
||||
// flags (e.g. --load-obfuscation-map=...) for direct gen_snapshot/linker
|
||||
// calls made by Apple patchers outside the Flutter build.
|
||||
final extraBuildArgs = <String>[];
|
||||
if (obfuscationMapFile != null) {
|
||||
extraBuildArgs.addAll([
|
||||
'--obfuscate',
|
||||
'--extra-gen-snapshot-options='
|
||||
'--load-obfuscation-map=${obfuscationMapFile.path}',
|
||||
// Strip unobfuscated DWARF debug info from the compiled snapshot so
|
||||
// it doesn't leak identifiers that obfuscation was meant to hide.
|
||||
'--extra-gen-snapshot-options=--strip',
|
||||
]);
|
||||
}
|
||||
// Flutter requires --split-debug-info with --obfuscate. Auto-add it
|
||||
// if --obfuscate will be in the build args (from the user or from
|
||||
// the obfuscation map injection above) but --split-debug-info is not.
|
||||
final hasObfuscate =
|
||||
(results.wasParsed('obfuscate') && results['obfuscate'] == true) ||
|
||||
extraBuildArgs.contains('--obfuscate');
|
||||
final hasSplitDebugInfo = results.wasParsed('split-debug-info');
|
||||
if (hasObfuscate && !hasSplitDebugInfo) {
|
||||
extraBuildArgs.add(
|
||||
'--split-debug-info=${p.join('build', 'shorebird', 'symbols')}',
|
||||
);
|
||||
}
|
||||
patcher.extraBuildArgs = extraBuildArgs;
|
||||
|
||||
final releaseFlutterShorebirdEnv = shorebirdEnv.copyWith(
|
||||
flutterRevisionOverride: release.flutterRevision,
|
||||
);
|
||||
@@ -417,7 +501,9 @@ Building with Flutter $flutterVersionString to determine the release version...
|
||||
Building patch with Flutter $flutterVersionString
|
||||
''');
|
||||
patchArtifactFile = await _tryBuildingArtifact<File>(
|
||||
() => patcher.buildPatchArtifact(releaseVersion: release.version),
|
||||
() => patcher.buildPatchArtifact(
|
||||
releaseVersion: release.version,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -431,7 +517,7 @@ Building patch with Flutter $flutterVersionString
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
releaseArtifact: releaseArchive,
|
||||
supplementArtifact: supplementArchive,
|
||||
supplementDirectory: supplementDirectory,
|
||||
);
|
||||
|
||||
final dryRun = results['dry-run'] == true;
|
||||
|
||||
@@ -57,6 +57,34 @@ More info: ${troubleshootingUrl.toLink()}.
|
||||
/// The target script to run, if any.
|
||||
final String? target;
|
||||
|
||||
/// The path to the obfuscation map downloaded from the release, if any.
|
||||
/// Set by the patch command after downloading the map. Used by Apple
|
||||
/// patchers to pass obfuscation flags to gen_snapshot and the linker.
|
||||
String? obfuscationMapPath;
|
||||
|
||||
/// Extra build arguments injected by the patch command. These are included
|
||||
/// in the Flutter build command args by patchers. Currently used to inject
|
||||
/// obfuscation flags when the release was built with obfuscation.
|
||||
List<String> extraBuildArgs = const [];
|
||||
|
||||
/// Additional gen_snapshot arguments needed to match the release's
|
||||
/// obfuscation flags. Used by Apple patchers for [buildElfAotSnapshot]
|
||||
/// and linker calls.
|
||||
List<String> get obfuscationGenSnapshotArgs => [
|
||||
if (obfuscationMapPath != null) ...[
|
||||
'--obfuscate',
|
||||
'--load-obfuscation-map=$obfuscationMapPath',
|
||||
// --dwarf-stack-traces must match the release build so the patch
|
||||
// produces the same stack trace format for correct symbolication.
|
||||
// --split-debug-info already implies --dwarf-stack-traces, so we only
|
||||
// need to pass it as a standalone flag when split debug info isn't used.
|
||||
if (splitDebugInfoPath == null) '--dwarf-stack-traces',
|
||||
// Strip DWARF debug info so obfuscated snapshots don't leak the
|
||||
// identifiers that obfuscation was meant to hide.
|
||||
'--strip',
|
||||
],
|
||||
];
|
||||
|
||||
/// The type of artifact we are creating a release for.
|
||||
ReleaseType get releaseType;
|
||||
|
||||
@@ -96,7 +124,7 @@ More info: ${troubleshootingUrl.toLink()}.
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
});
|
||||
|
||||
/// Updates the provided metadata to include patcher-specific fields.
|
||||
|
||||
@@ -37,6 +37,9 @@ class WindowsPatcher extends Patcher {
|
||||
@override
|
||||
String get primaryReleaseArtifactArch => primaryWindowsReleaseArtifactArch;
|
||||
|
||||
@override
|
||||
String? get supplementaryReleaseArtifactArch => 'windows_supplement';
|
||||
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.windows;
|
||||
|
||||
@@ -71,9 +74,10 @@ class WindowsPatcher extends Patcher {
|
||||
|
||||
@override
|
||||
Future<File> buildPatchArtifact({String? releaseVersion}) async {
|
||||
final buildArgs = [...argResults.forwardedArgs, ...extraBuildArgs];
|
||||
final releaseDir = await artifactBuilder.buildWindowsApp(
|
||||
target: target,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: argResults.encodedPublicKey,
|
||||
);
|
||||
return releaseDir.zipToTempFile();
|
||||
@@ -84,7 +88,7 @@ class WindowsPatcher extends Patcher {
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) async {
|
||||
final createDiffProgress = logger.progress('Creating patch artifacts');
|
||||
final patchArtifactPath = p.join(
|
||||
|
||||
@@ -44,6 +44,12 @@ class AarReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.aar;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'android';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'aar_supplement';
|
||||
|
||||
@override
|
||||
String get artifactDisplayName => 'Android archive';
|
||||
|
||||
@@ -70,17 +76,23 @@ class AarReleaser extends Releaser {
|
||||
logger.err('Missing required argument: --release-version');
|
||||
throw ProcessExit(ExitCode.usage.code);
|
||||
}
|
||||
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FileSystemEntity> buildReleaseArtifacts() async {
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await artifactBuilder.buildAar(
|
||||
buildNumber: buildNumber,
|
||||
targetPlatforms: architectures,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
verifyObfuscationMap();
|
||||
|
||||
// Copy release AAR to a new directory to avoid overwriting with
|
||||
// subsequent patch builds.
|
||||
@@ -126,6 +138,8 @@ class AarReleaser extends Releaser {
|
||||
extractedAarDir: extractedAarDir.path,
|
||||
architectures: architectures,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -31,6 +31,12 @@ class AndroidReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.android;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'android';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'android_supplement';
|
||||
|
||||
@override
|
||||
String get artifactDisplayName => 'Android app bundle';
|
||||
|
||||
@@ -98,26 +104,33 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
|
||||
);
|
||||
throw ProcessExit(ExitCode.unavailable.code);
|
||||
}
|
||||
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FileSystemEntity> buildReleaseArtifacts() async {
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
final aab = await artifactBuilder.buildAppBundle(
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
targetPlatforms: architectures,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
|
||||
verifyObfuscationMap();
|
||||
|
||||
if (generateApk) {
|
||||
logger.info('Building APK');
|
||||
await artifactBuilder.buildApk(
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
targetPlatforms: architectures,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
}
|
||||
@@ -150,12 +163,12 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
|
||||
Future<void> uploadReleaseArtifacts({
|
||||
required String appId,
|
||||
required Release release,
|
||||
}) {
|
||||
}) async {
|
||||
final aabFile = shorebirdAndroidArtifacts.findAab(
|
||||
project: projectRoot,
|
||||
flavor: flavor,
|
||||
);
|
||||
return codePushClientWrapper.createAndroidReleaseArtifacts(
|
||||
await codePushClientWrapper.createAndroidReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
projectRoot: projectRoot.path,
|
||||
@@ -164,6 +177,8 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
|
||||
architectures: architectures,
|
||||
flavor: flavor,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -42,12 +42,20 @@ class IosFrameworkReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.iosFramework;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'ios';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'ios_framework_supplement';
|
||||
|
||||
@override
|
||||
Future<void> assertArgsAreValid() async {
|
||||
if (!argResults.wasParsed('release-version')) {
|
||||
logger.err('Missing required argument: --release-version');
|
||||
throw ProcessExit(ExitCode.usage.code);
|
||||
}
|
||||
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -79,10 +87,14 @@ class IosFrameworkReleaser extends Releaser {
|
||||
}
|
||||
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await artifactBuilder.buildIosFramework(
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
verifyObfuscationMap();
|
||||
|
||||
// Copy release xcframework to a new directory to avoid overwriting with
|
||||
// subsequent patch builds.
|
||||
@@ -117,13 +129,14 @@ class IosFrameworkReleaser extends Releaser {
|
||||
Future<void> uploadReleaseArtifacts({
|
||||
required Release release,
|
||||
required String appId,
|
||||
}) {
|
||||
return codePushClientWrapper.createIosFrameworkReleaseArtifacts(
|
||||
}) async {
|
||||
await codePushClientWrapper.createIosFrameworkReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
appFrameworkPath: p.join(releaseDirectory.path, 'App.xcframework'),
|
||||
supplementPath: artifactManager.getIosReleaseSupplementDirectory()?.path,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -37,6 +37,12 @@ class IosReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.ios;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'ios';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'ios_supplement';
|
||||
|
||||
@override
|
||||
String get artifactDisplayName => 'iOS app';
|
||||
|
||||
@@ -46,22 +52,13 @@ class IosReleaser extends Releaser {
|
||||
logger.err(
|
||||
'''
|
||||
The "--release-version" flag is only supported for aar and ios-framework releases.
|
||||
|
||||
|
||||
To change the version of this release, change your app's version in your pubspec.yaml.''',
|
||||
);
|
||||
throw ProcessExit(ExitCode.usage.code);
|
||||
}
|
||||
|
||||
if (argResults.rest.contains('--obfuscate')) {
|
||||
// Obfuscated releases break patching, so we don't support them.
|
||||
// See https://github.com/shorebirdtech/shorebird/issues/1619
|
||||
logger
|
||||
..err('Shorebird does not currently support obfuscation on iOS.')
|
||||
..info(
|
||||
'''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''',
|
||||
);
|
||||
throw ProcessExit(ExitCode.unavailable.code);
|
||||
}
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -103,14 +100,21 @@ To change the version of this release, change your app's version in your pubspec
|
||||
}
|
||||
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
|
||||
await artifactBuilder.buildIpa(
|
||||
codesign: codesign,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
|
||||
verifyObfuscationMap();
|
||||
|
||||
final xcarchiveDirectory = artifactManager.getXcarchiveDirectory();
|
||||
if (xcarchiveDirectory == null) {
|
||||
logger.err('Unable to find .xcarchive directory');
|
||||
@@ -172,8 +176,9 @@ To change the version of this release, change your app's version in your pubspec
|
||||
.path,
|
||||
isCodesigned: codesign,
|
||||
podfileLockHash: podfileLockHash,
|
||||
supplementPath: artifactManager.getIosReleaseSupplementDirectory()?.path,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -29,6 +29,12 @@ class LinuxReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.linux;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'linux';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'linux_supplement';
|
||||
|
||||
@override
|
||||
String get artifactDisplayName => 'Linux app';
|
||||
|
||||
@@ -43,6 +49,8 @@ To change the version of this release, change your app's version in your pubspec
|
||||
);
|
||||
throw ProcessExit(ExitCode.usage.code);
|
||||
}
|
||||
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -65,11 +73,15 @@ To change the version of this release, change your app's version in your pubspec
|
||||
@override
|
||||
Future<FileSystemEntity> buildReleaseArtifacts() async {
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
await artifactBuilder.buildLinuxApp(
|
||||
target: target,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
verifyObfuscationMap();
|
||||
|
||||
return artifactManager.linuxBundleDirectory;
|
||||
}
|
||||
@@ -92,9 +104,13 @@ Linux release created at ${artifactManager.linuxBundleDirectory.path}.
|
||||
Future<void> uploadReleaseArtifacts({
|
||||
required Release release,
|
||||
required String appId,
|
||||
}) => codePushClientWrapper.createLinuxReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
bundle: artifactManager.linuxBundleDirectory,
|
||||
);
|
||||
}) async {
|
||||
await codePushClientWrapper.createLinuxReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
bundle: artifactManager.linuxBundleDirectory,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,12 @@ class MacosReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.macos;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'macos';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'macos_supplement';
|
||||
|
||||
@override
|
||||
String get artifactDisplayName => 'macOS app';
|
||||
|
||||
@@ -46,22 +52,13 @@ class MacosReleaser extends Releaser {
|
||||
logger.err(
|
||||
'''
|
||||
The "--release-version" flag is only supported for aar and ios-framework releases.
|
||||
|
||||
|
||||
To change the version of this release, change your app's version in your pubspec.yaml.''',
|
||||
);
|
||||
throw ProcessExit(ExitCode.usage.code);
|
||||
}
|
||||
|
||||
if (argResults.rest.contains('--obfuscate')) {
|
||||
// Obfuscated releases break patching, so we don't support them.
|
||||
// See https://github.com/shorebirdtech/shorebird/issues/1619
|
||||
logger
|
||||
..err('Shorebird does not currently support obfuscation on macOS.')
|
||||
..info(
|
||||
'''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''',
|
||||
);
|
||||
throw ProcessExit(ExitCode.unavailable.code);
|
||||
}
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,14 +91,21 @@ To change the version of this release, change your app's version in your pubspec
|
||||
}
|
||||
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
|
||||
await artifactBuilder.buildMacos(
|
||||
codesign: codesign,
|
||||
flavor: flavor,
|
||||
target: target,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
|
||||
verifyObfuscationMap();
|
||||
|
||||
final appDirectory = artifactManager.getMacOSAppDirectory(flavor: flavor);
|
||||
if (appDirectory == null) {
|
||||
logger.err('Unable to find .app directory');
|
||||
@@ -160,6 +164,8 @@ To change the version of this release, change your app's version in your pubspec
|
||||
isCodesigned: codesign,
|
||||
podfileLockHash: podfileLockHash,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -153,6 +153,11 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
|
||||
..addOption(
|
||||
CommonArguments.splitDebugInfoArg.name,
|
||||
help: CommonArguments.splitDebugInfoArg.description,
|
||||
)
|
||||
..addFlag(
|
||||
CommonArguments.obfuscateArg.name,
|
||||
help: CommonArguments.obfuscateArg.description,
|
||||
negatable: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/extensions/arg_results.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/metadata/metadata.dart';
|
||||
import 'package:shorebird_cli/src/release_type.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
|
||||
export 'package:pub_semver/pub_semver.dart';
|
||||
|
||||
/// Minimum Flutter version for obfuscation support across all platforms.
|
||||
final _minimumObfuscationFlutterVersion = Version(3, 41, 2);
|
||||
|
||||
/// {@template releaser}
|
||||
/// Executes platform-specific functionality to create a release.
|
||||
/// {@endtemplate}
|
||||
@@ -87,4 +97,123 @@ abstract class Releaser {
|
||||
///
|
||||
/// Returns null if no public key is configured.
|
||||
Future<String?> getEncodedPublicKey() => argResults.getEncodedPublicKey();
|
||||
|
||||
/// Whether the user is building with obfuscation.
|
||||
bool get useObfuscation => argResults['obfuscate'] == true;
|
||||
|
||||
/// Path where the obfuscation map is saved during obfuscated builds.
|
||||
String get obfuscationMapPath => p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
|
||||
/// Auto-adds --split-debug-info when --obfuscate is used without it.
|
||||
void addSplitDebugInfoDefault(List<String> buildArgs) {
|
||||
if (useObfuscation &&
|
||||
!buildArgs.any((a) => a.startsWith('--split-debug-info'))) {
|
||||
buildArgs.add(
|
||||
'--split-debug-info=${p.join('build', 'shorebird', 'symbols')}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds obfuscation-related gen_snapshot options to [buildArgs].
|
||||
///
|
||||
/// When obfuscation is enabled, passes --save-obfuscation-map to capture the
|
||||
/// mapping and --strip to remove unobfuscated DWARF debugging information
|
||||
/// from the compiled snapshot (the DWARF sections would otherwise leak
|
||||
/// identifiers that obfuscation was meant to hide).
|
||||
void addObfuscationMapArgs(List<String> buildArgs) {
|
||||
if (!useObfuscation) return;
|
||||
final mapDir = Directory(p.dirname(obfuscationMapPath));
|
||||
if (!mapDir.existsSync()) mapDir.createSync(recursive: true);
|
||||
buildArgs.addAll([
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=$obfuscationMapPath',
|
||||
'--extra-gen-snapshot-options=--strip',
|
||||
]);
|
||||
}
|
||||
|
||||
/// Platform subdirectory for the supplement directory (e.g. 'android',
|
||||
/// 'ios'). Used to construct `build/<platformSubdir>/shorebird/`.
|
||||
String get supplementPlatformSubdir;
|
||||
|
||||
/// Arch string for the supplement artifact on the server (e.g.
|
||||
/// 'android_supplement').
|
||||
String get supplementArtifactArch;
|
||||
|
||||
/// Assembles the supplement directory: copies the obfuscation map (if
|
||||
/// present) into the platform supplement dir. Returns the directory, or null
|
||||
/// if empty.
|
||||
Directory? assembleSupplementDirectory() {
|
||||
final obfuscationMapFile = File(obfuscationMapPath);
|
||||
final hasObfuscationMap = useObfuscation && obfuscationMapFile.existsSync();
|
||||
final supplementDir = artifactManager.getReleaseSupplementDirectory(
|
||||
platformSubdir: supplementPlatformSubdir,
|
||||
create: hasObfuscationMap,
|
||||
);
|
||||
if (hasObfuscationMap && supplementDir != null) {
|
||||
obfuscationMapFile.copySync(
|
||||
p.join(supplementDir.path, 'obfuscation_map.json'),
|
||||
);
|
||||
}
|
||||
return supplementDir;
|
||||
}
|
||||
|
||||
/// Uploads the supplement artifact (e.g. obfuscation map) if one was
|
||||
/// assembled. Call this at the end of [uploadReleaseArtifacts].
|
||||
///
|
||||
// TODO(bdero): This is a separate network call from the primary artifact
|
||||
// upload, so an interruption between the two leaves the release without its
|
||||
// supplement. Now that the supplement drives patching decisions (e.g.
|
||||
// obfuscation), we should make this atomic or recoverable.
|
||||
// https://github.com/shorebirdtech/shorebird/issues/3630
|
||||
Future<void> uploadSupplementArtifact({
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
}) async {
|
||||
final supplementDir = assembleSupplementDirectory();
|
||||
if (supplementDir != null) {
|
||||
await codePushClientWrapper.createSupplementReleaseArtifact(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
platform: releaseType.releasePlatform,
|
||||
supplementDirectoryPath: supplementDir.path,
|
||||
arch: supplementArtifactArch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that the current Flutter version supports obfuscation, if
|
||||
/// obfuscation is enabled.
|
||||
Future<void> assertObfuscationIsSupported() async {
|
||||
if (!useObfuscation) return;
|
||||
final flutterVersion = await shorebirdFlutter.resolveFlutterVersion(
|
||||
shorebirdEnv.flutterRevision,
|
||||
);
|
||||
if (flutterVersion != null &&
|
||||
flutterVersion < _minimumObfuscationFlutterVersion) {
|
||||
logger.err(
|
||||
'Obfuscation on ${releaseType.releasePlatform.displayName} requires Flutter '
|
||||
'$_minimumObfuscationFlutterVersion or later '
|
||||
'(current: $flutterVersion).',
|
||||
);
|
||||
throw ProcessExit(ExitCode.unavailable.code);
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the obfuscation map was generated after build.
|
||||
void verifyObfuscationMap() {
|
||||
if (!useObfuscation) return;
|
||||
final mapFile = File(obfuscationMapPath);
|
||||
if (!mapFile.existsSync()) {
|
||||
logger.err(
|
||||
'Obfuscation was enabled but the obfuscation map was not '
|
||||
'generated at $obfuscationMapPath',
|
||||
);
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
logger.detail('Obfuscation map saved to $obfuscationMapPath');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ class WindowsReleaser extends Releaser {
|
||||
@override
|
||||
ReleaseType get releaseType => ReleaseType.windows;
|
||||
|
||||
@override
|
||||
String get supplementPlatformSubdir => 'windows';
|
||||
|
||||
@override
|
||||
String get supplementArtifactArch => 'windows_supplement';
|
||||
|
||||
@override
|
||||
String get artifactDisplayName => 'Windows app';
|
||||
|
||||
@@ -41,11 +47,13 @@ class WindowsReleaser extends Releaser {
|
||||
logger.err(
|
||||
'''
|
||||
The "--release-version" flag is only supported for aar and ios-framework releases.
|
||||
|
||||
|
||||
To change the version of this release, change your app's version in your pubspec.yaml.''',
|
||||
);
|
||||
throw ProcessExit(ExitCode.usage.code);
|
||||
}
|
||||
|
||||
await assertObfuscationIsSupported();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -68,11 +76,16 @@ To change the version of this release, change your app's version in your pubspec
|
||||
@override
|
||||
Future<FileSystemEntity> buildReleaseArtifacts() async {
|
||||
final base64PublicKey = await getEncodedPublicKey();
|
||||
return artifactBuilder.buildWindowsApp(
|
||||
final buildArgs = [...argResults.forwardedArgs];
|
||||
addSplitDebugInfoDefault(buildArgs);
|
||||
addObfuscationMapArgs(buildArgs);
|
||||
final result = await artifactBuilder.buildWindowsApp(
|
||||
target: target,
|
||||
args: argResults.forwardedArgs,
|
||||
args: buildArgs,
|
||||
base64PublicKey: base64PublicKey,
|
||||
);
|
||||
verifyObfuscationMap();
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -107,6 +120,8 @@ To change the version of this release, change your app's version in your pubspec
|
||||
projectRoot: projectRoot.path,
|
||||
releaseZipPath: zippedRelease.path,
|
||||
);
|
||||
|
||||
await uploadSupplementArtifact(appId: appId, releaseId: release.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -137,6 +137,16 @@ Command that reads data from stdin and outputs a base64 signature to stdout.
|
||||
description: 'The version of the release (e.g. "1.0.0").',
|
||||
);
|
||||
|
||||
/// The Flutter --obfuscate argument.
|
||||
static const obfuscateArg = ArgumentDescriber(
|
||||
name: 'obfuscate',
|
||||
description:
|
||||
'In a release build, this flag removes identifiers and replaces '
|
||||
'them with randomized values for the purposes of source code '
|
||||
'obfuscation. This flag must always be combined with '
|
||||
'"--split-debug-info" command, to not break crash reports.',
|
||||
);
|
||||
|
||||
/// An argument that allows the user to specify a directory where program
|
||||
/// symbols are stored.
|
||||
static const splitDebugInfoArg = ArgumentDescriber(
|
||||
|
||||
@@ -251,6 +251,16 @@ extension ForwardedArgs on ArgResults {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `['--$name']` when the boolean flag [name] was parsed and is
|
||||
/// `true`, or an empty iterable otherwise.
|
||||
Iterable<String> _flagNamed(String name) {
|
||||
if (!wasParsed(name)) {
|
||||
return [];
|
||||
}
|
||||
final value = this[name] as bool;
|
||||
return value ? ['--$name'] : [];
|
||||
}
|
||||
|
||||
/// A list of arguments parsed by Shorebird commands that will be forwarded
|
||||
/// to the underlying Flutter commands (that is, placed after `--`).
|
||||
List<String> get forwardedArgs {
|
||||
@@ -269,6 +279,7 @@ extension ForwardedArgs on ArgResults {
|
||||
..._argsNamed(CommonArguments.splitDebugInfoArg.name),
|
||||
..._argsNamed(CommonArguments.exportMethodArg.name),
|
||||
..._argsNamed(CommonArguments.exportOptionsPlistArg.name),
|
||||
..._flagNamed(CommonArguments.obfuscateArg.name),
|
||||
]);
|
||||
|
||||
return forwarded;
|
||||
|
||||
@@ -1836,7 +1836,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
const podfileLockHash = 'podfile-lock-hash';
|
||||
final xcarchivePath = p.join('path', 'to', 'app.xcarchive');
|
||||
final runnerPath = p.join('path', 'to', 'runner.app');
|
||||
final releaseSupplementPath = p.join('path', 'to', 'supplement');
|
||||
|
||||
void setUpProjectRoot({String? flavor}) {
|
||||
Directory(
|
||||
@@ -1845,9 +1844,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
Directory(
|
||||
p.join(projectRoot.path, runnerPath),
|
||||
).createSync(recursive: true);
|
||||
Directory(
|
||||
p.join(projectRoot.path, releaseSupplementPath),
|
||||
).createSync(recursive: true);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
@@ -1895,7 +1891,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
runnerPath: p.join(projectRoot.path, runnerPath),
|
||||
isCodesigned: true,
|
||||
podfileLockHash: podfileLockHash,
|
||||
supplementPath: p.join(projectRoot.path, releaseSupplementPath),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
@@ -1935,7 +1930,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
runnerPath: p.join(projectRoot.path, runnerPath),
|
||||
isCodesigned: false,
|
||||
podfileLockHash: podfileLockHash,
|
||||
supplementPath: p.join(projectRoot.path, releaseSupplementPath),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
@@ -1975,47 +1969,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
runnerPath: p.join(projectRoot.path, runnerPath),
|
||||
isCodesigned: false,
|
||||
podfileLockHash: podfileLockHash,
|
||||
supplementPath: p.join(projectRoot.path, releaseSupplementPath),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(() => progress.fail(any(that: contains(error)))).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'exits with code 70 when supplement artifact creation fails',
|
||||
() async {
|
||||
const error = 'something went wrong';
|
||||
when(
|
||||
() => codePushClient.createReleaseArtifact(
|
||||
appId: any(named: 'appId'),
|
||||
artifactPath: any(
|
||||
named: 'artifactPath',
|
||||
that: endsWith('ios_supplement.zip'),
|
||||
),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
hash: any(named: 'hash'),
|
||||
canSideload: any(named: 'canSideload'),
|
||||
podfileLockHash: any(named: 'podfileLockHash'),
|
||||
),
|
||||
).thenThrow(error);
|
||||
setUpProjectRoot();
|
||||
|
||||
await expectLater(
|
||||
() async => runWithOverrides(
|
||||
() async => codePushClientWrapper.createIosReleaseArtifacts(
|
||||
appId: app.appId,
|
||||
releaseId: releaseId,
|
||||
xcarchivePath: p.join(projectRoot.path, xcarchivePath),
|
||||
runnerPath: p.join(projectRoot.path, runnerPath),
|
||||
isCodesigned: false,
|
||||
podfileLockHash: podfileLockHash,
|
||||
supplementPath: p.join(projectRoot.path, releaseSupplementPath),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
@@ -2048,7 +2001,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
runnerPath: p.join(projectRoot.path, runnerPath),
|
||||
isCodesigned: true,
|
||||
podfileLockHash: podfileLockHash,
|
||||
supplementPath: p.join(projectRoot.path, releaseSupplementPath),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2158,15 +2110,11 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
|
||||
group('createMacosReleaseArtifacts', () {
|
||||
final appPath = p.join('path', 'to', 'Runner.app');
|
||||
final releaseSupplementPath = p.join('path', 'to', 'supplement');
|
||||
|
||||
void setUpProjectRoot({String? flavor}) {
|
||||
Directory(
|
||||
p.join(projectRoot.path, appPath),
|
||||
).createSync(recursive: true);
|
||||
Directory(
|
||||
p.join(projectRoot.path, releaseSupplementPath),
|
||||
).createSync(recursive: true);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
@@ -2240,15 +2188,11 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
|
||||
group('createIosFrameworkReleaseArtifacts', () {
|
||||
final frameworkPath = p.join('path', 'to', 'App.xcframework');
|
||||
final releaseSupplementPath = p.join('path', 'to', 'supplement');
|
||||
|
||||
void setUpProjectRoot({String? flavor}) {
|
||||
Directory(
|
||||
p.join(projectRoot.path, frameworkPath),
|
||||
).createSync(recursive: true);
|
||||
Directory(
|
||||
p.join(projectRoot.path, releaseSupplementPath),
|
||||
).createSync(recursive: true);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
@@ -2289,7 +2233,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
appId: app.appId,
|
||||
releaseId: releaseId,
|
||||
appFrameworkPath: p.join(projectRoot.path, frameworkPath),
|
||||
supplementPath: null,
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
@@ -2297,46 +2240,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'exits with code 70 when supplement artifact creation fails',
|
||||
() async {
|
||||
const error = 'something went wrong';
|
||||
when(
|
||||
() => codePushClient.createReleaseArtifact(
|
||||
appId: any(named: 'appId'),
|
||||
artifactPath: any(
|
||||
named: 'artifactPath',
|
||||
that: endsWith('ios_framework_supplement.zip'),
|
||||
),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
hash: any(named: 'hash'),
|
||||
canSideload: any(named: 'canSideload'),
|
||||
podfileLockHash: any(named: 'podfileLockHash'),
|
||||
),
|
||||
).thenThrow(error);
|
||||
|
||||
await expectLater(
|
||||
() async => runWithOverrides(
|
||||
() async =>
|
||||
codePushClientWrapper.createIosFrameworkReleaseArtifacts(
|
||||
appId: app.appId,
|
||||
releaseId: releaseId,
|
||||
appFrameworkPath: p.join(projectRoot.path, frameworkPath),
|
||||
supplementPath: p.join(
|
||||
projectRoot.path,
|
||||
releaseSupplementPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(() => progress.fail(any(that: contains(error)))).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
test('completes successfully when release artifact is created', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(
|
||||
@@ -2344,7 +2247,6 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
|
||||
appId: app.appId,
|
||||
releaseId: releaseId,
|
||||
appFrameworkPath: p.join(projectRoot.path, frameworkPath),
|
||||
supplementPath: null,
|
||||
),
|
||||
),
|
||||
completes,
|
||||
|
||||
@@ -449,6 +449,48 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('when obfuscationMapPath is set', () {
|
||||
late File obfuscationMapFile;
|
||||
|
||||
setUp(() {
|
||||
obfuscationMapFile =
|
||||
File(
|
||||
p.join(
|
||||
Directory.systemTemp.createTempSync().path,
|
||||
'obfuscation_map.json',
|
||||
),
|
||||
)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"key": "value"}');
|
||||
});
|
||||
|
||||
test(
|
||||
'passes obfuscationGenSnapshotArgs to buildElfAotSnapshot',
|
||||
() async {
|
||||
patcher.obfuscationMapPath = obfuscationMapFile.path;
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
outFilePath: any(named: 'outFilePath'),
|
||||
genSnapshotArtifact: any(named: 'genSnapshotArtifact'),
|
||||
additionalArgs: captureAny(named: 'additionalArgs'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, contains('--obfuscate'));
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith('--load-obfuscation-map='),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('when platform was specified via arg results rest', () {
|
||||
setUp(() {
|
||||
when(() => argResults.rest).thenReturn(['ios', '--verbose']);
|
||||
@@ -539,7 +581,7 @@ void main() {
|
||||
canSideload: true,
|
||||
);
|
||||
late File releaseArtifactFile;
|
||||
late File supplementArtifactFile;
|
||||
late Directory supplementDirectory;
|
||||
|
||||
void setUpProjectRootArtifacts() {
|
||||
File(
|
||||
@@ -582,12 +624,7 @@ void main() {
|
||||
'release.xcframework',
|
||||
),
|
||||
)..createSync(recursive: true);
|
||||
supplementArtifactFile = File(
|
||||
p.join(
|
||||
Directory.systemTemp.createTempSync().path,
|
||||
'ios_framework_supplement.zip',
|
||||
),
|
||||
)..createSync(recursive: true);
|
||||
supplementDirectory = Directory.systemTemp.createTempSync();
|
||||
|
||||
when(
|
||||
() => codePushClientWrapper.getReleaseArtifact(
|
||||
@@ -784,6 +821,57 @@ void main() {
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscationMapPath is set', () {
|
||||
late File obfuscationMapFile;
|
||||
|
||||
setUp(() {
|
||||
obfuscationMapFile =
|
||||
File(
|
||||
p.join(
|
||||
Directory.systemTemp.createTempSync().path,
|
||||
'obfuscation_map.json',
|
||||
),
|
||||
)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"key": "value"}');
|
||||
});
|
||||
|
||||
test(
|
||||
'passes obfuscationGenSnapshotArgs to runLinker',
|
||||
() async {
|
||||
patcher.obfuscationMapPath = obfuscationMapFile.path;
|
||||
await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
|
||||
final captured = verify(
|
||||
() => apple.runLinker(
|
||||
kernelFile: any(named: 'kernelFile'),
|
||||
aotOutputFile: any(named: 'aotOutputFile'),
|
||||
releaseArtifact: any(named: 'releaseArtifact'),
|
||||
vmCodeFile: any(named: 'vmCodeFile'),
|
||||
splitDebugInfoArgs: captureAny(
|
||||
named: 'splitDebugInfoArgs',
|
||||
),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, contains('--obfuscate'));
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith('--load-obfuscation-map='),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('returns linked patch artifact in patch bundle', () async {
|
||||
final patchBundle = await runWithOverrides(
|
||||
() => patcher.createPatchArtifacts(
|
||||
@@ -818,21 +906,12 @@ void main() {
|
||||
p.join(outDir.path, 'ios-arm64', 'App.framework', 'App'),
|
||||
).createSync(recursive: true);
|
||||
});
|
||||
when(
|
||||
() => artifactManager.extractZip(
|
||||
zipFile: supplementArtifactFile,
|
||||
outputDirectory: any(named: 'outputDirectory'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final outDir =
|
||||
invocation.namedArguments[#outputDirectory] as Directory;
|
||||
File(
|
||||
p.join(outDir.path, 'App.ct.link'),
|
||||
).createSync(recursive: true);
|
||||
File(
|
||||
p.join(outDir.path, 'App.class_table.json'),
|
||||
).createSync(recursive: true);
|
||||
});
|
||||
File(
|
||||
p.join(supplementDirectory.path, 'App.ct.link'),
|
||||
).createSync(recursive: true);
|
||||
File(
|
||||
p.join(supplementDirectory.path, 'App.class_table.json'),
|
||||
).createSync(recursive: true);
|
||||
});
|
||||
|
||||
test('returns linked patch artifact in patch bundle', () async {
|
||||
@@ -841,7 +920,7 @@ void main() {
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
supplementArtifact: supplementArtifactFile,
|
||||
supplementDirectory: supplementDirectory,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -864,7 +943,7 @@ void main() {
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
supplementArtifact: supplementArtifactFile,
|
||||
supplementDirectory: supplementDirectory,
|
||||
),
|
||||
);
|
||||
expect(patcher.linkPercentage, isNotNull);
|
||||
|
||||
@@ -758,6 +758,84 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
expect(copiedKernelFile.existsSync(), isTrue);
|
||||
});
|
||||
|
||||
group('when extraBuildArgs has obfuscation flags', () {
|
||||
late File obfuscationMapFile;
|
||||
|
||||
setUp(() {
|
||||
obfuscationMapFile =
|
||||
File(
|
||||
p.join(
|
||||
Directory.systemTemp.createTempSync().path,
|
||||
'obfuscation_map.json',
|
||||
),
|
||||
)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"key": "value"}');
|
||||
});
|
||||
|
||||
test('includes obfuscation flags in build args', () async {
|
||||
patcher.obfuscationMapPath = obfuscationMapFile.path;
|
||||
patcher.extraBuildArgs = [
|
||||
'--obfuscate',
|
||||
'--extra-gen-snapshot-options='
|
||||
'--load-obfuscation-map=${obfuscationMapFile.path}',
|
||||
'--split-debug-info=build/shorebird/symbols',
|
||||
];
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
args: captureAny(named: 'args'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, contains('--obfuscate'));
|
||||
expect(
|
||||
args.any((a) => a.startsWith('--split-debug-info=')),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
args,
|
||||
contains(
|
||||
'--extra-gen-snapshot-options='
|
||||
'--load-obfuscation-map=${obfuscationMapFile.path}',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when extraBuildArgs is empty', () {
|
||||
test('does not inject obfuscation flags', () async {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
args: captureAny(named: 'args'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, isNot(contains('--obfuscate')));
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--load-obfuscation-map',
|
||||
),
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -783,7 +861,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
canSideload: true,
|
||||
);
|
||||
late File releaseArtifactFile;
|
||||
late File supplementArtifactFile;
|
||||
late Directory supplementDirectory;
|
||||
|
||||
void setUpProjectRootArtifacts() {
|
||||
File(
|
||||
@@ -839,12 +917,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
'release.xcarchive',
|
||||
),
|
||||
)..createSync(recursive: true);
|
||||
supplementArtifactFile = File(
|
||||
p.join(
|
||||
Directory.systemTemp.createTempSync().path,
|
||||
'ios_supplement.zip',
|
||||
),
|
||||
)..createSync(recursive: true);
|
||||
supplementDirectory = Directory.systemTemp.createTempSync();
|
||||
|
||||
when(
|
||||
() => codePushClientWrapper.getReleaseArtifact(
|
||||
@@ -1115,21 +1188,12 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
|
||||
group('when class table link info & debug info are present', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => artifactManager.extractZip(
|
||||
zipFile: supplementArtifactFile,
|
||||
outputDirectory: any(named: 'outputDirectory'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final outDir =
|
||||
invocation.namedArguments[#outputDirectory] as Directory;
|
||||
File(
|
||||
p.join(outDir.path, 'App.ct.link'),
|
||||
).createSync(recursive: true);
|
||||
File(
|
||||
p.join(outDir.path, 'App.class_table.json'),
|
||||
).createSync(recursive: true);
|
||||
});
|
||||
File(
|
||||
p.join(supplementDirectory.path, 'App.ct.link'),
|
||||
).createSync(recursive: true);
|
||||
File(
|
||||
p.join(supplementDirectory.path, 'App.class_table.json'),
|
||||
).createSync(recursive: true);
|
||||
});
|
||||
|
||||
test('returns linked patch artifact in patch bundle', () async {
|
||||
@@ -1138,7 +1202,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
supplementArtifact: supplementArtifactFile,
|
||||
supplementDirectory: supplementDirectory,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1160,7 +1224,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
appId: appId,
|
||||
releaseId: releaseId,
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
supplementArtifact: supplementArtifactFile,
|
||||
supplementDirectory: supplementDirectory,
|
||||
),
|
||||
);
|
||||
expect(patcher.linkPercentage, isNotNull);
|
||||
|
||||
@@ -252,6 +252,52 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when extraBuildArgs has obfuscation flags', () {
|
||||
setUp(() {
|
||||
final releaseDir = Directory(
|
||||
p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'linux',
|
||||
'x64',
|
||||
'release',
|
||||
'bundle',
|
||||
),
|
||||
)..createSync(recursive: true);
|
||||
when(
|
||||
() => artifactManager.linuxBundleDirectory,
|
||||
).thenReturn(releaseDir);
|
||||
when(
|
||||
() => artifactBuilder.buildLinuxApp(
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer((_) async => {});
|
||||
});
|
||||
|
||||
test('passes extraBuildArgs to buildLinuxApp', () async {
|
||||
patcher.extraBuildArgs = [
|
||||
'--obfuscate',
|
||||
'--split-debug-info=build/shorebird/symbols',
|
||||
];
|
||||
await runWithOverrides(() => patcher.buildPatchArtifact());
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildLinuxApp(
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
args: captureAny(named: 'args'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, contains('--obfuscate'));
|
||||
expect(
|
||||
args.any((a) => a.startsWith('--split-debug-info=')),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('createPatchArtifacts', () {
|
||||
@@ -342,7 +388,7 @@ void main() {
|
||||
appId: 'com.example.app',
|
||||
releaseId: 1,
|
||||
releaseArtifact: releaseArtifact,
|
||||
supplementArtifact: File('supplement.zip'),
|
||||
supplementDirectory: Directory('supplement'),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -183,6 +183,15 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('supplementaryReleaseArtifactArch', () {
|
||||
test('is "macos_supplement"', () {
|
||||
expect(
|
||||
patcher.supplementaryReleaseArtifactArch,
|
||||
'macos_supplement',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('releaseType', () {
|
||||
test('is ReleaseType.macos', () {
|
||||
expect(patcher.releaseType, ReleaseType.macos);
|
||||
@@ -866,6 +875,84 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}'''),
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
expect(copiedKernelFile.existsSync(), isTrue);
|
||||
});
|
||||
|
||||
group('when extraBuildArgs has obfuscation flags', () {
|
||||
late File obfuscationMapFile;
|
||||
|
||||
setUp(() {
|
||||
obfuscationMapFile =
|
||||
File(
|
||||
p.join(
|
||||
Directory.systemTemp.createTempSync().path,
|
||||
'obfuscation_map.json',
|
||||
),
|
||||
)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"key": "value"}');
|
||||
});
|
||||
|
||||
test('includes obfuscation flags in build args', () async {
|
||||
patcher.obfuscationMapPath = obfuscationMapFile.path;
|
||||
patcher.extraBuildArgs = [
|
||||
'--obfuscate',
|
||||
'--extra-gen-snapshot-options='
|
||||
'--load-obfuscation-map=${obfuscationMapFile.path}',
|
||||
'--split-debug-info=build/shorebird/symbols',
|
||||
];
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
args: captureAny(named: 'args'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, contains('--obfuscate'));
|
||||
expect(
|
||||
args.any((a) => a.startsWith('--split-debug-info=')),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
args,
|
||||
contains(
|
||||
'--extra-gen-snapshot-options='
|
||||
'--load-obfuscation-map=${obfuscationMapFile.path}',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when extraBuildArgs is empty', () {
|
||||
test('does not inject obfuscation flags', () async {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
args: captureAny(named: 'args'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(args, isNot(contains('--obfuscate')));
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--load-obfuscation-map',
|
||||
),
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ void main() {
|
||||
when(
|
||||
() => argResults.wasParsed(CommonArguments.signCmd.name),
|
||||
).thenReturn(false);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
|
||||
when(aotTools.isLinkDebugInfoSupported).thenAnswer((_) async => true);
|
||||
|
||||
@@ -209,6 +210,12 @@ void main() {
|
||||
message: any(named: 'message'),
|
||||
),
|
||||
).thenAnswer((_) async => File(''));
|
||||
when(
|
||||
() => artifactManager.extractZip(
|
||||
zipFile: any(named: 'zipFile'),
|
||||
outputDirectory: any(named: 'outputDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
when(() => cache.updateAll()).thenAnswer((_) async => {});
|
||||
|
||||
@@ -291,7 +298,7 @@ void main() {
|
||||
appId: any(named: 'appId'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
releaseArtifact: any(named: 'releaseArtifact'),
|
||||
supplementArtifact: any(named: 'supplementArtifact'),
|
||||
supplementDirectory: any(named: 'supplementDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => patchArtifactBundles);
|
||||
when(
|
||||
@@ -541,7 +548,7 @@ void main() {
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
releaseArtifact: any(named: 'releaseArtifact'),
|
||||
supplementArtifact: any(named: 'supplementArtifact'),
|
||||
supplementDirectory: any(named: 'supplementDirectory'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
@@ -565,12 +572,36 @@ void main() {
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
releaseArtifact: any(named: 'releaseArtifact'),
|
||||
supplementArtifact: any(named: 'supplementArtifact'),
|
||||
supplementDirectory: any(named: 'supplementDirectory'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group(
|
||||
'when --obfuscate is passed but release has no obfuscation map',
|
||||
() {
|
||||
setUp(() {
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(() => command.createPatch(patcher)),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(
|
||||
() => logger.err(
|
||||
'--obfuscate was passed, but the release was not built with '
|
||||
'obfuscation. A patch cannot change the obfuscation mode of '
|
||||
'a release.',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -633,7 +633,7 @@ class _TestPatcher extends Patcher {
|
||||
required String appId,
|
||||
required int releaseId,
|
||||
required File releaseArtifact,
|
||||
File? supplementArtifact,
|
||||
Directory? supplementDirectory,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ void main() {
|
||||
appId: 'com.example.app',
|
||||
releaseId: 1,
|
||||
releaseArtifact: releaseArtifact,
|
||||
supplementArtifact: File('supplement.zip'),
|
||||
supplementDirectory: Directory('supplement'),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ 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:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/aar_releaser.dart';
|
||||
@@ -18,6 +20,7 @@ import 'package:shorebird_cli/src/platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/release_type.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_android_artifacts.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_validator.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
@@ -33,6 +36,7 @@ void main() {
|
||||
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
late ArtifactManager artifactManager;
|
||||
late CodePushClientWrapper codePushClientWrapper;
|
||||
late CodeSigner codeSigner;
|
||||
late Directory projectRoot;
|
||||
@@ -41,6 +45,7 @@ void main() {
|
||||
late Progress progress;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
late ShorebirdFlutter shorebirdFlutter;
|
||||
late ShorebirdValidator shorebirdValidator;
|
||||
late ShorebirdAndroidArtifacts shorebirdAndroidArtifacts;
|
||||
late AarReleaser aarReleaser;
|
||||
@@ -50,6 +55,7 @@ void main() {
|
||||
body,
|
||||
values: {
|
||||
artifactBuilderRef.overrideWith(() => artifactBuilder),
|
||||
artifactManagerRef.overrideWith(() => artifactManager),
|
||||
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
|
||||
codeSignerRef.overrideWith(() => codeSigner),
|
||||
engineConfigRef.overrideWith(() => const EngineConfig.empty()),
|
||||
@@ -57,6 +63,7 @@ void main() {
|
||||
osInterfaceRef.overrideWith(() => operatingSystemInterface),
|
||||
processRef.overrideWith(() => shorebirdProcess),
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
shorebirdFlutterRef.overrideWith(() => shorebirdFlutter),
|
||||
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
|
||||
shorebirdAndroidArtifactsRef.overrideWith(
|
||||
() => shorebirdAndroidArtifacts,
|
||||
@@ -74,6 +81,7 @@ void main() {
|
||||
setUp(() {
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
artifactManager = MockArtifactManager();
|
||||
codePushClientWrapper = MockCodePushClientWrapper();
|
||||
codeSigner = MockCodeSigner();
|
||||
operatingSystemInterface = MockOperatingSystemInterface();
|
||||
@@ -82,6 +90,7 @@ void main() {
|
||||
logger = MockShorebirdLogger();
|
||||
shorebirdProcess = MockShorebirdProcess();
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
shorebirdFlutter = MockShorebirdFlutter();
|
||||
shorebirdValidator = MockShorebirdValidator();
|
||||
shorebirdAndroidArtifacts = MockShorebirdAndroidArtifacts();
|
||||
|
||||
@@ -233,6 +242,58 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults.wasParsed('release-version')).thenReturn(true);
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(aarReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(aarReleaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is not passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults.wasParsed('release-version')).thenReturn(true);
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(aarReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('buildReleaseArtifacts', () {
|
||||
@@ -411,6 +472,93 @@ void main() {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildAar(
|
||||
buildNumber: any(named: 'buildNumber'),
|
||||
targetPlatforms: any(named: 'targetPlatforms'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
final mapPath = p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
File(mapPath)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
});
|
||||
});
|
||||
|
||||
test('injects --save-obfuscation-map into build args', () async {
|
||||
await runWithOverrides(aarReleaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildAar(
|
||||
buildNumber: any(named: 'buildNumber'),
|
||||
targetPlatforms: any(named: 'targetPlatforms'),
|
||||
args: captureAny(named: 'args'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('logs detail about map location', () async {
|
||||
await runWithOverrides(aarReleaser.buildReleaseArtifacts);
|
||||
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: startsWith('Obfuscation map saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map is not generated', () {
|
||||
setUp(() {
|
||||
// Override to NOT create the map file.
|
||||
when(
|
||||
() => artifactBuilder.buildAar(
|
||||
buildNumber: any(named: 'buildNumber'),
|
||||
targetPlatforms: any(named: 'targetPlatforms'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(aarReleaser.buildReleaseArtifacts),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(
|
||||
that: contains(
|
||||
'Obfuscation was enabled but the obfuscation map was not',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ 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:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/android_releaser.dart';
|
||||
@@ -20,6 +22,7 @@ import 'package:shorebird_cli/src/platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/release_type.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_android_artifacts.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_validator.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
@@ -34,6 +37,7 @@ void main() {
|
||||
group(AndroidReleaser, () {
|
||||
late ArgResults argResults;
|
||||
late ArtifactBuilder artifactBuilder;
|
||||
late ArtifactManager artifactManager;
|
||||
late CodePushClientWrapper codePushClientWrapper;
|
||||
late CodeSigner codeSigner;
|
||||
late Doctor doctor;
|
||||
@@ -44,6 +48,7 @@ void main() {
|
||||
late Progress progress;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
late ShorebirdFlutter shorebirdFlutter;
|
||||
late ShorebirdValidator shorebirdValidator;
|
||||
late ShorebirdAndroidArtifacts shorebirdAndroidArtifacts;
|
||||
late AndroidReleaser androidReleaser;
|
||||
@@ -53,6 +58,7 @@ void main() {
|
||||
body,
|
||||
values: {
|
||||
artifactBuilderRef.overrideWith(() => artifactBuilder),
|
||||
artifactManagerRef.overrideWith(() => artifactManager),
|
||||
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
|
||||
codeSignerRef.overrideWith(() => codeSigner),
|
||||
doctorRef.overrideWith(() => doctor),
|
||||
@@ -61,6 +67,7 @@ void main() {
|
||||
osInterfaceRef.overrideWith(() => operatingSystemInterface),
|
||||
processRef.overrideWith(() => shorebirdProcess),
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
shorebirdFlutterRef.overrideWith(() => shorebirdFlutter),
|
||||
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
|
||||
shorebirdAndroidArtifactsRef.overrideWith(
|
||||
() => shorebirdAndroidArtifacts,
|
||||
@@ -78,6 +85,7 @@ void main() {
|
||||
setUp(() {
|
||||
argResults = MockArgResults();
|
||||
artifactBuilder = MockArtifactBuilder();
|
||||
artifactManager = MockArtifactManager();
|
||||
codePushClientWrapper = MockCodePushClientWrapper();
|
||||
codeSigner = MockCodeSigner();
|
||||
doctor = MockDoctor();
|
||||
@@ -88,6 +96,7 @@ void main() {
|
||||
logger = MockShorebirdLogger();
|
||||
shorebirdProcess = MockShorebirdProcess();
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
shorebirdFlutter = MockShorebirdFlutter();
|
||||
shorebirdValidator = MockShorebirdValidator();
|
||||
shorebirdAndroidArtifacts = MockShorebirdAndroidArtifacts();
|
||||
|
||||
@@ -277,6 +286,60 @@ To change the version of this release, change your app's version in your pubspec
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['artifact']).thenReturn('aab');
|
||||
when(() => argResults['split-per-abi']).thenReturn(false);
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(androidReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(androidReleaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is not passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['artifact']).thenReturn('aab');
|
||||
when(() => argResults['split-per-abi']).thenReturn(false);
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(androidReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('buildReleaseArtifacts', () {
|
||||
|
||||
@@ -157,6 +157,58 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults.wasParsed('release-version')).thenReturn(true);
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(iosFrameworkReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(iosFrameworkReleaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is not passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults.wasParsed('release-version')).thenReturn(true);
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(iosFrameworkReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('assertPreconditions', () {
|
||||
@@ -426,6 +478,93 @@ void main() {
|
||||
expect(xcframework.path, p.join(projectRoot.path, 'release'));
|
||||
verify(() => artifactBuilder.buildIosFramework(args: [])).called(1);
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildIosFramework(
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
final mapPath = p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
File(mapPath)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
return AppleBuildResult(kernelFile: File('/path/to/app.dill'));
|
||||
});
|
||||
});
|
||||
|
||||
test('injects --save-obfuscation-map into build args', () async {
|
||||
await runWithOverrides(iosFrameworkReleaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildIosFramework(
|
||||
args: captureAny(named: 'args'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('logs detail about map location', () async {
|
||||
await runWithOverrides(iosFrameworkReleaser.buildReleaseArtifacts);
|
||||
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: startsWith('Obfuscation map saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map is not generated', () {
|
||||
setUp(() {
|
||||
// Override to NOT create the map file.
|
||||
when(
|
||||
() => artifactBuilder.buildIosFramework(
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async =>
|
||||
AppleBuildResult(kernelFile: File('/path/to/app.dill')),
|
||||
);
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(
|
||||
iosFrameworkReleaser.buildReleaseArtifacts,
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(
|
||||
that: contains(
|
||||
'Obfuscation was enabled but the obfuscation map was not',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('getReleaseVersion', () {
|
||||
@@ -468,7 +607,6 @@ void main() {
|
||||
appId: any(named: 'appId'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
appFrameworkPath: any(named: 'appFrameworkPath'),
|
||||
supplementPath: any(named: 'supplementPath'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
});
|
||||
@@ -490,7 +628,6 @@ void main() {
|
||||
'release',
|
||||
ArtifactManager.appXcframeworkName,
|
||||
),
|
||||
supplementPath: null,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
@@ -237,7 +237,7 @@ void main() {
|
||||
() => logger.err(
|
||||
'''
|
||||
The "--release-version" flag is only supported for aar and ios-framework releases.
|
||||
|
||||
|
||||
To change the version of this release, change your app's version in your pubspec.yaml.''',
|
||||
),
|
||||
).called(1);
|
||||
@@ -246,25 +246,39 @@ To change the version of this release, change your app's version in your pubspec
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults.rest).thenReturn(['--obfuscate']);
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(iosReleaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
'Shorebird does not currently support obfuscation on iOS.',
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(
|
||||
'''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''',
|
||||
),
|
||||
).called(1);
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(iosReleaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(iosReleaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -532,6 +546,158 @@ To change the version of this release, change your app's version in your pubspec
|
||||
verify(() => logger.err('Unable to find .app directory')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
// By default, simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
final mapPath = p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
File(mapPath)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
return AppleBuildResult(
|
||||
kernelFile: File('/path/to/app.dill'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('injects --save-obfuscation-map into build args', () async {
|
||||
await runWithOverrides(iosReleaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('auto-defaults --split-debug-info when not provided', () async {
|
||||
await runWithOverrides(iosReleaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith('--split-debug-info='),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
group('when --split-debug-info is also provided', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => argResults.wasParsed('split-debug-info'),
|
||||
).thenReturn(true);
|
||||
when(
|
||||
() => argResults['split-debug-info'],
|
||||
).thenReturn('custom/symbols');
|
||||
});
|
||||
|
||||
test('preserves user-provided --split-debug-info', () async {
|
||||
await runWithOverrides(iosReleaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args,
|
||||
contains('--split-debug-info=custom/symbols'),
|
||||
);
|
||||
// Should not contain a second auto-defaulted one.
|
||||
expect(
|
||||
args.where((a) => a.startsWith('--split-debug-info=')).length,
|
||||
equals(1),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('logs detail about map location', () async {
|
||||
await runWithOverrides(iosReleaser.buildReleaseArtifacts);
|
||||
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: startsWith('Obfuscation map saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map is not generated', () {
|
||||
setUp(() {
|
||||
// Override to NOT create the map file.
|
||||
when(
|
||||
() => artifactBuilder.buildIpa(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async =>
|
||||
AppleBuildResult(kernelFile: File('/path/to/app.dill')),
|
||||
);
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(iosReleaser.buildReleaseArtifacts),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(
|
||||
that: contains(
|
||||
'Obfuscation was enabled but the obfuscation map was not',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('getReleaseVersion', () {
|
||||
@@ -674,6 +840,9 @@ To change the version of this release, change your app's version in your pubspec
|
||||
|
||||
setUp(() {
|
||||
when(() => argResults['codesign']).thenReturn(codesign);
|
||||
when(
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
|
||||
xcarchiveDirectory = Directory.systemTemp.createTempSync();
|
||||
iosAppDirectory = Directory.systemTemp.createTempSync();
|
||||
@@ -706,7 +875,6 @@ To change the version of this release, change your app's version in your pubspec
|
||||
runnerPath: any(named: 'runnerPath'),
|
||||
isCodesigned: any(named: 'isCodesigned'),
|
||||
podfileLockHash: any(named: 'podfileLockHash'),
|
||||
supplementPath: any(named: 'supplementPath'),
|
||||
),
|
||||
).thenAnswer((_) async => {});
|
||||
when(() => shorebirdEnv.iosPodfileLockFile).thenReturn(podfileLockFile);
|
||||
@@ -729,10 +897,46 @@ To change the version of this release, change your app's version in your pubspec
|
||||
isCodesigned: codesign,
|
||||
podfileLockHash:
|
||||
'${sha256.convert(utf8.encode(podfileLockContent))}',
|
||||
supplementPath: supplementDirectory.path,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map exists', () {
|
||||
setUp(() {
|
||||
// Create the obfuscation map file at the expected path.
|
||||
File(
|
||||
p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
),
|
||||
)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"key": "value"}');
|
||||
});
|
||||
|
||||
test('passes obfuscation map path to wrapper', () async {
|
||||
await runWithOverrides(
|
||||
() => iosReleaser.uploadReleaseArtifacts(
|
||||
release: release,
|
||||
appId: appId,
|
||||
),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => codePushClientWrapper.createIosReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
xcarchivePath: xcarchiveDirectory.path,
|
||||
runnerPath: iosAppDirectory.path,
|
||||
isCodesigned: codesign,
|
||||
podfileLockHash:
|
||||
'${sha256.convert(utf8.encode(podfileLockContent))}',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('updatedReleaseMetadata', () {
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:shorebird_cli/src/doctor.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/release_type.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
@@ -154,6 +155,53 @@ To change the version of this release, change your app's version in your pubspec
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(releaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(releaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is not passed', () {
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(releaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('assertPreconditions', () {
|
||||
@@ -356,6 +404,93 @@ To change the version of this release, change your app's version in your pubspec
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildLinuxApp(
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
final mapPath = p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
File(mapPath)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
});
|
||||
});
|
||||
|
||||
test('injects --save-obfuscation-map into build args', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildLinuxApp(
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('logs detail about map location', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: startsWith('Obfuscation map saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map is not generated', () {
|
||||
setUp(() {
|
||||
// Override to NOT create the map file.
|
||||
when(
|
||||
() => artifactBuilder.buildLinuxApp(
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(releaser.buildReleaseArtifacts),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(
|
||||
that: contains(
|
||||
'Obfuscation was enabled but the obfuscation map was not',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('getReleaseVersion', () {
|
||||
|
||||
@@ -219,7 +219,7 @@ void main() {
|
||||
() => logger.err(
|
||||
'''
|
||||
The "--release-version" flag is only supported for aar and ios-framework releases.
|
||||
|
||||
|
||||
To change the version of this release, change your app's version in your pubspec.yaml.''',
|
||||
),
|
||||
).called(1);
|
||||
@@ -228,25 +228,39 @@ To change the version of this release, change your app's version in your pubspec
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults.rest).thenReturn(['--obfuscate']);
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(releaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
'Shorebird does not currently support obfuscation on macOS.',
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(
|
||||
'''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''',
|
||||
),
|
||||
).called(1);
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(releaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(releaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -435,6 +449,163 @@ To change the version of this release, change your app's version in your pubspec
|
||||
verify(() => logger.err('Unable to find .app directory')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
// By default, simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
final mapPath = p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
File(mapPath)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
return AppleBuildResult(
|
||||
kernelFile: File('/path/to/app.dill'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('injects --save-obfuscation-map into build args', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('auto-defaults --split-debug-info when not provided', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith('--split-debug-info='),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
group('when --split-debug-info is also provided', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => argResults.wasParsed('split-debug-info'),
|
||||
).thenReturn(true);
|
||||
when(
|
||||
() => argResults['split-debug-info'],
|
||||
).thenReturn('custom/symbols');
|
||||
});
|
||||
|
||||
test('preserves user-provided --split-debug-info', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args,
|
||||
contains('--split-debug-info=custom/symbols'),
|
||||
);
|
||||
// Should not contain a second auto-defaulted one.
|
||||
expect(
|
||||
args.where((a) => a.startsWith('--split-debug-info=')).length,
|
||||
equals(1),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('logs detail about map location', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: startsWith('Obfuscation map saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map is not generated', () {
|
||||
setUp(() {
|
||||
// Override to NOT create the map file.
|
||||
when(
|
||||
() => artifactBuilder.buildMacos(
|
||||
codesign: any(named: 'codesign'),
|
||||
flavor: any(named: 'flavor'),
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async =>
|
||||
AppleBuildResult(kernelFile: File('/path/to/app.dill')),
|
||||
);
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(releaser.buildReleaseArtifacts),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(
|
||||
that: contains(
|
||||
'Obfuscation was enabled but the obfuscation map was not',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('getReleaseVersion', () {
|
||||
@@ -577,6 +748,9 @@ To change the version of this release, change your app's version in your pubspec
|
||||
|
||||
setUp(() {
|
||||
when(() => argResults['codesign']).thenReturn(codesign);
|
||||
when(
|
||||
() => shorebirdEnv.getShorebirdProjectRoot(),
|
||||
).thenReturn(projectRoot);
|
||||
|
||||
appDirectory = Directory.systemTemp.createTempSync();
|
||||
|
||||
@@ -644,6 +818,42 @@ To change the version of this release, change your app's version in your pubspec
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map exists', () {
|
||||
setUp(() {
|
||||
// Create the obfuscation map file at the expected path.
|
||||
File(
|
||||
p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
),
|
||||
)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"key": "value"}');
|
||||
});
|
||||
|
||||
test('passes obfuscation map path to wrapper', () async {
|
||||
await runWithOverrides(
|
||||
() => releaser.uploadReleaseArtifacts(
|
||||
release: release,
|
||||
appId: appId,
|
||||
),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => codePushClientWrapper.createMacosReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
appPath: appDirectory.path,
|
||||
isCodesigned: codesign,
|
||||
podfileLockHash:
|
||||
'${sha256.convert(utf8.encode(podfileLockContent))}',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('updatedReleaseMetadata', () {
|
||||
|
||||
@@ -18,6 +18,7 @@ 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/release_type.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
@@ -160,12 +161,59 @@ void main() {
|
||||
() => logger.err(
|
||||
'''
|
||||
The "--release-version" flag is only supported for aar and ios-framework releases.
|
||||
|
||||
|
||||
To change the version of this release, change your app's version in your pubspec.yaml.''',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn('deadbeef');
|
||||
});
|
||||
|
||||
group('when Flutter version supports obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 41, 2));
|
||||
});
|
||||
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(releaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when Flutter version does not support obfuscation', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => shorebirdFlutter.resolveFlutterVersion(any()),
|
||||
).thenAnswer((_) async => Version(3, 27, 4));
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(releaser.assertArgsAreValid),
|
||||
exitsWithCode(ExitCode.unavailable),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is not passed', () {
|
||||
test('returns normally', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(releaser.assertArgsAreValid),
|
||||
completes,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('assertPreconditions', () {
|
||||
@@ -371,6 +419,94 @@ To change the version of this release, change your app's version in your pubspec
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate is passed', () {
|
||||
setUp(() {
|
||||
when(() => argResults['obfuscate']).thenReturn(true);
|
||||
when(() => argResults.wasParsed('obfuscate')).thenReturn(true);
|
||||
// Simulate the build creating the obfuscation map.
|
||||
when(
|
||||
() => artifactBuilder.buildWindowsApp(
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
final mapPath = p.join(
|
||||
projectRoot.path,
|
||||
'build',
|
||||
'shorebird',
|
||||
'obfuscation_map.json',
|
||||
);
|
||||
File(mapPath)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
return projectRoot;
|
||||
});
|
||||
});
|
||||
|
||||
test('injects --save-obfuscation-map into build args', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
final captured = verify(
|
||||
() => artifactBuilder.buildWindowsApp(
|
||||
target: any(named: 'target'),
|
||||
args: captureAny(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
final args = captured.last as List<String>;
|
||||
expect(
|
||||
args.any(
|
||||
(a) => a.startsWith(
|
||||
'--extra-gen-snapshot-options=--save-obfuscation-map=',
|
||||
),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('logs detail about map location', () async {
|
||||
await runWithOverrides(releaser.buildReleaseArtifacts);
|
||||
|
||||
verify(
|
||||
() => logger.detail(
|
||||
any(that: startsWith('Obfuscation map saved to')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when obfuscation map is not generated', () {
|
||||
setUp(() {
|
||||
// Override to NOT create the map file.
|
||||
when(
|
||||
() => artifactBuilder.buildWindowsApp(
|
||||
target: any(named: 'target'),
|
||||
args: any(named: 'args'),
|
||||
base64PublicKey: any(named: 'base64PublicKey'),
|
||||
),
|
||||
).thenAnswer((_) async => projectRoot);
|
||||
});
|
||||
|
||||
test('logs error and exits', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(releaser.buildReleaseArtifacts),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(
|
||||
() => logger.err(
|
||||
any(
|
||||
that: contains(
|
||||
'Obfuscation was enabled but the obfuscation map was not',
|
||||
),
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('getReleaseVersion', () {
|
||||
|
||||
@@ -138,6 +138,10 @@ void main() {
|
||||
'platforms',
|
||||
allowed: ReleaseType.values.map((e) => e.cliName),
|
||||
)
|
||||
..addFlag(
|
||||
CommonArguments.obfuscateArg.name,
|
||||
negatable: false,
|
||||
)
|
||||
..addFlag('verbose', abbr: 'v');
|
||||
});
|
||||
|
||||
@@ -317,6 +321,23 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate flag is provided', () {
|
||||
test('forwards it', () {
|
||||
final args = ['--verbose', '--obfuscate'];
|
||||
final result = parser.parse(args);
|
||||
expect(result.forwardedArgs, hasLength(1));
|
||||
expect(result.forwardedArgs, contains('--obfuscate'));
|
||||
});
|
||||
});
|
||||
|
||||
group('when --obfuscate flag is not provided', () {
|
||||
test('does not forward it', () {
|
||||
final args = ['--verbose'];
|
||||
final result = parser.parse(args);
|
||||
expect(result.forwardedArgs, isNot(contains('--obfuscate')));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('CodeSign', () {
|
||||
|
||||
Reference in New Issue
Block a user