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

Thinking Outside The Box with Swift Package Manager - Hacking With Swift Live, Bath, UK, July 2019

Thinking Outside The Box with Swift Package Manager - Hacking With Swift Live, Bath, UK, July 2019

A brief history of being able to use other people's code with your code, and then some examples of neat stuff you can do using SPM for things you'd normally have to do with bash.

Sample code: https://github.com/designatednerd/PanoramaRama

Ellen Shapiro
PRO

July 08, 2019
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. THINKING OUTSIDE THE BOX WITH
    !
    SWIFT PACKAGE MANAGER
    HACKING WITH SWIFT LIVE | BATH, UK | JULY 2019
    BY ELLEN SHAPIRO | @DESIGNATEDNERD | APOLLOGRAPHQL.COM

    View Slide

  2. View Slide

  3. DRAGGING THINGS INTO PROJECTS

    View Slide

  4. UPDATES?
    (˽°□°҂˽Ɨ ˍʓˍ

    View Slide

  5. GIT SUBMODULES

    View Slide

  6. CHANGES?

    View Slide

  7. COCOAPODS

    View Slide

  8. View Slide

  9. !
    CARTHAGE

    View Slide

  10. CODE SIGNING
    !
    SILLINESS

    View Slide

  11. SWIFT PACKAGE MANAGER

    View Slide

  12. View Slide

  13. SERVER:

    View Slide

  14. SERVER:
    COMMAND LINE:

    View Slide

  15. SERVER:
    COMMAND LINE:
    IOS:

    View Slide

  16. View Slide

  17. !
    XCODE 11

    View Slide

  18. !
    BETA?!

    View Slide

  19. RIGHT NOW!

    View Slide

  20. Command line. Sorry.

    View Slide

  21. View Slide

  22. swift package init

    View Slide

  23. swift package init --type executable

    View Slide

  24. swift package init --type library

    View Slide

  25. swift package generate-xcodeproj

    View Slide

  26. swift build

    View Slide

  27. swift run

    View Slide

  28. swift run [project] [arguments]

    View Slide

  29. I HATE BASH

    View Slide

  30. I HATE BASH
    (I DON'T UNDERSTAND BASH)

    View Slide

  31. I HATE BASH
    (I AM VERY BAD AT BASH)

    View Slide

  32. TYPE SAFETY?

    View Slide

  33. !
    BETTER BUILD SCRIPT WRAPPERS

    View Slide

  34. RUN WHEN YOU BUILD YOUR MAIN PROJECT

    View Slide

  35. IN YOUR SPM EXECUTABLE PROJECT

    View Slide

  36. main.swift
    let args = CommandLine.arguments.dropFirst()
    print("Arguments: \(args)")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    }

    View Slide

  37. main.swift
    let args = CommandLine.arguments.dropFirst()
    print("Arguments: \(args)")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    }

    View Slide

  38. main.swift
    let args = CommandLine.arguments.dropFirst()
    print("Arguments: \(args)")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    }

    View Slide

  39. main.swift
    let args = CommandLine.arguments.dropFirst()
    print("Arguments: \(args)")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    }

    View Slide

  40. IN YOUR ACTUAL PROJECT

    View Slide

  41. IN YOUR ACTUAL PROJECT
    (EX: HTTPS://GITHUB.COM/DESIGNATEDNERD/PANORAMARAMA)

    View Slide

  42. View Slide

  43. View Slide

  44. <--- Main app
    <--- SPM executable

    View Slide

  45. PRE-BUILD SCRIPT
    cd Scripty
    swift run Scripty --prebuild

    View Slide

  46. PRE-BUILD SCRIPT
    cd Scripty
    swift run Scripty --prebuild
    POST-BUILD SCRIPT
    cd Scripty
    swift run Scripty --postbuild

    View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  50. WHAT CAN I DO WITH THIS?

    View Slide

  51. HIDE AN OPEN-SOURCE PROJECT'S
    !
    BUILD SECRETS

    View Slide

  52. Environment.plist

    View Slide

  53. View Slide

  54. View Slide

  55. HTTPS://GITHUB.COM/DESIGNATEDNERD/
    PLISTER

    View Slide

  56. EnvironmentUpdater.swift
    import Files
    import Plister
    struct EnvironmentUpdater {
    static func updateEnvironment(file: File) throws {
    try Plister.setValue("I was updated!", for: "string_secret", in: file)
    }
    static func resetEnvironment(file: File) throws {
    try file.resetToGitHEAD()
    }
    }

    View Slide

  57. EnvironmentUpdater.swift
    import Files
    import Plister
    struct EnvironmentUpdater {
    static func updateEnvironment(file: File) throws {
    try Plister.setValue("I was updated!", for: "string_secret", in: file)
    }
    static func resetEnvironment(file: File) throws {
    try file.resetToGitHEAD()
    }
    }

    View Slide

  58. EnvironmentUpdater.swift
    import Files
    import Plister
    struct EnvironmentUpdater {
    static func updateEnvironment(file: File) throws {
    try Plister.setValue("I was updated!", for: "string_secret", in: file)
    }
    static func resetEnvironment(file: File) throws {
    try file.resetToGitHEAD()
    }
    }

    View Slide

  59. EnvironmentUpdater.swift
    import Files
    import Plister
    struct EnvironmentUpdater {
    static func updateEnvironment(file: File) throws {
    try Plister.setValue("I was updated!", for: "string_secret", in: file)
    }
    static func resetEnvironment(file: File) throws {
    try file.resetToGitHEAD()
    }
    }

    View Slide

  60. EnvironmentUpdater.swift
    import Files
    import Plister
    struct EnvironmentUpdater {
    static func updateEnvironment(file: File) throws {
    try Plister.setValue("I was updated!", for: "string_secret", in: file)
    }
    static func resetEnvironment(file: File) throws {
    try file.resetToGitHEAD()
    }
    }

    View Slide

  61. main.swift
    let args = CommandLine.arguments.dropFirst()
    let plistFile = try SourceFolders.appRoot.file(named: "Environment.plist")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    try EnvironmentUpdater.updateEnvironment(file: plistFile)
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    try EnvironmentUpdater.resetEnvironment(file: plistFile)
    }

    View Slide

  62. main.swift
    let args = CommandLine.arguments.dropFirst()
    let plistFile = try SourceFolders.appRoot.file(named: "Environment.plist")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    try EnvironmentUpdater.updateEnvironment(file: plistFile)
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    try EnvironmentUpdater.resetEnvironment(file: plistFile)
    }

    View Slide

  63. main.swift
    let args = CommandLine.arguments.dropFirst()
    let plistFile = try SourceFolders.appRoot.file(named: "Environment.plist")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    try EnvironmentUpdater.updateEnvironment(file: plistFile)
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    try EnvironmentUpdater.resetEnvironment(file: plistFile)
    }

    View Slide

  64. View Slide

  65. main.swift
    let args = CommandLine.arguments.dropFirst()
    let plistFile = try SourceFolders.appRoot.file(named: "Environment.plist")
    if args.contains("--prebuild") {
    print("Run pre-build tasks")
    try EnvironmentUpdater.updateEnvironment(file: plistFile)
    }
    if args.contains("--postbuild") {
    print("Run post-build tasks")
    try EnvironmentUpdater.resetEnvironment(file: plistFile)
    }

    View Slide

  66. View Slide

  67. EnvironmentUpdater.swift
    import Files
    import Plister
    struct EnvironmentUpdater {
    static func updateEnvironment(file: File) throws {
    try Plister.setValue("I was updated!", for: "string_secret", in: file)
    }
    static func resetEnvironment(file: File) throws {
    try file.resetToGitHEAD()
    }
    }

    View Slide

  68. View Slide

  69. View Slide

  70. View Slide

  71. EnvironmentUpdater.swift
    static func updateEnvironment(file: File) throws {
    if
    let secretsFolder = SourceFolders.secrets,
    let secrets = try? secretsFolder.file(named: "production.json") {
    let json = try JSONLoader.loadJSONDictionary(from: secrets)
    try json.forEach { key, value in
    try Plister.setValue(value, for: key, in: file)
    }
    } else {
    let env = ProcessInfo.processInfo.environment
    guard let stringSecret = env["string_secret"] else {
    throw EnvironmentUpdater.EnvError.couldntLoadFromEnv
    }
    try Plister.setValue(stringSecret, for: "string_secret", in: file)
    }
    }

    View Slide

  72. EnvironmentUpdater.swift
    static func updateEnvironment(file: File) throws {
    if
    let secretsFolder = SourceFolders.secrets,
    let secrets = try? secretsFolder.file(named: "production.json") {
    let json = try JSONLoader.loadJSONDictionary(from: secrets)
    try json.forEach { key, value in
    try Plister.setValue(value, for: key, in: file)
    }
    } else {
    let env = ProcessInfo.processInfo.environment
    guard let stringSecret = env["string_secret"] else {
    throw EnvironmentUpdater.EnvError.couldntLoadFromEnv
    }
    try Plister.setValue(stringSecret, for: "string_secret", in: file)
    }
    }

    View Slide

  73. EnvironmentUpdater.swift
    static func updateEnvironment(file: File) throws {
    if
    let secretsFolder = SourceFolders.secrets,
    let secrets = try? secretsFolder.file(named: "production.json") {
    let json = try JSONLoader.loadJSONDictionary(from: secrets)
    try json.forEach { key, value in
    try Plister.setValue(value, for: key, in: file)
    }
    } else {
    let env = ProcessInfo.processInfo.environment
    guard let stringSecret = env["string_secret"] else {
    throw EnvironmentUpdater.EnvError.couldntLoadFromEnv
    }
    try Plister.setValue(stringSecret, for: "string_secret", in: file)
    }
    }

    View Slide

  74. View Slide

  75. CONTINUOUS INTEGRATION SERVERS

    View Slide

  76. EnvironmentUpdater.swift
    static func updateEnvironment(file: File) throws {
    if
    let secretsFolder = SourceFolders.secrets,
    let secrets = try? secretsFolder.file(named: "production.json") {
    let json = try JSONLoader.loadJSONDictionary(from: secrets)
    try json.forEach { key, value in
    try Plister.setValue(value, for: key, in: file)
    }
    } else {
    let env = ProcessInfo.processInfo.environment
    guard let stringSecret = env["string_secret"] else {
    throw EnvironmentUpdater.EnvError.couldntLoadFromEnv
    }
    try Plister.setValue(stringSecret, for: "string_secret", in: file)
    }
    }

    View Slide

  77. EnvironmentUpdater.swift
    static func updateEnvironment(file: File) throws {
    if
    let secretsFolder = SourceFolders.secrets,
    let secrets = try? secretsFolder.file(named: "production.json") {
    let json = try JSONLoader.loadJSONDictionary(from: secrets)
    try json.forEach { key, value in
    try Plister.setValue(value, for: key, in: file)
    }
    } else {
    let env = ProcessInfo.processInfo.environment
    guard let stringSecret = env["string_secret"] else {
    throw EnvironmentUpdater.EnvError.couldntLoadFromEnv
    }
    try Plister.setValue(stringSecret, for: "string_secret", in: file)
    }
    }

    View Slide

  78. View Slide

  79. DIDN'T TOUCH THE COMMAND LINE!

    View Slide

  80. WHAT ELSE CAN I DO?

    View Slide

  81. "Stringly-typed cdoe is a
    tribleee idae."

    View Slide

  82. "Stringly-typed code is a
    terrible idea."

    View Slide

  83. WHAT CAN WE DETERMINE AT BUILD TIME TO
    AVOID USING HARD-CODED STRINGS?

    View Slide

  84. GENERATE CODE FOR
    !
    ASSET CATALOGS

    View Slide

  85. BEFORE
    UIImage(named: "fragile")!
    UIColor(named: "uhoh")!

    View Slide

  86. GENERATE METHODS TO ACCESS
    !
    LOCALIZED STRINGS

    View Slide

  87. YourCode.swift
    NSLocalizedString("The key that will be displayed by default",
    "Some comment")

    View Slide

  88. YourCode.swift
    NSLocalizedString("The key that will be displayed by default",
    "Some comment")
    Base.lproj/Localizable.strings
    /* Some Comment */
    "The key that will be displayed by default" =
    "The key that will be displayed by default";

    View Slide

  89. YourCode.swift
    NSLocalizedString("The key that will be displayed by default",
    "Some comment")
    Base.lproj/Localizable.strings
    /* Some Comment */
    "The key that will be displayed by default" =
    "The key that will be displayed by default";
    nl.lproj/Localizable.strings
    /* Some Comment */
    "The key that will be displayed by default" =
    "De sleutel die standaard wordt weergegeven";

    View Slide

  90. YourCode.swift
    NSLocalizedString("default_key",
    "Some comment")
    Base.lproj/Localizable.strings
    /* Some Comment */
    "default_key" = "The key that will be displayed by default";
    nl.lproj/Localizable.strings
    /* Some Comment */
    "default_key" = "De sleutel die standaard wordt weergegeven";

    View Slide


  91. GENERATE ENUMS!

    View Slide

  92. YourCode.swift
    LocalizedString(key: .default_key)
    Base.lproj/Localizable.strings
    /* Some Comment */
    "default_key" = "The key that will be displayed by default";
    nl.lproj/Localizable.strings
    /* Some Comment */
    "default_key" = "De sleutel die standaard wordt weergegeven";

    View Slide


  93. GENERATE ENUMS!

    View Slide

  94. View Slide

  95. AFTER
    UIImage(from: .fragile)
    UIColor(from: .uhoh)

    View Slide

  96. THINGS I HAVE LEARNED
    THE HARD WAY
    (SO YOU DON'T HAVE TO)

    View Slide

  97. KEEP AS MUCH IN SWIFT AS POSSIBLE

    View Slide

  98. CREATE REUSABLE LIBRARIES

    View Slide

  99. CREATE PER-APP EXECUTABLES
    TO TAKE TAKE ADVANTAGE OF THOSE LIBRARIES

    View Slide

  100. TEST!

    View Slide

  101. LEVERAGE GIT

    View Slide

  102. !
    MARATHON

    View Slide

  103. swift-sh

    View Slide

  104. OBLIGATORY SUMMARY SLIDE

    View Slide

  105. OBLIGATORY SUMMARY SLIDE
    > Swift Package Manager can help you make your iOS
    Projects better today

    View Slide

  106. OBLIGATORY SUMMARY SLIDE
    > Swift Package Manager can help you make your iOS
    Projects better today
    > Move build scripts to swift and keep type safety

    View Slide

  107. OBLIGATORY SUMMARY SLIDE
    > Swift Package Manager can help you make your iOS
    Projects better today
    > Move build scripts to swift and keep type safety
    > Actually test your build scripts!

    View Slide

  108. OBLIGATORY SUMMARY SLIDE
    > Swift Package Manager can help you make your iOS
    Projects better today
    > Move build scripts to swift and keep type safety
    > Actually test your build scripts!
    > In Xcode 11, you should be able to use it for your main
    app and scripts in the same project.

    View Slide

  109. !

    View Slide

  110. LINKS!
    > It's time to use Swift Package Manager
    https://artsy.github.io/blog/2019/01/05/its-time-to-
    use-spm/
    > Marathon
    https://github.com/JohnSundell/Marathon
    > swift-sh
    https://github.com/mxcl/swift-sh

    View Slide