Skip to content

Commit 456f2be

Browse files
authored
Load imported module list for plugin targets (#5913)
Resolves rdar://97680091, rdar://92758063
1 parent 87254d2 commit 456f2be

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed

Sources/PackageModel/Target.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import TSCBasic
14+
import Dispatch
1415

1516
import protocol TSCUtility.PolymorphicCodableProtocol
1617
import Basics

Sources/Workspace/Workspace.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,37 @@ extension Workspace {
11791179
}
11801180
}
11811181

1182+
public func loadPluginImports(
1183+
packageGraph: PackageGraph,
1184+
completion: @escaping(Result<[PackageIdentity: [String: [String]]], Error>) -> Void) {
1185+
let pluginTargets = packageGraph.allTargets.filter{$0.type == .plugin}
1186+
let scanner = SwiftcImportScanner(swiftCompilerEnvironment: hostToolchain.swiftCompilerEnvironment, swiftCompilerFlags: hostToolchain.swiftCompilerFlags, swiftCompilerPath: hostToolchain.swiftCompilerPath)
1187+
var importList = [PackageIdentity: [String: [String]]]()
1188+
1189+
for pluginTarget in pluginTargets {
1190+
let paths = pluginTarget.sources.paths
1191+
guard let pkgId = packageGraph.package(for: pluginTarget)?.identity else { continue }
1192+
1193+
if importList[pkgId] == nil {
1194+
importList[pkgId] = [pluginTarget.name: []]
1195+
} else if importList[pkgId]?[pluginTarget.name] == nil {
1196+
importList[pkgId]?[pluginTarget.name] = []
1197+
}
1198+
1199+
for path in paths {
1200+
do {
1201+
let result = try tsc_await {
1202+
scanner.scanImports(path, callbackQueue: DispatchQueue.sharedConcurrent, completion: $0)
1203+
}
1204+
importList[pkgId]?[pluginTarget.name]?.append(contentsOf: result)
1205+
} catch {
1206+
completion(.failure(error))
1207+
}
1208+
}
1209+
}
1210+
completion(.success(importList))
1211+
}
1212+
11821213
/// Loads a single package in the context of a previously loaded graph. This can be useful for incremental loading in a longer-lived program, like an IDE.
11831214
public func loadPackage(
11841215
with identity: PackageIdentity,

Tests/SPMBuildCoreTests/PluginInvocationTests.swift

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,4 +899,160 @@ class PluginInvocationTests: XCTestCase {
899899
}
900900
}
901901
}
902+
903+
func testScanImportsInPluginTargets() throws {
904+
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
905+
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")
906+
907+
try testWithTemporaryDirectory { tmpPath in
908+
// Create a sample package with a library target and a plugin.
909+
let packageDir = tmpPath.appending(components: "MyPackage")
910+
try localFileSystem.createDirectory(packageDir, recursive: true)
911+
try localFileSystem.writeFileContents(packageDir.appending(component: "Package.swift"), string: """
912+
// swift-tools-version: 5.7
913+
import PackageDescription
914+
let package = Package(
915+
name: "MyPackage",
916+
dependencies: [
917+
.package(path: "../OtherPackage"),
918+
],
919+
targets: [
920+
.target(
921+
name: "MyLibrary",
922+
dependencies: [.product(name: "OtherPlugin", package: "OtherPackage")]
923+
),
924+
.plugin(
925+
name: "XPlugin",
926+
capability: .buildTool()
927+
),
928+
.plugin(
929+
name: "YPlugin",
930+
capability: .command(
931+
intent: .custom(verb: "YPlugin", description: "Plugin example"),
932+
permissions: []
933+
)
934+
)
935+
]
936+
)
937+
""")
938+
939+
let myLibraryTargetDir = packageDir.appending(components: "Sources", "MyLibrary")
940+
try localFileSystem.createDirectory(myLibraryTargetDir, recursive: true)
941+
try localFileSystem.writeFileContents(myLibraryTargetDir.appending(component: "library.swift"), string: """
942+
public func hello() { }
943+
""")
944+
let xPluginTargetDir = packageDir.appending(components: "Plugins", "XPlugin")
945+
try localFileSystem.createDirectory(xPluginTargetDir, recursive: true)
946+
try localFileSystem.writeFileContents(xPluginTargetDir.appending(component: "plugin.swift"), string: """
947+
import PackagePlugin
948+
import XcodeProjectPlugin
949+
@main struct XBuildToolPlugin: BuildToolPlugin {
950+
func createBuildCommands(
951+
context: PluginContext,
952+
target: Target
953+
) throws -> [Command] { }
954+
}
955+
""")
956+
let yPluginTargetDir = packageDir.appending(components: "Plugins", "YPlugin")
957+
try localFileSystem.createDirectory(yPluginTargetDir, recursive: true)
958+
try localFileSystem.writeFileContents(yPluginTargetDir.appending(component: "plugin.swift"), string: """
959+
import PackagePlugin
960+
import Foundation
961+
@main struct YPlugin: BuildToolPlugin {
962+
func createBuildCommands(
963+
context: PluginContext,
964+
target: Target
965+
) throws -> [Command] { }
966+
}
967+
""")
968+
969+
970+
//////
971+
972+
let otherPackageDir = tmpPath.appending(components: "OtherPackage")
973+
try localFileSystem.createDirectory(otherPackageDir, recursive: true)
974+
try localFileSystem.writeFileContents(otherPackageDir.appending(component: "Package.swift"), string: """
975+
// swift-tools-version: 5.7
976+
import PackageDescription
977+
let package = Package(
978+
name: "OtherPackage",
979+
products: [
980+
.plugin(
981+
name: "OtherPlugin",
982+
targets: ["QPlugin"])
983+
],
984+
targets: [
985+
.plugin(
986+
name: "QPlugin",
987+
capability: .buildTool()
988+
),
989+
.plugin(
990+
name: "RPlugin",
991+
capability: .command(
992+
intent: .custom(verb: "RPlugin", description: "Plugin example"),
993+
permissions: []
994+
)
995+
)
996+
]
997+
)
998+
""")
999+
1000+
let qPluginTargetDir = otherPackageDir.appending(components: "Plugins", "QPlugin")
1001+
try localFileSystem.createDirectory(qPluginTargetDir, recursive: true)
1002+
try localFileSystem.writeFileContents(qPluginTargetDir.appending(component: "plugin.swift"), string: """
1003+
import PackagePlugin
1004+
import XcodeProjectPlugin
1005+
@main struct QBuildToolPlugin: BuildToolPlugin {
1006+
func createBuildCommands(
1007+
context: PluginContext,
1008+
target: Target
1009+
) throws -> [Command] { }
1010+
}
1011+
""")
1012+
/////////
1013+
// Load a workspace from the package.
1014+
let observability = ObservabilitySystem.makeForTesting()
1015+
let workspace = try Workspace(
1016+
fileSystem: localFileSystem,
1017+
forRootPackage: packageDir,
1018+
customManifestLoader: ManifestLoader(toolchain: UserToolchain.default),
1019+
delegate: MockWorkspaceDelegate()
1020+
)
1021+
1022+
// Load the root manifest.
1023+
let rootInput = PackageGraphRootInput(packages: [packageDir], dependencies: [])
1024+
let rootManifests = try tsc_await {
1025+
workspace.loadRootManifests(
1026+
packages: rootInput.packages,
1027+
observabilityScope: observability.topScope,
1028+
completion: $0
1029+
)
1030+
}
1031+
XCTAssert(rootManifests.count == 1, "\(rootManifests)")
1032+
1033+
let graph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope)
1034+
workspace.loadPluginImports(packageGraph: graph) { (result: Result<[PackageIdentity : [String : [String]]], Error>) in
1035+
1036+
var count = 0
1037+
if let dict = try? result.get() {
1038+
for (pkg, entry) in dict {
1039+
if pkg.description == "mypackage" {
1040+
XCTAssertNotNil(entry["XPlugin"])
1041+
XCTAssertEqual(entry["XPlugin"], ["PackagePlugin", "XcodeProjectPlugin"])
1042+
XCTAssertEqual(entry["YPlugin"], ["PackagePlugin", "Foundation"])
1043+
count += 1
1044+
} else if pkg.description == "otherpackage" {
1045+
XCTAssertNotNil(dict[pkg]?["QPlugin"])
1046+
XCTAssertEqual(entry["QPlugin"], ["PackagePlugin", "XcodeProjectPlugin"])
1047+
count += 1
1048+
}
1049+
}
1050+
} else {
1051+
XCTFail("Scanned import list should not be empty")
1052+
}
1053+
1054+
XCTAssertEqual(count, 2)
1055+
}
1056+
}
1057+
}
9021058
}

0 commit comments

Comments
 (0)