Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

わいわい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. SE-0303 Extensible Build Tools Implemented in 5.6 • Swift PM͸ϏϧυதͷΧελϜΞΫγϣϯΛఏڙ͍ͯ͠ͳ͍

    • Ϗϧυ࣌ʹίʔυੜ੒Λߦ͏Package PluginΛ௥Ճ • e.g.) SwiftProtobuf, SwiftGen, SwiftSyntax • ଞʹ΋কདྷతʹ૿΍͍ͨ͠ϓϥάΠϯ͸͋Δ͕ɺ͜ͷProposalͷର৅֎
  2. 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" ), ] )
  3. SE-0303 Extensible Build Tools Plugin • Target, executableTarget, testTargetʹpluginsΛ௥Ճ •

    Package resolve / validationͷ͋ͱʹಈ࡞͢Δ • ҎԼ΁ͷΞΫηε͕Մೳ • plugin͕ద༻͞ΕΔλʔήοτͷ৘ใΛఏڙ͢ΔInput context • TargetͷSource directoryͷread-only access • Output directory
  4. 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Ͱར༻Ͱ͖Δɻ
  5. 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
  6. 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 └ ...
  7. 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 └ ...
  8. 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
  9. 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 └ ...
  10. 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 └ ...
  11. SE-0303 Extensible Build Tools Future Directions • ΫϥΠΞϯτͱ͸ҟͳΔόʔδϣϯͷdependencyΛར༻Ͱ͖Δػೳ • Build

    graph΁ͷΞΫηε • Prebuild actions • ଞͷλʔήοτͰఏڙ͞ΕΔϥΠϒϥϦΛར༻ͨ͠ϓϥάΠϯεΫϦϓτ • Ϗϧυதʹ৽͍͠λεΫΛੜ੒͢Δػೳ • Type-safe option • Ϗϧυͱςετͷ׬ྃޙʹ࣮ߦ͢ΔίϚϯυΛఆٛ͢Δػೳ • Swift packageϓϥάΠϯεΫϦϓτͷςεταϙʔτ • Linter / Formatterͷαϙʔτ
  12. 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Ͱར༻Ͱ͖Δ
  13. SE-0305 Binary Target Improvements Artifact bundle • ෳ਺ͷartifactؚ͕·Εɺ໊લ͸ϢχʔΫ • Plugin

    APIͰartifactͷݕࡧʹ࢖ΘΕΔ • artifactʹ͸unique identi fi erΛ΋ͭvariant • ݸʑͷartifactͱvariantʹؔ͢ΔManifest json <name>.artifactbundle ᵓ info.json ᵓ <artifact> │ ᵓ <variant> │ │ ᵓ <executable> │ │ └ <other fi les> │ └ <variant> │ ᵓ <executable> │ └ <other fi les> ᵓ <artifact> │ └ <variant> │ ᵓ <executable> │ └ <other fi les> │ <artifact> ᴽ └ᴻ
  14. SE-0305 Binary Target Improvements Artifact bundle manifest • ArtifactͷmetadataΛ؅ཧ͢Δ •

    Version • Type: executable (reserved) • Variants: • Path: • Supported triples: 
 <arch><sub>-<vendor>-<sys>-<abi>
 arm64-apple-ios-simulator { "schemaVersion": " 1.0 ", "artifacts": { "<identifier>": { "version": " <version number> ", "type": " executable ", "variants": [ { "path": " <relative-path-to-executable> ", "supportedTriples": [ " <triple1>", ... ] }, ... ] }ɺ... } }
  15. SE-0305 Binary Target Improvements Artifact bundleͷॲཧ 1. ZipϑΝΠϧΛμ΢ϯϩʔυ͢Δ 2. ղౚ͢Δ

    3. .xcframeworkؚ͕·Ε͍ͯͳ͚Ε͹ɺ.artifactbundleΛ୳͢
 .artifactbundleͱ.xcframework͕྆ํؚ·Ε͍ͯΔͱΤϥʔ 4. info.jsonΛಡΈࠐΉɻschemaVersion͕ͳ͚Ε͹ऴྃɻ͋Δ৔߹͸artifactΛ ొ࿥͢Δɻ
  16. SE-0305 Binary Target Improvements Artifact bundle Index • ෆཁͳϑΝΠϧͷμ΢ϯϩʔυΛճආ ͢ΔͨΊʹɺartifact

    bundleΛvariant͝ ͱʹෳ਺ͷόϯυϧʹ෼ׂͰ͖Δɻ • *.artifactbundleindex • .artifactbundleindexͱಉ͡σΟϨΫτ ϦʹݸʑͷzipΛ഑ஔ͢Δ { "schemaVersion": "1.0", "bundles": [ { "fileName": "<name of .zip file containing bundle>", "checksum": "<checksum of .zip file>", "supportedTriples": [ "<triple1>", ... ] }, ... ] }
  17. SE-0325 Additional Plugin APIs Accepted: ৄࡉ͸ׂѪ • ίʔυੜ੒ʹ੍ݶ͍ͯͨ͠Plugin APIʹॊೈੑΛ࣋ͨͤΔมߋ •

    PluginContextΛಋೖͯ͠ɺPackage, Targetͷ৘ใΛऔΓग़͠΍ͯͦ͘͢͠͏ • Identi fi ableʹconform͕͍ͨͬͯ͠Δ͕ɺProtocol͕ExistentialʹͳΔ (SE-0309)ͷΛ଴͍ͬͯΔ • ԿΛಋೖ͕͍ͨͬͯ͠Δͷ͔͸͍·͍ͪݟ͑ͳ͍ͷͰɺSwiftUIͷResult Builderͱಉ͡೏͍͕͢Δ
  18. SE-0332 Command Plugins Accepted with modi fi cations • SE-0303Ͱ͸ϏϧυதʹࣗಈͰ࣮ߦ͞ΕΔΧελϜπʔϧΛʢίʔυੜ੒ʹ޲

    ͚ͯʣ࡞ͬͨɻͦͷͨΊɺPackage directory಺ͷϑΝΠϧΛมߋͰ͖ͳ͍ͳ ͲɺϓϥάΠϯͷ࣮ߦʹ͸੍ݶ͕͋Δ • ͜͜Ͱ͸ɺϢʔβʔ͕௚઀CLI΍IDEͰΧελϜΞΫγϣϯΛ࣮ߦͰ͖Δ Command PluginΛఆٛ͢Δɻ • υΩϡϝϯτͷੜ੒ɺιʔείʔυͷformatɺUnit test reportɺbuild artifact ͷޙॲཧͳͲɺϏϧυγεςϜ͔Βಠཱͯ͠ϫʔΫϑϩʔΛ֦ுͰ͖Δɻ
  19. Command • swift package <verb: command> Ͱ࣮ߦͰ͖Δ • intentΛࢦఆ •

    Prede fi ned: Generate documantion / source code formatting • Custom: • ࣮ߦʹඞཁͳpermission΋ఆٛͰ͖Δ
  20. 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 }
  21. 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 } }
  22. 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 } } }
  23. 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Λදࣔ͢ΔՄೳੑ΋͋Δɻ
  24. Discovering command plugins • swift package plugin --list --capability=buildTool •

    swift package plugin --list --capability=command --json
  25. 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() ) ) ] )
  26. 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)") } } } }
  27. 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"), ] ) ] )
  28. 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)") } } } }
  29. 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" ) ), ) ] )
  30. 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)") } } }
  31. 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 • Կ͔࣍ͷେ͖ͳൃදͷ෬ઢ͔ɺ୯७ʹͦ͏͍͏໨ඪઃఆͳͷ͔
  32. SE-0301 Editing Commands Accepted • Product / Target / DependencyΛ௥Ճͨ͠ΒPackage.swiftʹ൓ө͠ɺखಈͰ

    ฤू͠ͳͯ͘ࡁΉΑ͏ʹ͢Δ • ϞδϡʔϧΛ௥Ճͨ͠ΒɺࣗಈͰtargetʹ൓ө͢Δ • importΛॻ͍ͨΒɺෆ଍͍ͯ͠ΔdependencyΛ௥Ճͨ͠ΓɺPackage collection͔Β௥Ճ͢Δ
 c.f.) npm / cargo / elm
  33. 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
 • ̎ͭͷόʔδϣϯͷҟͳΔύοέʔδ͕ଘࡏ͢Δ৔߹ɺยํΛἧ͑Α͏ͱ͢ ΔͱյΕΔՄೳੑ͕͋ΔͷͰɺڞଘ͍ͤͨ͞
  34. 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
  35. Detailed Design • Frontend: -module-alias [name] = [new_name] • module͸[new_name].swiftmoduleͱͯ͠ੜ੒͞ΕΔ

    • Symbol mangling΍serializationʹӨڹ͢Δ SE-0339 Module Aliasing For Disambiguation
  36. 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
  37. • 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
  38. 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
  39. 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
  40. Changes to Swift Package Manager Package Identity • ݱࡏpackage ID͸URLͷlast

    path components • scope.package-name ʹมߋ͢Δ • scope͸namespaceͰ࠷େ39จࣈ • package-name͸scope಺ͷϢχʔΫͳ໊લͰ࠷େ100จࣈ
  41. 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 }
  42. 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
  43. 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
  44. 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 }
  45. 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)
  46. 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`.
  47. 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
  48. 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
  49. Archive-source subcommand • σϑΥϧτͰ͸ɺ<Package name>.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
  50. 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 <url> [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