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

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 full-size slide

  2. DRAGGING THINGS INTO PROJECTS

    View full-size slide

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

    View full-size slide

  4. GIT SUBMODULES

    View full-size slide

  5. CODE SIGNING
    !
    SILLINESS

    View full-size slide

  6. SWIFT PACKAGE MANAGER

    View full-size slide

  7. SERVER:
    COMMAND LINE:

    View full-size slide

  8. SERVER:
    COMMAND LINE:
    IOS:

    View full-size slide

  9. Command line. Sorry.

    View full-size slide

  10. swift package init

    View full-size slide

  11. swift package init --type executable

    View full-size slide

  12. swift package init --type library

    View full-size slide

  13. swift package generate-xcodeproj

    View full-size slide

  14. swift run [project] [arguments]

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. TYPE SAFETY?

    View full-size slide

  18. !
    BETTER BUILD SCRIPT WRAPPERS

    View full-size slide

  19. RUN WHEN YOU BUILD YOUR MAIN PROJECT

    View full-size slide

  20. IN YOUR SPM EXECUTABLE PROJECT

    View full-size slide

  21. 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 full-size slide

  22. 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 full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

  25. IN YOUR ACTUAL PROJECT

    View full-size slide

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

    View full-size slide

  27. <--- Main app
    <--- SPM executable

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. WHAT CAN I DO WITH THIS?

    View full-size slide

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

    View full-size slide

  32. Environment.plist

    View full-size slide

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

    View full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. 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 full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. 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 full-size slide

  41. 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 full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 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 full-size slide

  47. CONTINUOUS INTEGRATION SERVERS

    View full-size slide

  48. 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 full-size slide

  49. 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 full-size slide

  50. DIDN'T TOUCH THE COMMAND LINE!

    View full-size slide

  51. WHAT ELSE CAN I DO?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. GENERATE CODE FOR
    !
    ASSET CATALOGS

    View full-size slide

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

    View full-size slide

  57. GENERATE METHODS TO ACCESS
    !
    LOCALIZED STRINGS

    View full-size slide

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

    View full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

  61. 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 full-size slide


  62. GENERATE ENUMS!

    View full-size slide

  63. 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 full-size slide


  64. GENERATE ENUMS!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  67. KEEP AS MUCH IN SWIFT AS POSSIBLE

    View full-size slide

  68. CREATE REUSABLE LIBRARIES

    View full-size slide

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

    View full-size slide

  70. LEVERAGE GIT

    View full-size slide

  71. OBLIGATORY SUMMARY SLIDE

    View full-size slide

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

    View full-size slide

  73. 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 full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

  76. 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 full-size slide