Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Latest updates

Slide 3

Slide 3 text

Swift Playground 4

Slide 4

Slide 4 text

VS Code for Swift

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Package Plugins

Slide 7

Slide 7 text

SwiftPMͰBuild / Command ToolΛͭ͘Ζ͏

Slide 8

Slide 8 text

SE-0303 Extensible Build Tools

Slide 9

Slide 9 text

SE-0303 Extensible Build Tools Implemented in 5.6 • Swift PM͸ϏϧυதͷΧελϜΞΫγϣϯΛఏڙ͍ͯ͠ͳ͍ • Ϗϧυ࣌ʹίʔυੜ੒Λߦ͏Package PluginΛ௥Ճ • e.g.) SwiftProtobuf, SwiftGen, SwiftSyntax • ଞʹ΋কདྷతʹ૿΍͍ͨ͠ϓϥάΠϯ͸͋Δ͕ɺ͜ͷProposalͷର৅֎

Slide 10

Slide 10 text

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" ), ] )

Slide 11

Slide 11 text

SE-0303 Extensible Build Tools Plugin • Target, executableTarget, testTargetʹpluginsΛ௥Ճ • Package resolve / validationͷ͋ͱʹಈ࡞͢Δ • ҎԼ΁ͷΞΫηε͕Մೳ • plugin͕ద༻͞ΕΔλʔήοτͷ৘ใΛఏڙ͢ΔInput context • TargetͷSource directoryͷread-only access • Output directory

Slide 12

Slide 12 text

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Ͱར༻Ͱ͖Δɻ

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Examples SwiftProtobuf • .protoϑΝΠϧ͔ΒSwiftProtobufΛར༻ͯ͠ιʔεϑΝΠϧΛੜ੒͢Δ • ੜ੒͞ΕͨϑΝΠϧΛར༻͢ΔϥϯλΠϜϥΠϒϥϦ΋ఏڙ͢Δ • ProtoϑΝΠϧ͸protocίϯύΠϥͰॲཧͯ͠ɺSwiftιʔείʔυΛੜ੒͠ɺ ίϯύΠϧ͢Δ • protocίϯύΠϥʹgeneratorͷύεΛ౉͢

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Examples Sourcery / SwiftSyntax • .protoϑΝΠϧ͔ΒSwiftProtobufΛར༻ͯ͠ιʔεϑΝΠϧΛੜ੒͢Δ • ੜ੒͞ΕͨϑΝΠϧΛར༻͢ΔϥϯλΠϜϥΠϒϥϦ΋ఏڙ͢Δ • ProtoϑΝΠϧ͸protocίϯύΠϥͰॲཧͯ͠ɺSwiftιʔείʔυΛੜ੒͠ɺ ίϯύΠϧ͢Δ • protocίϯύΠϥʹgeneratorͷύεΛ౉͢

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

SE-0303 Extensible Build Tools Future Directions • ΫϥΠΞϯτͱ͸ҟͳΔόʔδϣϯͷdependencyΛར༻Ͱ͖Δػೳ • Build graph΁ͷΞΫηε • Prebuild actions • ଞͷλʔήοτͰఏڙ͞ΕΔϥΠϒϥϦΛར༻ͨ͠ϓϥάΠϯεΫϦϓτ • Ϗϧυதʹ৽͍͠λεΫΛੜ੒͢Δػೳ • Type-safe option • Ϗϧυͱςετͷ׬ྃޙʹ࣮ߦ͢ΔίϚϯυΛఆٛ͢Δػೳ • Swift packageϓϥάΠϯεΫϦϓτͷςεταϙʔτ • Linter / Formatterͷαϙʔτ

Slide 24

Slide 24 text

SE-0305 Binary Target Improvements

Slide 25

Slide 25 text

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Ͱར༻Ͱ͖Δ

Slide 26

Slide 26 text

SE-0305 Binary Target Improvements Artifact bundle • ෳ਺ͷartifactؚ͕·Εɺ໊લ͸ϢχʔΫ • Plugin APIͰartifactͷݕࡧʹ࢖ΘΕΔ • artifactʹ͸unique identi fi erΛ΋ͭvariant • ݸʑͷartifactͱvariantʹؔ͢ΔManifest json .artifactbundle ᵓ info.json ᵓ │ ᵓ │ │ ᵓ │ │ └ │ └ │ ᵓ │ └ ᵓ │ └ │ ᵓ │ └ │ ᴽ └ᴻ

