Skip to content

Show files generated by build plugins under Target in Project Panel #1592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
12 changes: 10 additions & 2 deletions assets/test/targets/Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.6
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -25,7 +25,10 @@ let package = Package(
],
targets: [
.target(
name: "LibraryTarget"
name: "LibraryTarget",
plugins: [
.plugin(name: "BuildToolPlugin")
]
),
.executableTarget(
name: "ExecutableTarget"
Expand All @@ -36,6 +39,11 @@ let package = Package(
intent: .custom(verb: "testing", description: "A plugin for testing plugins")
)
),
.plugin(
name: "BuildToolPlugin",
capability: .buildTool(),
dependencies: ["ExecutableTarget"]
),
.testTarget(
name: "TargetsTests",
dependencies: ["LibraryTarget"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import PackagePlugin
import Foundation

@main
struct SimpleBuildToolPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
guard let sourceFiles = target.sourceModule?.sourceFiles else { return [] }

// Build tool plugins are not being called on Windows with Swift < 6.0.
#if os(Windows) && swift(<6.0)
return []
#endif

let generatorTool = try context.tool(named: "ExecutableTarget")

// Construct a build command for each source file with a particular suffix.
return sourceFiles.map(\.path).compactMap {
createBuildCommand(
for: $0,
in: context.pluginWorkDirectory,
with: generatorTool.path
)
}
}

/// Calls a build tool that transforms JSON files into Swift files.
func createBuildCommand(for inputPath: Path, in outputDirectoryPath: Path, with generatorToolPath: Path) -> Command? {
let inputURL = URL(fileURLWithPath: inputPath.string)
let outputDirectoryURL = URL(fileURLWithPath: outputDirectoryPath.string)

// Skip any file that doesn't have the extension we're looking for (replace this with the actual one).
guard inputURL.pathExtension == "json" else { return .none }

// Produces .swift files in the same directory structure as the input JSON files appear in the target.
let components = inputURL.absoluteString.split(separator: "LibraryTarget", omittingEmptySubsequences: false).map(String.init)
let inputName = inputURL.lastPathComponent
let outputDir = outputDirectoryURL.appendingPathComponent(components[1]).deletingLastPathComponent()
let outputName = inputURL.deletingPathExtension().lastPathComponent + ".swift"
let outputURL = outputDir.appendingPathComponent(outputName)

return .buildCommand(
displayName: "Generating \(outputName) from \(inputName)",
executable: generatorToolPath,
arguments: ["\(inputPath)", "\(outputURL.path)"],
inputFiles: [inputPath],
outputFiles: [Path(outputURL.path)]
)
}
}
40 changes: 39 additions & 1 deletion assets/test/targets/Sources/ExecutableTarget/main.swift
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
print("Executable Target Hello World!")
import Foundation

@main
@available(macOS 13.0.0, *)
struct CodeGenerator {
static func main() async throws {
// Use swift-argument-parser or just CommandLine, here we just imply that 2 paths are passed in: input and output
guard CommandLine.arguments.count == 3 else {
throw CodeGeneratorError.invalidArguments
}
// arguments[0] is the path to this command line tool
guard let input = URL(string: "file://\(CommandLine.arguments[1])"), let output = URL(string: "file://\(CommandLine.arguments[2])") else {
return
}
let jsonData = try Data(contentsOf: input)
let enumFormat = try JSONDecoder().decode(JSONFormat.self, from: jsonData)

let code = """
enum \(enumFormat.name): CaseIterable {
\t\(enumFormat.cases.map({ "case \($0)" }).joined(separator: "\n\t"))
}
"""
guard let data = code.data(using: .utf8) else {
throw CodeGeneratorError.invalidData
}
try data.write(to: output, options: .atomic)
}
}

struct JSONFormat: Decodable {
let name: String
let cases: [String]
}

@available(macOS 13.00.0, *)
enum CodeGeneratorError: Error {
case invalidArguments
case invalidData
}
8 changes: 8 additions & 0 deletions assets/test/targets/Sources/LibraryTarget/Bar/Baz.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "Baz",
"cases": [
"bar",
"baz",
"bbb"
]
}
8 changes: 8 additions & 0 deletions assets/test/targets/Sources/LibraryTarget/Foo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "Foo",
"cases": [
"bar",
"baz",
"qux"
]
}
Loading
Loading