Skip to content

Commit f0eae33

Browse files
authored
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
1 parent 93ec7b1 commit f0eae33

File tree

2 files changed

+63
-13
lines changed

2 files changed

+63
-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) async 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: CommandPlugin {
473+
// Entry point for command plugins applied to Xcode projects.
474+
func performCommand(context: XcodePluginContext, arguments: [String]) async throws {
475+
print("Hello, World!")
476+
}
477+
}
478+
479+
#endif
480+
439481
"""
440482
}
441483

Tests/WorkspaceTests/InitTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,14 @@ 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("performCommand(context: XcodePluginContext"))
179183
}
180184
}
181185

@@ -201,10 +205,14 @@ class InitTests: XCTestCase {
201205
XCTAssertMatch(manifestContents, .and(.contains(".plugin("), .contains("targets: [\"MyBuildToolPlugin\"]")))
202206
XCTAssertMatch(manifestContents, .and(.contains(".plugin("), .contains("capability: .buildTool()")))
203207

208+
// Check basic content that we expect in the plugin source file
204209
let source = path.appending("Plugins", "MyBuildToolPlugin.swift")
205210
XCTAssertFileExists(source)
206211
let sourceContents: String = try localFileSystem.readFileContents(source)
207212
XCTAssertMatch(sourceContents, .contains("struct MyBuildToolPlugin: BuildToolPlugin"))
213+
XCTAssertMatch(sourceContents, .contains("createBuildCommands(context: PluginContext"))
214+
XCTAssertMatch(sourceContents, .contains("import XcodeProjectPlugin"))
215+
XCTAssertMatch(sourceContents, .contains("createBuildCommands(context: XcodePluginContext"))
208216
}
209217
}
210218

0 commit comments

Comments
 (0)