Skip to content

Commit 6c27a9e

Browse files
committed
Fix error when writing multiple index.html files to the same location
Addresses a regression where `docc convert` would throw an error when writing multiple `index.html` files to the same location on disk. This can occur if docc is writing to an output directory that already contains built documentation or because when docc is given bad input such that multiple documentation pages have the same path on the filesystem. Resolves rdar://94311195
1 parent fc01258 commit 6c27a9e

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ class JSONEncodingRenderNodeWriter {
116116
attributes: nil
117117
)
118118

119-
try fileManager.copyItem(at: indexHTML, to: htmlTargetFileURL)
119+
do {
120+
try fileManager.copyItem(at: indexHTML, to: htmlTargetFileURL)
121+
} catch let error as NSError where error.code == NSFileWriteFileExistsError {
122+
// We already have an 'index.html' file at this path. This could be because
123+
// we're writing to an output directory that already contains built documentation
124+
// or because we we're given bad input such that multiple documentation pages
125+
// have the same path on the filesystem. Either way, we don't want this to error out
126+
// so just remove the destination item and try the copy operation again.
127+
try fileManager.removeItem(at: htmlTargetFileURL)
128+
try fileManager.copyItem(at: indexHTML, to: htmlTargetFileURL)
129+
}
120130
}
121131
}

Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import XCTest
1212
import Foundation
1313
@testable import SwiftDocC
1414
@testable import SwiftDocCUtilities
15+
import SymbolKit
1516
import Markdown
1617
import SwiftDocCTestUtilities
1718

@@ -2361,7 +2362,75 @@ class ConvertActionTests: XCTestCase {
23612362
var action = try ConvertAction(fromConvertCommand: convertCommand)
23622363
_ = try action.perform(logHandle: .none)
23632364
}
2365+
2366+
func emitEmptySymbolGraph(moduleName: String, destination: URL) throws {
2367+
let symbolGraph = SymbolGraph(
2368+
metadata: .init(
2369+
formatVersion: .init(major: 0, minor: 0, patch: 1),
2370+
generator: "unit-test"
2371+
),
2372+
module: .init(
2373+
name: moduleName,
2374+
platform: .init()
2375+
),
2376+
symbols: [],
2377+
relationships: []
2378+
)
2379+
2380+
// Create a unique subfolder to place the symbol graph in
2381+
// in case we're emitting multiple symbol graphs with the same filename.
2382+
let uniqueSubfolder = destination.appendingPathComponent(
2383+
ProcessInfo.processInfo.globallyUniqueString
2384+
)
2385+
try FileManager.default.createDirectory(
2386+
at: uniqueSubfolder,
2387+
withIntermediateDirectories: false
2388+
)
2389+
2390+
try JSONEncoder().encode(symbolGraph).write(
2391+
to: uniqueSubfolder
2392+
.appendingPathComponent(moduleName, isDirectory: false)
2393+
.appendingPathExtension("symbols.json")
2394+
)
2395+
}
23642396

2397+
// Tests that when `docc convert` is given input that produces multiple pages at the same path
2398+
// on disk it does not throw an error when attempting to transform it for static hosting. (94311195)
2399+
func testConvertDocCCatalogThatProducesMultipleDocumentationPagesAtTheSamePathDoesNotThrowError() throws {
2400+
let temporaryDirectory = try createTemporaryDirectory()
2401+
2402+
let catalogURL = try Folder(
2403+
name: "unit-test.docc",
2404+
content: [
2405+
InfoPlist(displayName: "TestBundle", identifier: "com.test.example"),
2406+
]
2407+
).write(inside: temporaryDirectory)
2408+
try emitEmptySymbolGraph(moduleName: "docc", destination: catalogURL)
2409+
try emitEmptySymbolGraph(moduleName: "DocC", destination: catalogURL)
2410+
2411+
let htmlTemplateDirectory = try Folder.emptyHTMLTemplateDirectory.write(
2412+
inside: temporaryDirectory
2413+
)
2414+
2415+
let targetDirectory = temporaryDirectory.appendingPathComponent("target.doccarchive", isDirectory: true)
2416+
let dataProvider = try LocalFileSystemDataProvider(rootURL: catalogURL)
2417+
2418+
var action = try ConvertAction(
2419+
documentationBundleURL: catalogURL,
2420+
outOfProcessResolver: nil,
2421+
analyze: false,
2422+
targetDirectory: targetDirectory,
2423+
htmlTemplateDirectory: htmlTemplateDirectory,
2424+
emitDigest: false,
2425+
currentPlatforms: nil,
2426+
dataProvider: dataProvider,
2427+
fileManager: FileManager.default,
2428+
temporaryDirectory: createTemporaryDirectory(),
2429+
transformForStaticHosting: true
2430+
)
2431+
2432+
XCTAssertNoThrow(try action.performAndHandleResult())
2433+
}
23652434
func testConvertWithCustomTemplates() throws {
23662435
let info = InfoPlist(displayName: "TestConvertWithCustomTemplates", identifier: "com.test.example")
23672436
let index = TextFile(name: "index.html", utf8Content: """

0 commit comments

Comments
 (0)