Skip to content

Commit 04aed7c

Browse files
authored
Merge branch 'main' into add-extended-types-flag
2 parents 9f979e0 + e013865 commit 04aed7c

18 files changed

+401
-99
lines changed

IntegrationTests/Tests/DocCArchiveIndexGenerationTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class DocCArchiveIndexGenerationTests: XCTestCase {
11+
final class DocCArchiveIndexGenerationTests: ConcurrencyRequiringTestCase {
1212
func testGenerateDocumentationWithIndexingEnabled() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation",

IntegrationTests/Tests/Fixtures/PackageWithSnippets/Package.swift

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import PackageDescription
1313

1414
let package = Package(
1515
name: "PackageWithSnippets",
16+
products: [
17+
.library(name: "Library", targets: ["Library"])
18+
],
1619
targets: [
1720
.target(name: "Library"),
1821
]

IntegrationTests/Tests/MixedTargetsTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class MixedTargetsTests: XCTestCase {
11+
final class MixedTargetsTests: ConcurrencyRequiringTestCase {
1212
func testGenerateDocumentationForSpecificTarget() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation", "--target", "Executable",

IntegrationTests/Tests/SingleExecutableTargetTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class SingleExecutableTargetTests: XCTestCase {
11+
final class SingleExecutableTargetTests: ConcurrencyRequiringTestCase {
1212
func testGenerateDocumentation() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation",

IntegrationTests/Tests/SingleLibraryTargetTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class SingleLibraryTargetTests: XCTestCase {
11+
final class SingleLibraryTargetTests: ConcurrencyRequiringTestCase {
1212
func testGenerateDocumentation() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation",

IntegrationTests/Tests/SingleTestTargetTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class SingleTestTargetTests: XCTestCase {
11+
final class SingleTestTargetTests: ConcurrencyRequiringTestCase {
1212
func testDoesNotGenerateDocumentation() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation",

IntegrationTests/Tests/SnippetDocumentationGenerationTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class SnippetDocumentationGenerationTests: XCTestCase {
11+
final class SnippetDocumentationGenerationTests: ConcurrencyRequiringTestCase {
1212
func testGenerateDocumentationForPackageWithSnippets() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation",

IntegrationTests/Tests/SwiftDocCPreviewTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import Foundation
1010
import XCTest
1111

12-
final class SwiftDocCPreview: XCTestCase {
12+
final class SwiftDocCPreview: ConcurrencyRequiringTestCase {
1313
func testRunPreviewServerOnSamePortRepeatedly() throws {
1414
// Because only a single server can bind to a given port at a time,
1515
// this test ensures that the preview server running in the `docc`

IntegrationTests/Tests/SwiftPMSandboxTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class SwiftPMSandboxTests: XCTestCase {
11+
final class SwiftPMSandboxTests: ConcurrencyRequiringTestCase {
1212
func testEnableAdditionalSandboxedDirectories() throws {
1313
let outputDirectory = try temporaryDirectory()
1414

IntegrationTests/Tests/TargetWithDocCCatalogTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import XCTest
1010

11-
final class TargetWithDocCCatalogTests: XCTestCase {
11+
final class TargetWithDocCCatalogTests: ConcurrencyRequiringTestCase {
1212
func testGenerateDocumentation() throws {
1313
let result = try swiftPackage(
1414
"generate-documentation",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See https://swift.org/LICENSE.txt for license information
7+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
8+
9+
import Foundation
10+
import XCTest
11+
12+
/// A test case that requires the host toolchain to support Swift concurrency in order
13+
/// to pass.
14+
///
15+
/// All SwiftPM command plugins depend on Swift concurrency so we don't expect to be able
16+
/// to run any integration test that actually invokes the Swift-DocC Plugin without
17+
/// the Swift concurrency libraries.
18+
class ConcurrencyRequiringTestCase: XCTestCase {
19+
override func setUpWithError() throws {
20+
try XCTSkipUnless(
21+
supportsSwiftConcurrency(),
22+
"The current SDK and/or OS do not support Swift concurrency."
23+
)
24+
}
25+
26+
private static var _supportsSwiftConcurrency: Bool?
27+
28+
// Adapted from https://github.com/apple/swift-package-manager/blob/dd7e9cc6/Sources/SPMTestSupport/Toolchain.swift#L55
29+
private func supportsSwiftConcurrency() throws -> Bool {
30+
#if os(macOS)
31+
if #available(macOS 12.0, *) {
32+
// On macOS 12 and later, concurrency is assumed to work.
33+
return true
34+
} else {
35+
if let _supportsSwiftConcurrency = Self._supportsSwiftConcurrency {
36+
return _supportsSwiftConcurrency
37+
}
38+
39+
let temporaryDirectory = try temporaryDirectory()
40+
41+
// On macOS 11 and earlier, we don't know if concurrency actually works because not all
42+
// SDKs and toolchains have the right bits. We could examine the SDK and the various
43+
// libraries, but the most accurate test is to just try to compile and run a snippet of
44+
// code that requires async/await support. It doesn't have to actually do anything,
45+
// it's enough that all the libraries can be found (but because the library reference
46+
// is weak we do need the linkage reference to `_swift_task_create` and the like).
47+
do {
48+
let inputPath = temporaryDirectory.appendingPathComponent("foo.swift")
49+
50+
try """
51+
public func foo() async {}
52+
53+
Task { await foo() }
54+
""".write(to: inputPath, atomically: true, encoding: .utf8)
55+
56+
let outputPath = temporaryDirectory.appendingPathComponent("foo")
57+
let process = Process()
58+
process.executableURL = try swiftExecutableURL
59+
.deletingLastPathComponent()
60+
.appendingPathComponent("swiftc")
61+
62+
process.arguments = [inputPath.path, "-o", outputPath.path]
63+
64+
try process.run()
65+
process.waitUntilExit()
66+
guard process.terminationStatus == EXIT_SUCCESS else {
67+
Self._supportsSwiftConcurrency = false
68+
return false
69+
}
70+
} catch {
71+
// On any failure we assume false.
72+
Self._supportsSwiftConcurrency = false
73+
return false
74+
}
75+
// If we get this far we could compile and run a trivial executable that uses
76+
// libConcurrency, so we can say that this toolchain supports concurrency on this host.
77+
Self._supportsSwiftConcurrency = true
78+
return true
79+
}
80+
#else
81+
// On other platforms, concurrency is assumed to work since with new enough versions
82+
// of the toolchain.
83+
return true
84+
#endif
85+
}
86+
}
87+

IntegrationTests/Tests/Utility/XCTestCase+swiftPackage.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ extension XCTestCase {
110110
}
111111
}
112112

113-
private var swiftExecutableURL: URL {
113+
var swiftExecutableURL: URL {
114114
get throws {
115115
let whichProcess = Process.shell("which swift")
116116

Package.swift

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ let package = Package(
5656
dependencies: [
5757
"Snippets",
5858
"SwiftDocCPluginUtilities",
59+
"snippet-extract",
5960
],
6061
resources: [
6162
.copy("Test Fixtures"),

Sources/SwiftDocCPluginUtilities/Snippets/SnippetExtractor.swift

+41-13
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class SnippetExtractor {
1515

1616
enum SymbolGraphExtractionResult {
1717
case packageDoesNotProduceSnippets
18-
case packageContainsSnippets(symbolGraphDirectory: URL)
18+
case packageContainsSnippets(symbolGraphFile: URL)
1919
}
2020

2121
private let snippetTool: URL
@@ -65,6 +65,27 @@ public class SnippetExtractor {
6565
var _fileExists: (_ path: String) -> Bool = { path in
6666
return FileManager.default.fileExists(atPath: path)
6767
}
68+
69+
/// Returns all of the `.swift` files under a directory recursively.
70+
///
71+
/// Provided for testing.
72+
var _findSnippetFilesInDirectory: (_ directory: URL) -> [String] = { directory -> [String] in
73+
guard let snippetEnumerator = FileManager.default.enumerator(at: directory,
74+
includingPropertiesForKeys: nil,
75+
options: [.skipsHiddenFiles]) else {
76+
return []
77+
78+
}
79+
var snippetInputFiles = [String]()
80+
for case let potentialSnippetURL as URL in snippetEnumerator {
81+
guard potentialSnippetURL.pathExtension.lowercased() == "swift" else {
82+
continue
83+
}
84+
snippetInputFiles.append(potentialSnippetURL.path)
85+
}
86+
87+
return snippetInputFiles
88+
}
6889

6990
/// Generate snippets for the given package.
7091
///
@@ -79,16 +100,15 @@ public class SnippetExtractor {
79100
/// The snippet extractor will look for a `Snippets` subdirectory
80101
/// within this directory.
81102
///
82-
/// - Returns: A URL for the directory containing the generated snippets or nil if
83-
/// no snippets were produced.
103+
/// - Returns: A URL for the output file of the generated snippets symbol graph JSON file.
84104
public func generateSnippets(
85105
for packageIdentifier: PackageIdentifier,
86106
packageDisplayName: String,
87107
packageDirectory: URL
88108
) throws -> URL? {
89109
switch snippetSymbolGraphExtractionResults[packageIdentifier] {
90-
case .packageContainsSnippets(symbolGraphDirectory: let symbolGraphDirectory):
91-
return symbolGraphDirectory
110+
case .packageContainsSnippets(symbolGraphFile: let symbolGraphFile):
111+
return symbolGraphFile
92112
case .packageDoesNotProduceSnippets:
93113
return nil
94114
case .none:
@@ -100,26 +120,34 @@ public class SnippetExtractor {
100120
snippetSymbolGraphExtractionResults[packageIdentifier] = .packageDoesNotProduceSnippets
101121
return nil
102122
}
123+
124+
let snippetInputFiles = _findSnippetFilesInDirectory(snippetsDirectory)
125+
126+
guard !snippetInputFiles.isEmpty else {
127+
snippetSymbolGraphExtractionResults[packageIdentifier] = .packageDoesNotProduceSnippets
128+
return nil
129+
}
103130

104131
let outputDirectory = snippetsOutputDirectory(
105132
in: workingDirectory,
106133
packageIdentifier: packageIdentifier,
107134
packageDisplayName: packageDisplayName
108135
)
136+
137+
let outputFile = outputDirectory.appendingPathComponent("\(packageDisplayName)-snippets.symbols.json")
109138

110139
let process = Process()
111140
process.executableURL = snippetTool
112141
process.arguments = [
113-
snippetsDirectory.path,
114-
outputDirectory.path,
115-
packageDisplayName,
116-
]
117-
142+
"--output", outputFile.path,
143+
"--module-name", packageDisplayName,
144+
] + snippetInputFiles
145+
118146
try _runProcess(process)
119147

120-
if _fileExists(outputDirectory.path) {
121-
snippetSymbolGraphExtractionResults[packageIdentifier] = .packageContainsSnippets(symbolGraphDirectory: outputDirectory)
122-
return outputDirectory
148+
if _fileExists(outputFile.path) {
149+
snippetSymbolGraphExtractionResults[packageIdentifier] = .packageContainsSnippets(symbolGraphFile: outputFile)
150+
return outputFile
123151
} else {
124152
snippetSymbolGraphExtractionResults[packageIdentifier] = .packageDoesNotProduceSnippets
125153
return nil

0 commit comments

Comments
 (0)