Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Generating Code And Other Mischief With Swift Package Manager Plugins - Server-Side Swift, London, December 2022

Ellen Shapiro
PRO
December 09, 2022
94

Generating Code And Other Mischief With Swift Package Manager Plugins - Server-Side Swift, London, December 2022

It’s the build tooling step everyone hates: “Now, add a new Run Script Build Phase.” The Swift and Xcode teams have worked to try to make things at least a little bit better for things which need to happen at build time by adding Build Tool Plugins and Command Plugins to Swift Package manager.

In this talk, you’ll get a look at how to set these up to generate code and documentation, plus a look at some other silly things you can make it do.

Note: This talk has a section that refers to the 360iDev version of the talk. Slides of that version are available here.

Sample App: https://github.com/designatednerd/RadioactiveCat
Sample Plugin: https://github.com/designatednerd/DatedImageGenerator

Ellen Shapiro
PRO

December 09, 2022
Tweet

More Decks by Ellen Shapiro

Transcript

  1. GENERATING CODE AND OTHER MISCHIEF WITH
    SWIFT PACKAGE MANAGER PLUGINS
    SEVER-SIDE SWIFT | LONDON | DECEMBER 2022
    ELLEN SHAPIRO | @DESIGNATEDNERD | GUSTO.COM

    View Slide

  2. GUSTO != GOUSTO

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. [SCENE MISSING]

    View Slide

  7. View Slide

  8. AN AMERICAN PAYROLL / HR COMPANY

    View Slide

  9. @GUSTOHQ

    View Slide

  10. View Slide

  11. A UK MEAL PREP COMPANY

    View Slide

  12. @GOUSTOCOOKING

    View Slide

  13. PLEASE STOP SENDING AMERICANS
    WHO CAN'T DO ANYTHING ABOUT IT
    PHOTOS OF YOUR MOULDY FOOD

    View Slide

  14. PLEASE STOP SENDING AMERICANS
    WHO CAN'T DO ANYTHING ABOUT IT
    PHOTOS OF YOUR MOULDY FOOD
    Cheers!

    View Slide

  15. !
    SPM PLUGINS!

    View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. ALCATRAZ

    View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. !
    XCODE GHOST

    View Slide

  33. !
    XCODE GHOST

    View Slide

  34. !
    STRAWHORSE

    View Slide

  35. View Slide

  36. View Slide

  37. !

    View Slide

  38. View Slide

  39. XCODE SOURCE EXTENSIONS

    View Slide

  40. REQUIRED HOST MAC APP

    View Slide

  41. REQUIRED HOST MAC APP

    !
    Can now be distributed easily on the Mac App store

    View Slide

  42. REQUIRED HOST MAC APP

    !
    Can now be distributed easily on the Mac App store

    "
    Sandboxing restrictions

    View Slide

  43. REQUIRED HOST MAC APP

    !
    Can now be distributed easily on the Mac App store

    "
    Sandboxing restrictions

    #
    Often winds up with an empty app

    View Slide

  44. func perform(with invocation: XCSourceEditorCommandInvocation,
    completionHandler: @escaping (Error?) -> Void) {
    // Actual code of plugin
    }

    View Slide

  45. View Slide

  46. ONLY SINGLE-FILE TEXT BUFFER ACCESS

    View Slide

  47. ONLY SINGLE-FILE TEXT BUFFER ACCESS

    !
    Does allow at least some sorting, linting, formatting

    View Slide

  48. ONLY SINGLE-FILE TEXT BUFFER ACCESS

    !
    Does allow at least some sorting, linting, formatting


    Only ever the current file

    View Slide

  49. ONLY SINGLE-FILE TEXT BUFFER ACCESS

    !
    Does allow at least some sorting, linting, formatting


    Only ever the current file

    #
    No access to the AST

    View Slide

  50. ONLY SINGLE-FILE TEXT BUFFER ACCESS

    !
    Does allow at least some sorting, linting, formatting


    Only ever the current file

    #
    No access to the AST

    $
    No access to file or project structure

    View Slide

  51. ONLY SINGLE-FILE TEXT BUFFER ACCESS

    !
    Does allow at least some sorting, linting, formatting


    Only ever the current file

    #
    No access to the AST

    $
    No access to file or project structure

    $
    No user interface permitted

    View Slide

  52. FURTHER INVOCATION LIMITATIONS

    View Slide

  53. FURTHER INVOCATION LIMITATIONS

    !
    Cannot run in background

    View Slide

  54. FURTHER INVOCATION LIMITATIONS

    !
    Cannot run in background

    "
    Must be actively invoked by the user

    View Slide

  55. FURTHER INVOCATION LIMITATIONS

    !
    Cannot run in background

    "
    Must be actively invoked by the user

    #
    No default key binding

    View Slide

  56. View Slide

  57. View Slide

  58. View Slide

  59. View Slide

  60. View Slide

  61. SWIFT PACKAGE MANAGER
    !
    PLUGINS

    View Slide

  62. View Slide

  63. View Slide

  64. View Slide

  65. View Slide


  66. SWIFT 5.6

    View Slide


  67. SWIFT 5.6
    !
    XCODE 14

    View Slide

  68. 1. COMMAND PLUGINS
    2. BUILD TOOL PLUGINS

    View Slide

  69. COMMAND PLUGINS

    View Slide

  70. CODE FROM "CREATE SWIFT PACKAGE PLUGINS"
    HTTPS://DEVELOPER.APPLE.COM/VIDEOS/PLAY/WWDC2022/110401/

    View Slide

  71. COMMAND PLUGINS
    ▸ Can ask permission to write in the package directory

    View Slide

  72. // Package.swift
    .plugin(
    name: "GenerateContributors",
    capability: .command(
    intent: .custom(verb: "regenerate-contributors-list",
    description: "Generates the CONTRIBUTORS.txt
    file based on Git logs"),
    permissions: [
    .writeToPackageDirectory(reason: "This command write the
    new CONTRIBUTORS.txt to the source root.")
    ]
    )),

    View Slide

  73. // Package.swift
    .plugin(
    name: "GenerateContributors",
    capability: .command(
    intent: .custom(verb: "regenerate-contributors-list",
    description: "Generates the CONTRIBUTORS.txt
    file based on Git logs"),
    permissions: [
    .writeToPackageDirectory(reason: "This command write the
    new CONTRIBUTORS.txt to the source root.")
    ]
    )),

    View Slide

  74. COMMAND PLUGINS
    ▸ Can ask permission to write in the package directory
    ▸ Can be invoked at the command line

    View Slide

  75. // Get all plugins available on a particular package
    swift package plugin --list
    // Run a specific plugin, in this case GenerateContributors
    swift package regenerate-contributors-list

    View Slide

  76. View Slide

  77. COMMAND PLUGINS
    ▸ Can ask permission to write in the package directory
    ▸ Can be invoked at the command line
    ▸ Can be invoked from a menu item in Xcode

    View Slide

  78. View Slide

  79. View Slide

  80. View Slide

  81. View Slide

  82. View Slide

  83. COMMAND PLUGINS
    ▸ Can ask permission to write in the package directory
    ▸ Can be invoked at the command line
    ▸ Can be invoked from a menu item Xcode
    ▸ Either way, must be invoked separately from the build process

    View Slide

  84. BUILD TOOL PLUGINS

    View Slide

  85. BUILD TOOL PLUGINS
    RUN AUTOMATICALLY ON EVERY BUILD

    View Slide

  86. BUILD TOOL PLUGINS
    (THE THING I REALLY WANT TO
    USE)

    View Slide

  87. THE MORE COMPLICATED BUT LESS COMPLICATED WAY:
    EMBED A PACKAGE
    IN YOUR .XCODEPROJ

    View Slide

  88. MORE COMPLICATED
    (YOU HAVE TO ADD A WHOLE SWIFT PACKAGE TO YOUR PROJECT)

    View Slide

  89. LESS COMPLICATED
    (IT WORKS WAY MORE RELIABLY)

    View Slide

  90. SAMPLE APP
    https://github.com/designatednerd/
    RadioactiveCat

    View Slide

  91. via https://energyeducation.ca/encyclopedia/Half_life

    View Slide

  92. View Slide

  93. View Slide

  94. View Slide

  95. BUILD TOOL EASY MODE
    BRING IN SOMEONE ELSE'S PLUGIN

    View Slide

  96. DO NIL DISTURB
    HTTPS://GITHUB.COM/ICANZILB/
    DONILDISTURBPLUGIN

    View Slide

  97. // Package.swift
    .dependencies: [
    .package(url: "https://github.com/icanzilb/
    DoNilDisturbPlugin.git", from: "0.0.5"),
    ]

    View Slide

  98. // Package.swift
    .target(
    name: "Cats",
    dependencies: [
    ],
    plugins: [
    .plugin(name: "DoNilDisturbPlugin",
    package: "DoNilDisturbPlugin")
    ]
    ),

    View Slide

  99. View Slide

  100. DO NOT BLINDLY CLICK
    TRUST & ENABLE ALL

    View Slide

  101. View Slide

  102. View Slide

  103. View Slide

  104. View Slide

  105. View Slide

  106. BUILD TOOL HARD MODE:
    CREATE YOUR OWN PLUGIN

    View Slide

  107. DON'T BUILD YOUR PLUGIN
    AND THE PACKAGE YOU'RE TESTING IT AGAINST
    IN THE SAME REPO

    View Slide

  108. GITHUB.COM/DESIGNATEDNERD/
    DATEDIMAGEGENERATOR

    View Slide

  109. PLUGIN VS. EXECUTABLE

    View Slide

  110. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  111. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  112. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  113. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  114. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage // <-- Exported library used in executable and main app
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  115. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage // <-- Exported Framework used in executable and main app
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable // <-- The thing that does all the work
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  116. // File structure of the Swift Lib
    !
    DatedImageGenerator
    !
    Plugins
    !
    DatedImageGenerator
    "
    DatedImageGenerator.swift
    !
    Sources
    !
    DatedImage
    "
    DatedImage.swift
    "
    ExifDateFormatter.swift
    !
    DatedImageGeneratorExecutable
    "
    DatedImageGeneratorExecutable.swift
    !
    Tests
    !
    DatedImageGeneratorTests
    "
    DatedImageGeneratorExecutable.swift
    "
    Media.xcassets

    View Slide

  117. // Plugins/DatedImageGenerator.swift
    @main
    struct DatedImageGenerator: BuildToolPlugin {
    func createBuildCommands(context: PluginContext,
    target: Target) async throws -> [Command] {
    // TODO: Create info for build command
    return [
    .buildCommand(
    displayName: "Generate Dated Images",
    executable: try context.tool(named: "DatedImageGeneratorExecutable").path,
    // more to come here
    arguments: "TODO",
    outputFiles: ["TODO"]
    )
    ]
    }
    }

    View Slide

  118. WHAT'S IN THE
    EXECUTABLE?

    View Slide

  119. // Sources/DatedImageGeneratorExecutable/
    // DatedImageGeneratorExecutable.swift
    @main
    struct DatedImageGeneratorExecutable {
    static func main() throws {
    // How do we get information from the Plugin?
    }
    }

    View Slide

  120. // Sources/DatedImageGeneratorExecutable/
    // DatedImageGeneratorExecutable.swift
    @main
    struct DatedImageGeneratorExecutable {
    static func main() throws {
    // Grab the 2nd argument as a string
    ProcessInfo.processInfo.arguments[1]
    }
    }

    View Slide

  121. // Sources/DatedImageGeneratorExecutable/
    // DatedImageGeneratorExecutable.swift
    @main
    struct DatedImageGeneratorExecutable {
    static func main() throws {
    // Turn it into Data
    Data(ProcessInfo.processInfo.arguments[1].utf8))
    }
    }

    View Slide

  122. // Sources/DatedImageGeneratorExecutable/
    // DatedImageGeneratorExecutable.swift
    @main
    struct DatedImageGeneratorExecutable {
    static func main() throws {
    // Decode it into a codable object!
    let invocation = try JSONDecoder()
    .decode(PluginInvocation.self,
    from: Data(ProcessInfo.processInfo.arguments[1].utf8))
    }
    }

    View Slide

  123. "I should just put the codable object in
    the executable and import it into the
    plugin"

    View Slide

  124. View Slide

  125. View Slide

  126. View Slide

  127. // One copy of this in the plugin, one in the executable
    struct PluginInvocation: Codable {
    let catalogPaths: [String]
    let outputPath: String
    func encodedString() throws -> String {
    let data = try JSONEncoder().encode(self)
    return String(decoding: data, as: UTF8.self)
    }
    }

    View Slide

  128. // One copy of this in the plugin, one in the executable
    // (or share with a common lib)
    struct PluginInvocation: Codable {
    let catalogPaths: [String]
    let outputPath: String
    func encodedString() throws -> String {
    let data = try JSONEncoder().encode(self)
    return String(decoding: data, as: UTF8.self)
    }
    }

    View Slide

  129. // Plugins/DatedImageGenerator.swift
    @main
    struct DatedImageGenerator: BuildToolPlugin {
    func createBuildCommands(context: PluginContext,
    target: Target) async throws -> [Command] {
    guard let target = target as? SourceModuleTarget else { throw ImageGenError.notASourceModule }
    let assetCatalogPaths = target.sourceFiles(withSuffix: "xcassets")
    .compactMap { assetCatalog in
    return assetCatalog.path.string
    }
    let outputPath = context.pluginWorkDirectory
    .appending(["DatedImages.swift"])
    let invocation = PluginInvocation(catalogPaths: assetCatalogPaths,
    outputPath: outputPath.string)
    return [
    .buildCommand(
    displayName: "Generate Dated Images",
    executable: try context.tool(named: "DatedImageGeneratorExecutable").path,
    arguments: [try invocation.encodedString()],
    outputFiles: [outputPath]
    )
    ]
    }
    }

    View Slide

  130. // Plugins/DatedImageGenerator.swift
    @main
    struct DatedImageGenerator: BuildToolPlugin {
    func createBuildCommands(context: PluginContext,
    target: Target) async throws -> [Command] {
    guard let target = target as? SourceModuleTarget else { throw ImageGenError.notASourceModule }
    let assetCatalogPaths = target.sourceFiles(withSuffix: "xcassets")
    .compactMap { assetCatalog in
    return assetCatalog.path.string
    }
    let outputPath = context.pluginWorkDirectory
    .appending(["DatedImages.swift"])
    let invocation = PluginInvocation(catalogPaths: assetCatalogPaths,
    outputPath: outputPath.string)
    return [
    .buildCommand(
    displayName: "Generate Dated Images",
    executable: try context.tool(named: "DatedImageGeneratorExecutable").path,
    arguments: [try invocation.encodedString()],
    outputFiles: [outputPath]
    )
    ]
    }
    }

    View Slide

  131. !
    TARGET
    (TECHNICALLY PACKAGEPLUGIN.TARGET)

    View Slide

  132. // Plugins/DatedImageGenerator.swift
    @main
    struct DatedImageGenerator: BuildToolPlugin {
    func createBuildCommands(context: PluginContext,
    target: Target) async throws -> [Command] {
    guard let target = target as? SourceModuleTarget else { throw ImageGenError.notASourceModule }
    let assetCatalogPaths = target.sourceFiles(withSuffix: "xcassets")
    .compactMap { assetCatalog in
    return assetCatalog.path.string
    }
    let outputPath = context.pluginWorkDirectory
    .appending(["DatedImages.swift"])
    let invocation = PluginInvocation(catalogPaths: assetCatalogPaths,
    outputPath: outputPath.string)
    return [
    .buildCommand(
    displayName: "Generate Dated Images",
    executable: try context.tool(named: "DatedImageGeneratorExecutable").path,
    arguments: [try invocation.encodedString()],
    outputFiles: [outputPath]
    )
    ]
    }
    }

    View Slide

  133. !
    PLUGIN CONTEXT
    (TECHNICALLY PACKAGEPLUGIN.PLUGINCONTEXT)

    View Slide

  134. PLUGINWORKDIRECTORY
    IS YOUR SANDBOX

    View Slide

  135. // Plugins/DatedImageGenerator.swift
    @main
    struct DatedImageGenerator: BuildToolPlugin {
    func createBuildCommands(context: PluginContext,
    target: Target) async throws -> [Command] {
    guard let target = target as? SourceModuleTarget else { throw ImageGenError.notASourceModule }
    let assetCatalogPaths = target.sourceFiles(withSuffix: "xcassets")
    .compactMap { assetCatalog in
    return assetCatalog.path.string
    }
    let outputPath = context.pluginWorkDirectory
    .appending(["DatedImages.swift"])
    let invocation = PluginInvocation(catalogPaths: assetCatalogPaths,
    outputPath: outputPath.string)
    return [
    .buildCommand(
    displayName: "Generate Dated Images",
    executable: try context.tool(named: "DatedImageGeneratorExecutable").path,
    arguments: [try invocation.encodedString()],
    outputFiles: [outputPath]
    )
    ]
    }
    }

    View Slide

  136. // Plugins/DatedImageGenerator.swift
    @main
    struct DatedImageGenerator: BuildToolPlugin {
    func createBuildCommands(context: PluginContext,
    target: Target) async throws -> [Command] {
    guard let target = target as? SourceModuleTarget else { throw ImageGenError.notASourceModule }
    let assetCatalogPaths = target.sourceFiles(withSuffix: "xcassets")
    .compactMap { assetCatalog in
    return assetCatalog.path.string
    }
    let outputPath = context.pluginWorkDirectory
    .appending(["DatedImages.swift"])
    let invocation = PluginInvocation(catalogPaths: assetCatalogPaths,
    outputPath: outputPath.string)
    return [
    .buildCommand(
    displayName: "Generate Dated Images",
    executable: try context.tool(named: "DatedImageGeneratorExecutable").path,
    arguments: [try invocation.encodedString()],
    outputFiles: [outputPath]
    )
    ]
    }
    }

    View Slide

  137. View Slide

  138. QUICK RECAP OF PLUGIN CREATION

    View Slide

  139. QUICK RECAP OF PLUGIN CREATION
    ▸ Create a plugin in Plugins

    View Slide

  140. QUICK RECAP OF PLUGIN CREATION
    ▸ Create a plugin in Plugins
    ▸ Create an executable it will call in Sources

    View Slide

  141. QUICK RECAP OF PLUGIN CREATION
    ▸ Create a plugin in Plugins
    ▸ Create an executable it will call in Sources
    ▸ Use a codable object to pass data between them

    View Slide

  142. QUICK RECAP OF PLUGIN CREATION
    ▸ Create a plugin in Plugins
    ▸ Create an executable it will call in Sources
    ▸ Use a codable object to pass data between them
    ▸ Note that the plugin depends on but cannot import the executable

    View Slide

  143. !
    BACK TO THE KITTAHS!

    View Slide

  144. // In Cats/Package.swift
    dependencies: [
    .package(url: "https://github.com/designatednerd/
    DatedImageGenerator.git", from: "0.0.1"),
    .package(url: "https://github.com/icanzilb/
    DoNilDisturbPlugin.git", from: "0.0.5"),
    ],

    View Slide

  145. // In Cats/Package.swift
    .target(
    name: "Cats",
    dependencies: [
    .product(name: "DatedImage", package: "DatedImageGenerator"),
    ],
    plugins: [
    .plugin(name: "DatedImageGenerator", package: "DatedImageGenerator"),
    .plugin(name: "DoNilDisturbPlugin", package: "DoNilDisturbPlugin")
    ]
    ),

    View Slide

  146. // In Cats/Package.swift
    .target(
    name: "Cats",
    dependencies: [
    .product(name: "DatedImage", package: "DatedImageGenerator"),
    ],
    plugins: [
    .plugin(name: "DatedImageGenerator", package: "DatedImageGenerator"),
    .plugin(name: "DoNilDisturbPlugin", package: "DoNilDisturbPlugin")
    ]
    ),

    View Slide

  147. // In Cats/Package.swift
    .target(
    name: "Cats",
    dependencies: [
    .product(name: "DatedImage", package: "DatedImageGenerator"),
    ],
    plugins: [
    .plugin(name: "DatedImageGenerator", package: "DatedImageGenerator"),
    .plugin(name: "DoNilDisturbPlugin", package: "DoNilDisturbPlugin")
    ]
    ),

    View Slide

  148. View Slide

  149. View Slide

  150. View Slide

  151. View Slide

  152. View Slide

  153. View Slide

  154. WHAT ABOUT RUNNING PLUGINS
    ON AN APP TARGET?

    View Slide

  155. XCODE PROJECT PLUGIN

    View Slide

  156. XCODE PROJECT PLUGIN
    (A KIND OF BUILD TOOL PLUGIN)

    View Slide

  157. THIS TALK, 360IDEV EDITION:
    HTTPS://WWW.YOUTUBE.COM/WATCH?V=1GCU70XZ-P8

    View Slide

  158. View Slide

  159. !
    TRUST

    View Slide

  160. View Slide

  161. View Slide

  162. View Slide

  163. xcodebuild -skipPackagePluginValidation

    View Slide

  164. View Slide

  165. !

    View Slide

  166. https://forums.swift.org/t/how-do-i-get-
    xcode-to-programmatically-trust-a-specific-
    build-tool-plugin-for-ci/61331/2

    View Slide

  167. View Slide

  168. View Slide

  169. ! ➡ !
    LOCAL REPO CI

    View Slide

  170. !
    LOCALLY

    View Slide

  171. !
    LOCALLY
    !
    CI MACHINE

    View Slide

  172. View Slide

  173. View Slide

  174. View Slide

  175. !
    LOCALLY
    !
    CI MACHINE

    View Slide

  176. !
    FB11772479

    View Slide

  177. SPM PLUGIN LIMITATIONS

    View Slide

  178. SPM PLUGIN LIMITATIONS
    (that I've found so far)

    View Slide

  179. SPM PLUGIN LIMITATIONS
    (that I've found so far)
    ▸ Still no UI for you

    View Slide

  180. SPM PLUGIN LIMITATIONS
    (that I've found so far)
    ▸ Still no UI for you
    ▸ You can't even ask to use the network

    View Slide

  181. SPM PLUGIN LIMITATIONS
    (that I've found so far)
    ▸ Still no UI for you
    ▸ You can't even ask to use the network
    ▸ You can't share data between plugins

    View Slide

  182. SPM PLUGIN LIMITATIONS
    (that I've found so far)
    ▸ Still no UI for you
    ▸ You can't even ask to use the network
    ▸ You can't share data between plugins
    ▸ Can't do anything with build results

    View Slide

  183. !
    PLUGIN WISH LIST

    View Slide

  184. PLUGIN WISH LIST
    ▸ File metadata

    View Slide

  185. PLUGIN WISH LIST
    ▸ File metadata
    ▸ Ability to show UI in Xcode

    View Slide

  186. OBLIGATORY SUMMARY SLIDE

    View Slide

  187. OBLIGATORY SUMMARY SLIDE
    ▸ Alcatraz

    View Slide

  188. OBLIGATORY SUMMARY SLIDE
    ▸ Alcatraz
    ! "
    Xcode 8 Extensions

    View Slide

  189. OBLIGATORY SUMMARY SLIDE
    ▸ Alcatraz
    ! "
    Xcode 8 Extensions
    ▸ SPM Plugins to the rescue!

    View Slide

  190. OBLIGATORY SUMMARY SLIDE
    ▸ Alcatraz
    ! "
    Xcode 8 Extensions
    ▸ SPM Plugins to the rescue!
    ▸ Command plugins: On demand, Build tool plugins: On every build

    View Slide

  191. OBLIGATORY SUMMARY SLIDE
    ▸ Alcatraz
    ! "
    Xcode 8 Extensions
    ▸ SPM Plugins to the rescue!
    ▸ Command plugins: On demand, Build tool plugins: On every build
    ▸ Security: Be super-mindful of which plugins you use

    View Slide

  192. OBLIGATORY SUMMARY SLIDE
    ▸ Alcatraz
    ! "
    Xcode 8 Extensions
    ▸ SPM Plugins to the rescue!
    ▸ Command plugins: On demand, Build tool plugins: On every build
    ▸ Security: Be super-mindful of which plugins you use
    ▸ Building plugins is a bit complicated, but also pretty fun

    View Slide

  193. View Slide

  194. THANK YOU!

    View Slide

  195. SWIFT-EVOLUTION LINKS!
    ▸ SE-0303: Extensible Build Tools github.com/apple/swift-evolution/
    blob/main/proposals/0303-swiftpm-extensible-build-tools.md
    ▸ SE-0325: Additional Plugin APIs github.com/apple/swift-evolution/
    blob/main/proposals/0325-swiftpm-additional-plugin-apis.md
    ▸ SE-0332: SPM Command Plugins github.com/apple/swift-evolution/
    blob/main/proposals/0332-swiftpm-command-plugins.md

    View Slide

  196. !
    SLIGHTLY TERRIFYING LINKS!
    ▸ Xcode Ghost details: en.wikipedia.org/wiki/XcodeGhost
    ▸ Strawhorse leaked document: theintercept.com/document/
    2015/03/10/strawhorse-attacking-macos-ios-software-
    development-kit
    ▸ Strawhorse Context: theintercept.com/2015/03/10/ispy-cia-
    campaign-steal-apples-secrets/

    View Slide

  197. !
    PLUGIN LINKS!
    ▸ Do Nil Disturb: github.com/icanzilb/DoNilDisturbPlugin
    ▸ Swift SafeURL: github.com/baguio/SwiftSafeURL

    View Slide