Skip to content

Commit 2cd23b2

Browse files
authored
[5.9] Newly created build tool and command plugin templates should have conditional support for XcodeProjectPlugin #6446 (#6456)
* New build tool plugin and command plugin templates should have conditional support for XcodeProjectPlugin (#6446) Many package plugins are used from Xcode, so it makes sense for newly created ones to have that by default. If there is support for other IDEs in the future, it would make sense to add that as well. This support is currently always added but we could consider having options in the API for whether to add it. rdar://108166582 (cherry picked from commit f0eae33) * Supplemental fix for XcodeProjectPlugin support in package init template (#6451) This is a supplemental fix for #6446 that removes the `async` specifier and corrects the protocol conformance of the Xcode command plug-in. This change should have been part of that PR. - remove the `async` specifier since the entry point that XcodeProjectPlugin defines isn't async - correct the protocol conformance of the Xcode command plug-in - update the unit tests rdar://108166582 (cherry picked from commit 87ecb5d)
1 parent 29ee7d6 commit 2cd23b2

File tree

2 files changed

+65
-13
lines changed

2 files changed

+65
-13
lines changed

Sources/Workspace/InitPackage.swift

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -402,40 +402,82 @@ public final class InitPackage {
402402
if packageType == .buildToolPlugin {
403403
content += """
404404
struct \(typeName): BuildToolPlugin {
405+
/// Entry point for creating build commands for targets in Swift packages.
405406
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
406-
// The plugin can choose what parts of the package to process.
407+
// This plugin only runs for package targets that can have source files.
407408
guard let sourceFiles = target.sourceModule?.sourceFiles else { return [] }
408409
409410
// Find the code generator tool to run (replace this with the actual one).
410411
let generatorTool = try context.tool(named: "my-code-generator")
411412
412413
// Construct a build command for each source file with a particular suffix.
413-
return sourceFiles.map(\\.path).compactMap { inputPath in
414-
guard inputPath.extension == "my-input-suffix" else { return .none }
415-
let inputName = inputPath.lastComponent
416-
let outputName = inputPath.stem + ".swift"
417-
let outputPath = context.pluginWorkDirectory.appending(outputName)
418-
return .buildCommand(
419-
displayName: "Generating \\(outputName) from \\(inputName)",
420-
executable: generatorTool.path,
421-
arguments: ["\\(inputPath)", "-o", "\\(outputPath)"],
422-
inputFiles: [inputPath],
423-
outputFiles: [outputPath]
424-
)
414+
return sourceFiles.map(\\.path).compactMap {
415+
createBuildCommand(for: $0, in: context.pluginWorkDirectory, with: generatorTool.path)
425416
}
426417
}
427418
}
428419
420+
#if canImport(XcodeProjectPlugin)
421+
import XcodeProjectPlugin
422+
423+
extension \(typeName): XcodeBuildToolPlugin {
424+
// Entry point for creating build commands for targets in Xcode projects.
425+
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
426+
// Find the code generator tool to run (replace this with the actual one).
427+
let generatorTool = try context.tool(named: "my-code-generator")
428+
429+
// Construct a build command for each source file with a particular suffix.
430+
return target.inputFiles.map(\\.path).compactMap {
431+
createBuildCommand(for: $0, in: context.pluginWorkDirectory, with: generatorTool.path)
432+
}
433+
}
434+
}
435+
436+
#endif
437+
438+
extension \(typeName) {
439+
/// Shared function that returns a configured build command if the input files is one that should be processed.
440+
func createBuildCommand(for inputPath: Path, in outputDirectoryPath: Path, with generatorToolPath: Path) -> Command? {
441+
// Skip any file that doesn't have the extension we're looking for (replace this with the actual one).
442+
guard inputPath.extension == "my-input-suffix" else { return .none }
443+
444+
// Return a command that will run during the build to generate the output file.
445+
let inputName = inputPath.lastComponent
446+
let outputName = inputPath.stem + ".swift"
447+
let outputPath = outputDirectoryPath.appending(outputName)
448+
return .buildCommand(
449+
displayName: "Generating \\(outputName) from \\(inputName)",
450+
executable: generatorToolPath,
451+
arguments: ["\\(inputPath)", "-o", "\\(outputPath)"],
452+
inputFiles: [inputPath],
453+
outputFiles: [outputPath]
454+
)
455+
}
456+
}
457+
429458
"""
430459
}
431460
else {
432461
content += """
433462
struct \(typeName): CommandPlugin {
463+
// Entry point for command plugins applied to Swift Packages.
434464
func performCommand(context: PluginContext, arguments: [String]) async throws {
435465
print("Hello, World!")
436466
}
437467
}
438468
469+
#if canImport(XcodeProjectPlugin)
470+
import XcodeProjectPlugin
471+
472+
extension MyCommandPlugin: XcodeCommandPlugin {
473+
// Entry point for command plugins applied to Xcode projects.
474+
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
475+
print("Hello, World!")
476+
}
477+
}
478+
479+
#endif
480+
439481
"""
440482
}
441483

Tests/WorkspaceTests/InitTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,15 @@ class InitTests: XCTestCase {
172172
XCTAssertMatch(manifestContents, .and(.contains(".plugin("),
173173
.and(.contains("capability: .command(intent: .custom("), .contains("verb: \"MyCommandPlugin\""))))
174174

175+
// Check basic content that we expect in the plugin source file
175176
let source = path.appending("Plugins", "MyCommandPlugin.swift")
176177
XCTAssertFileExists(source)
177178
let sourceContents: String = try localFileSystem.readFileContents(source)
178179
XCTAssertMatch(sourceContents, .contains("struct MyCommandPlugin: CommandPlugin"))
180+
XCTAssertMatch(sourceContents, .contains("performCommand(context: PluginContext"))
181+
XCTAssertMatch(sourceContents, .contains("import XcodeProjectPlugin"))
182+
XCTAssertMatch(sourceContents, .contains("extension MyCommandPlugin: XcodeCommandPlugin"))
183+
XCTAssertMatch(sourceContents, .contains("performCommand(context: XcodePluginContext"))
179184
}
180185
}
181186

@@ -201,10 +206,15 @@ class InitTests: XCTestCase {
201206
XCTAssertMatch(manifestContents, .and(.contains(".plugin("), .contains("targets: [\"MyBuildToolPlugin\"]")))
202207
XCTAssertMatch(manifestContents, .and(.contains(".plugin("), .contains("capability: .buildTool()")))
203208

209+
// Check basic content that we expect in the plugin source file
204210
let source = path.appending("Plugins", "MyBuildToolPlugin.swift")
205211
XCTAssertFileExists(source)
206212
let sourceContents: String = try localFileSystem.readFileContents(source)
207213
XCTAssertMatch(sourceContents, .contains("struct MyBuildToolPlugin: BuildToolPlugin"))
214+
XCTAssertMatch(sourceContents, .contains("createBuildCommands(context: PluginContext"))
215+
XCTAssertMatch(sourceContents, .contains("import XcodeProjectPlugin"))
216+
XCTAssertMatch(sourceContents, .contains("extension MyBuildToolPlugin: XcodeBuildToolPlugin"))
217+
XCTAssertMatch(sourceContents, .contains("createBuildCommands(context: XcodePluginContext"))
208218
}
209219
}
210220

0 commit comments

Comments
 (0)