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 }
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 } } }
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() ) ) ] )
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)") } } } }
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" ) ), ) ] )
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)") } } }
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
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
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 }
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
of downloaded source archive of dependency 'mona.LinkedList' (c2b934fe66e55747d912f1cfd03150883c4f037370c40ca2ad4203805db79457) does not match checksum speci fi ed by the manifest (ed008d5af44c1d0ea0e3668033cae9b695235f18b1a99240b7cf0f3d9559a30d)
ίϚϯυඪ४ͷ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`.
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