Save 37% off PRO during our Black Friday Sale! »

Swift Package Manager

Swift Package Manager

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

9d2ea021919ff81e02d48530aae191bd?s=128

Boris Bügling

April 07, 2016
Tweet

Transcript

  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

    files
  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 /

    .xcworkspace
  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