Skip to content

Commit 73e7adc

Browse files
authored
Update the DocC documentation script so that it works with the changes to the merge command (#830)
* Update script to remove output dir before merging documentation * Ensure that the combined archive has a data directory * Add check that either all archives support static hosting or none do * Support merging archives that don't have static hosting files rdar://124270602 * Raise error in TestFileSystem if intermediate directories are missing Also, ensure that temp directory always exists * Fix tests that used TestFileSystem but created real temp directories * Only copy static hosting files if input archives has them
1 parent 5bf9c6d commit 73e7adc

File tree

6 files changed

+691
-127
lines changed

6 files changed

+691
-127
lines changed

Sources/SwiftDocCTestUtilities/TestFileSystem.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProv
7070

7171
// Default system paths
7272
files["/"] = Self.folderFixtureData
73+
files["/tmp"] = Self.folderFixtureData
7374

7475
// Import given folders
7576
try updateDocumentationBundles(withFolders: folders)
@@ -121,7 +122,7 @@ public class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProv
121122
defer { filesLock.unlock() }
122123

123124
guard let file = files[url.path] else {
124-
throw CocoaError.error(.fileReadNoSuchFile)
125+
throw makeFileNotFoundError(url)
125126
}
126127
return file
127128
}
@@ -190,7 +191,9 @@ public class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProv
190191

191192
filesLock.lock()
192193
defer { filesLock.unlock() }
193-
194+
195+
try ensureParentDirectoryExists(for: dstURL)
196+
194197
let srcPath = srcURL.path
195198
let dstPath = dstURL.path
196199

@@ -222,13 +225,12 @@ public class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProv
222225
filesLock.lock()
223226
defer { filesLock.unlock() }
224227

225-
let parent = URL(fileURLWithPath: path).deletingLastPathComponent()
228+
let url = URL(fileURLWithPath: path)
229+
let parent = url.deletingLastPathComponent()
226230
if parent.pathComponents.count > 1 {
227231
// If it's not the root folder, check if parents exist
228232
if createIntermediates == false {
229-
guard files.keys.contains(parent.path) else {
230-
throw CocoaError.error(.fileReadNoSuchFile)
231-
}
233+
try ensureParentDirectoryExists(for: url)
232234
} else {
233235
// Create missing parent directories
234236
try createDirectory(atPath: parent.path, withIntermediateDirectories: true)
@@ -266,16 +268,14 @@ public class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProv
266268
}
267269
}
268270

269-
public func createFile(at: URL, contents: Data) throws {
271+
public func createFile(at url: URL, contents: Data) throws {
270272
filesLock.lock()
271273
defer { filesLock.unlock() }
272274

273-
guard files.keys.contains(at.deletingLastPathComponent().path) else {
274-
throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, userInfo: [NSFilePathErrorKey: at.path])
275-
}
275+
try ensureParentDirectoryExists(for: url)
276276

277277
if !disableWriting {
278-
files[at.path] = contents
278+
files[url.path] = contents
279279
}
280280
}
281281

@@ -377,6 +377,17 @@ public class TestFileSystem: FileManagerProtocol, DocumentationWorkspaceDataProv
377377
}
378378
return allSubpaths
379379
}
380+
381+
private func ensureParentDirectoryExists(for url: URL) throws {
382+
let parentURL = url.deletingLastPathComponent()
383+
guard directoryExists(atPath: parentURL.path) else {
384+
throw makeFileNotFoundError(parentURL)
385+
}
386+
}
387+
388+
private func makeFileNotFoundError(_ url: URL) -> Error {
389+
return CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path])
390+
}
380391
}
381392