Slide 27

Slide 27 text

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": [ " ", ... ] }, ... ] }ɺ... } }

Slide 28

Slide 28 text

SE-0305 Binary Target Improvements Artifact bundleͷॲཧ 1. ZipϑΝΠϧΛμ΢ϯϩʔυ͢Δ 2. ղౚ͢Δ 3. .xcframeworkؚ͕·Ε͍ͯͳ͚Ε͹ɺ.artifactbundleΛ୳͢
 .artifactbundleͱ.xcframework͕྆ํؚ·Ε͍ͯΔͱΤϥʔ 4. info.jsonΛಡΈࠐΉɻschemaVersion͕ͳ͚Ε͹ऴྃɻ͋Δ৔߹͸artifactΛ ొ࿥͢Δɻ

Slide 29

Slide 29 text

SE-0305 Binary Target Improvements Artifact bundle Index • ෆཁͳϑΝΠϧͷμ΢ϯϩʔυΛճආ ͢ΔͨΊʹɺartifact bundleΛvariant͝ ͱʹෳ਺ͷόϯυϧʹ෼ׂͰ͖Δɻ • *.artifactbundleindex • .artifactbundleindexͱಉ͡σΟϨΫτ ϦʹݸʑͷzipΛ഑ஔ͢Δ { "schemaVersion": "1.0", "bundles": [ { "fileName": "", "checksum": "", "supportedTriples": [ "", ... ] }, ... ] }

Slide 30

Slide 30 text

SE-0325 Additional Plugin APIs

Slide 31

Slide 31 text

SE-0325 Additional Plugin APIs Accepted: ৄࡉ͸ׂѪ • ίʔυੜ੒ʹ੍ݶ͍ͯͨ͠Plugin APIʹॊೈੑΛ࣋ͨͤΔมߋ • PluginContextΛಋೖͯ͠ɺPackage, Targetͷ৘ใΛऔΓग़͠΍ͯͦ͘͢͠͏ • Identi fi ableʹconform͕͍ͨͬͯ͠Δ͕ɺProtocol͕ExistentialʹͳΔ (SE-0309)ͷΛ଴͍ͬͯΔ • ԿΛಋೖ͕͍ͨͬͯ͠Δͷ͔͸͍·͍ͪݟ͑ͳ͍ͷͰɺSwiftUIͷResult Builderͱಉ͡೏͍͕͢Δ

Slide 32

Slide 32 text

SE-0332 Command Plugins

Slide 33

Slide 33 text

SE-0332 Command Plugins Accepted with modi fi cations • SE-0303Ͱ͸ϏϧυதʹࣗಈͰ࣮ߦ͞ΕΔΧελϜπʔϧΛʢίʔυੜ੒ʹ޲ ͚ͯʣ࡞ͬͨɻͦͷͨΊɺPackage directory಺ͷϑΝΠϧΛมߋͰ͖ͳ͍ͳ ͲɺϓϥάΠϯͷ࣮ߦʹ͸੍ݶ͕͋Δ • ͜͜Ͱ͸ɺϢʔβʔ͕௚઀CLI΍IDEͰΧελϜΞΫγϣϯΛ࣮ߦͰ͖Δ Command PluginΛఆٛ͢Δɻ • υΩϡϝϯτͷੜ੒ɺιʔείʔυͷformatɺUnit test reportɺbuild artifact ͷޙॲཧͳͲɺϏϧυγεςϜ͔Βಠཱͯ͠ϫʔΫϑϩʔΛ֦ுͰ͖Δɻ

Slide 34

Slide 34 text

Command • swift package Ͱ࣮ߦͰ͖Δ • intentΛࢦఆ • Prede fi ned: Generate documantion / source code formatting • Custom: • ࣮ߦʹඞཁͳpermission΋ఆٛͰ͖Δ

Slide 35

Slide 35 text

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 }

Slide 36

Slide 36 text

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 } }

Slide 37

