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

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro

July 08, 2019
Tweet

Transcript

  1. THINKING OUTSIDE THE BOX WITH ! SWIFT PACKAGE MANAGER HACKING

    WITH SWIFT LIVE | BATH, UK | JULY 2019 BY ELLEN SHAPIRO | @DESIGNATEDNERD | APOLLOGRAPHQL.COM
  2. None
  3. DRAGGING THINGS INTO PROJECTS

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

  5. GIT SUBMODULES

  6. CHANGES?

  7. COCOAPODS

  8. None
  9. ! CARTHAGE

  10. CODE SIGNING ! SILLINESS

  11. SWIFT PACKAGE MANAGER

  12. None
  13. SERVER:

  14. SERVER: COMMAND LINE:

  15. SERVER: COMMAND LINE: IOS:

  16. None
  17. ! XCODE 11

  18. ! BETA?!

  19. RIGHT NOW!

  20. Command line. Sorry.

  21. None
  22. swift package init

  23. swift package init --type executable

  24. swift package init --type library

  25. swift package generate-xcodeproj

  26. swift build

  27. swift run

  28. swift run [project] [arguments]

  29. I HATE BASH

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

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

  32. TYPE SAFETY?

  33. ! BETTER BUILD SCRIPT WRAPPERS

  34. RUN WHEN YOU BUILD YOUR MAIN PROJECT

  35. IN YOUR SPM EXECUTABLE PROJECT

  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") }
  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") }
  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") }
  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") }
  40. IN YOUR ACTUAL PROJECT

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

  42. None
  43. None
  44. <--- Main app <--- SPM executable

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

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

    cd Scripty swift run Scripty --postbuild
  47. None
  48. None
  49. None
  50. WHAT CAN I DO WITH THIS?

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

  52. Environment.plist

  53. None
  54. None
  55. HTTPS://GITHUB.COM/DESIGNATEDNERD/ PLISTER

  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() } }
  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() } }
  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() } }
  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() } }
  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() } }
  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) }
  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) }
  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) }
  64. None
  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) }
  66. None
  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() } }
  68. None
  69. None
  70. None
  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) } }
  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) } }
  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) } }
  74. None
  75. CONTINUOUS INTEGRATION SERVERS

  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) } }
  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) } }
  78. None
  79. DIDN'T TOUCH THE COMMAND LINE!

  80. WHAT ELSE CAN I DO?

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

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

  83. WHAT CAN WE DETERMINE AT BUILD TIME TO AVOID USING

    HARD-CODED STRINGS?
  84. GENERATE CODE FOR ! ASSET CATALOGS

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

  86. GENERATE METHODS TO ACCESS ! LOCALIZED STRINGS

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

    comment")
  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";
  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";
  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";
  91. ✨ GENERATE ENUMS!

  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";
  93. ✨ GENERATE ENUMS!

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

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

    HAVE TO)
  97. KEEP AS MUCH IN SWIFT AS POSSIBLE

  98. CREATE REUSABLE LIBRARIES

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

  100. TEST!

  101. LEVERAGE GIT

  102. ! MARATHON

  103. swift-sh

  104. OBLIGATORY SUMMARY SLIDE

  105. OBLIGATORY SUMMARY SLIDE > Swift Package Manager can help you

    make your iOS Projects better today
  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
  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!
  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.
  109. !

  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