Skip to content

Commit 1c37aeb

Browse files
authored
rdar://146539299
1 parent 8bf7925 commit 1c37aeb

File tree

8 files changed

+36
-15
lines changed

8 files changed

+36
-15
lines changed

Sources/SwiftDocC/Utility/FileManagerProtocol.swift

+22-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ package protocol FileManagerProtocol: DataProvider {
3939
/// Returns `true` if a file exists at the given path.
4040
func fileExists(atPath: String) -> Bool
4141
/// Copies a file from one location on the file-system to another.
42-
func copyItem(at: URL, to: URL) throws
42+
func _copyItem(at: URL, to: URL) throws // Use a different name than FileManager to work around https://github.com/swiftlang/swift-foundation/issues/1125
4343
/// Moves a file from one location on the file-system to another.
4444
func moveItem(at: URL, to: URL) throws
4545
/// Creates a new file folder at the given location.
@@ -144,4 +144,25 @@ extension FileManager: FileManagerProtocol {
144144
directories: Array( allContents[partitionIndex...] )
145145
)
146146
}
147+
148+
package func _copyItem(at source: URL, to destination: URL) throws {
149+
// Call `NSFileManager/copyItem(at:to:)` and catch the error to workaround https://github.com/swiftlang/swift-foundation/issues/1125
150+
do {
151+
try copyItem(at: source, to: destination)
152+
} catch let error as CocoaError {
153+
// In Swift 6 on Linux, `FileManager/copyItems(at:to:)` raises an error _after_ successfully copying the files when it's moving over file attributes from the source to the destination.
154+
// To workaround this issue, we check if the destination exist and the error wasn't that the destination _already_ existed.
155+
if error.code != CocoaError.Code.fileWriteFileExists,
156+
fileExists(atPath: destination.path)
157+
{
158+
// Ignore this error.
159+
// The consequence is that the copied item may have some different attributes (creation date, owner, etc.) compared to the source.
160+
// These attributes aren't critical for copying input files over to the output documentation archive.
161+
return
162+
}
163+
164+
// Otherwise, if this was any other error or if the destination file doesn't exist after calling `FileManager/copyItems(at:to:)`, re-throw the error to the caller.
165+
throw error
166+
}
167+
}
147168
}

Sources/SwiftDocCTestUtilities/TestFileSystem.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ package class TestFileSystem: FileManagerProtocol {
160160
return files.keys.contains(path)
161161
}
162162

