Upgrade to PRO for Only $50/Yearโ€”Limited-Time Offer! ๐Ÿ”ฅ

[NSManchester] Code generation using Swift Pack...

[NSManchester] Code generation using Swift Packageย plugins

Avatar for Pol Piella Abadia

Pol Piella Abadia

July 04, 2022
Tweet

More Decks by Pol Piella Abadia

Other Decks in Programming

Transcript

  1. Letโ€™s get started! ๐Ÿš€ - Context โ€ข A new package

    exposes a protocol - very similar to decodable. โ€ข Many features implement types which conform to it. New package decodes these types from data. โ€ข A fallback json fi le is shipped with the app. It must always work with all models. โ€ข Decoding errors are all runtime and would cause issues when certain screens are accessed. โ€ข We gained con fi dence by testing, but doing this manually is not very scalableโ€ฆ โ€ข Very di ff i cult to automate at the time as we have a mix of Swift Packages and Xcode projects. โ€ข Xcode 14 will allow us to use plugins for Xcode projects too! ๐ŸŽ‰
  2. Letโ€™s get started! ๐Ÿš€ - Specifics โ€ข A build tool

    Swift Package Plugin. โ€ข Uses SourceKitten (SourceKit under the hood) to fi nd all types conforming to a speci fi c protocol. โ€ข Automatically generates tests for each type which conforms to such protocol.
  3. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  4. The executable ๐Ÿƒ Extract types conforming to the protocol ๐Ÿ”Ž

    Generate a XCTestCase ๐Ÿ”จ Scan target for Swift Files ๐Ÿ—„
  5. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  6. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  7. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ), .executableTarget( name: "PluginExecutable", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "ArgumentParser", package: "swift-argument-parser") ] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  8. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  9. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  10. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  11. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  12. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) let structures = try files.map { try Structure(file: File(path: $0.path)!) } } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  13. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) let structures = try files.map { try Structure(file: File(path: $0.path)!) } var matchedTypes = [String]() structures.forEach { walkTree(dictionary: $0.dictionary, acc: &matchedTypes) } } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  14. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) let structures = try files.map { try Structure(file: File(path: $0.path)!) } var matchedTypes = [String]() structures.forEach { walkTree(dictionary: $0.dictionary, acc: &matchedTypes) } try createOutputFile(withContent: matchedTypes) } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  15. ๐Ÿ“‚ PluginExecutable.swift import SourceKittenFramework import ArgumentParser import Foundation @main struct

    PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { // Needed to ensure that sourcekit runs in a single process setenv("IN_PROCESS_SOURCEKIT", "YES", 1) let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) let structures = try files.map { try Structure(file: File(path: $0.path)!) } var matchedTypes = [String]() structures.forEach { walkTree(dictionary: $0.dictionary, acc: &matchedTypes) } try createOutputFile(withContent: matchedTypes) } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  16. More contextโ€ฆ โญ Thanks to @jozasvalancius, which did all the

    work for this. ๐ŸŽ‰ This is also a fi x for getting SwiftLint to work with SPM plugins ๐ŸŒ Link to the PR: https://github.com/jpsim/SourceKitten/pull/728
  17. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ), .executableTarget( name: "PluginExecutable", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "ArgumentParser", package: "swift-argument-parser") ] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift
  18. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ), .executableTarget( name: "PluginExecutable", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "ArgumentParser", package: "swift-argument-parser") ] ), .plugin( name: "SourceKitPlugin", capability: .buildTool(), dependencies: [.target(name: "PluginExecutable")] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  19. ๐Ÿ“‚ Package.swift // swift-tools-version: 5.6 // The swift-tools-version declares the

    minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: [โ€œCodeGenSample"], plugins: [โ€œSourceKitPluginโ€], ), .executableTarget( name: "PluginExecutable", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "ArgumentParser", package: "swift-argument-parser") ] ), .plugin( name: "SourceKitPlugin", capability: .buildTool(), dependencies: [.target(name: "PluginExecutable")] ) ] ) Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  20. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift

    ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  21. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  22. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", ๐Ÿคท, "--input", ๐Ÿคท, "--output", ๐Ÿคท ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [๐Ÿคท] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  23. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(โ€œGeneratedTests.swiftโ€) return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", ๐Ÿคท, "--input", ๐Ÿคท, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  24. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(โ€œGeneratedTests.swiftโ€) let dependencyTarget = target .dependencies return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", ๐Ÿคท, "--input", ๐Ÿคท, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  25. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(โ€œGeneratedTests.swiftโ€) let dependencyTarget = target .dependencies .compactMap { dependency -> Target? in switch dependency { case .target(let target): return target default: return nil } } return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", ๐Ÿคท, "--input", ๐Ÿคท, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  26. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(โ€œGeneratedTests.swiftโ€) let dependencyTarget = target .dependencies .compactMap { dependency -> Target? in switch dependency { case .target(let target): return target default: return nil } } .filter { "\($0.name)Tests" == target.name } .first return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", ๐Ÿคท, "--input", ๐Ÿคท, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  27. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(โ€œGeneratedTests.swiftโ€) guard let dependencyTarget = target .dependencies .compactMap { dependency -> Target? in switch dependency { case .target(let target): return target default: return nil } } .filter { "\($0.name)Tests" == target.name } .first else { Diagnostics.error("Could not get a dependency to scan!โ€) return [] } return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", ๐Ÿคท, "--input", ๐Ÿคท, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift
  28. ๐Ÿ“‚ SourceKitPlugin.swift import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func

    createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(โ€œGeneratedTests.swiftโ€) guard let dependencyTarget = target .dependencies .compactMap { dependency -> Target? in switch dependency { case .target(let target): return target default: return nil } } .filter { "\($0.name)Tests" == target.name } .first else { Diagnostics.error("Could not get a dependency to scan!โ€) return [] } return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", dependencyTarget.name, "--input", dependencyTarget.directory, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } } Package.swift ๐Ÿ“‚ Sources ๐Ÿ“‚ CodeGenSample CodeGenSample.swift ๐Ÿ“‚ PluginExecutable PluginExecutable.swift ๐Ÿ“‚ Tests ๐Ÿ“‚ CodeGenSampleTests Empty.swift ๐Ÿ“‚ Plugins ๐Ÿ“‚ SourceKitPlugin SourceKitPlugin.swift