Slide 37 text

Command Plugin API΁ͷ৽͍͠Entry pointͷఆٛ • PluginContext.tool(named:)Λར༻ͯ͠ɺϑΝΠϧγεςϜͷCLIͷϑϧύεΛ औಘɻFoundationͷAPIͰexecutableͷऔಘ΋Ͱ͖Δɻ • ϑΝΠϧͷread / writeɺJSONͷencode / decodeͳͲ΋࣮ߦՄೳ

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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 } } }

Slide 40

Slide 40 text

Permission • FormatͳͲɺϑΝΠϧγεςϜΛมߋ͢Δ৔߹͸ɺඞཁͳݖݶΛ௥Ճ͢Δɻ • ௥ՃͷPermissionͷඞཁੑʹ͍ͭͯɺϢʔβʔʹ௨஌͠ɺঝೝΛಘΔ • ঝೝ͕ಘΒΕΕ͹ɺSandboxΛมߋ͢Δ

Slide 41

Slide 41 text

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Λදࣔ͢ΔՄೳੑ΋͋Δɻ

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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() ) ) ] )

Slide 44

Slide 44 text

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)") } } } }

Slide 45

Slide 45 text

Examples DocC • swift package generate-documentation

Slide 46

Slide 46 text

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"), ] ) ] )

Slide 47

Slide 47 text

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)") } } } }

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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" ) ), ) ] )

Slide 50

Slide 50 text

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)") } } }

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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 • Կ͔࣍ͷେ͖ͳൃදͷ෬ઢ͔ɺ୯७ʹͦ͏͍͏໨ඪઃఆͳͷ͔

Slide 53

Slide 53 text

SE-0301 Editing Commands

Slide 54

Slide 54 text

SE-0301 Editing Commands Accepted • Product / Target / DependencyΛ௥Ճͨ͠ΒPackage.swiftʹ൓ө͠ɺखಈͰ ฤू͠ͳͯ͘ࡁΉΑ͏ʹ͢Δ • ϞδϡʔϧΛ௥Ճͨ͠ΒɺࣗಈͰtargetʹ൓ө͢Δ • importΛॻ͍ͨΒɺෆ଍͍ͯ͠ΔdependencyΛ௥Ճͨ͠ΓɺPackage collection͔Β௥Ճ͢Δ
 c.f.) npm / cargo / elm

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

SE-0339 Module Aliasing For Disambiguation

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Detailed Design • Frontend: -module-alias [name] = [new_name] • module͸[new_name].swiftmoduleͱͯ͠ੜ੒͞ΕΔ • Symbol mangling΍serializationʹӨڹ͢Δ SE-0339 Module Aliasing For Disambiguation

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

• 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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

SE-0292 Package Registry Service

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Proposed solution • URLΛࢦఆ͢ΔͱɺϓϩδΣΫτͷPackage registryΛߏ੒Ͱ͖Δ • Swift PM͕Package.swiftʹهࡌ͞Εͨ֎෦ͷґଘؔ܎Λղܾ͢Δ • ֎෦ͷґଘؔ܎͸ scope.package-name ͷܗࣜͰهࡌ

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Changes to Swift Package Manager Package Identity • ݱࡏpackage ID͸URLͷlast path components • scope.package-name ʹมߋ͢Δ • scope͸namespaceͰ࠷େ39จࣈ • package-name͸scope಺ͷϢχʔΫͳ໊લͰ࠷େ100จࣈ

Slide 68

Slide 68 text

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 }

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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 }

Slide 72

Slide 72 text

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)

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Package Registry Service Publish Point

Slide 79

Slide 79 text

Package Registry Summary • GitΛ࢖Θͣʹɺ੒Ռ෺ϕʔεͷμ΢ϯϩʔυʹͳΔͷͰɺґଘղܾ͕ૣ͘ͳ ΔՄೳੑ͕ߴ͍ • Author͸Mattt͞Μ͸͡ΊશһGitHubͳͷͰɺ͜ͷProposal΁ͷϞνϕʔγϣ ϯ͸ߴͦ͏ • Package Pluginͷ࿩ͱͷॏෳͰɺଟগมߋ͕͋Δ͔΋͠Εͳ͍