163-
package func copyItem(at source: URL, to destination: URL) throws {
163+
package func _copyItem(at source: URL, to destination: URL) throws {
164164
guard !disableWriting else { return }
165165

166166
filesLock.lock()
@@ -185,7 +185,7 @@ package class TestFileSystem: FileManagerProtocol {
185185

186186
let srcPath = srcURL.path
187187

188-
try copyItem(at: srcURL, to: dstURL)
188+
try _copyItem(at: srcURL, to: dstURL)
189189
files.removeValue(forKey: srcPath)
190190

191191
for (path, _) in files where path.hasPrefix(srcPath) {

Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extension AsyncAction {
2626
// If a template directory has been provided, create the temporary build folder with its contents
2727
// Ensure that the container exists
2828
try? fileManager.createDirectory(at: targetURL.deletingLastPathComponent(), withIntermediateDirectories: false, attributes: nil)
29-
try fileManager.copyItem(at: template, to: targetURL)
29+
try fileManager._copyItem(at: template, to: targetURL)
3030
} else {
3131
// Otherwise, create an empty directory
3232
try fileManager.createDirectory(at: targetURL, withIntermediateDirectories: true, attributes: nil)

Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer {
7171
func copyAsset(_ asset: DataAsset, to destinationFolder: URL) throws {
7272
for sourceURL in asset.variants.values where !sourceURL.isAbsoluteWebURL {
7373
let assetName = sourceURL.lastPathComponent
74-
try fileManager.copyItem(
74+
try fileManager._copyItem(
7575
at: sourceURL,
7676
to: destinationFolder.appendingPathComponent(assetName, isDirectory: false)
7777
)
@@ -143,7 +143,7 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer {
143143
if fileManager.fileExists(atPath: targetFile.path) {
144144
try fileManager.removeItem(at: targetFile)
145145
}
146-
try fileManager.copyItem(at: themeSettings, to: targetFile)
146+
try fileManager._copyItem(at: themeSettings, to: targetFile)
147147
}
148148
}
149149

Sources/SwiftDocCUtilities/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ class JSONEncodingRenderNodeWriter {
104104
)
105105

106106
do {
107-
try fileManager.copyItem(at: indexHTML, to: htmlTargetFileURL)
107+
try fileManager._copyItem(at: indexHTML, to: htmlTargetFileURL)
108108
} catch let error as NSError where error.code == NSFileWriteFileExistsError {
109109
// We already have an 'index.html' file at this path. This could be because
110110
// we're writing to an output directory that already contains built documentation
111111
// or because we we're given bad input such that multiple documentation pages
112112
// have the same path on the filesystem. Either way, we don't want this to error out
113113
// so just remove the destination item and try the copy operation again.
114114
try fileManager.removeItem(at: htmlTargetFileURL)
115-
try fileManager.copyItem(at: indexHTML, to: htmlTargetFileURL)
115+
try fileManager._copyItem(at: indexHTML, to: htmlTargetFileURL)
116116
}
117117
}
118118
}

Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ struct MergeAction: AsyncAction {
7070
try? fileManager.createDirectory(at: toDirectory, withIntermediateDirectories: false, attributes: nil)
7171
for from in (try? fileManager.contentsOfDirectory(at: fromDirectory, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)) ?? [] {
7272
// Copy each file or subdirectory
73-
try fileManager.copyItem(at: from, to: toDirectory.appendingPathComponent(from.lastPathComponent))
73+
try fileManager._copyItem(at: from, to: toDirectory.appendingPathComponent(from.lastPathComponent))
7474
}
7575
}
7676
guard let jsonIndexData = fileManager.contents(atPath: archive.appendingPathComponent("index/index.json").path) else {
@@ -125,7 +125,7 @@ struct MergeAction: AsyncAction {
125125
contents: RenderJSONEncoder.makeEncoder().encode(renderNode)
126126
)
127127
// It's expected that this will fail if combined archive doesn't support static hosting.
128-
try? fileManager.copyItem(
128+
try? fileManager._copyItem(
129129
at: targetURL.appendingPathComponent("index.html"),
130130
to: targetURL.appendingPathComponent("/documentation/index.html")
131131
)

Sources/SwiftDocCUtilities/Action/Actions/TransformForStaticHostingAction.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct TransformForStaticHostingAction: AsyncAction {
6161
// as we want to preserve anything intentionally left in the output URL by `setupOutputDirectory`
6262
for sourceItem in try fileManager.contentsOfDirectory(at: rootURL, includingPropertiesForKeys: [], options:[.skipsHiddenFiles]) {
6363
let targetItem = outputURL.appendingPathComponent(sourceItem.lastPathComponent)
64-
try fileManager.copyItem(at: sourceItem, to: targetItem)
64+
try fileManager._copyItem(at: sourceItem, to: targetItem)
6565
}
6666
}
6767

@@ -81,7 +81,7 @@ struct TransformForStaticHostingAction: AsyncAction {
8181
if fileManager.fileExists(atPath: target.path){
8282
try fileManager.removeItem(at: target)
8383
}
84-
try fileManager.copyItem(at: source, to: target)
84+
try fileManager._copyItem(at: source, to: target)
8585
}
8686

8787
// Transform the indexHTML if needed.

Tests/SwiftDocCUtilitiesTests/Utility/TestFileSystemTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class TestFileSystemTests: XCTestCase {
106106
func testCopyFiles() throws {
107107
let fs = try makeTestFS()
108108

109-
try fs.copyItem(at: URL(string: "/main/nested/myfile1.txt")!, to: URL(string: "/main/myfile1.txt")!)
109+
try fs._copyItem(at: URL(string: "/main/nested/myfile1.txt")!, to: URL(string: "/main/myfile1.txt")!)
110110
XCTAssertEqual(fs.dump(), """
111111
/
112112
├─ main/
@@ -121,7 +121,7 @@ class TestFileSystemTests: XCTestCase {
121121
func testCopyFolders() throws {
122122
let fs = try makeTestFS()
123123

124-
try fs.copyItem(at: URL(string: "/main/nested")!, to: URL(string: "/copy")!)
124+
try fs._copyItem(at: URL(string: "/main/nested")!, to: URL(string: "/copy")!)
125125
XCTAssertEqual(fs.dump(), """
126126
/
127127
├─ copy/
@@ -308,7 +308,7 @@ class TestFileSystemTests: XCTestCase {
308308
XCTAssertEqual(fs.contents(atPath: "/main/test.txt"), Data(base64Encoded: "TEST"))
309309

310310
// Copy a file and test the contents are identical with original
311-
try fs.copyItem(at: URL(string: "/main/test.txt")!, to: URL(string: "/main/clone.txt")!)
311+
try fs._copyItem(at: URL(string: "/main/test.txt")!, to: URL(string: "/main/clone.txt")!)
312312
XCTAssertTrue(fs.contentsEqual(atPath: "/main/test.txt", andPath: "/main/clone.txt"))
313313

314314
_ = try fs.createFile(at: URL(string:"/main/notclone.txt")!, contents: Data(base64Encoded: "TESTTEST")!)

0 commit comments

Comments
 (0)