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

Cross-Platform Swift

Cross-Platform Swift

A talk about writing Swift targeting multiple platforms.

Boris Bügling

February 17, 2016
Tweet

More Decks by Boris Bügling

Other Decks in Programming

Transcript

  1. AGENDA ▸ Which platforms can we target? ▸ How to

    share code between them ▸ Some practical examples
  2. THERE'S MORE DEPENDING ON THE SUBSET WE TARGET Accelerate.framework AudioToolbox.framework

    AudioUnit.framework AVFoundation.framework AVKit.framework CloudKit.framework CoreBluetooth.framework
  3. THERE'S MORE DEPENDING ON THE SUBSET WE TARGET CoreImage.framework CoreMedia.framework

    CoreVideo.framework EventKit.framework GameController.framework GameKit.framework GLKit.framework MapKit.framework
  4. THERE'S MORE DEPENDING ON THE SUBSET WE TARGET MediaAccessibility.framework Metal.framework

    MobileCoreServices.framework SceneKit.framework SpriteKit.framework StoreKit.framework SystemConfiguration.framework
  5. NIBS ! If you don't feel like copy-pasting stuff between

    NIBs ! https://github.com/neonichu/bohne
  6. FOUNDATION IS INCOMPLETE AND SOMETIMES DIFFERENT FROM OS X #if

    os(Linux) let index = p.startIndex.distanceTo(p.startIndex.successor()) path = NSString(string: p).substringFromIndex(index) #else path = p.substringFromIndex(p.startIndex.successor()) #endif
  7. EVEN SOME THINGS IN THE STANDARD LIBRARY MIGHT NOT BE

    AVAILABLE #if _runtime(_ObjC) // Excluded due to use of dynamic casting and Builtin.autorelease, neither // of which correctly work without the ObjC Runtime right now. // See rdar://problem/18801510 [...] public func getVaList(args: [CVarArgType]) -> CVaListPointer {
  8. let flags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK if system_glob(cPattern,

    flags, nil, &gt) == 0 { #if os(Linux) let matchc = gt.gl_pathc #else let matchc = gt.gl_matchc #endif
  9. AND OTHER RANDOM FUN ./.build/debug/spectre-build /usr/bin/ld: .build/debug/Clock.a(ISO8601Parser.swift.o): undefined reference to

    symbol '_swift_FORCE_LOAD_$_swiftGlibc' /home/travis/.swiftenv/versions/swift-2.2-SNAPSHOT-2015-12-22-a/ usr/lib/swift/linux/libswiftGlibc.so: error adding symbols: DSO missing from command line clang: error: linker command failed with exit code 1 (use -v to see invocation)
  10. #if os(OSX) print("Running on OS X") #elseif os(watchOS) print("Running on

    watchOS") #else print("Running on any platform but OS X and watchOS") #end
  11. [XCODE 7 BETA] LINKING DUAL (IPHONEOS AND WATCHOS) FRAMEWORKS WITH

    SAME PRODUCT NAME CAUSES ARCHIVE TO FAIL http://openradar.appspot.com/22392501
  12. IF A POD DOES NOT SUPPORT A CERTAIN PLATFORM ▸

    Fork and add it ▸ Submit a PR ▸ For the adventurous ! https://github.com/orta/cocoapods-expert-difficulty
  13. COCOAPODS AVOIDS 22392501 ▸ Each duplicated target gets scoped ▸

    <.../Build/Debug-iphoneos/Pods-UserTarget/ *.framework>
  14. CONCHE $ conche build Downloading Dependencies -> PathKit 0.5.0 ->

    Commander 0.5.0 Building Dependencies -> PathKit -> Commander Building Conche Building Entry Points -> conche -> .conche/bin/conche
  15. import PackageDescription let package = Package( name: "Hello", dependencies: [

    .Package(url: "ssh://[email protected]/Greeter.git", versions: Version(1,0,0)..<Version(2,0,0)), ] )
  16. WHAT DOES IT DO? ▸ Compiles and links Swift packages

    ▸ Resolves, fetches and builds their dependencies
  17. CURRENT STATE ▸ Currently builds static libraries or binaries ▸

    Supported platforms are OS X and Ubuntu Linux ▸ Only builds Swift code, no C/C++/Objective-C/...
  18. $ swift build --help OVERVIEW: Build sources into binary products

    USAGE: swift build [options] MODES: --configuration <value> Build with configuration (debug|release) [-c] --clean Delete all build intermediaries and products [-k] OPTIONS: --chdir <value> Change working directory before any other operation [-C] -v Increase verbosity of informational output
  19. SPECTRE describe("a person") { let person = Person(name: "Kyle") $0.it("has

    a name") { try expect(person.name) == "Kyle" } $0.it("returns the name as description") { try expect(person.description) == "Kyle" } }
  20. SPECTRE-BUILD $ swift build $ .build/debug/spectre-build -> a person ->

    has a name -> returns the name as description 2 passes and 0 failures
  21. import PackageDescription let package = Package( name: "Clock", testDependencies: [

    .Package(url: "https://github.com/neonichu/spectre-build.git", majorVersion: 0), ] )
  22. $ make test swift build ./.build/debug/spectre-build -> Converting dates to

    strings -> can convert NSDate to an ISO8601 GMT string -> Parsing of localtime dates -> can parse dates -> can parse dates with negative timezone offsets -> can parse timezone offsets without colons -> Parsing of UTC dates -> can parse dates -> can parse epoch -> can parse dates without seconds -> is resilient against Y2K bugs 8 passes and 0 failures
  23. TRAVIS CI os: - linux - osx language: generic sudo:

    required dist: trusty osx_image: xcode7.2 install: - curl -sL https://gist.github.com/kylef/ 5c0475ff02b7c7671d2a/raw/ 621ef9b29bbb852fdfd2e10ed147b321d792c1e4/swiftenv-install.sh | bash script: - . ~/.swiftenv/init
  24. public func parse_package(packagePath: String) throws -> PackageDescription.Package { // FIXME:

    We depend on `chswift` installation and use here let toolchainPath = PathKit.Path(POSIX.getenv("CHSWIFT_TOOLCHAIN") ?? "") libc.setenv("SPM_INSTALL_PATH", toolchainPath.parent().description, 1) print_if("Using libPath \(Resources.runtimeLibPath)", false) let package = (try Manifest(path: packagePath)).package print_if("Converting package \(package.name) at \(packagePath)", false) return package }
  25. INTEGRATE SYSTEM LIBRARIES ▸ Empty Package.swift ▸ module.modulemap: module curl

    [system] { header "/usr/include/curl/curl.h" link "curl" export * }
  26. MODULES INSIDE SWIFTPACKAGEMANAGER ▸ OS abstractions: libc, POSIX, sys ▸

    Package: PackageDescription ▸ Manifest: dep ▸ Downloading code: swift-get ▸ Building code: swift-build
  27. DEPENDENCIES ▸ Can be local or remote Git repositories ▸

    Need to be tagged ▸ Will be fetched to ./Packages/ MyPackage-0.0.1
  28. ROUGH BUILD PROCESS ▸ PackageDescription generates TOML ▸ dep parses

    the TOML and can generate YAML ▸ Dependencies are fetched by swift-get ▸ YAML is used as input to llbuild ▸ swift-build calls out to llbuild
  29. commands: <Clock-swiftc>: tool: swift-compiler executable: "/usr/bin/swiftc" inputs: ["ISO8601Parser.swift","ISO8601Writer.swift"] outputs: ["<Clock-swiftc>","Clock.swiftmodule",

    "ISO8601Parser.swift.o","ISO8601Writer.swift.o"] module-name: "Clock" module-output-path: "Clock.swiftmodule" is-library: true sources: ["ISO8601Parser.swift","ISO8601Writer.swift"] objects: ["ISO8601Parser.swift.o","ISO8601Writer.swift.o"] import-paths: ["/Users/boris/Projects/Clock/.build/debug"] temps-path: "/Users/boris/Projects/Clock/.build/debug/Clock.o/Clock" other-args: ["-j8","-Onone","-g","-target","x86_64-apple-macosx10.10", "-enable-testing","-sdk", "/.../Developer/SDKs/MacOSX10.11.sdk","-I","/usr/local/include"]
  30. <Clock>: tool: shell inputs: ["<Clock-swiftc>","ISO8601Parser.swift.o","ISO8601Writer.swift.o"] outputs: ["<Clock>","Clock.a"] args: ["/bin/sh","-c","rm -f

    'Clock.a'; ar cr 'Clock.a' 'ISO8601Parser.swift.o' 'ISO8601Writer.swift.o'"] description: "Linking Library: .build/debug/Clock.a"
  31. TARGETS import PackageDescription let package = Package( name: "Example", targets:

    [ Target( name: "top", dependencies: [.Target(name: "bottom")]), Target( name: "bottom") ] )
  32. TEST DEPENDENCIES import PackageDescription let package = Package( name: "Hello",

    testDependencies: [ .Package(url: "ssh://[email protected]/Tester.git", versions: Version(1,0,0)..<Version(2,0,0)), ] )
  33. CUSTOMIZING BUILDS import PackageDescription var package = Package() #if os(Linux)

    let target = Target(name: "LinuxSources/foo") package.targets.append(target) #endif
  34. You should think of it as an alpha code base

    that hasn't had a release yet. Yes, it is useful for doing some things [...] — Daniel Dunbar
  35. CLOCK ~/Clock# make swift build ./.build/debug/spectre-build -> Parsing of localtime

    dates fatal error: init(forSecondsFromGMT:) is not yet implemented: file Foundation/NSTimeZone.swift, line 69 Illegal instruction InvocationError() Makefile:6: recipe for target 'test' failed make: *** [test] Error 1
  36. CONCLUSION ▸ It's complicated :) ▸ Still a lot of

    code can be shared ▸ Swift 3.0 will help a lot