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

Scripting in Swift For a Testable Build - iOS Conf SG, Singapore, January 2020

Scripting in Swift For a Testable Build - iOS Conf SG, Singapore, January 2020

Abstract:

Underlying many large iOS projects is a tangled nest of bash scripts that developers are often afraid to touch for fear of breaking something, and which is littered with print statements from generations of developers trying to figure out how on earth it works. How can we move away from this mess and to something more sustainable? Ellen will discuss moving a codebase like this to a command line tool that can be called from a Swift script, and which can be tested and breakpointed, and just might save a tiny bit of your sanity.

Ellen Shapiro

January 17, 2020
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. SCRIPTING IN SWIFT FOR A TESTABLE BUILD IOS CONF SG

    | SINGAPORE | JANUARY 2020 ELLEN SHAPIRO | @DESIGNATEDNERD | APOLLOGRAPHQL.COM
  2. YOUR IOS PROJECT HAS A NEST OF BASH SCRIPTS (IT'S

    OKAY, MY PROJECTS DO TOO) @DesignatedNerd
  3. DON'T IT ALWAYS SEEM TO GO THAT YOU DON'T KNOW

    WHAT YOU GOT 'TIL IT'S GONE - JONI MITCHELL @DesignatedNerd
  4. WHAT APOLLO'S SCRIPT DOES Is there a zipped version of

    the CLI? NO Download and unzip current version of CLI. @DesignatedNerd
  5. WHAT APOLLO'S SCRIPT DOES Is there a zipped version of

    the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? @DesignatedNerd
  6. WHAT APOLLO'S SCRIPT DOES Is there a zipped version of

    the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? NO @DesignatedNerd
  7. WHAT APOLLO'S SCRIPT DOES Is there a zipped version of

    the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? NO Download and unzip current version of CLI. @DesignatedNerd
  8. WHAT APOLLO'S SCRIPT DOES Is there a zipped version of

    the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? YES @DesignatedNerd
  9. WHAT APOLLO'S SCRIPT DOES Is there a zipped version of

    the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? YES Unzip if needed @DesignatedNerd
  10. IF YOU CAN'T RELEASE YOUR APP WITHOUT IT TEST IT

    (AND IT'S A LOT EASIER TO TEST IT IN SWIFT) @DesignatedNerd
  11. CREATE IT WITH SWIFT PACKAGE MANAGER $ mkdir Codegen $

    cd Codegen $ swift package init --type executable @DesignatedNerd
  12. Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name:

    "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd
  13. Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name:

    "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd
  14. Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name:

    "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd
  15. Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name:

    "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd
  16. Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name:

    "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd
  17. Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name:

    "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd
  18. main.swift import Foundation enum MyCodegenError: Error { case sourceRootNotProvided case

    sourceRootNotADirectory case targetDoesntExist } guard let sourceRootPath = ProcessInfo.processInfo.environment["SRCROOT"] else { throw MyCodegenError.sourceRootNotProvided } @DesignatedNerd
  19. main.swift import Foundation import ApolloCodegenLib enum MyCodegenError: Error { case

    sourceRootNotProvided case sourceRootNotADirectory case targetDoesntExist } guard let sourceRootPath = ProcessInfo.processInfo.environment["SRCROOT"] else { throw MyCodegenError.sourceRootNotProvided } guard FileManager.default.apollo_folderExists(at: sourceRootPath) else { throw MyCodegenError.sourceRootNotADirectory } @DesignatedNerd
  20. main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at:

    applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd
  21. main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at:

    applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd
  22. main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at:

    applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd
  23. main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at:

    applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd
  24. main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at:

    applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd
  25. 3. ! PROFIT!* * - BY WASTING LESS OF YOUR

    TIME FAFFING AROUND WITH BASH @DesignatedNerd
  26. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when stuff breaks @DesignatedNerd
  27. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when (not if) stuff breaks @DesignatedNerd
  28. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework @DesignatedNerd
  29. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without @DesignatedNerd
  30. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without > Use Swift Package Manager to build an executable @DesignatedNerd
  31. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without > Use Swift Package Manager to build an executable > Run that from a build script run phase in your main app @DesignatedNerd
  32. OBLIGATORY SUMMARY SLIDE > Use swift to write testable script

    code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without > Use Swift Package Manager to build an executable > Run that from a build script run phase in your main app > Document the crap out of everything @DesignatedNerd