1616
1717import ContainerizationArchive
1818import ContainerizationOS
19+ import CryptoKit
1920import Foundation
2021
2122public final class Archiver : Sendable {
22- public struct ArchiveEntryInfo : Sendable {
23+ public struct ArchiveEntryInfo : Sendable , Codable {
2324 public let pathOnHost : URL
2425 public let pathInArchive : URL
2526
@@ -48,13 +49,15 @@ public final class Archiver: Sendable {
4849 followSymlinks: Bool = false ,
4950 writerConfiguration: ArchiveWriterConfiguration = ArchiveWriterConfiguration ( format: . paxRestricted, filter: . gzip) ,
5051 closure: ( URL ) -> ArchiveEntryInfo ?
51- ) throws {
52+ ) throws -> SHA256 . Digest {
5253 let source = source. standardizedFileURL
5354 let destination = destination. standardizedFileURL
5455
5556 let fileManager = FileManager . default
5657 try ? fileManager. removeItem ( at: destination)
5758
59+ var hasher = SHA256 ( )
60+
5861 do {
5962 let directory = destination. deletingLastPathComponent ( )
6063 try fileManager. createDirectory ( at: directory, withIntermediateDirectories: true )
@@ -71,7 +74,8 @@ public final class Archiver: Sendable {
7174 entryInfo. append ( info)
7275 }
7376 } else {
74- while let relPath = enumerator. nextObject ( ) as? String {
77+ let relPaths = enumerator. compactMap { $0 as? String }
78+ for relPath in relPaths. sorted ( by: { $0 < $1 } ) {
7579 let url = source. appending ( path: relPath) . standardizedFileURL
7680 guard let info = closure ( url) else {
7781 continue
@@ -85,17 +89,23 @@ public final class Archiver: Sendable {
8589 )
8690 try archiver. open ( file: destination)
8791
92+ let encoder = JSONEncoder ( )
93+ encoder. outputFormatting = . sortedKeys
94+
8895 for info in entryInfo {
8996 guard let entry = try Self . _createEntry ( entryInfo: info) else {
9097 throw Error . failedToCreateEntry
9198 }
92- try Self . _compressFile ( item: info. pathOnHost, entry: entry, archiver: archiver)
99+ hasher. update ( data: try encoder. encode ( info) )
100+ try Self . _compressFile ( item: info. pathOnHost, entry: entry, archiver: archiver, hasher: & hasher)
93101 }
94102 try archiver. finishEncoding ( )
95103 } catch {
96104 try ? fileManager. removeItem ( at: destination)
97105 throw error
98106 }
107+
108+ return hasher. finalize ( )
99109 }
100110
101111 public static func uncompress( source: URL , destination: URL ) throws {
@@ -186,7 +196,7 @@ public final class Archiver: Sendable {
186196 }
187197
188198 // MARK: private functions
189- private static func _compressFile( item: URL , entry: WriteEntry , archiver: ArchiveWriter ) throws {
199+ private static func _compressFile( item: URL , entry: WriteEntry , archiver: ArchiveWriter , hasher : inout SHA256 ) throws {
190200 guard let stream = InputStream ( url: item) else {
191201 return
192202 }
@@ -204,6 +214,7 @@ public final class Archiver: Sendable {
204214 break
205215 } else {
206216 let data = Data ( bytes: readBuffer, count: byteRead)
217+ hasher. update ( data: data)
207218 try data. withUnsafeBytes { pointer in
208219 try writer. writeChunk ( data: pointer)
209220 }
0 commit comments