Skip to content

Commit

Permalink
Support --output-path when building documentation for multiple targ…
Browse files Browse the repository at this point in the history
…ets. (#89)

* Support using a custom `--output-path` when building a combined archive

Also, print all archive output paths at the end instead of during build

* Customize the synthesized landing page when DocC supports it

* Provide more information about the steps for each target's build task

* Support `--output-path` with multiple targets, even without combined documentation

* Add integration tests for building combined documentation

* Build documentation for different targets concurrently

* Code review feedback: move sorting archives to where it's used

* Remove unused local variable

* Code review feedback: simplify creation of DocCFeatures

* Code review feedback: move assertion for better locality with the potential bug.

* Fix bug in test helper where other log output was sometimes parsed as an archive output path

* Update integration tests to reflect new DocC behavior (synthesized landing pages for combined archives)
  • Loading branch information
d-ronnqvist authored Aug 23, 2024
1 parent c807246 commit a255bbd
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 86 deletions.
97 changes: 97 additions & 0 deletions IntegrationTests/Tests/MixedTargetsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,105 @@ final class MixedTargetsTests: ConcurrencyRequiringTestCase {
)
XCTAssertEqual(try relativeFilePathsIn(.dataSubdirectory, of: libraryArchiveURL), expectedLibraryDataFiles)
}

func testMultipleTargetsOutputPath() throws {
let outputDirectory = try temporaryDirectory().appendingPathComponent("output")

let result = try swiftPackage(
"--disable-sandbox",
"generate-documentation", "--target", "Executable", "--target", "Library",
"--output-path", outputDirectory.path,
workingDirectory: try setupTemporaryDirectoryForFixture(named: "MixedTargets")
)

result.assertExitStatusEquals(0)
let outputArchives = result.referencedDocCArchives
XCTAssertEqual(outputArchives.count, 2)
XCTAssertEqual(outputArchives.map(\.path), [
outputDirectory.appendingPathComponent("Executable.doccarchive").path,
outputDirectory.appendingPathComponent("Library.doccarchive").path,
])

let executableArchiveURL = try XCTUnwrap(
outputArchives.first(where: { $0.lastPathComponent == "Executable.doccarchive" })
)
let executableDataDirectoryContents = try filesIn(.dataSubdirectory, of: executableArchiveURL)
.map(\.relativePath)
.sorted()

XCTAssertEqual(executableDataDirectoryContents, expectedExecutableDataFiles)

let libraryArchiveURL = try XCTUnwrap(
outputArchives.first(where: { $0.lastPathComponent == "Library.doccarchive" })
)
let libraryDataDirectoryContents = try filesIn(.dataSubdirectory, of: libraryArchiveURL)
.map(\.relativePath)
.sorted()

XCTAssertEqual(libraryDataDirectoryContents, expectedLibraryDataFiles)
}

func testCombinedDocumentation() throws {
#if compiler(>=6.0)
let result = try swiftPackage(
"generate-documentation", "--target", "Executable", "--target", "Library",
"--enable-experimental-combined-documentation",
workingDirectory: try setupTemporaryDirectoryForFixture(named: "MixedTargets")
)

result.assertExitStatusEquals(0)
let outputArchives = result.referencedDocCArchives
XCTAssertEqual(outputArchives.count, 1)
XCTAssertEqual(outputArchives.map(\.lastPathComponent), [
"MixedTargets.doccarchive",
])

let combinedArchiveURL = try XCTUnwrap(outputArchives.first)
let combinedDataDirectoryContents = try filesIn(.dataSubdirectory, of: combinedArchiveURL)
.map(\.relativePath)
.sorted()

XCTAssertEqual(combinedDataDirectoryContents, expectedCombinedDataFiles)
#else
XCTSkip("This test requires a Swift-DocC version that support the link-dependencies feature")
#endif
}

