$30 off During Our Annual Pro Sale. View Details »

わいわいSwift PM part 1

d_date
January 24, 2022

わいわいSwift PM part 1

2022/01/24 わいわいswiftc

d_date

January 24, 2022
Tweet

More Decks by d_date

Other Decks in Programming

Transcript

  1. Daiki Matsudate / @d_date
    Θ͍Θ͍Swift PM
    Θ͍Θ͍swiftc

    View Slide

  2. Latest updates

    View Slide

  3. Swift Playground 4

    View Slide

  4. VS Code for Swift

    View Slide

  5. View Slide

  6. Package Plugins

    View Slide

  7. SwiftPMͰBuild / Command ToolΛͭ͘Ζ͏

    View Slide

  8. SE-0303 Extensible Build Tools

    View Slide

  9. SE-0303 Extensible Build Tools
    Implemented in 5.6
    • Swift PM͸ϏϧυதͷΧελϜΞΫγϣϯΛఏڙ͍ͯ͠ͳ͍

    • Ϗϧυ࣌ʹίʔυੜ੒Λߦ͏Package PluginΛ௥Ճ

    • e.g.) SwiftProtobuf, SwiftGen, SwiftSyntax

    • ଞʹ΋কདྷతʹ૿΍͍ͨ͠ϓϥάΠϯ͸͋Δ͕ɺ͜ͷProposalͷର৅֎

    View Slide

  10. SE-0303 Extensible Build Tools
    Client
    MyPackage

    ᵓ Package.swift

    ᵓ Plugins

    │ └ MySourceGenPlugin

    │ └ plugin.swift

    └ Sources

    ᵓ MyExe

    │ │
    fi
    le.dat

    │ └ main.swift

    └ MySourceGenTool

    └ main.swift
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "MyPackage",


    targets: [


    .executable(


    name: "MyExe",


    plugins: [


    .plugin("MySourceGenPlugin")


    ]


    ),


    .plugin(


    name: "MySourceGenPlugin",


    capability: .buildTool(),


    dependencies: ["MySourceGenTool"]


    ),


    .executableTarget(


    name: "MySourceGenTool"


    ),


    ]


    )


    View Slide

  11. SE-0303 Extensible Build Tools
    Plugin
    • Target, executableTarget, testTargetʹpluginsΛ௥Ճ

    • Package resolve / validationͷ͋ͱʹಈ࡞͢Δ

    • ҎԼ΁ͷΞΫηε͕Մೳ

    • plugin͕ద༻͞ΕΔλʔήοτͷ৘ใΛఏڙ͢ΔInput context

    • TargetͷSource directoryͷread-only access

    • Output directory

    View Slide

  12. SE-0303 Extensible Build Tools
    Plugin - Limitation
    • Target, executableTarget, testTargetʹpluginsΛ௥Ճ͠ɺbuild tool pluginΛར༻Ͱ͖ΔΑ͏ʹ͢Δɻ

    • binaryTarget, systemLibrary͸psudo-targetͳͷͰɺ͜ͷproposalͰ͸αϙʔτ͠ͳ͍

    • Package resolve / validationͷ͋ͱʹಈ࡞͢Δ

    • ҎԼ΁ͷΞΫηε͕Մೳ

    • plugin͕ద༻͞ΕΔλʔήοτͷ৘ใΛఏڙ͢ΔInput context

    • TargetͷSource directoryͷread-only access

    • Output directory

    • Package plugin target͸ManifestͰproductΛએݴ͠ͳͯ͘΋ɺಉPackage಺ͷଞͷTargetͰར༻Ͱ͖Δɻ

    View Slide

  13. Examples
    SwiftGen
    • ϦιʔεʹΞΫηε͢ΔιʔείʔυΛੜ੒͢Δύοέʔδ
    • ϏϧυதʹϑΝΠϧΛੜ੒͢ΔͷͰɺprebuildͰߦ͏ඞཁ͕͋Δ

    View Slide

  14. Examples
    SwiftGen - Client
    // swift-tools-version: 999.0


    import PackageDescription


    let package = Package(


    name: "MyPackage",


    dependencies: [


    .package(url: "https://github.com/SwiftGen/SwiftGen", from: "6.4.0")


    ]


    targets: [


    .executable(


    name: "MyLibrary",


    plugins: [


    .plugin(name: "SwiftGenPlugin", package: "SwiftGen")


    ]


    )


    ]


    )


    MyPackage

    ᵓ Package.swift

    ᵓ swiftgen.yml

    └ Sources

    └ MyLibrary

    ᵓ Assets.xcassets

    └ SourceFile.swift

    View Slide

  15. Examples
    SwiftGen - Plugin
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "SwiftGen",


    targets: [


    .plugin(


    name: "SwiftGenPlugin",


    capability: .buildTool(),


    dependencies: ["SwiftGen"]


    ),




    .binaryTarget(


    name: "SwiftGen",


    url: "https://url/to/the/built/swiftgen-executables.zip",


    checksum: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"


    ),


    ]


    )


    SwiftGen

    ᵓ Package.swift

    ᵓ Plugins

    │ └ SwiftGenPlugin

    │ └ plugin.swift

    └ Sources

    └ SwiftGen

    └ ...

    View Slide

  16. Examples
    SwiftGen - Plugin
    import PackagePlugin


    @main struct SwiftGenPlugin: BuildToolPlugin {


    func createBuildCommands(context: TargetBuildContext) throws -> [Command] {


    let swiftGenConfigFile = context.packageDirectory.appending("swiftgen.yml")




    let genSourcesDir = context.pluginWorkDirectory.appending("GeneratedSources")


    return [.prebuildCommand(


    displayName: "Running SwiftGen",


    executable: try context.tool(named: "swiftgen").path,


    arguments: [


    "config", "run",


    "--config", "\(swiftGenConfigFile)"


    ],


    environment: [


    "PROJECT_DIR": "\(context.packageDirectory)",


    "TARGET_NAME": "\(context.targetName)",


    "DERIVED_SOURCES_DIR": "\(genSourcesDir)",


    ],


    outputFilesDirectory: genSourcesDir)]


    }


    }


    SwiftGen

    ᵓ Package.swift

    ᵓ Plugins

    │ └ SwiftGenPlugin

    │ └ plugin.swift

    └ Sources

    └ SwiftGen

    └ ...

    View Slide

  17. Examples
    SwiftProtobuf
    • .protoϑΝΠϧ͔ΒSwiftProtobufΛར༻ͯ͠ιʔεϑΝΠϧΛੜ੒͢Δ

    • ੜ੒͞ΕͨϑΝΠϧΛར༻͢ΔϥϯλΠϜϥΠϒϥϦ΋ఏڙ͢Δ

    • ProtoϑΝΠϧ͸protocίϯύΠϥͰॲཧͯ͠ɺSwiftιʔείʔυΛੜ੒͠ɺ
    ίϯύΠϧ͢Δ

    • protocίϯύΠϥʹgeneratorͷύεΛ౉͢

    View Slide

  18. Examples
    SwiftProtobuf - Client
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "MyPackage",


    dependencies: [


    .package(url: "https://github.com/apple/swift-protobuf", from: "1.15.0")


    ]


    targets: [


    .executable(


    name: "MyExe",


    dependencies: [


    .product(name: "SwiftProtobufRuntimeLib", package: "swift-protobuf")


    ],


    plugins: [


    .plugin(name: "SwiftProtobuf", package: "swift-protobuf")


    ]


    )


    ]


    )


    MyPackage

    ᵓ Package.swift

    └ Sources

    └ MyExe

    ᵓ messages.proto

    └ main.swift

    View Slide

  19. Examples
    SwiftProtobuf - Plugin
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "SwiftProtobuf",


    targets: [


    .plugin(


    name: "SwiftProtobuf",


    capability: .buildTool(),


    dependencies: ["protoc", "protoc-gen-swift"]


    ),



    .binaryTarget(


    name: "protoc",


    url: "https://url/to/the/built/protoc-executables.zip",


    checksum: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"


    ),




    .executableTarget(


    name: "protoc-gen-swift"


    ),


    .libraryTarget(


    name: "SwiftProtobufRuntimeLib"


    ),


    ]


    )


    SwiftProtobuf

    ᵓ Package.swift

    ᵓ Plugins

    │ └ SwiftProtobuf

    │ └ plugin.swift

    └ Sources

    ᵓ SwiftProtobufRuntimeLib

    │ └ ...

    └ protoc-gen-swift

    └ ...

    View Slide

  20. Examples
    SwiftProtobuf - Plugin
    import PackagePlugin


    import Foundation


    @main struct MyPlugin: BuildToolPlugin {


    func createBuildCommands(context: TargetBuildContext) throws -> [Command] {


    let protocTool = try context.tool(named: "protoc")


    let protocGenSwiftTool = try context.tool(named: "protoc-gen-swift")




    var protoSearchPaths = context.dependencies.map { target in


    target.targetDirectory.appending("protos")


    }




    let genSourcesDir = context.pluginWorkDirectory.appending("GeneratedSources")


    let otherFilesDir = context.pluginWorkDirectory.appending("OtherFiles")


    protoSearchPaths.append(protocTool.path.removingLastComponent().appending("system-protos"))




    let moduleMappingsFile = otherFilesDir.appending("module-mappings")


    let outputString = ". . . module mappings file . . ."


    let outputData = outputString.data(using: .utf8)


    FileManager.default.createFile(atPath: moduleMappingsFile.string, contents: outputData)




    let inputFiles = context.inputFiles.filter { $0.path.extension == "proto" }


    return inputFiles.map { inputFile in


    let outputName = inputFile.path.stem + ".swift"


    let outputPath = genSourcesDir.appending(outputName)




    let inputFiles = [inputFile.path]


    let outputFiles = [outputPath]


    var commandArgs = [


    "--plugin=protoc-gen-swift=\(protocGenSwiftTool.path)",


    "--swift_out=\(genSourcesDir)",


    "--swift_opt=ProtoPathModuleMappings=\(moduleMappingsFile)"


    ]


    commandArgs.append(contentsOf: protoSearchPaths.flatMap { ["-I", "\($0)"] })


    commandArgs.append("\(inputFile.path)")


    return .buildCommand(


    displayName: "Generating \(outputName) from \(inputFile.path.stem)",


    executable: protocTool.path,


    arguments: commandArgs,


    inputFiles: inputFiles,


    outputFiles: outputFiles)


    }


    }


    }


    SwiftProtobuf

    ᵓ Package.swift

    ᵓ Plugins

    │ └ SwiftProtobuf

    │ └ plugin.swift

    └ Sources

    ᵓ SwiftProtobufRuntimeLib

    │ └ ...

    └ protoc-gen-swift

    └ ...

    View Slide

  21. Examples
    Sourcery / SwiftSyntax
    • .protoϑΝΠϧ͔ΒSwiftProtobufΛར༻ͯ͠ιʔεϑΝΠϧΛੜ੒͢Δ

    • ੜ੒͞ΕͨϑΝΠϧΛར༻͢ΔϥϯλΠϜϥΠϒϥϦ΋ఏڙ͢Δ

    • ProtoϑΝΠϧ͸protocίϯύΠϥͰॲཧͯ͠ɺSwiftιʔείʔυΛੜ੒͠ɺ
    ίϯύΠϧ͢Δ

    • protocίϯύΠϥʹgeneratorͷύεΛ౉͢

    View Slide

  22. SE-0303 Extensible Build Tools
    Security
    • Network accessෆՄɺϑΝΠϧγεςϜ΁ͷॻ͖ࠐΈʹ੍͍ͭͯݶͷ͋Δ
    sandbox؀ڥͰ࣮ߦ͞ΕΔɻʢಛఆͷσΟϨΫτϦ΁ͷॻ͖ࠐΈ͸ڐՄʣɻ

    • libSwiftPMΛར༻͢ΔIDE΋ಉ༷ɻ

    View Slide

  23. SE-0303 Extensible Build Tools
    Future Directions
    • ΫϥΠΞϯτͱ͸ҟͳΔόʔδϣϯͷdependencyΛར༻Ͱ͖Δػೳ

    • Build graph΁ͷΞΫηε

    • Prebuild actions

    • ଞͷλʔήοτͰఏڙ͞ΕΔϥΠϒϥϦΛར༻ͨ͠ϓϥάΠϯεΫϦϓτ

    • Ϗϧυதʹ৽͍͠λεΫΛੜ੒͢Δػೳ

    • Type-safe option

    • Ϗϧυͱςετͷ׬ྃޙʹ࣮ߦ͢ΔίϚϯυΛఆٛ͢Δػೳ

    • Swift packageϓϥάΠϯεΫϦϓτͷςεταϙʔτ

    • Linter / Formatterͷαϙʔτ

    View Slide

  24. SE-0305 Binary Target Improvements

    View Slide

  25. SE-0305 Binary Target Improvements
    Implemented in 5.6
    • ҰൠతʹCLI͸SwiftͰϏϧυ͞Εͳ͍ͷͰɺࣄલʹϏϧυ͓ͯ͘͠ඞཁ͕͋
    Δɻ

    • Pre buildதʹ࣮ߦ͢ΔπʔϧࣗମΛϏϧυதʹϏϧυ͢Δ͜ͱ͸Ͱ͖ͳ͍

    • binaryTarget͕αϙʔτ͢Δformatʹ `artifact bundle` Λ௥Ճ͢Δ

    • Remote URLͱͯ͠ɺartifact bundle indexϑΝΠϧ΋ࢀরͰ͖ΔΑ͏ʹ͢Δ

    • એݴ͞Εͨvariant͸Package pluginͰར༻Ͱ͖Δ

    View Slide

  26. SE-0305 Binary Target Improvements
    Artifact bundle
    • ෳ਺ͷartifactؚ͕·Εɺ໊લ͸ϢχʔΫ

    • Plugin APIͰartifactͷݕࡧʹ࢖ΘΕΔ

    • artifactʹ͸unique identi
    fi
    erΛ΋ͭvariant

    • ݸʑͷartifactͱvariantʹؔ͢ΔManifest json
    .artifactbundle

    ᵓ info.json



    │ ᵓ

    │ │ ᵓ

    │ │ └ fi
    les>

    │ └

    │ ᵓ

    │ └ fi
    les>



    │ └

    │ ᵓ

    │ └ fi
    les>



    ᴽ └ᴻ

    View Slide

  27. SE-0305 Binary Target Improvements
    Artifact bundle manifest
    • ArtifactͷmetadataΛ؅ཧ͢Δ

    • Version

    • Type: executable (reserved)

    • Variants:

    • Path:

    • Supported triples: 

    ---

    arm64-apple-ios-simulator
    {


    "schemaVersion": " 1.0 ",


    "artifacts": {


    "": {


    "version": " ",


    "type": " executable ",


    "variants": [


    {


    "path": " ",


    "supportedTriples": [


    " ", ...


    ]


    },


    ...


    ]


    }ɺ...


    }


    }


    View Slide

  28. SE-0305 Binary Target Improvements
    Artifact bundleͷॲཧ
    1. ZipϑΝΠϧΛμ΢ϯϩʔυ͢Δ

    2. ղౚ͢Δ

    3. .xcframeworkؚ͕·Ε͍ͯͳ͚Ε͹ɺ.artifactbundleΛ୳͢

    .artifactbundleͱ.xcframework͕྆ํؚ·Ε͍ͯΔͱΤϥʔ
    4. info.jsonΛಡΈࠐΉɻschemaVersion͕ͳ͚Ε͹ऴྃɻ͋Δ৔߹͸artifactΛ
    ొ࿥͢Δɻ

    View Slide

  29. SE-0305 Binary Target Improvements
    Artifact bundle Index
    • ෆཁͳϑΝΠϧͷμ΢ϯϩʔυΛճආ
    ͢ΔͨΊʹɺartifact bundleΛvariant͝
    ͱʹෳ਺ͷόϯυϧʹ෼ׂͰ͖Δɻ

    • *.artifactbundleindex

    • .artifactbundleindexͱಉ͡σΟϨΫτ
    ϦʹݸʑͷzipΛ഑ஔ͢Δ
    {


    "schemaVersion": "1.0",


    "bundles": [


    {


    "fileName": "",


    "checksum": "",


    "supportedTriples": [


    "", ...


    ]


    },


    ...


    ]


    }


    View Slide

  30. SE-0325 Additional Plugin APIs

    View Slide

  31. SE-0325 Additional Plugin APIs
    Accepted: ৄࡉ͸ׂѪ
    • ίʔυੜ੒ʹ੍ݶ͍ͯͨ͠Plugin APIʹॊೈੑΛ࣋ͨͤΔมߋ

    • PluginContextΛಋೖͯ͠ɺPackage, Targetͷ৘ใΛऔΓग़͠΍ͯͦ͘͢͠͏

    • Identi
    fi
    ableʹconform͕͍ͨͬͯ͠Δ͕ɺProtocol͕ExistentialʹͳΔ
    (SE-0309)ͷΛ଴͍ͬͯΔ

    • ԿΛಋೖ͕͍ͨͬͯ͠Δͷ͔͸͍·͍ͪݟ͑ͳ͍ͷͰɺSwiftUIͷResult
    Builderͱಉ͡೏͍͕͢Δ

    View Slide

  32. SE-0332 Command Plugins

    View Slide

  33. SE-0332 Command Plugins
    Accepted with modi
    fi
    cations
    • SE-0303Ͱ͸ϏϧυதʹࣗಈͰ࣮ߦ͞ΕΔΧελϜπʔϧΛʢίʔυੜ੒ʹ޲
    ͚ͯʣ࡞ͬͨɻͦͷͨΊɺPackage directory಺ͷϑΝΠϧΛมߋͰ͖ͳ͍ͳ
    ͲɺϓϥάΠϯͷ࣮ߦʹ͸੍ݶ͕͋Δ

    • ͜͜Ͱ͸ɺϢʔβʔ͕௚઀CLI΍IDEͰΧελϜΞΫγϣϯΛ࣮ߦͰ͖Δ
    Command PluginΛఆٛ͢Δɻ

    • υΩϡϝϯτͷੜ੒ɺιʔείʔυͷformatɺUnit test reportɺbuild artifact
    ͷޙॲཧͳͲɺϏϧυγεςϜ͔Βಠཱͯ͠ϫʔΫϑϩʔΛ֦ுͰ͖Δɻ

    View Slide

  34. Command
    • swift package Ͱ࣮ߦͰ͖Δ

    • intentΛࢦఆ

    • Prede
    fi
    ned: Generate documantion / source code formatting

    • Custom:

    • ࣮ߦʹඞཁͳpermission΋ఆٛͰ͖Δ

    View Slide

  35. Command
    Manifest API
    extension PluginCapability {


    public static func command(


    intent: PluginCommandIntent,


    permissions: [PluginPermission] = []


    ) -> PluginCapability


    }


    public struct PluginCommandIntent {


    /// Invoked by a `generate-documentation` verb to `swift package`.


    public static func documentationGeneration() -> PluginCommandIntent




    /// Invoked by a `format-source-code` verb to `swift package`.


    public static func sourceCodeFormatting() -> PluginCommandIntent




    /// An intent that doesn't fit into any of the other categories, with a custom


    /// verb through which it can be invoked.


    public static func custom(verb: String, description: String) -> PluginCommandIntent


    }


    public struct PluginPermission {


    /// The command plugin wants permission to modify the files under the package


    /// directory. The `reason` string is shown to the user at the time of request


    /// for approval, explaining why the plugin is requesting this access.


    public static func writeToPackageDirectory(reason: String) -> PluginPermission


    }


    View Slide

  36. Command
    Plugin API΁ͷ৽͍͠Entry pointͷఆٛ
    public protocol CommandPlugin: Plugin {


    func performCommand(


    /// Plugin͕࣮ߦ͞ΕΔίϯςΩετɻPackage graph΁ͷΞΫηεɺσΟϨΫτϦͷΩϟογϡͳͲ͕ఏڙ͞ΕΔɻ


    context: PluginContext,




    /// ίϚϯυ͕ద༻͞ΕΔλʔήοτɻࢦఆ͍ͯ͠ͳ͍৔߹͸ɺ͢΂ͯͷλʔήοτʹͳΔɻ


    targets: [Target],




    /// verbͷޙʹଓ͘ϦςϥϧҾ਺


    arguments: [String],


    ) async throws




    /// ίϚϯυϓϥάΠϯΛϗετ͍ͯ͠ΔSwift Package Manager΍IDE΁ͷϓϩΩγɻ


    /// ϓϥάΠϯ͕ಛఆͷ৘ใ΍ΞΫγϣϯΛཁٻͰ͖ΔΑ͏ʹ͢Δɻ


    var packageManager: PackageManager { get }


    }


    View Slide

  37. Command
    Plugin API΁ͷ৽͍͠Entry pointͷఆٛ
    • PluginContext.tool(named:)Λར༻ͯ͠ɺϑΝΠϧγεςϜͷCLIͷϑϧύεΛ
    औಘɻFoundationͷAPIͰexecutableͷऔಘ΋Ͱ͖Δɻ

    • ϑΝΠϧͷread / writeɺJSONͷencode / decodeͳͲ΋࣮ߦՄೳ

    View Slide

  38. Command
    Package Manager service΁ͷΞΫηε
    • packageManagerϓϩύςΟΛ࢖ͬͯɺBuild / Test / Symbol graphͷऔಘ͕Ͱ
    ͖Δ

    View Slide

  39. public struct PackageManager {


    public func build(


    _ subset: BuildSubset,


    parameters: BuildParameters


    ) async throws -> BuildResult




    public enum BuildSubset {


    /// Represents the subset consisting of all products and of either all


    /// targets or (if `includingTests` is false) just non-test targets.


    case all(includingTests: Bool)


    /// Represents the product with the specified name.


    case product(String)


    /// Represents the target with the specified name.


    case target(String)


    }




    public struct BuildParameters {


    public var configuration: BuildConfiguration = .debug


    public var logging: BuildLogVerbosity = .concise


    public var otherCFlags: [String] = []


    public var otherCxxFlags: [String] = []


    public var otherSwiftcFlags: [String] = []


    public var otherLinkerFlags: [String] = []


    /// Future proposals should add more controls over the build.


    }




    public enum BuildConfiguration {


    case debug, release


    }




    public enum BuildLogVerbosity {


    case concise, verbose, debug


    }




    public struct BuildResult {


    public var succeeded: Bool


    public var logText: String


    public var builtArtifacts: [BuiltArtifact]




    /// Represents a single artifact produced during a build.


    public struct BuiltArtifact {


    public var path: Path


    public var kind: Kind




    /// Represents the kind of artifact that was built. The specific file


    /// formats may vary from platform to platform — for example, on macOS


    /// a dynamic library may in fact be built as a framework.


    public enum Kind {


    case executable, dynamicLibrary, staticLibrary


    }


    }


    }


    View Slide

  40. Permission
    • FormatͳͲɺϑΝΠϧγεςϜΛมߋ͢Δ৔߹͸ɺඞཁͳݖݶΛ௥Ճ͢Δɻ

    • ௥ՃͷPermissionͷඞཁੑʹ͍ͭͯɺϢʔβʔʹ௨஌͠ɺঝೝΛಘΔ

    • ঝೝ͕ಘΒΕΕ͹ɺSandboxΛมߋ͢Δ

    View Slide

  41. Invoke Command Plugins
    • swift package do-something


    • swift package --target Foo --target Bar do-something


    • swift package --target Foo --target Bar do-something aParam -aFlag


    • swift package --allow-writing-to-package-directory do-something

    • কདྷతʹSwiftArgumentParserͷΑ͏ͳελΠϧͰએݴͰ͖ΔΑ͏ʹͳΕ͹ɺIDE͕࠷దͳUIΛදࣔ͢ΔՄೳੑ΋͋Δɻ

    View Slide

  42. Discovering command plugins
    • swift package plugin --list --capability=buildTool


    • swift package plugin --list --capability=command --json

    View Slide

  43. Examples
    DocC
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "MyDocCPlugin",


    products: [


    // Declaring the plugin product vends the plugin to clients of the package.


    .plugin(


    name: "MyDocCPlugin",


    targets: ["MyDocCPlugin"]


    ),


    ],


    targets: [


    // This is the target that implements the command plugin.


    .plugin(


    name: "MyDocCPlugin",


    capability: .command(


    intent: .documentationGeneration()


    )


    )


    ]


    )


    View Slide

  44. import PackagePlugin


    import Foundation


    @main


    struct MyDocCPlugin: CommandPlugin {


    func performCommand(


    context: PluginContext,


    targets: [Target],


    arguments: [String]


    ) async throws {


    // We'll be creating commands that invoke `docc`, so start by locating it.


    let doccTool = try context.tool(named: "docc")


    let outputDir = context.pluginWorkDirectory.appending("Outputs")


    for target in targets {


    guard let target = target as? SourceModuleTarget else { continue }


    let doccCatalog = target.sourceFiles.first { $0.path.extension == "docc" }


    let symbolGraphInfo = try await packageManager.getSymbolGraph(for: target,


    options: .init(


    minimumAccessLevel: .public,


    includeSynthesized: false,


    includeSPI: false))


    let doccExec = URL(fileURLWithPath: doccTool.path.string)


    var doccArgs = ["convert"]


    if let doccCatalog = doccCatalog {


    doccArgs += ["\(doccCatalog.path)"]


    }


    doccArgs += [


    "--fallback-display-name", target.name,


    "--fallback-bundle-identifier", target.name,


    "--fallback-bundle-version", "0",


    "--additional-symbol-graph-dir", "\(symbolGraphInfo.directoryPath)",


    "--output-dir", "\(outputDir)",


    ]


    let process = try Process.run(doccExec, arguments: doccArgs)


    process.waitUntilExit()


    // Check whether the subprocess invocation was successful.


    if process.terminationReason == .exit && process.terminationStatus == 0 {


    print("Generated documentation at \(outputDir).")


    }


    else {


    let problem = "\(process.terminationReason):\(process.terminationStatus)"


    Diagnostics.error("docc invocation failed: \(problem)")


    }


    }


    }


    }


    View Slide

  45. Examples
    DocC
    • swift package generate-documentation

    View Slide

  46. Examples
    Formatting Source code
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "MyFormatterPlugin",


    dependencies: [


    .package(url: "https://github.com/apple/swift-format.git", from: "0.50500.0"),


    ],


    targets: [


    .plugin(


    "MyFormatterPlugin",


    capability: .command(


    intent: .sourceCodeFormatting(),


    permissions: [


    .writeToPackageDirectory(reason: "This command reformats source files")


    ]


    ),


    dependencies: [


    .product(name: "swift-format", package: "swift-format"),


    ]


    )


    ]


    )


    View Slide

  47. import PackagePlugin


    import Foundation


    @main


    struct MyFormatterPlugin: CommandPlugin {


    func performCommand(


    context: PluginContext,


    targets: [Target],


    arguments: [String]


    ) async throws {


    // We'll be invoking `swift-format`, so start by locating it.


    let swiftFormatTool = try context.tool(named: "swift-format")


    // By convention, use a configuration file in the package directory.


    let configFile = context.package.directory.appending(".swift-format.json")


    // Iterate over the targets we've been asked to format.


    for target in targets {


    // Skip any type of target that doesn't have source files.


    // Note: We could choose to instead emit a warning or error here.


    guard let target = target as? SourceModuleTarget else { continue }


    // Invoke `swift-format` on the target directory, passing a configuration


    // file from the package directory.


    let swiftFormatExec = URL(fileURLWithPath: swiftFormatTool.path.string)


    let swiftFormatArgs = [


    "--configuration", "\(configFile)",


    "--in-place",


    "--recursive",


    "\(target.directory)"


    ]


    let process = try Process.run(swiftFormatExec, arguments: swiftFormatArgs)


    process.waitUntilExit()


    // Check whether the subprocess invocation was successful.


    if process.terminationReason == .exit && process.terminationStatus == 0 {


    print("Formatted the source code in \(target.directory).")


    }


    else {


    let problem = "\(process.terminationReason):\(process.terminationStatus)"


    Diagnostics.error("swift-format invocation failed: \(problem)")


    }


    }


    }


    }


    View Slide

  48. Examples
    Formatting Source code
    • swift package format-source-code

    View Slide

  49. Examples
    Building Deployment Artifacts
    // swift-tools-version: 5.6


    import PackageDescription


    let package = Package(


    name: "MyExecutable",


    products: [


    .executable(name: "MyExec", targets: ["MyExec"])


    ],


    targets: [


    // This is the hypothetical executable we want to distribute.


    .executableTarget(


    "MyExec"


    ),


    // This is the plugin that defines a custom command to distribute the executable.


    .plugin(


    "MyDistributionArchiveCreator",


    capability: .command(


    intent: .custom(


    verb: "create-distribution-archive",


    description: "Creates a .zip containing release builds of products"


    )


    ),


    )


    ]


    )


    View Slide

  50. import PackagePlugin


    import Foundation


    @main


    struct MyDistributionArchiveCreator: CommandPlugin {


    func performCommand(


    context: PluginContext,


    targets: [Target],


    arguments: [String]


    ) async throws {


    // Check that we were given the name of a product as the first argument


    // and the name of an archive as the second.


    guard arguments.count == 2 else {


    throw Error("Expected two arguments: product name and archive name")


    }


    let productName = arguments[0]


    let archiveName = arguments[1]


    // Ask the plugin host (SwiftPM or an IDE) to build our product.


    let result = try await packageManager.build(


    .product(productName),


    parameters: .init(configuration: .release, logging: .concise)


    )




    // Check the result. Ideally this would report more details.


    guard result.succeeded else { throw Error("couldn't build product") }


    // Get the list of built executables from the build result.


    let builtExecutables = result.builtArtifacts.filter{ $0.kind == .executable }


    // Decide on the output path for the archive.


    let outputPath = context.pluginWorkDirectory.appending("\(archiveName).zip")


    // Use Foundation to run `zip`. The exact details of using the Foundation


    // API aren't relevant; the point is that the built artifacts can be used


    // by the script.


    let zipTool = try context.tool(named: "zip")


    let zipArgs = ["-j", outputPath.string] + builtExecutables.map{ $0.path.string }


    let zipToolURL = URL(fileURLWithPath: zipTool.path.string)


    let process = try Process.run(zipToolURL, arguments: zipArgs)


    process.waitUntilExit()


    // Check whether the subprocess invocation was successful.


    if process.terminationReason == .exit && process.terminationStatus == 0 {


    print("Created distribution archive at \(outputPath).")


    }


    else {


    let problem = "\(process.terminationReason):\(process.terminationStatus)"


    Diagnostics.error("zip invocation failed: \(problem)")


    }


    }


    }


    View Slide

  51. Examples
    Building Deployment Artifacts
    • swift package create-distribution-archive MyExec MyDistributionArchive-1.0

    View Slide

  52. Package Plugins
    Summary
    • Swift PMͰར༻Ͱ͖ΔϓϥάΠϯ

    • Build Tool Plugin (Swift 5.6): Ϗϧυ࣌ʹࣗಈͰ࣮ߦ͞ΕΔɻ

    • SwiftGen / SwiftProtobuf / SwiftSyntaxͷΑ͏ͳίʔυੜ੒޲͚

    • Command Plugin (Accepted) : CLI / IDEͰར༻Ͱ͖ΔΧελϜΞΫγϣϯ

    • υΩϡϝϯτͷੜ੒΍ɺformat͕SwiftPM͚ͩͰ࣮ߦͰ͖Δ༧ఆ

    • xcodebuild΍xcrunΛcommand pluginʹஔ͖׵͑Δະདྷ΋͋Δͷ͔…ʁ

    • কདྷతʹ͍ΖΜͳϓϥάΠϯ͕௥Ճ͞ΕΔ༧ఆ

    • Author͸͢΂ͯAppleͷAnders Bertelurd

    • Կ͔࣍ͷେ͖ͳൃදͷ෬ઢ͔ɺ୯७ʹͦ͏͍͏໨ඪઃఆͳͷ͔

    View Slide

  53. SE-0301 Editing Commands

    View Slide

  54. SE-0301 Editing Commands
    Accepted
    • Product / Target / DependencyΛ௥Ճͨ͠ΒPackage.swiftʹ൓ө͠ɺखಈͰ
    ฤू͠ͳͯ͘ࡁΉΑ͏ʹ͢Δ

    • ϞδϡʔϧΛ௥Ճͨ͠ΒɺࣗಈͰtargetʹ൓ө͢Δ

    • importΛॻ͍ͨΒɺෆ଍͍ͯ͠ΔdependencyΛ௥Ճͨ͠ΓɺPackage
    collection͔Β௥Ճ͢Δ

    c.f.) npm / cargo / elm

    View Slide

  55. ࢒೦ͳ͕Β಴࠳ͨ͠
    • https://github.com/owenv/swift-package-editor
    SE-0301 Editing Commands

    View Slide

  56. SE-0339 Module Aliasing For
    Disambiguation

    View Slide

  57. SE-0339 Module Aliasing For Disambiguation
    Active Review … 2022/1/31
    • ݱࡏϞδϡʔϧʹ൚༻తͳ໊લ (e.g. Core, Utils)Λ͚ͭΔͱিಥ͢ΔͷͰ
    rename͢Δඞཁ͕͋Δ

    Vapor/Logging:

    error: multiple products named 'Logging' in: Console, swift-log

    error: multiple targets named 'Logging' in: Console, swift-log

    • ̎ͭͷόʔδϣϯͷҟͳΔύοέʔδ͕ଘࡏ͢Δ৔߹ɺยํΛἧ͑Α͏ͱ͢
    ΔͱյΕΔՄೳੑ͕͋ΔͷͰɺڞଘ͍ͤͨ͞

    View Slide

  58. Proposed Solution
    • Module AliasΛಋೖͯ͠ɺϞδϡʔϧʹผ໊Λ͚ͭΒΕΔΑ͏ʹ͢Δ

    targets: [


    .executableTarget(


    name: "App",


    dependencies: [


    .product(name: "Game", *moduleAliases*: ["Utils": "GameUtils"], package:
    "swift-game"),


    .product(name: "Utils", package: "swift-draw"),


    ])


    ]


    [App]
    import GameUtils
    import Utils
    SE-0339 Module Aliasing For Disambiguation

    View Slide

  59. Detailed Design
    • Frontend: -module-alias [name] = [new_name]

    • module͸[new_name].swiftmoduleͱͯ͠ੜ੒͞ΕΔ

    • Symbol mangling΍serializationʹӨڹ͢Δ
    SE-0339 Module Aliasing For Disambiguation

    View Slide

  60. Requirements / Limitations
    • ObjC / C / C++ / AsmͰ͸γϯϘϧͷিಥͷՄೳੑ͕͋ΔͷͰར༻Ͱ͖ͳ͍ɻ

    • @objc(name) ͸ਪ঑͞Εͳ͍

    • Mangling΍SerializationʹӨڹ͢ΔͨΊɺιʔείʔυͷϏϧυʹݶఆɻDistributed BinaryͰ͸ར༻
    Ͱ͖ͳ͍ɻ -> Binaryʹ൚༻Ϟδϡʔϧ໊͕ೖ͍ͬͯΔͱ٧Έɻ

    • NSClassFromString(…) ͰͷString΁ͷม׵͸ࣦഊ͢Δ

    • Resources: Asset CatalogͱLocalized StringsͷΈར༻Մೳ

    • Retroactive Conformance͸ආ͚Δɻ


    SE-0339 Module Aliasing For Disambiguation

    View Slide

  61. • Retroactive conformance: extending a class, struct, or enum from another module to conform to a
    protocol from another module. The protocol and the conforming type may be from the same module
    or from di
    ff
    erent modules; the important note is that neither is in the same module as the extension.


    // Framework A


    extension SomeStruct: CustomStringConvertible {


    var description: String {


    return "SomeStruct, via A"


    }


    }


    // Framework B


    extension SomeStruct: CustomStringConvertible {


    var description: String {


    return "SomeStruct, via B"


    }


    }


    // main.swift


    import A


    import B


    print(SomeStruct()) // ???
    Appendix: Retroactive Conformance

    View Slide

  62. git clone΍ΊͯSource ArchiveͰ؅ཧ͢Δ

    View Slide

  63. SE-0292 Package Registry Service

    View Slide

  64. SE-0292 Package Registry Service
    Accepted
    • DependencyͷdownloadʹιʔεϦϙδτϦͷURLΛࢦఆͯ͠gitΛ࢖͏ͷ͸ద͍ͯ͠ͳ͍

    • ࠶ݱੑ: όʔδϣϯλά͸͍ͭͰ΋ผίϛοτʹׂΓ౰ͯՄೳͳͷͰɺϏϧυ͞ΕͨλΠϛϯάʹΑͬͯҟͳΔ
    Ϗϧυ੒Ռ෺ʹͳΔՄೳੑ͕͋Δ

    • Մ༻ੑ: ϦϙδτϦ͸Ҡಈͨ͠Γ࡟আ͞ΕͨΓ͢Δ

    • ޮ཰: ύοέʔδͷ͢΂ͯͷόʔδϣϯ͕μ΢ϯϩʔυ͞Εͯ͠·͏

    • ଎౓: history͕େ͖͍ͱ஗͘ͳΔɻΫϩʔϯ͸αʔόʔʹ΋ΫϥΠΞϯτʹ΋ίετɻ

    • Package registry: RubyGems / PyPl / npm / crates.io

    • SwiftPMͷpackage registry͸gitΑΓ΋ߴ଎Ͱ৴པੑͷߴ͍ґଘؔ܎ղܾ͕๬ΊΔɻ

    • Package search, Security audit, local o
    ff
    l
    ine cache

    View Slide

  65. Proposed solution
    • URLΛࢦఆ͢ΔͱɺϓϩδΣΫτͷPackage registryΛߏ੒Ͱ͖Δ

    • Swift PM͕Package.swiftʹهࡌ͞Εͨ֎෦ͷґଘؔ܎Λղܾ͢Δ

    • ֎෦ͷґଘؔ܎͸ scope.package-name ͷܗࣜͰهࡌ

    View Slide

  66. Package registry service
    Method Path Description
    GET /{scope}/{name} List package releases
    GET /{scope}/{name}/{version} Fetch metadata for a package release
    GET /{scope}/{name}/{version}/Package.swift{?swift-version} Fetch manifest for a package release
    GET /{scope}/{name}/{version}.zip Download source archive for a package release
    GET /identi
    fi
    ers{?url} Lookup package identi
    fi
    ers registered for a URL

    View Slide

  67. Changes to Swift Package Manager
    Package Identity
    • ݱࡏpackage ID͸URLͷlast path components

    • scope.package-name ʹมߋ͢Δ

    • scope͸namespaceͰ࠷େ39จࣈ

    • package-name͸scope಺ͷϢχʔΫͳ໊લͰ࠷େ100จࣈ

    View Slide

  68. Changes to Swift Package Manager
    Dependency graph resolution
    • PackageContainerΛpackage
    resolutionͷtop-level unitͱ͢Δ

    • RegistryPackageContainer͕HTTPϦ
    ΫΤετͱಉ౳ͷૢ࡞Λߦ͏
    public protocol PackageContainer {


    /// The identifier for the package.


    var package: PackageReference { get }


    func isToolsVersionCompatible(at version: Version) -> Bool


    func toolsVersion(for version: Version) throws -> ToolsVersion


    /// Get the list of versions which are available for the package.


    ///


    /// The list will be returned in sorted order, with the latest version *first*.


    /// All versions will not be requested at once. Resolver will request the next one only


    /// if the previous one did not satisfy all constraints.


    func toolsVersionsAppropriateVersionsDescending() throws -> [Version]


    /// Get the list of versions in the repository sorted in the ascending order, that is the earliest


    /// version appears first.


    func versionsAscending() throws -> [Version]


    func versionsDescending() throws -> [Version]


    // FIXME: We should perhaps define some particularly useful error codes


    // here, so the resolver can handle errors more meaningfully.


    //


    /// Fetch the declared dependencies for a particular version.


    ///


    /// This property is expected to be efficient to access, and cached by the


    /// client if necessary.


    ///


    /// - Precondition: `versions.contains(version)`


    /// - Throws: If the version could not be resolved; this will abort


    /// dependency resolution completely.


    func getDependencies(at version: Version, productFilter: ProductFilter) throws -> [PackageContainerConstraint]


    /// Fetch the declared dependencies for a particular revision.


    ///


    /// This property is expected to be efficient to access, and cached by the


    /// client if necessary.


    ///


    /// - Throws: If the revision could not be resolved; this will abort


    /// dependency resolution completely.


    func getDependencies(at revision: String, productFilter: ProductFilter) throws -> [PackageContainerConstraint]


    /// Fetch the dependencies of an unversioned package container.


    ///


    /// NOTE: This method should not be called on a versioned container.


    func getUnversionedDependencies(productFilter: ProductFilter) throws -> [PackageContainerConstraint]


    /// Get the updated identifier at a bound version.


    ///


    /// This can be used by the containers to fill in the missing information that is obtained


    /// after the container is available. The updated identifier is returned in result of the


    /// dependency resolution.


    func loadPackageReference(at boundVersion: BoundVersion) throws -> PackageReference


    }


    View Slide

  69. Changes to Swift Package Manager
    Tasks performed by Swift Package Manager during dependency resolution
    Task Git operation Registry request
    Fetch the contents of a package git clone && git checkout GET /{scope}/{name}/{version}.zip
    List the available tags for a
    package
    git tag GET /{scope}/{name}
    Fetch a package manifest git clone GET /{scope}/{name}/{version}/Package.swift

    View Slide

  70. Appendix: HTTPClient in SwiftPM
    • Apple/swift-package-managerͷSources/Basics/HTTPClient

    • ඪ४తͳHTTPClientͷ࣮૷ͱͯ͠Α͍͔΋

    • ͦͷଞSwiftPM΍llbuildͷcommon infrastructure ͱͯ͠ apple/swift-tools-support-core ͱ͍͏ͷ΋͋Δ

    • https://github.com/apple/swift-tools-support-core

    View Slide

  71. Changes to Package.resolved
    • Swift Package Registry΁ͷϦϦʔε͸ZipϑΝΠϧͱͯ͠ΞʔΧΠϒ͞ΕΔɻ

    • ֎෦ύοέʔδͷґଘؔ܎͕RegistryΛհͯ͠μ΢ϯϩʔυ͞ΕΔͱɺPackage.resolvedͷchecksum
    ͱɺswift package compute-checksumίϚϯυͷchecksumͱൺֱ͢ΔɻPackage.resolvedʹͳ͚Ε͹
    ௥هɻ
    $ swift package compute-checksum LinkedList-1.2.0.zip

    1feec3d8d144814e99e694cd1d785928878d8d6892c4e59d12569e179252c535

    {


    "object": {


    "pins": [


    {


    "package": "mona.LinkedList",


    "state": {


    "checksum": "ed008d5af44c1d0ea0e3668033cae9b695235f18b1a99240b7cf0f3d9559a30d",


    "version": "1.2.0"


    }


    }


    ]


    },


    "version": 1


    }


    View Slide

  72. Changes to Package.resolved
    • Checksum͕ҟͳ͍ͬͯΕ͹ɺμ΢ϯϩʔυΛڋ൱͢Δɻ
    $ swift build

    error: checksum of downloaded source archive of dependency
    'mona.LinkedList' (c2b934fe66e55747d912f1cfd03150883c4f037370c40ca2ad4203805db79457) does not
    match checksum speci
    fi
    ed by the manifest
    (ed008d5af44c1d0ea0e3668033cae9b695235f18b1a99240b7cf0f3d9559a30d)

    View Slide

  73. Archive-source subcommand
    • ଞͷPackage ManagerͷࣄྫΛݟΔͱɺChecksumͷෆҰக͸ɺِ଄΍ഁଛΑΓ΋ɺArchiveͷ࡞੒΍ɺ
    ChecksumͷෆҰக͕ݪҼͷ͜ͱͷํ͕ଟ͍

    • swift package archive-source ίϚϯυ͸ඪ४ͷsource archiveΛ࡞੒͢Δํ๏Λఏڙ͢Δ
    SYNOPSIS

    swift package archive-source [--output=<
    fi
    le>]

    OPTIONS

    -o <
    fi
    le>, --output=<
    fi
    le>

    Write the archive to <
    fi
    le>.

    If unspeci
    fi
    ed, the package is written to `\(PackageName).zip`.

    View Slide

  74. Archive-source subcommand
    • ίϚϯυΛrootͰ࣮ߦ͢ΔͱɺݱࡏͷWorking treeͷsource archiveΛ࡞੒͢Δ
    $ tree -a -L 1

    LinkedList

    ᵓ── .git

    ᵓ── Package.swift

    ᵓ── README.md

    ᵓ── Sources

    └── Tests

    $ head -n 5 Package.swift

    // swift-tools-version:5.3

    import PackageDescription

    let package = Package(

    name: "LinkedList",

    $ swift package archive-source

    Created LinkedList.zip

    View Slide

  75. Archive-source subcommand
    • ίϚϯυΛrootͰ࣮ߦ͢ΔͱɺݱࡏͷWorking treeͷsource archiveΛ࡞੒͢
    Δ
    $ tree -a -L 1

    LinkedList

    ᵓ── .git

    ᵓ── Package.swift

    ᵓ── README.md

    ᵓ── Sources

    └── Tests

    $ head -n 5 Package.swift

    // swift-tools-version:5.3

    import PackageDescription

    let package = Package(

    name: "LinkedList",

    $ swift package archive-source

    Created LinkedList.zip

    View Slide

  76. Archive-source subcommand
    • σϑΥϧτͰ͸ɺ.zip ʹͳΔɻ- - outputͰมߋՄೳ

    • Archive-source ͸git-archive ʹzip formatΛࢦఆ͠ɺdefault compression levelΛࢦఆͨ͠΋ͷͱಉ౳ɻ

    Note: export-ignoreଐੑͷϑΝΠϧ͸ແࢹ͞ΕΔɻ.git΍.buildͷΑ͏ͳӅ͠ϑΝΠϧ / σΟϨΫτϦ΋ؚ
    Ήɻ
    $ git checkout 1.2.0

    $ swift package archive-source --output="LinkedList-1.2.0.zip"

    # Created LinkedList-1.2.0.zip
    $ git archive --format zip \

    --pre
    fi
    x LinkedList-1.2.0

    --output LinkedList-1.2.0.zip \

    1.2.0

    View Slide

  77. Registry configuration subcommands
    swift package-registry
    • registryͷ؅ཧΛߦ͏αϒίϚϯυ

    • Private dependencies: Public Registry͔Βfetch͞Εͨpackageʹprivate packageΛ૊ΈࠐΉ͜ͱ͕Ͱ͖Δ

    • Geographic colocation: ωοτϫʔΫ͕ශऑͳ৔߹ʹɺϛϥʔΛϗετ͢Δ

    • Policy enforcement: ΧελϜϨδετϦΛհͯ͠ঝೝ͞ΕͨύοέʔδͷΈΛར༻ͤ͞Δ

    • Auditing: ΧελϜϨδετϦ͸ɺϥϯΩϯά΍ϥΠηϯεྉͷ੥ٻΛ໨తͱͯ͠ɺύοέʔδ΁ͷΞΫηεͷޮՌଌఆΛ͢
    Δ৔߹͕͋Δ
    SYNOPSIS
    swift package-registry set [options]
    OPTIONS:
    --global Apply settings to all projects for this user
    --scope Associate the registry with a given scope
    --login Specify a user name for the remote machine
    --password Supply a password for the remote machine

    View Slide

  78. Package Registry Service
    Publish Point

    View Slide

  79. Package Registry
    Summary
    • GitΛ࢖Θͣʹɺ੒Ռ෺ϕʔεͷμ΢ϯϩʔυʹͳΔͷͰɺґଘղܾ͕ૣ͘ͳ
    ΔՄೳੑ͕ߴ͍

    • Author͸Mattt͞Μ͸͡ΊશһGitHubͳͷͰɺ͜ͷProposal΁ͷϞνϕʔγϣ
    ϯ͸ߴͦ͏

    • Package Pluginͷ࿩ͱͷॏෳͰɺଟগมߋ͕͋Δ͔΋͠Εͳ͍

    View Slide