fix(shorebird_cli): ignore Mach-O UUID when checking for iOS patchability (#1783)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:crypto/crypto.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/file_set_diff.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/macho.dart';
|
||||
|
||||
/// Finds differences between two IPAs or zipped Xcframeworks.
|
||||
///
|
||||
@@ -112,7 +114,14 @@ class IosArchiveDiffer extends ArchiveDiffer {
|
||||
}
|
||||
|
||||
final outFile = File(outPath);
|
||||
return _hash(outFile.readAsBytesSync());
|
||||
final Uint8List bytes;
|
||||
if (MachO.isMachOFile(outFile)) {
|
||||
bytes = MachO.bytesWithZeroedUUID(outFile);
|
||||
} else {
|
||||
bytes = outFile.readAsBytesSync();
|
||||
}
|
||||
|
||||
return _hash(bytes);
|
||||
}
|
||||
|
||||
/// Uses assetutil to write a json description of a .car file to disk and
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
const machOHeaderSize = 32;
|
||||
const uuidLoadCommandType = 0x1b;
|
||||
|
||||
/// Utilities for interacting with Mach-O files.
|
||||
/// See https://en.wikipedia.org/wiki/Mach-O.
|
||||
class MachO {
|
||||
/// Reads a 32-bit integer as a little-endian value from the provided bytes.
|
||||
static int _readInt32(Uint8List bytes, int offset) {
|
||||
return bytes[offset + 3] << 24 |
|
||||
bytes[offset + 2] << 16 |
|
||||
bytes[offset + 1] << 8 |
|
||||
bytes[offset];
|
||||
}
|
||||
|
||||
/// Returns `true` if the provided file is a Mach-O file.
|
||||
static bool isMachOFile(File file) {
|
||||
final bytes = file.readAsBytesSync();
|
||||
final magic = _readInt32(bytes, 0);
|
||||
|
||||
// These are the magic numbers for Mach-O files.
|
||||
// See https://en.wikipedia.org/wiki/Mach-O#Mach-O_header
|
||||
return magic == 0xfeedface || magic == 0xfeedfacf;
|
||||
}
|
||||
|
||||
/// Returns a copy of the provided Mach-O file with the UUID load command
|
||||
/// zeroed out. This is necessary because the same code built in different
|
||||
/// locations will generate Mach-O files with different UUIDs as the only
|
||||
/// difference.
|
||||
static Uint8List bytesWithZeroedUUID(File file) {
|
||||
final bytes = file.readAsBytesSync();
|
||||
|
||||
// The number of load commands is a 32-bit int at offset 16. We could
|
||||
// probably write a more robust MachO header parser, but this is all we need
|
||||
// for now.
|
||||
final numberOfLoadCommands = _readInt32(bytes, 16);
|
||||
|
||||
// The load commands are immediately after the header.
|
||||
var offset = machOHeaderSize;
|
||||
for (var i = 0; i < numberOfLoadCommands; i++) {
|
||||
final commandType = _readInt32(bytes, offset);
|
||||
final commandLength = _readInt32(bytes, offset + 4);
|
||||
|
||||
if (commandType == uuidLoadCommandType) {
|
||||
// Zero out the UUID bytes.
|
||||
final loadCommandStart = offset + 8;
|
||||
for (var j = loadCommandStart; j < offset + commandLength; j++) {
|
||||
bytes[j] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
offset += commandLength;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
BIN
Binary file not shown.
@@ -9,15 +9,19 @@ void main() {
|
||||
xcarchiveFixturesBasePath,
|
||||
'base.xcarchive.zip',
|
||||
);
|
||||
final changedAssetIpaPath = p.join(
|
||||
final baseChangedUuidPath = p.join(
|
||||
xcarchiveFixturesBasePath,
|
||||
'base_changed_uuid.xcarchive.zip',
|
||||
);
|
||||
final changedAssetXcarchivePath = p.join(
|
||||
xcarchiveFixturesBasePath,
|
||||
'changed_asset.xcarchive.zip',
|
||||
);
|
||||
final changedDartIpaPath = p.join(
|
||||
final changedDartXcarchivePath = p.join(
|
||||
xcarchiveFixturesBasePath,
|
||||
'changed_dart.xcarchive.zip',
|
||||
);
|
||||
final changedSwiftIpaPath = p.join(
|
||||
final changedSwiftXcarchivePath = p.join(
|
||||
xcarchiveFixturesBasePath,
|
||||
'changed_swift.xcarchive.zip',
|
||||
);
|
||||
@@ -72,10 +76,21 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('finds no differences when only Mach-O UUID differs', () async {
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
baseChangedUuidPath,
|
||||
);
|
||||
expect(
|
||||
fileSetDiff,
|
||||
isEmpty,
|
||||
);
|
||||
});
|
||||
|
||||
test('finds differences between two different xcarchives', () async {
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedAssetIpaPath,
|
||||
changedAssetXcarchivePath,
|
||||
);
|
||||
if (platform.isMacOS) {
|
||||
expect(
|
||||
@@ -102,8 +117,10 @@ void main() {
|
||||
|
||||
group('changedFiles', () {
|
||||
test('detects asset changes', () async {
|
||||
final fileSetDiff =
|
||||
await differ.changedFiles(baseIpaPath, changedAssetIpaPath);
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedAssetXcarchivePath,
|
||||
);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(
|
||||
differ.dartFileSetDiff(fileSetDiff),
|
||||
@@ -113,16 +130,20 @@ void main() {
|
||||
});
|
||||
|
||||
test('detects dart changes', () async {
|
||||
final fileSetDiff =
|
||||
await differ.changedFiles(baseIpaPath, changedDartIpaPath);
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedDartXcarchivePath,
|
||||
);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty);
|
||||
});
|
||||
|
||||
test('detects swift changes', () async {
|
||||
final fileSetDiff =
|
||||
await differ.changedFiles(baseIpaPath, changedSwiftIpaPath);
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedSwiftXcarchivePath,
|
||||
);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
@@ -134,7 +155,7 @@ void main() {
|
||||
() async {
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedAssetIpaPath,
|
||||
changedAssetXcarchivePath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
|
||||
@@ -146,7 +167,7 @@ void main() {
|
||||
() async {
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedDartIpaPath,
|
||||
changedDartXcarchivePath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
|
||||
@@ -159,7 +180,7 @@ void main() {
|
||||
test('returns true if Swift files have been changed', () async {
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedSwiftIpaPath,
|
||||
changedSwiftXcarchivePath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff),
|
||||
@@ -170,7 +191,7 @@ void main() {
|
||||
test('returns false if Swift files have not been changed', () async {
|
||||
final fileSetDiff = await differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedAssetIpaPath,
|
||||
changedAssetXcarchivePath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff),
|
||||
|
||||
Reference in New Issue
Block a user