func testCombinedDocumentationWithOutputPath() throws {
#if compiler(>=6.0)
let outputDirectory = try temporaryDirectory().appendingPathComponent("output")

let result = try swiftPackage(
"--disable-sandbox",
"generate-documentation", "--target", "Executable", "--target", "Library",
"--enable-experimental-combined-documentation",
"--output-path", outputDirectory.path,
workingDirectory: try setupTemporaryDirectoryForFixture(named: "MixedTargets")
)

result.assertExitStatusEquals(0)
let outputArchives = result.referencedDocCArchives
XCTAssertEqual(outputArchives.count, 1)
XCTAssertEqual(outputArchives.map(\.path), [
outputDirectory.path
])

let combinedArchiveURL = try XCTUnwrap(outputArchives.first)
let combinedDataDirectoryContents = try filesIn(.dataSubdirectory, of: combinedArchiveURL)
.map(\.relativePath)
.sorted()

XCTAssertEqual(combinedDataDirectoryContents, expectedCombinedDataFiles)
#else
XCTSkip("This test requires a Swift-DocC version that support the link-dependencies feature")
#endif
}
}

private let expectedCombinedDataFiles = [
"documentation.json"
] + expectedExecutableDataFiles + expectedLibraryDataFiles

private let expectedExecutableDataFiles = [
"documentation/executable.json",
"documentation/executable/foo.json",
Expand Down
29 changes: 15 additions & 14 deletions IntegrationTests/Tests/Utility/XCTestCase+swiftPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,21 +148,22 @@ struct SwiftInvocationResult {
let exitStatus: Int

var referencedDocCArchives: [URL] {
return standardOutput
let reverseLogLines = standardOutput
// Remove trailing empty lines
.trimmingCharacters(in: .newlines)
// The last few lines is a list of all the output archives
.components(separatedBy: .newlines)
.filter { line in
line.hasPrefix("Generated DocC archive at")
}
.flatMap { line in
line.components(separatedBy: .whitespaces)
.map { component in
return component.trimmingCharacters(in: CharacterSet(charactersIn: "'."))
}
.filter { component in
return component.hasSuffix(".doccarchive")
}
.compactMap(URL.init(fileURLWithPath:))
}
.reversed()

guard let startOfArchiveOutputIndex = reverseLogLines.firstIndex(where: { $0.hasPrefix("Generated ") }) else {
return []
}

return reverseLogLines[..<startOfArchiveOutputIndex]
// Create absolute URLs for each archive
.map { URL(fileURLWithPath: $0.trimmingCharacters(in: .whitespaces)) }
// Restore the original output order
.reversed()
}

var onlyOutputArchive: URL? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
import Foundation
import PackagePlugin

/// Generating symbol graphs is the only task that can't run in parallel.
///
/// We serialize its execution to support build concurrency for the other tasks.
private let symbolGraphGenerationLock = NSLock()

extension PackageManager {
struct DocCSymbolGraphResult {
let unifiedSymbolGraphsDirectory: URL
Expand Down Expand Up @@ -66,7 +71,9 @@ extension PackageManager {
print("symbol graph options: '\(symbolGraphOptions)'")
}

let targetSymbolGraphs = try getSymbolGraph(for: target, options: symbolGraphOptions)
let targetSymbolGraphs = try symbolGraphGenerationLock.withLock {
try getSymbolGraph(for: target, options: symbolGraphOptions)
}
let targetSymbolGraphsDirectory = URL(
fileURLWithPath: targetSymbolGraphs.directoryPath.string,
isDirectory: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Copyright (c) 2022-2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors

import PackagePlugin
import Foundation

extension Target {
func doccArchiveOutputPath(in context: PluginContext) -> String {
return context.pluginWorkDirectory.appending("\(name).doccarchive").string
context.pluginWorkDirectory.appending(archiveName).string
}

func dependencyDocCArchiveOutputPath(in context: PluginContext) -> String {
context.pluginWorkDirectory.appending("dependencies").appending(archiveName).string
}

private var archiveName: String {
"\(name).doccarchive"
}
}
Loading

0 comments on commit a255bbd

Please sign in to comment.