@@ -95,6 +95,10 @@ public final class Archiver: Sendable {
9595 }
9696 }
9797
98+ let archivedPathsByHostPath = entryInfo. reduce ( into: [ URL: [ URL] ] ( ) ) { result, info in
99+ result [ info. pathOnHost. standardizedFileURL, default: [ ] ] . append ( info. pathInArchive)
100+ }
101+
98102 let archiver = try ArchiveWriter (
99103 configuration: writerConfiguration
100104 )
@@ -104,7 +108,7 @@ public final class Archiver: Sendable {
104108 encoder. outputFormatting = . sortedKeys
105109
106110 for info in entryInfo {
107- guard let entry = try Self . _createEntry ( entryInfo: info) else {
111+ guard let entry = try Self . _createEntry ( entryInfo: info, archivedPathsByHostPath : archivedPathsByHostPath ) else {
108112 throw Error . failedToCreateEntry
109113 }
110114 let hashInfo = ArchiveEntryHashInfo (
@@ -252,7 +256,11 @@ public final class Archiver: Sendable {
252256 try writer. finish ( )
253257 }
254258
255- private static func _createEntry( entryInfo: ArchiveEntryInfo , pathPrefix: String = " " ) throws -> WriteEntry ? {
259+ private static func _createEntry(
260+ entryInfo: ArchiveEntryInfo ,
261+ archivedPathsByHostPath: [ URL : [ URL ] ] = [ : ] ,
262+ pathPrefix: String = " "
263+ ) throws -> WriteEntry ? {
256264 let entry = WriteEntry ( )
257265 let fileManager = FileManager . default
258266 let attributes = try fileManager. attributesOfItem ( atPath: entryInfo. pathOnHost. path)
@@ -270,7 +278,11 @@ public final class Archiver: Sendable {
270278 entry. fileType = . symbolicLink
271279 entry. size = 0
272280 let symlinkTarget = try fileManager. destinationOfSymbolicLink ( atPath: entryInfo. pathOnHost. path)
273- entry. symlinkTarget = symlinkTarget
281+ entry. symlinkTarget = Self . _rewriteArchivedAbsoluteSymlinkTarget (
282+ symlinkTarget,
283+ entryInfo: entryInfo,
284+ archivedPathsByHostPath: archivedPathsByHostPath
285+ )
274286 default :
275287 return nil
276288 }
@@ -332,6 +344,48 @@ public final class Archiver: Sendable {
332344 return trimmedPath
333345 }
334346
347+ private static func _rewriteArchivedAbsoluteSymlinkTarget(
348+ _ symlinkTarget: String ,
349+ entryInfo: ArchiveEntryInfo ,
350+ archivedPathsByHostPath: [ URL : [ URL ] ]
351+ ) -> String {
352+ guard symlinkTarget. hasPrefix ( " / " ) else {
353+ return symlinkTarget
354+ }
355+
356+ let targetPath = URL ( fileURLWithPath: symlinkTarget) . standardizedFileURL
357+ guard let targetArchivePaths = archivedPathsByHostPath [ targetPath] , targetArchivePaths. count == 1 , let targetArchivePath = targetArchivePaths. first else {
358+ return symlinkTarget
359+ }
360+
361+ let sourceDirectory = entryInfo. pathInArchive. deletingLastPathComponent ( ) . relativePath
362+ return Self . _relativeArchivePath ( fromDirectory: sourceDirectory, to: targetArchivePath. relativePath)
363+ }
364+
365+ private static func _relativeArchivePath( fromDirectory: String , to path: String ) -> String {
366+ let fromComponents = Self . _archivePathComponents ( fromDirectory)
367+ let toComponents = Self . _archivePathComponents ( path)
368+
369+ var commonPrefixCount = 0
370+ while commonPrefixCount < fromComponents. count,
371+ commonPrefixCount < toComponents. count,
372+ fromComponents [ commonPrefixCount] == toComponents [ commonPrefixCount]
373+ {
374+ commonPrefixCount += 1
375+ }
376+
377+ let upwardTraversal = Array ( repeating: " .. " , count: fromComponents. count - commonPrefixCount)
378+ let remainder = Array ( toComponents. dropFirst ( commonPrefixCount) )
379+ let relativeComponents = upwardTraversal + remainder
380+ return relativeComponents. isEmpty ? " . " : relativeComponents. joined ( separator: " / " )
381+ }
382+
383+ private static func _archivePathComponents( _ path: String ) -> [ String ] {
384+ NSString ( string: path) . pathComponents. filter { component in
385+ component != " / " && component != " . "
386+ }
387+ }
388+
335389 private static func _isSymbolicLink( _ path: URL ) throws -> Bool {
336390 let resourceValues = try path. resourceValues ( forKeys: [ . isSymbolicLinkKey] )
337391 if let isSymbolicLink = resourceValues. isSymbolicLink {
0 commit comments