382393
private extension File {

Sources/SwiftDocCUtilities/Action/Actions/MergeAction.swift

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct MergeAction: Action {
2626

2727
try validateThatOutputIsEmpty()
2828
try validateThatArchivesHaveDisjointData()
29+
let supportsStaticHosting = try validateThatAllArchivesOrNoArchivesSupportStaticHosting()
2930

3031
let targetURL = try Self.createUniqueDirectory(inside: fileManager.uniqueTemporaryDirectory(), template: firstArchive, fileManager: fileManager)
3132
defer {
@@ -40,18 +41,19 @@ struct MergeAction: Action {
4041
}
4142
var combinedJSONIndex = try JSONDecoder().decode(RenderIndex.self, from: jsonIndexData)
4243

44+
// Ensure that the destination has a data directory in case the first archive didn't have any pages.
45+
try? fileManager.createDirectory(at: targetURL.appendingPathComponent("data", isDirectory: true), withIntermediateDirectories: false, attributes: nil)
46+
47+
let directoriesToCopy = ["data/documentation", "data/tutorials", "images", "videos", "downloads"] + (supportsStaticHosting ? ["documentation", "tutorials"] : [])
4348
for archive in archives.dropFirst() {
44-
for directoryToCopy in ["data/documentation", "data/tutorials", "documentation", "tutorials", "images", "videos", "downloads"] {
49+
for directoryToCopy in directoriesToCopy {
4550
let fromDirectory = archive.appendingPathComponent(directoryToCopy, isDirectory: true)
4651
let toDirectory = targetURL.appendingPathComponent(directoryToCopy, isDirectory: true)
4752

48-
var mkdir_p = false
53+
// Ensure that the destination directory exist in case the first archive didn't have that kind of pages.
54+
// This is necessary when merging a reference-only archive with a tutorial-only archive.
55+
try? fileManager.createDirectory(at: toDirectory, withIntermediateDirectories: false, attributes: nil)
4956
for from in (try? fileManager.contentsOfDirectory(at: fromDirectory, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)) ?? [] {
50-
// Create the full path to the destination directory if necessary, once for each directory to copy
51-
if !mkdir_p {
52-
try fileManager.createDirectory(at: toDirectory, withIntermediateDirectories: true, attributes: nil)
53-
mkdir_p = true
54-
}
5557
// Copy each file or subdirectory
5658
try fileManager.copyItem(at: from, to: toDirectory.appendingPathComponent(from.lastPathComponent))
5759
}
@@ -75,6 +77,7 @@ struct MergeAction: Action {
7577
return ActionResult(didEncounterError: false, outputs: [outputURL])
7678
}
7779

80+
/// Validate that the different archives don't have overlapping data.
7881
private func validateThatArchivesHaveDisjointData() throws {
7982
// Check that the archives don't have overlapping data
8083
typealias ArchivesByDirectoryName = [String: [String: Set<String>]]
@@ -131,6 +134,7 @@ struct MergeAction: Action {
131134
}
132135
}
133136

137+
/// Validate that the output directory is empty.
134138
private func validateThatOutputIsEmpty() throws {
135139
guard fileManager.directoryExists(atPath: outputURL.path) else {
136140
return
@@ -162,4 +166,44 @@ struct MergeAction: Action {
162166
throw NonEmptyOutputError(existingContents: existingContents, fileManager: fileManager)
163167
}
164168
}
169+
170+
/// Validate that either all archives support static hosting or that no archives support static hosting.
171+
/// - Returns: `true` if all archives support static hosting; `false` otherwise.
172+
private func validateThatAllArchivesOrNoArchivesSupportStaticHosting() throws -> Bool {
173+
let nonEmptyArchives = archives.filter {
174+
fileManager.directoryExists(atPath: $0.appendingPathComponent("data").path)
175+
}
176+
177+
let archivesWithStaticHostingSupport = nonEmptyArchives.filter {
178+
return fileManager.directoryExists(atPath: $0.appendingPathComponent("documentation").path)
179+
|| fileManager.directoryExists(atPath: $0.appendingPathComponent("tutorials").path)
180+
}
181+
182+
guard archivesWithStaticHostingSupport.count == nonEmptyArchives.count // All archives support static hosting
183+
|| archivesWithStaticHostingSupport.count == 0 // No archives support static hosting
184+
else {
185+
struct DifferentStaticHostingSupportError: DescribedError {
186+
var withSupport: Set<String>
187+
var withoutSupport: Set<String>
188+
189+
var errorDescription: String {
190+
"""
191+
Different static hosting support in different archives.
192+
193+
\(withSupport.sorted().joined(separator: ", ")) support\(withSupport.count == 1 ? "s" : "") static hosting \
194+
but \(withoutSupport.sorted().joined(separator: ", ")) do\(withoutSupport.count == 1 ? "es" : "")n't.
195+
"""
196+
}
197+
}
198+
let allArchiveNames = Set(nonEmptyArchives.map(\.lastPathComponent))
199+
let archiveNamesWithStaticHostingSupport = Set(archivesWithStaticHostingSupport.map(\.lastPathComponent))
200+
201+
throw DifferentStaticHostingSupportError(
202+
withSupport: archiveNamesWithStaticHostingSupport,
203+
withoutSupport: allArchiveNames.subtracting(archiveNamesWithStaticHostingSupport)
204+
)
205+
}
206+
207+
return !archivesWithStaticHostingSupport.isEmpty
208+
}
165209
}

0 commit comments

Comments
 (0)