1
1
// This source file is part of the Swift.org open source project
2
2
//
3
- // Copyright (c) 2022 Apple Inc. and the Swift project authors
3
+ // Copyright (c) 2022-2024 Apple Inc. and the Swift project authors
4
4
// Licensed under Apache License v2.0 with Runtime Library Exception
5
5
//
6
6
// See https://swift.org/LICENSE.txt for license information
@@ -30,6 +30,19 @@ import PackagePlugin
30
30
}
31
31
32
32
let verbose = argumentExtractor. extractFlag ( named: " verbose " ) > 0
33
+ let isCombinedDocumentationEnabled = argumentExtractor. extractFlag ( named: PluginFlag . enableCombinedDocumentationSupportFlagName) > 0
34
+
35
+ if isCombinedDocumentationEnabled {
36
+ let doccFeatures = try ? DocCFeatures ( doccExecutable: doccExecutableURL)
37
+ guard doccFeatures? . contains ( . linkDependencies) == true else {
38
+ // The developer uses the combined documentation plugin flag with a DocC version that doesn't support combined documentation.
39
+ Diagnostics . error ( """
40
+ Unsupported use of '-- \( PluginFlag . enableCombinedDocumentationSupportFlagName) '. \
41
+ DocC version at ' \( doccExecutableURL. path) ' doesn't support combined documentation.
42
+ """ )
43
+ return
44
+ }
45
+ }
33
46
34
47
// Parse the given command-line arguments
35
48
let parsedArguments = ParsedArguments ( argumentExtractor. remainingArguments)
@@ -56,14 +69,9 @@ import PackagePlugin
56
69
let snippetExtractor : SnippetExtractor ? = nil
57
70
#endif
58
71
59
-
60
- // Iterate over the Swift source module targets we were given.
61
- for (index, target) in swiftSourceModuleTargets. enumerated ( ) {
62
- if index != 0 {
63
- // Emit a line break if this is not the first target being built.
64
- print ( )
65
- }
66
-
72
+ // An inner function that defines the work to build documentation for a given target.
73
+ func performBuildTask( _ task: DocumentationBuildGraph < SwiftSourceModuleTarget > . Task ) throws -> URL ? {
74
+ let target = task. target
67
75
print ( " Generating documentation for ' \( target. name) '... " )
68
76
69
77
let symbolGraphs = try packageManager. doccSymbolGraphs (
@@ -74,27 +82,24 @@ import PackagePlugin
74
82
customSymbolGraphOptions: parsedArguments. symbolGraphArguments
75
83
)
76
84
77
- if try FileManager . default. contentsOfDirectory ( atPath: symbolGraphs. targetSymbolGraphsDirectory. path) . isEmpty {
78
- // This target did not produce any symbol graphs. Let's check if it has a
79
- // DocC catalog.
85
+ if target. doccCatalogPath == nil ,
86
+ try FileManager . default. contentsOfDirectory ( atPath: symbolGraphs. targetSymbolGraphsDirectory. path) . isEmpty
87
+ {
88
+ // This target did not produce any symbol graphs and has no DocC catalog.
89
+ let message = """
90
+ ' \( target. name) ' does not contain any documentable symbols or a \
91
+ DocC catalog and will not produce documentation
92
+ """
80
93
81
- guard target. doccCatalogPath != nil else {
82
- let message = """
83
- ' \( target. name) ' does not contain any documentable symbols or a \
84
- DocC catalog and will not produce documentation
85
- """
86
-
87
- if swiftSourceModuleTargets. count > 1 {
88
- // We're building multiple targets, just throw a warning for this
89
- // one target that does not produce documentation.
90
- Diagnostics . warning ( message)
91
- continue
92
- } else {
93
- // This is the only target being built so throw an error
94
- Diagnostics . error ( message)
95
- return
96
- }
94
+ if swiftSourceModuleTargets. count > 1 {
95
+ // We're building multiple targets, just emit a warning for this
96
+ // one target that does not produce documentation.
97
+ Diagnostics . warning ( message)
98
+ } else {
99
+ // This is the only target being built so emit an error
100
+ Diagnostics . error ( message)
97
101
}
102
+ return nil
98
103
}
99
104
100
105
// Construct the output path for the generated DocC archive
@@ -108,14 +113,22 @@ import PackagePlugin
108
113
// arguments to pass to `docc`. ParsedArguments will merge the flags provided
109
114
// by the user with default fallback values for required flags that were not
110
115
// provided.
111
- let doccArguments = parsedArguments. doccArguments (
116
+ var doccArguments = parsedArguments. doccArguments (
112
117
action: . convert,
113
118
targetKind: target. kind == . executable ? . executable : . library,
114
119
doccCatalogPath: target. doccCatalogPath,
115
120
targetName: target. name,
116
121
symbolGraphDirectoryPath: symbolGraphs. unifiedSymbolGraphsDirectory. path,
117
122
outputPath: doccArchiveOutputPath
118
123
)
124
+ if isCombinedDocumentationEnabled {
125
+ doccArguments. append ( CommandLineOption . enableExternalLinkSupport. defaultName)
126
+
127
+ for taskDependency in task. dependencies {
128
+ let dependencyArchivePath = taskDependency. target. doccArchiveOutputPath ( in: context)
129
+ doccArguments. append ( contentsOf: [ CommandLineOption . externalLinkDependency. defaultName, dependencyArchivePath] )
130
+ }
131
+ }
119
132
120
133
if verbose {
121
134
let arguments = doccArguments. joined ( separator: " " )
@@ -138,15 +151,57 @@ import PackagePlugin
138
151
let describedOutputPath = doccArguments. outputPath ?? " unknown location "
139
152
print ( " Generated DocC archive at ' \( describedOutputPath) ' " )
140
153
} else {
141
- Diagnostics . error ( """
142
- 'docc convert' invocation failed with a nonzero exit code: ' \( process. terminationStatus) '
143
- """
144
- )
154
+ Diagnostics . error ( " 'docc convert' invocation failed with a nonzero exit code: ' \( process. terminationStatus) ' " )
145
155
}
156
+
157
+ return URL ( fileURLWithPath: doccArchiveOutputPath)
146
158
}
147
159
148
- if swiftSourceModuleTargets. count > 1 {
149
- print ( " \n Multiple DocC archives generated at ' \( context. pluginWorkDirectory. string) ' " )
160
+ let buildGraphRunner = DocumentationBuildGraphRunner ( buildGraph: . init( targets: swiftSourceModuleTargets) )
161
+ var documentationArchives = try buildGraphRunner. perform ( performBuildTask)
162
+ . compactMap { $0 }
163
+
164
+ if documentationArchives. count > 1 {
165
+ documentationArchives = documentationArchives. sorted ( by: { $0. lastPathComponent < $1. lastPathComponent } )
166
+
167
+ if isCombinedDocumentationEnabled {
168
+ // Merge the archives into a combined archive
169
+ let combinedArchiveName = " Combined \( context. package . displayName) Documentation.doccarchive "
170
+ let combinedArchiveOutput = URL ( fileURLWithPath: context. pluginWorkDirectory. appending ( combinedArchiveName) . string)
171
+
172
+ var mergeCommandArguments = [ " merge " ]
173
+ mergeCommandArguments. append ( contentsOf: documentationArchives. map ( \. standardizedFileURL. path) )
174
+ mergeCommandArguments. append ( contentsOf: [ " --output-path " , combinedArchiveOutput. path] )
175
+
176
+ // Remove the combined archive if it already exists
177
+ try ? FileManager . default. removeItem ( at: combinedArchiveOutput)
178
+
179
+ // Create a new combined archive
180
+ let process = try Process . run ( doccExecutableURL, arguments: mergeCommandArguments)
181
+ process. waitUntilExit ( )
182
+
183
+ // Display the combined archive before the other generated archives
184
+ documentationArchives. insert ( combinedArchiveOutput, at: 0 )
185
+ }
186
+
187
+ print ( """
188
+ Generated \( documentationArchives. count) DocC archives in ' \( context. pluginWorkDirectory. string) ':
189
+ \( documentationArchives. map ( \. lastPathComponent) . joined ( separator: " \n " ) )
190
+ """ )
191
+ }
192
+ }
193
+ }
194
+
195
+ // We add the conformance here so that 'DocumentationBuildGraphTarget' doesn't need to know about 'SwiftSourceModuleTarget' or import 'PackagePlugin'.
196
+ extension SwiftSourceModuleTarget : DocumentationBuildGraphTarget {
197
+ var dependencyIDs : [ String ] {
198
+ // List all the target dependencies in a flat list.
199
+ dependencies. flatMap {
200
+ switch $0 {
201
+ case . target( let target) : return [ target. id]
202
+ case . product( let product) : return product. targets. map { $0. id }
203
+ @unknown default : return [ ]
204
+ }
150
205
}
151
206
}
152
207
}
0 commit comments