diff --git a/Sources/PackagePlugin/ArgumentExtractor.swift b/Sources/PackagePlugin/ArgumentExtractor.swift index 4b39c9eba6b..746e73892d2 100644 --- a/Sources/PackagePlugin/ArgumentExtractor.swift +++ b/Sources/PackagePlugin/ArgumentExtractor.swift @@ -10,12 +10,22 @@ // //===----------------------------------------------------------------------===// -/// A rudimentary helper for extracting options and flags from a string list representing command line arguments. The idea is to extract all known options and flags, leaving just the positional arguments. This does not handle well the case in which positional arguments (or option argument values) happen to have the same name as an option or a flag. It only handles the long `--` form of options, but it does respect `--` as an indication that all remaining arguments are positional. +/// A structure that extracts options and flags from a string list representing command-line arguments. +/// +/// `ArgumentExtractor` leaves positional arguments, and extracts option arguments and flags. +/// It supports long-form option names with two hyphens (for example, `--verbose`), and treats `--` as an indicator that all remaining arguments are positional. +/// +/// > Warning: +/// > `ArgumentExtractor` doesn't detect situations in which positional arguments or optional parameters have the same name as a long option argument. public struct ArgumentExtractor { private var args: [String] private let literals: [String] - /// Initializes a ArgumentExtractor with a list of strings from which to extract flags and options. If the list contains `--`, any arguments that follow it are considered to be literals. + /// Initializes an argument with a list of strings from which to extract flags and options. + /// + /// If the list contains `--`, `ArgumentExtractor` treats any arguments that follow it as positional arguments. + /// + /// - Parameter arguments: The list of command-line arguments. public init(_ arguments: [String]) { // Split the array on the first `--`, if there is one. Everything after that is a literal. let parts = arguments.split(separator: "--", maxSplits: 1, omittingEmptySubsequences: false) @@ -23,7 +33,18 @@ public struct ArgumentExtractor { self.literals = Array(parts.count == 2 ? parts[1] : []) } - /// Extracts options of the form `-- ` or `--=` from the remaining arguments, and returns the extracted values. + /// Extracts the value of a named argument from the list of remaining arguments. + /// + /// A named argument has one of these two forms: + /// * `--=` + /// * `-- ` + /// + /// If this method detects an argument that matches the supplied name, it removes the argument and its value from the list of arguments and returns the value. + /// The same name can appear in the list of arguments multiple times, and this method returns a list of all matching values. + /// + /// - Parameters: + /// - name: The option name to extract the value for. + /// - Returns: An array of values for the named option. public mutating func extractOption(named name: String) -> [String] { var values: [String] = [] var idx = 0 @@ -49,7 +70,10 @@ public struct ArgumentExtractor { return values } - /// Extracts flags of the form `--` from the remaining arguments, and returns the count. + /// Extracts options with the given name from the remaining arguments. + /// + /// - Parameter name: The option to search for. The method prefixes it with two hyphens. For example, pass `verbose` to extract the `--verbose` option. + /// - Returns: The number of matching options in the list of arguments. public mutating func extractFlag(named name: String) -> Int { var count = 0 var idx = 0 @@ -66,12 +90,16 @@ public struct ArgumentExtractor { return count } - /// Returns any unextracted flags or options (based strictly on whether remaining arguments have a "--" prefix). + /// A list of unextracted flags or options. + /// + /// A flag or option is any argument that has the prefix `--` (two hyphens). public var unextractedOptionsOrFlags: [String] { return args.filter{ $0.hasPrefix("--") } } - /// Returns all remaining arguments, including any literals after the first `--` if there is one. + /// A list of all remaining arguments. + /// + /// If the arguments list contains the string `--`, then all arguments after it are included in this list even if they would otherwise match named flags or options. public var remainingArguments: [String] { return args + literals } diff --git a/Sources/PackagePlugin/Command.swift b/Sources/PackagePlugin/Command.swift index 3dd5e50b37d..fed124b6b7b 100644 --- a/Sources/PackagePlugin/Command.swift +++ b/Sources/PackagePlugin/Command.swift @@ -12,35 +12,38 @@ import Foundation -/// A command to run during the build, including executable, command lines, -/// environment variables, initial working directory, etc. All paths should be -/// based on the ones passed to the plugin in the target build context. +/// A command to run during a build. +/// +/// A `Command` represents all of the parameters of the build comment, +/// including the executable, command-line arguments, +/// environment variables, initial working directory, and input and output files. +/// +/// The system interprets relative paths starting from the path passed to the plugin in the target build context. public enum Command { - /// Returns a command that runs when any of its output files are needed by - /// the build, but out-of-date. + /// Returns a command that the system runs when a build needs updated versions of any of its output files. /// /// An output file is out-of-date if it doesn't exist, or if any input files - /// have changed since the command was last run. + /// are newer than the output file. /// - /// - Note: the paths in the list of output files may depend on the list of - /// input file paths, but **must not** depend on reading the contents of - /// any input files. Such cases must be handled using a `prebuildCommand`. + /// - Note: The paths in the list of output files can depend on the paths in the list of input files, + /// but not on the content of input files. To create a command that generates output files based + /// on the content of its input files, use ``prebuildCommand(displayName:executable:arguments:environment:outputFilesDirectory:)-enum.case``. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The absolute path to the executable to be invoked. - /// - arguments: Command-line arguments to be passed to the executable. + /// - executable: The absolute path to the command's executable. + /// - arguments: Command-line arguments the system passes to the executable. /// - environment: Environment variable assignments visible to the /// executable. /// - inputFiles: Files on which the contents of output files may depend. - /// Any paths passed as `arguments` should typically be passed here as - /// well. - /// - outputFiles: Files to be generated or updated by the executable. - /// Any files recognizable by their extension as source files - /// (e.g. `.swift`) are compiled into the target for which this command - /// was generated as if in its source directory; other files are treated - /// as resources as if explicitly listed in `Package.swift` using + /// You should pass any paths you pass in `arguments` as input files. + /// - outputFiles: Files the build command generates or updates. + /// Any files the system recognizes by their extension as source files + /// (for example, `.swift`) are compiled into the target for which this command + /// is generated as if they are in the target's source directory; the system + /// treats other files as resources as if they are explicitly listed in + /// `Package.swift` using /// `.process(...)`. @available(_PackageDescription, introduced: 6.0) case buildCommand( @@ -52,28 +55,28 @@ public enum Command { outputFiles: [URL] = [] ) - /// Returns a command that runs unconditionally before every build. + /// Returns a command that the build system runs unconditionally before every build. /// - /// Prebuild commands can have a significant performance impact - /// and should only be used when there would be no way to know the + /// Prebuild commands can have a significant performance impact, + /// use them only when there's no way to know the /// list of output file paths without first reading the contents - /// of one or more input files. Typically there is no way to - /// determine this list without first running the command, so - /// instead of encoding that list, the caller supplies an - /// `outputFilesDirectory` parameter, and all files in that - /// directory after the command runs are treated as output files. + /// of one or more input files. + /// + /// Use the `outputFilesDirectory` parameter to tell the build system where the + /// command creates or updates output files. The system treats all files in that + /// directory after the command runs as output files. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The absolute path to the executable to be invoked. - /// - arguments: Command-line arguments to be passed to the executable. + /// - executable: The absolute path to the command's executable. + /// - arguments: Command-line arguments the system passes to the executable. /// - environment: Environment variable assignments visible to the executable. /// - outputFilesDirectory: A directory into which the command writes its - /// output files. Any files there recognizable by their extension as - /// source files (e.g. `.swift`) are compiled into the target for which - /// this command was generated as if in its source directory; other - /// files are treated as resources as if explicitly listed in + /// output files. Any files in that directory that the system recognizes by their + /// extension as source files (for example, `.swift`) are compiled into the target + /// for which the system generated this command, as if they are in its source + /// directory; the system treats other files as resources as if explicitly listed in /// `Package.swift` using `.process(...)`. @available(_PackageDescription, introduced: 6.0) case prebuildCommand( @@ -86,31 +89,30 @@ public enum Command { } extension Command { - /// Returns a command that runs when any of its output files are needed by - /// the build, but out-of-date. + /// Returns a command that the system runs when the build needs updated versions of any of its output files. /// - /// An output file is out-of-date if it doesn't exist, or if any input files - /// have changed since the command was last run. + /// An output file is out of date if it doesn't exist, or if any input files + /// are newer than the output file. /// - /// - Note: the paths in the list of output files may depend on the list of - /// input file paths, but **must not** depend on reading the contents of - /// any input files. Such cases must be handled using a `prebuildCommand`. + /// - Note: The paths in the list of output files can depend on the paths in the list of input files, + /// but not on the content of input files. To create a command that generates output files based + /// on the content of its input files, use ``prebuildCommand(displayName:executable:arguments:environment:outputFilesDirectory:)-enum.case``. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The absolute path to the executable to be invoked. - /// - arguments: Command-line arguments to be passed to the executable. + /// - executable: The absolute path to the command's executable. + /// - arguments: Command-line arguments the system passes to the executable. /// - environment: Environment variable assignments visible to the /// executable. /// - inputFiles: Files on which the contents of output files may depend. - /// Any paths passed as `arguments` should typically be passed here as - /// well. - /// - outputFiles: Files to be generated or updated by the executable. - /// Any files recognizable by their extension as source files - /// (e.g. `.swift`) are compiled into the target for which this command - /// was generated as if in its source directory; other files are treated - /// as resources as if explicitly listed in `Package.swift` using + /// You should pass any paths you pass in `arguments` as input files. + /// - outputFiles: Files the build command generates or updates. + /// Any files the system recognizes by their extension as source files + /// (for example, `.swift`) are compiled into the target for which this command + /// is generated as if they are in the target's source directory; the system + /// treats other files as resources as if they are explicitly listed in + /// `Package.swift` using /// `.process(...)`. @available(_PackageDescription, deprecated: 6.0, message: "Use `URL` type instead of `Path`.") public static func buildCommand( @@ -131,31 +133,30 @@ extension Command { ) } - /// Returns a command that runs when any of its output files are needed - /// by the build, but out-of-date. + /// Returns a command that the system runs when the build needs updated versions of any of its output files. /// - /// An output file is out-of-date if it doesn't exist, or if any input - /// files have changed since the command was last run. + /// An output file is out of date if it doesn't exist, or if any input files + /// are newer than the output file. /// - /// - Note: the paths in the list of output files may depend on the list - /// of input file paths, but **must not** depend on reading the contents - /// of any input files. Such cases must be handled using a `prebuildCommand`. + /// - Note: The paths in the list of output files can depend on the paths in the list of input files, + /// but not on the content of input files. To create a command that generates output files based + /// on the content of its input files, use ``prebuildCommand(displayName:executable:arguments:environment:outputFilesDirectory:)-enum.case``. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The absolute path to the executable to be invoked. - /// - arguments: Command-line arguments to be passed to the executable. - /// - environment: Environment variable assignments visible to the executable. - /// - workingDirectory: Optional initial working directory when the executable - /// runs. + /// - executable: The absolute path to the command's executable. + /// - arguments: Command-line arguments the system passes to the executable. + /// - environment: Environment variable assignments visible to the + /// executable. /// - inputFiles: Files on which the contents of output files may depend. - /// Any paths passed as `arguments` should typically be passed here as well. - /// - outputFiles: Files to be generated or updated by the executable. - /// Any files recognizable by their extension as source files - /// (e.g. `.swift`) are compiled into the target for which this command - /// was generated as if in its source directory; other files are treated - /// as resources as if explicitly listed in `Package.swift` using + /// You should pass any paths you pass in `arguments` as input files. + /// - outputFiles: Files the build command generates or updates. + /// Any files the system recognizes by their extension as source files + /// (for example, `.swift`) are compiled into the target for which this command + /// is generated as if they are in the target's source directory; the system + /// treats other files as resources as if they are explicitly listed in + /// `Package.swift` using /// `.process(...)`. @available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported") public static func buildCommand( @@ -177,30 +178,28 @@ extension Command { ) } - /// Returns a command that runs unconditionally before every build. + /// Returns a command that the build system runs unconditionally before every build. /// - /// Prebuild commands can have a significant performance impact - /// and should only be used when there would be no way to know the + /// Prebuild commands can have a significant performance impact, + /// use them only when there's no way to know the /// list of output file paths without first reading the contents - /// of one or more input files. Typically there is no way to - /// determine this list without first running the command, so - /// instead of encoding that list, the caller supplies an - /// `outputFilesDirectory` parameter, and all files in that - /// directory after the command runs are treated as output files. + /// of one or more input files. + /// + /// Use the `outputFilesDirectory` parameter to tell the build system where the + /// command creates or updates output files. The system treats all files in that + /// directory after the command runs as output files. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The absolute path to the executable to be invoked. - /// - arguments: Command-line arguments to be passed to the executable. + /// - executable: The absolute path to the command's executable. + /// - arguments: Command-line arguments the system passes to the executable. /// - environment: Environment variable assignments visible to the executable. - /// - workingDirectory: Optional initial working directory when the executable - /// runs. /// - outputFilesDirectory: A directory into which the command writes its - /// output files. Any files there recognizable by their extension as - /// source files (e.g. `.swift`) are compiled into the target for which - /// this command was generated as if in its source directory; other - /// files are treated as resources as if explicitly listed in + /// output files. Any files in that directory that the system recognizes by their + /// extension as source files (for example, `.swift`) are compiled into the target + /// for which the system generated this command, as if they are in its source + /// directory; the system treats other files as resources as if explicitly listed in /// `Package.swift` using `.process(...)`. @available(_PackageDescription, deprecated: 6.0, message: "Use `URL` type instead of `Path`.") public static func prebuildCommand( @@ -219,30 +218,28 @@ extension Command { ) } - /// Returns a command that runs unconditionally before every build. + /// Returns a command that the build system runs unconditionally before every build. + /// + /// Prebuild commands can have a significant performance impact, + /// use them only when there's no way to know the + /// list of output file paths without first reading the contents + /// of one or more input files. /// - /// Because prebuild commands are run on every build, they can have a - /// significant performance impact and should only be used when there - /// would be no way to know the list of output file paths without first - /// reading the contents of one or more input files. Typically there is - /// no way to determine this list without first running the command, so - /// instead of encoding that list, the caller supplies an - /// `outputFilesDirectory` parameter, and all files in that directory - /// after the command runs are treated as output files. + /// Use the `outputFilesDirectory` parameter to tell the build system where the + /// command creates or updates output files. The system treats all files in that + /// directory after the command runs as output files. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The absolute path to the executable to be invoked. - /// - arguments: Command-line arguments to be passed to the executable. + /// - executable: The absolute path to the command's executable. + /// - arguments: Command-line arguments the system passes to the executable. /// - environment: Environment variable assignments visible to the executable. - /// - workingDirectory: Optional initial working directory when the executable - /// runs. /// - outputFilesDirectory: A directory into which the command writes its - /// output files. Any files there recognizable by their extension as - /// source files (e.g. `.swift`) are compiled into the target for which - /// this command was generated as if in its source directory; other - /// files are treated as resources as if explicitly listed in + /// output files. Any files in that directory that the system recognizes by their + /// extension as source files (for example, `.swift`) are compiled into the target + /// for which the system generated this command, as if they are in its source + /// directory; the system treats other files as resources as if explicitly listed in /// `Package.swift` using `.process(...)`. @available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported") public static func prebuildCommand( diff --git a/Sources/PackagePlugin/Context.swift b/Sources/PackagePlugin/Context.swift index 1dc25a56ef7..f5899c878fc 100644 --- a/Sources/PackagePlugin/Context.swift +++ b/Sources/PackagePlugin/Context.swift @@ -12,47 +12,60 @@ import Foundation -/// Provides information about the package for which the plugin is invoked, -/// as well as contextual information based on the plugin's stated intent -/// and requirements. +/// Provides information about the package for which the plugin is invoked. +/// +/// The plugin context includes metadata about the package, and information about the +/// build environment in which the plugin runs. public struct PluginContext { - /// Information about the package to which the plugin is being applied. + /// Information about the package to which the plugin is applied. public let package: Package - /// The path of a writable directory into which the plugin or the build - /// commands it constructs can write anything it wants. This could include - /// any generated source files that should be processed further, and it - /// could include any caches used by the build tool or the plugin itself. - /// The plugin is in complete control of what is written under this di- - /// rectory, and the contents are preserved between builds. + /// The path to a directory into which the plugin or its build + /// commands can write data. /// - /// A plugin would usually create a separate subdirectory of this directory - /// for each command it creates, and the command would be configured to - /// write its outputs to that directory. The plugin may also create other - /// directories for cache files and other file system content that either - /// it or the command will need. + /// @DeprecationSummary{Use ``pluginWorkDirectoryURL`` instead.} + /// + /// The plugin and its build commands use the work directory to + /// store any generated source files that the build system processes further, + /// and for cache files that the plugin and its build commands use. + /// The plugin is in complete control of what is written under this directory, + /// and the system preserves its contents between builds. + /// + /// A common pattern is for a plugin to create a separate subdirectory of this + /// directory for each build command it creates, and configure the build + /// command to write its outputs to that subdirectory. The plugin may also + /// create other directories for cache files and other file system content that either + /// it or its build commands need. @available(_PackageDescription, deprecated: 6.0, renamed: "pluginWorkDirectoryURL") public let pluginWorkDirectory: Path - /// The path of a writable directory into which the plugin or the build - /// commands it constructs can write anything it wants. This could include - /// any generated source files that should be processed further, and it - /// could include any caches used by the build tool or the plugin itself. - /// The plugin is in complete control of what is written under this di- - /// rectory, and the contents are preserved between builds. + /// The URL that locates a directory into which the plugin or its build + /// commands can write data. + /// + /// @DeprecationSummary{Use ``pluginWorkDirectoryURL`` instead.} /// - /// A plugin would usually create a separate subdirectory of this directory - /// for each command it creates, and the command would be configured to - /// write its outputs to that directory. The plugin may also create other - /// directories for cache files and other file system content that either - /// it or the command will need. + /// The plugin and its build commands use the work directory to + /// store any generated source files that the build system processes further, + /// and for cache files that the plugin and its build commands use. + /// The plugin is in complete control of what is written under this directory, + /// and the system preserves its contents between builds. + /// + /// A common pattern is for a plugin to create a separate subdirectory of this + /// directory for each build command it creates, and configure the build + /// command to write its outputs to that subdirectory. The plugin may also + /// create other directories for cache files and other file system content that either + /// it or its build commands need. @available(_PackageDescription, introduced: 6.0) public let pluginWorkDirectoryURL: URL - /// Looks up and returns the path of a named command line executable tool. - /// The executable must be provided by an executable target or a binary + /// Finds a named command-line tool. + /// + /// The tool's executable must be provided by an executable target or a binary /// target on which the package plugin target depends. This function throws - /// an error if the tool cannot be found. The lookup is case sensitive. + /// an error if the system can't find the tool. + /// + /// - Parameter name: The case-sensitive name of the tool to find. + /// - Returns An object that represents the command-line tool. public func tool(named name: String) throws -> Tool { if let tool = self.accessibleTools[name] { // For PluginAccessibleTool.builtTool, the triples value is not saved, thus @@ -97,14 +110,18 @@ public struct PluginContext { /// Information about a particular tool that is available to a plugin. public struct Tool { - /// Name of the tool (suitable for display purposes). + /// The tool's name. + /// + /// This property is suitable for display in a UI. public let name: String - /// Full path of the built or provided tool in the file system. + /// The full path to the tool in the file system. + /// + /// @DeprecationSummary{Use ``url`` instead.} @available(_PackageDescription, deprecated: 6.0, renamed: "url") public let path: Path - /// Full path of the built or provided tool in the file system. + /// A URL that locates the tool in the file system. @available(_PackageDescription, introduced: 6.0) public let url: URL diff --git a/Sources/PackagePlugin/Diagnostics.swift b/Sources/PackagePlugin/Diagnostics.swift index 0d05eff2f92..76b891fb0b8 100644 --- a/Sources/PackagePlugin/Diagnostics.swift +++ b/Sources/PackagePlugin/Diagnostics.swift @@ -10,17 +10,29 @@ // //===----------------------------------------------------------------------===// -/// Emits errors, warnings, and remarks to be shown as a result of running the -/// plugin. After emitting one or more errors, the plugin should return a -/// non-zero exit code. +/// Emits messages the system shows as a result of running the plugin. +/// +/// > Note: If the plugin emits one or more errors, it should return a +/// > non-zero exit code. public struct Diagnostics { - /// Severity of the diagnostic. + /// Severity of the diagnostic message. public enum Severity: String, Encodable { - case error, warning, remark + /// The diagnostic message is an error. + case error + /// The diagnostic message is a warning. + case warning + /// The diagnostic message is a remark. + case remark } - /// Emits an error with a specified severity and message, and optional file path and line number. + /// Emits a message with a specified severity, and optional file path and line number. + /// + /// - Parameters: + /// - severity: The severity of the message. + /// - description: The message to display. + /// - file: The source file to which the message relates. + /// - line: The line number in the source file to which the message relates. public static func emit(_ severity: Severity, _ description: String, file: String? = #file, line: Int? = #line) { let message: PluginToHostMessage switch severity { @@ -36,21 +48,38 @@ public struct Diagnostics { } /// Emits an error with the specified message, and optional file path and line number. + /// + /// - Parameters: + /// - message: The text of the error. + /// - file: The source file to which the error relates. + /// - line: The line number in the source file to which the error relates. public static func error(_ message: String, file: String? = #file, line: Int? = #line) { self.emit(.error, message, file: file, line: line) } /// Emits a warning with the specified message, and optional file path and line number. + /// + /// - Parameters: + /// - message: The text of the warning. + /// - file: The source file to which the warning relates. + /// - line: The line number in the source file to which the warning relates. public static func warning(_ message: String, file: String? = #file, line: Int? = #line) { self.emit(.warning, message, file: file, line: line) } /// Emits a remark with the specified message, and optional file path and line number. + /// + /// - Parameters: + /// - message: The text of the remark. + /// - file: The source file to which the remark relates. + /// - line: The line number in the source file to which the remark relates. public static func remark(_ message: String, file: String? = #file, line: Int? = #line) { self.emit(.remark, message, file: file, line: line) } - /// Emits a progress message + /// Emits a progress message. + /// + /// - Parameter message: The text of the progress update. public static func progress(_ message: String) { try? pluginHostConnection.sendMessage(.emitProgress(message: message)) } diff --git a/Sources/PackagePlugin/Errors.swift b/Sources/PackagePlugin/Errors.swift index 2d7a08c21f5..0faa65d54fb 100644 --- a/Sources/PackagePlugin/Errors.swift +++ b/Sources/PackagePlugin/Errors.swift @@ -10,18 +10,21 @@ // //===----------------------------------------------------------------------===// +/// Errors the system can encounter discovering a plugin's context. public enum PluginContextError: Error { - /// Could not find a tool with the given name. This could be either because - /// it doesn't exist, or because the plugin doesn't have a dependency on it. + /// The system couldn't find a tool with the given name. + /// + /// This error can occur because the tool doesn't exist, + /// or because the plugin doesn't have a dependency on it. case toolNotFound(name: String) - /// Tool is not supported on the target platform + /// The tool isn't supported on the target platform. case toolNotSupportedOnTargetPlatform(name: String) - /// Could not find a target with the given name. + /// The system couldn't find a target with the specified name in the package. case targetNotFound(name: String, package: Package) - /// Could not find a product with the given name. + /// The system couldn't find a product with the specified name in the package. case productNotFound(name: String, package: Package) } @@ -40,16 +43,27 @@ extension PluginContextError: CustomStringConvertible { } } +/// Errors the system can encounter deserializing a plugin. public enum PluginDeserializationError: Error { - /// The input JSON is malformed in some way; the message provides more details. + /// The input JSON is malformed. + /// + /// The associated message provides more details about the problem. case malformedInputJSON(_ message: String) - /// The plugin doesn't support Xcode (it doesn't link against XcodeProjectPlugin). + /// The plugin doesn't support Xcode. + /// + /// To support Xcode, a plugin needs to link against `XcodeProjectPlugin`. case missingXcodeProjectPluginSupport - /// The plugin doesn't conform to an expected specialization of the BuildToolPlugin protocol. + /// The package uses a build-tool plugin that doesn't conform to the correct protocol. + /// + /// To act as a build-tool plugin, the plugin needs to conform to ``BuildToolPlugin``. case missingBuildToolPluginProtocolConformance(protocolName: String) - /// The plugin doesn't conform to an expected specialization of the CommandPlugin protocol. + /// The package uses a command plugin that doesn't conform to the correct protocol. + /// + /// To act as a command plugin, the plugin needs to conform to ``CommandPlugin``. case missingCommandPluginProtocolConformance(protocolName: String) - /// An internal error of some kind; the message provides more details. + /// An internal error occurred. + /// + /// The associated message provides more details about the problem. case internalError(_ message: String) } diff --git a/Sources/PackagePlugin/PackagePlugin.docc/Info.plist b/Sources/PackagePlugin/PackagePlugin.docc/Info.plist new file mode 100644 index 00000000000..1d479ae109f --- /dev/null +++ b/Sources/PackagePlugin/PackagePlugin.docc/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleName + PackagePlugin + CFBundleDisplayName + PackagePlugin + CFBundleIdentifier + org.swift.swiftpm.packageplugin + CFBundleDevelopmentRegion + en + CFBundleIconFile + DocumentationIcon + CFBundleIconName + DocumentationIcon + CFBundlePackageType + DOCS + CFBundleShortVersionString + 0.1.0 + CDDefaultCodeListingLanguage + swift + CFBundleVersion + 0.1.0 + + diff --git a/Sources/PackagePlugin/PackagePlugin.docc/PackagePlugin.md b/Sources/PackagePlugin/PackagePlugin.docc/PackagePlugin.md new file mode 100644 index 00000000000..9f8847d59ad --- /dev/null +++ b/Sources/PackagePlugin/PackagePlugin.docc/PackagePlugin.md @@ -0,0 +1,223 @@ +# ``PackagePlugin`` + +Create custom build steps and command-line actions for your Swift package. + +## Overview + +Use `PackagePlugin` to create plugins that extend Swift package manager's behavior in one of two ways: + +* term Build-tool plugins: Create a command that Swift package manager runs either as a pre-build step before it performs a build action, or as a specific step in a target's build process. +* term Command plugins: Create a command that someone runs either by passing its name as an argument to the `swift package` command-line tool, or from UI their developer environment. + +Define your plugins as targets in your `Package.swift` file. +To make a build-tool plugin available for other packages to use, define a product that exports the plugin. + +### Create a build-tool plugin + +Add a build-tool plugin target to your package by creating a `.plugin` target with the `buildTool()` capability in your package description. +List any command-line tools the plugin uses as dependencies of the plugin target, and their packages as dependencies of your package. +For example: + +```swift +// swift-tools-version: 5.6 +import PackageDescription + +let package = Package( + name: "MyPluginPackage", + dependencies: [ + .package( + url: "https://github.com/example/sometool", + from: "0.1.0" + ) + ], + targets: [ + .plugin( + name: "MyBuildToolPlugin", + capability: .buildTool(), + dependencies: [ + .product(name: "SomeTool", package: "sometool"), + ] + ) + ] +) + +``` + +Create a `Plugins` folder in your Swift package, and a sub-folder with the same name as your build-tool plugin. +Inside that sub-folder, create a Swift file that contains your plugin source. +Your plugin needs to conform to ``BuildToolPlugin``. + +Your implementation of ``BuildToolPlugin/createBuildCommands(context:target:)`` returns either a ``Command/buildCommand(displayName:executable:arguments:environment:inputFiles:outputFiles:)-enum.case`` or ``Command/prebuildCommand(displayName:executable:arguments:environment:outputFilesDirectory:)-enum.case``, depending on where in the build process your plugin's command runs: + + - Return ``Command/buildCommand(displayName:executable:arguments:environment:inputFiles:outputFiles:)-enum.case`` if your plugin depends on one or more input files in the target's source (including files that the build process generates), and creates one or more output files that you know the paths to. + Swift package manager adds your plugin's command to the dependency graph at a point where all of its inputs are available. +- Return ``Command/prebuildCommand(displayName:executable:arguments:environment:outputFilesDirectory:)-enum.case`` if your plugin creates a collection of output files with names that you don't know until after the command runs. + Swift package manager runs your plugin's command every time it builds the target, before it calculates the target's dependency graph. + +Use the ``PluginContext`` Swift package manager passes to your plugin to get information about the target Swift package manager is building, and to find the paths to commands your plugin uses. + +### Use a build-tool plugin + +In your target that uses a build-tool plugin as part of its build process, add the `plugins:` argument to the target's definition in your package description. +Each entry in the list is a `.plugin(name:package:)` that specifies the plugin's name, and the package that provides the plugin. +For example: + +```swift +// swift-tools-version: 5.6 +import PackageDescription + +let package = Package( + name: "my-plugin-using-example", + dependencies: [ + .package(url: "https://github.com/example/my-plugin-package.git", from: "1.0"), + ], + targets: [ + .executableTarget( + name: "MyExample", + plugins: [ + .plugin(name: "MyBuildToolPlugin", package: "my-plugin-package"), + ] + ) + ] +) +``` + +### Create a command plugin + +Add a command plugin to your package by creating a `.plugin` target with the `command()` capability in your package description. +The capability describes the intent of your command plugin, and the permissions it needs to work. +List any commands your plugin uses as the target's dependencies. +For example: + +```swift +// swift-tools-version: 5.6 +import PackageDescription + +let package = Package( + name: "MyPluginPackage", + dependencies: [ + .package( + url: "https://github.com/example/sometool", + from: "0.1.0" + ) + ], + targets: [ + .plugin( + name: "MyCommandPlugin", + capability: .command( + intent: .sourceCodeFormatting(), + permissions: [ + .writeToPackageDirectory(reason: "This command reformats source files") + ] + ), + dependencies: [ + .product(name: "SomeTool", package: "sometool"), + ] + ) + ] +) +``` +Create a `Plugins` folder in your Swift package, and a sub-folder with the same name as your build-tool plugin. +Inside that sub-folder, create a Swift file that contains your plugin source. +Your plugin needs to conform to ``CommandPlugin``. + +Your implementation of ``CommandPlugin/performCommand(context:arguments:)`` runs the command and prints any output. +Use methods on ``Diagnostics`` to report errors, warnings, progress, and other information to the person who runs your command plugin. +Use ``ArgumentExtractor`` to parse arguments someone supplies to your command. + +Use the ``PluginContext`` Swift package manager passes to your plugin to find paths to commands your plugin uses. +Command plugins don't have target information in their `PluginContext` structure, because Swift package manager isn't building a target when it runs your command plugin. + +### Use a command plugin + +To run a command plugin, pass its name and any arguments it needs as arguments to the `swift package` command, for example: + +```sh +% swift package my-plugin --example-flag parameter +``` + +### Share a plugin with other packages + +Make your plugin available to developers to use in other packages by declaring a `.plugin` product in your package description, that has your plugin target in its targets list. +For example: + +```swift +// swift-tools-version: 5.6 +import PackageDescription + +let package = Package( + name: "MyPluginPackage", + products: [ + .plugin( + name: "MyBuildToolPlugin", + targets: [ + "MyBuildToolPlugin" + ] + ) + ], + // Define the plugin. +) +``` + +## Topics + +### Build-tool plugins + +- ``BuildToolPlugin`` +- ``Command`` +- ``Target`` + +### Command plugins + +- ``CommandPlugin`` +- ``ArgumentExtractor`` + +### Contextual information + +- ``PluginContext`` +- ``PackageManager`` +- ``ToolsVersion`` + +### Packages + +- ``Package`` +- ``PackageOrigin`` + +### Package products + +- ``Product`` +- ``ExecutableProduct`` +- ``LibraryProduct`` + +### Targets and modules + +- ``BinaryArtifactTarget`` +- ``SourceModuleTarget`` +- ``ClangSourceModuleTarget`` +- ``SwiftSourceModuleTarget`` +- ``SystemLibraryTarget`` +- ``ModuleKind`` + +### Files and paths + +- ``FileList`` +- ``File`` +- ``FileType`` +- ``PathList`` +- ``Path`` + +### Dependencies + +- ``PackageDependency`` +- ``TargetDependency`` + +### Errors and feedback + +- ``Diagnostics`` +- ``PackageManagerProxyError`` +- ``PluginContextError`` +- ``PluginDeserializationError`` + +### Types that support plugins + +- ``Plugin`` diff --git a/Sources/PackagePlugin/Protocols.swift b/Sources/PackagePlugin/Protocols.swift index 3e3fd35fb05..e1346850fc5 100644 --- a/Sources/PackagePlugin/Protocols.swift +++ b/Sources/PackagePlugin/Protocols.swift @@ -25,42 +25,46 @@ public protocol Plugin { init() } -/// Defines functionality for all plugins having a `buildTool` capability. +/// A protocol you implement to define a build-tool plugin. public protocol BuildToolPlugin: Plugin { - /// Invoked by SwiftPM to create build commands for a particular target. - /// The context parameter contains information about the package and its - /// dependencies, as well as other environmental inputs. + /// Creates build commands for the given target. /// - /// This function should create and return build commands or prebuild - /// commands, configured based on the information in the context. Note - /// that it does not directly run those commands. + /// - Parameters: + /// - context: Information about the package and its + /// dependencies, as well as other environmental inputs. + /// - target: The build target for which the package manager invokes the plugin. + /// - Returns: A list of commands that the system runs before it performs the build action (for ``Command/prebuildCommand(displayName:executable:arguments:environment:outputFilesDirectory:)-enum.case``), + /// or as specific steps during the build (for ``Command/buildCommand(displayName:executable:arguments:environment:inputFiles:outputFiles:)-enum.case``). + /// + /// You don't run commands directly in your implementation of this method. Instead, create and return ``Command`` instances. + /// The system runs pre-build commands before it performs the build action, and adds build commands to the dependency tree for the build + /// based on which steps create the command's inputs, and which steps depend on the command's outputs. func createBuildCommands( context: PluginContext, target: Target ) async throws -> [Command] } -/// Defines functionality for all plugins that have a `command` capability. +/// A protocol you implement to define a command plugin. public protocol CommandPlugin: Plugin { - /// Invoked by SwiftPM to perform the custom actions of the command. + /// Performs the command's custom actions. + /// + /// - Parameters: + /// - context: Information about the package and other environmental inputs. + /// - arguments: Literal arguments that someone passed in the command invocation, after the command verb. func performCommand( - /// The context in which the plugin is invoked. This is the same for all - /// kinds of plugins, and provides access to the package graph, to cache - /// directories, etc. context: PluginContext, - - /// Any literal arguments passed after the verb in the command invocation. arguments: [String] ) async throws - /// A proxy to the Swift Package Manager or IDE hosting the command plugin, - /// through which the plugin can ask for specialized information or actions. + /// An object that represents the Swift Package Manager or IDE hosting the command plugin. + /// + /// Use this object to discover specialized information about the plugin host, or actions your plugin can invoke. var packageManager: PackageManager { get } } extension CommandPlugin { - /// A proxy to the Swift Package Manager or IDE hosting the command plugin, - /// through which the plugin can ask for specialized information or actions. + /// An object that represents the Swift Package Manager or IDE hosting the command plugin. public var packageManager: PackageManager { return PackageManager() }