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

Swift Package Manager

Swift Package Manager

Overview talk on the Swift Package Manager, given at NSBarcelona, April 2016.


Boris Bügling

April 07, 2016


  1. Swift Package Manager NSBarcelona, April 2016 Boris Bügling - @NeoNacho

  2. CocoaPods

  3. Contentful

  4. Agenda • What is swiftpm? • Making our own package

    • How does it work? • Comparison with related tools
  5. What is swiftpm?

  6. Development Snapshot March 24, 2016 $ swift --version Apple Swift

    version 3.0-dev (LLVM b010debd0e, Clang 3e4d01d89b, Swift 7182c58cb2)
  7. import PackageDescription let package = Package( name: "Hello", dependencies: [

    .Package(url: "ssh://git@example.com/Greeter.git", versions: Version(1,0,0)..<Version(2,0,0)), ] )
  8. $ swift build Compiling Swift Module 'Clock' (2 sources) Linking

    Library: .build/debug/Clock.a
  9. What does it do? • Compiles and links Swift packages

    • Resolves, fetches and builds their dependencies • Runs tests
  10. Current state • Currently builds dynamic/static libraries or binaries •

    Supported platforms are OS X and Ubuntu Linux • Only builds Swift code, no C/C++/Objective-C/... (but there's an accepted proposal SE-0038)
  11. swift build OVERVIEW: Build sources into binary products USAGE: swift

    build [options] MODES: --configuration <value> Build with configuration (debug|release) [-c] --clean[=<mode>] Delete artefacts (build|dist) [-k] --init <mode> Creates a new Swift package (executable|library) --fetch Fetch package dependencies --generate-xcodeproj [<path>] Generates an Xcode project for this package [-X] OPTIONS: --chdir <value> Change working directory before any other operation [-C] -v[v] Increase verbosity of informational output -Xcc <flag> Pass flag through to all C compiler instantiations -Xlinker <flag> Pass flag through to all linker instantiations -Xswiftc <flag> Pass flag through to all Swift compiler instantiations
  12. swift test OVERVIEW: Build and run tests USAGE: swift test

    [options] OPTIONS: TestModule.TestCase Run a test case subclass TestModule.TestCase/test1 Run a specific test method
  13. None
  14. Making our own package

  15. ! A small library for parsing and writing ISO8601 date

    strings. https://github.com/neonichu/Clock/tree/swift-3.0
  16. Sources └── Clock ├── ISO8601Parser.swift └── ISO8601Writer.swift 1 directory, 2

  17. $ touch Package.swift $ swift build

  18. Tests?

  19. Tests ├── Clock │ ├── Localtime.swift │ ├── String.swift │

    ├── TMStruct.swift │ └── UTC.swift └── LinuxMain.swift 1 directory, 5 files
  20. XCTest import XCTest @testable import Clock class DateToStringConversionTests: XCTestCase {

    func testConvertTMStructToAnISO8601GMTString() { let actual = tm_struct(year: 1971, month: 2, day: 3, hour: 9, minute: 16, second: 6) XCTAssertEqual(actual.toISO8601GMTString(), "1971-02-03T09:16:06Z") } }
  21. Linux #if os(Linux) extension DateToStringConversionTests { static var allTests :

    [(String, DateToStringConversionTests -> () throws -> Void)] { return [ ("testConvertTMStructToAnISO8601GMTString", testConvertTMStructToAnISO8601GMTString) ] } } #endif
  22. LinuxMain.swift import XCTest @testable import ClockTestSuite XCTMain([ testCase(TMStructTests.allTests), testCase(DateToStringConversionTests.allTests), ])

  23. Side-note about Linux

  24. 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
  25. 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 {
  26. OS X libc and Glibc can differ: 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
  27. 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)
  28. Running the tests $ swift test Test Suite 'All tests'

    started at 2016-04-06 17:06:00.900 Test Suite 'Package.xctest' started at 2016-04-06 17:06:00.901 Test Suite 'DateToStringConversionTests' started at 2016-04-06 17:06:00.901 Test Case '-[ClockTestSuite.DateToStringConversionTests testCanConvertNSDateToAnISO8601GMString]' started. [...] Test Suite 'All tests' passed at 2016-04-06 17:06:00.911. Executed 12 tests, with 0 failures (0 unexpected) in 0.008 (0.011) seconds
  29. Swift versions $ cat .swift-version swift-2.2-SNAPSHOT-2015-12-22-a • Is read by

    either chswift or swiftenv
  30. 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
  31. How does it work?

  32. Modules inside SwiftPackageManager Target( /** “Swifty” POSIX functions from libc

    */ name: "POSIX", dependencies: ["libc"]), Target( /** Abstractions for common operations */ name: "Utility", dependencies: ["POSIX"]), Target( /** Base types for the package-engine */ name: "PackageType", dependencies: ["PackageDescription", "Utility"]), Target( name: "ManifestParser", dependencies: ["PackageDescription", "PackageType"]), Target( /** Turns Packages into Modules & Products */ name: "Transmute", dependencies: ["PackageDescription", "PackageType"]), Target( /** Fetches Packages and their dependencies */ name: "Get", dependencies: ["PackageDescription", "PackageType"]),
  33. Modules inside SwiftPackageManager Target( /** Builds Modules and Products */

    name: "Build", dependencies: ["PackageType"]), Target( /** Common components of both executables */ name: "Multitool", dependencies: ["PackageType"]), Target( /** Generates Xcode projects */ name: "Xcodeproj", dependencies: ["PackageType"]), Target( /** The main executable provided by SwiftPM */ name: "swift-build", dependencies: ["ManifestParser", "Get", "Transmute", "Build", "Multitool", "Xcodeproj"]), Target( /** Runs package tests */ name: "swift-test", dependencies: ["Multitool"]),
  34. Dependencies • Can be local or remote Git repositories •

    Need to be tagged • Will be fetched to ./Packages/MyPackage-0.0.1
  35. Git tagging • Package.swift only supports tagged dependencies • Don't

    forget to push your tags to GitHub
  36. Rough build process • PackageDescription generates TOML • Transmute parses

    the TOML and can generate YAML • Dependencies are fetched by Get • YAML is used as input to llbuild • Build calls out to llbuild
  37. llbuild

  38. $ cat .build/debug.yaml client: name: swift-build tools: {} targets: test:

    [<Clock.testsuite.module>, <Package.test>] default: [<Clock.module>]
  39. commands: .../.build/debug/Clock.build: tool: mkdir outputs: [.../.build/debug/Clock.build] <Clock.module>: tool: swift-compiler executable:

    .../bin/swiftc module-name: Clock module-output-path: .../.build/debug/Clock.swiftmodule inputs: [] outputs: [<Clock.module>, .../.build/debug/Clock.build/ISO8601Parser.swift.o, .../.build/debug/Clock.build/ISO8601Writer.swift.o] import-paths: .../.build/debug temps-path: .../.build/debug/Clock.build objects: [.../.build/debug/Clock.build/ISO8601Parser.swift.o, .../.build/debug/Clock.build/ISO8601Writer.swift.o] other-args: ["-j8", "-Onone", "-g", "-D", SWIFT_PACKAGE, "-enable-testing", "-F", ".../Developer/Library/Frameworks", "-target", "x86_64-apple-macosx10.10", "-sdk", ".../Developer/SDKs/MacOSX10.11.sdk"] sources: [.../Sources/Clock/ISO8601Parser.swift, .../Sources/Clock/ISO8601Writer.swift] is-library: true
  40. .../.build/debug/ClockTestSuite.build: tool: mkdir outputs: [.../.build/debug/ClockTestSuite.build] <Clock.testsuite.module>: tool: swift-compiler executable: /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a.xctoolchain/usr/bin/swiftc

    module-name: ClockTestSuite module-output-path: .../.build/debug/ClockTestSuite.swiftmodule inputs: [<Clock.module>] outputs: [<Clock.testsuite.module>, .../.build/debug/ClockTestSuite.build/Localtime.swift.o, .../.build/debug/ClockTestSuite.build/String.swift.o, .../.build/debug/ClockTestSuite.build/TMStruct.swift.o, .../.build/debug/ClockTestSuite.build/UTC.swift.o] import-paths: .../.build/debug temps-path: .../.build/debug/ClockTestSuite.build objects: [.../.build/debug/ClockTestSuite.build/Localtime.swift.o, .../.build/debug/ClockTestSuite.build/String.swift.o, .../.build/debug/ClockTestSuite.build/TMStruct.swift.o, .../.build/debug/ClockTestSuite.build/UTC.swift.o] other-args: ["-j8", "-Onone", "-g", "-D", SWIFT_PACKAGE, "-enable-testing", "-F", ".../Developer/Library/Frameworks", "-target", "x86_64-apple-macosx10.10", "-sdk", ".../Developer/SDKs/MacOSX10.11.sdk"] sources: [.../Tests/Clock/Localtime.swift, .../Tests/Clock/String.swift, .../Tests/Clock/TMStruct.swift, .../Tests/Clock/UTC.swift] is-library: true
  41. <Package.test>: tool: shell description: Linking .build/debug/Package.xctest/Contents/MacOS/Package inputs: [<Clock.module>, <Clock.testsuite.module>, .../.build/debug/ClockTestSuite.build/Localtime.swift.o,

    .../.build/debug/ClockTestSuite.build/String.swift.o, .../.build/debug/ClockTestSuite.build/TMStruct.swift.o, .../.build/debug/ClockTestSuite.build/UTC.swift.o] outputs: [<Package.test>, .../.build/debug/Package.xctest/Contents/MacOS/Package] args: [".../bin/swiftc", "-target", "x86_64-apple-macosx10.10", "-sdk", ".../Developer/SDKs/MacOSX10.11.sdk", "-g", "-L.../.build/debug", "-o", .../.build/debug/Package.xctest/Contents/MacOS/Package, "-Xlinker", "-bundle", "-F", ".../Developer/Library/Frameworks", .../.build/debug/ClockTestSuite.build/Localtime.swift.o, .../.build/debug/ClockTestSuite.build/String.swift.o, .../.build/debug/ClockTestSuite.build/TMStruct.swift.o, .../.build/debug/ClockTestSuite.build/UTC.swift.o, .../.build/debug/Clock.build/ISO8601Parser.swift.o, .../.build/debug/Clock.build/ISO8601Writer.swift.o]
  42. Additional Package.swift syntax

  43. Targets import PackageDescription let package = Package( name: "Example", targets:

    [ Target( name: "top", dependencies: [.Target(name: "bottom")]), Target( name: "bottom") ] )
  44. Exclusion let package = Package( name: "Example", exclude: ["tools", "docs",

    "Sources/libA/images"] )
  45. Test dependencies import PackageDescription let package = Package( name: "Hello",

    testDependencies: [ .Package(url: "ssh://git@example.com/Tester.git", versions: Version(1,0,0)..<Version(2,0,0)), ] )
  46. Customizing builds import PackageDescription var package = Package() #if os(Linux)

    let target = Target(name: "LinuxSources/foo") package.targets.append(target) #endif
  47. Integrate system libraries • Empty Package.swift • module.modulemap: module curl

    [system] { header "/usr/include/curl/curl.h" link "curl" export * }
  48. let package = Package( name: "example", dependencies: [ .Package(url: "https://github.com/neonichu/curl",

    majorVersion: 1) ] )
  49. Comparison with related tools

  50. CocoaPods • Centralized discovery • Xcode integration • Support for

    other languages • Additional metadata in the Manifest • Does not cover the build process
  51. Carthage • No manifest for packages • Xcode integration •

    Support for other languages
  52. Support all three • Package.swift • .podspec • .xcodeproj /

  53. !

  54. CocoaPods • chocolat-cli converts Package.swift to a JSON Podspec

  55. 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 }
  56. 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
  57. References • https://swift.org • https://github.com/apple/swift-package-manager • https://github.com/apple/llbuild • https://github.com/neonichu/chocolat •

    https://github.com/neonichu/chswift • https://github.com/neonichu/freedom • https://github.com/kylef/swiftenv
  58. Thank you!

  59. @NeoNacho boris@contentful.com http://buegling.com/talks