From 7e77109fbbc97f82abc0bb1b6360375bb2c1dcc2 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 16 Aug 2023 17:58:08 -0400 Subject: [PATCH] fix(shorebird_cli): compare contents of .car files when checking for asset diffs (#1103) --- .../archive_analysis/ios_archive_differ.dart | 54 +++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/shorebird_cli/lib/src/archive_analysis/ios_archive_differ.dart b/packages/shorebird_cli/lib/src/archive_analysis/ios_archive_differ.dart index cc929a00..57d3eb4f 100644 --- a/packages/shorebird_cli/lib/src/archive_analysis/ios_archive_differ.dart +++ b/packages/shorebird_cli/lib/src/archive_analysis/ios_archive_differ.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:archive/archive_io.dart'; +import 'package:collection/collection.dart'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart'; @@ -33,11 +34,11 @@ class IosArchiveDiffer extends ArchiveDiffer { final oldPathHashes = fileHashes(File(oldArchivePath)); final newPathHashes = fileHashes(File(newArchivePath)); - _updateToUnsignedHashes( + _updateHashes( archivePath: oldArchivePath, pathHashes: oldPathHashes, ); - _updateToUnsignedHashes( + _updateHashes( archivePath: newArchivePath, pathHashes: newPathHashes, ); @@ -48,13 +49,21 @@ class IosArchiveDiffer extends ArchiveDiffer { ); } - void _updateToUnsignedHashes({ + /// Replaces crc32s from zip file headers where needed. This currently + /// includes: + /// - Signed files (those with a .app extension) + /// - Compiled asset catalogs (those with a .car extension) + void _updateHashes({ required String archivePath, required PathHashes pathHashes, }) { for (final file in _filesToUnsign(archivePath)) { pathHashes[file.name] = _unsignedFileHash(file); } + + for (final file in _carFiles(archivePath)) { + pathHashes[file.name] = _carFileHash(file); + } } List _filesToUnsign(String archivePath) { @@ -71,6 +80,14 @@ class IosArchiveDiffer extends ArchiveDiffer { .toList(); } + List _carFiles(String archivePath) { + return ZipDecoder() + .decodeBuffer(InputFileStream(archivePath)) + .files + .where((file) => file.isFile && p.extension(file.name) == '.car') + .toList(); + } + String _unsignedFileHash(ArchiveFile file) { final tempDir = Directory.systemTemp.createTempSync(); final outPath = p.join(tempDir.path, file.name); @@ -89,6 +106,35 @@ class IosArchiveDiffer extends ArchiveDiffer { return hash; } + /// Uses assetutil to write a json description of a .car file to disk and + /// diffs the contents of that file, less a timestamp line that chnages based + /// on when the .car file was created. + String _carFileHash(ArchiveFile file) { + final tempDir = Directory.systemTemp.createTempSync(); + final outPath = p.join(tempDir.path, file.name); + final outputStream = OutputFileStream(outPath); + file.writeContent(outputStream); + outputStream.close(); + + final assetInfoPath = '$outPath.json'; + + if (Platform.isMacOS) { + // coverage:ignore-start + Process.runSync('assetutil', ['--info', outPath, '-o', assetInfoPath]); + // coverage:ignore-end + } else { + // This is just for testing + File(assetInfoPath).createSync(recursive: true); + } + + // Remove the timestamp line from the json file + final jsonFile = File(assetInfoPath); + final lines = jsonFile.readAsLinesSync(); + final timestampRegex = RegExp(r'^\W+"Timestamp" : \d+$'); + final linesToKeep = lines.whereNot(timestampRegex.hasMatch); + return _hash(linesToKeep.join('\n').codeUnits); + } + @override bool containsPotentiallyBreakingAssetDiffs(FileSetDiff fileSetDiff) => assetsFileSetDiff(fileSetDiff).isNotEmpty; @@ -102,7 +148,7 @@ class IosArchiveDiffer extends ArchiveDiffer { /// The flutter_assets directory contains the assets listed in the assets /// section of the pubspec.yaml file. /// Assets.car is the compiled asset catalog(s) (.xcassets files). - return p.basename(filePath) == 'Assets.car' || + return p.extension(filePath) == '.car' || p.split(filePath).contains('flutter_assets'); }