diff --git a/Plugins/VersionFile/Extensions/PackagePlugin+Utils.swift b/Plugins/VersionFile/Extensions/PackagePlugin+Utils.swift new file mode 100644 index 0000000..f63dd71 --- /dev/null +++ b/Plugins/VersionFile/Extensions/PackagePlugin+Utils.swift @@ -0,0 +1,23 @@ +// +// PackagePlugin+Utils.swift +// +// +// Created by Mathew Gacy on 12/3/23. +// + +import Foundation +import PackagePlugin + +extension PackagePlugin.Target { + var debugDescription: String { + if let sourceModuleTarget = self as? SourceModuleTarget { + return """ + SourceModuleTarget(id: "\(id)", name: "\(name)", moduleName: "\(sourceModuleTarget.moduleName)", kind: ModuleKind.\(sourceModuleTarget.kind)) + """ + } else { + return """ + Target(id: "\(id)", name: "\(name)", directory: \(directory)) + """ + } + } +} diff --git a/Plugins/VersionFile/VersionFile.swift b/Plugins/VersionFile/VersionFile.swift index e9d4231..396351f 100644 --- a/Plugins/VersionFile/VersionFile.swift +++ b/Plugins/VersionFile/VersionFile.swift @@ -8,24 +8,37 @@ import Foundation import PackagePlugin +/// Constants used by the VersionFile plugin. enum Constants { + /// The name of the version file. static let versionFile = "Version.swift" + /// A regular expression pattern for a semantic version number. static let versionPattern = #"([0-9]+\.*)+"# } +/// A semantic version release type. enum Release: String, CaseIterable { + /// Increment the patch version. case patch + /// Increment the minor version. case minor + /// Increment the major version. case major + /// Increment the pre-release version. case release + /// Increment the pre-release version. case prerelease = "prerel" } +/// A VersionFile plugin command. enum Command { + /// Bump a VersionFile with the given release type. case bump(Release) + /// Create a VersionFile with the given version string. case create(String) } +/// The entry point of the VersionFile plugin. @main struct VersionFile: CommandPlugin { /// This entry point is called when operating on a Swift package. @@ -34,7 +47,9 @@ struct VersionFile: CommandPlugin { arguments: [String] ) async throws { if arguments.contains("--verbose") { - print("Command plugin execution with arguments \(arguments.description) for Swift package \(context.package.displayName). All target information: \(context.package.targets.description)") + let targetsDescription = context.package.targets.map(\.debugDescription).joined(separator: "\n - ") + let packageDescription = "`\(context.package.displayName)`.\nTargets:\n - \(targetsDescription)" + print("\nCommand plugin execution with arguments `\(arguments.description)` for Swift package \(packageDescription)\n") } var argExtractor = ArgumentExtractor(arguments) @@ -68,6 +83,10 @@ struct VersionFile: CommandPlugin { } private extension VersionFile { + /// Extracts a ``Command`` from the given argument extractor and returns it. + /// + /// - Parameter argExtractor: The argument extractor. + /// - Returns: The extracted command. func extractCommand(from argExtractor: inout ArgumentExtractor) throws -> Command { if let releaseString = argExtractor.extractOption(named: "bump").first { guard let release = Release(rawValue: releaseString) else { @@ -83,21 +102,36 @@ private extension VersionFile { } } + /// Returns the targets to process in the given package. + /// + /// - Parameters: + /// - package: The package to process. + /// - selectedTargets: The names of the targets to process. + /// - Returns: The targets to process. func targetsToProcess(in package: Package, selectedTargets: [String]) -> [SourceModuleTarget] { var targetsToProcess: [Target] = package.targets if selectedTargets.isEmpty == false { - targetsToProcess = package.targets.filter { selectedTargets.contains($0.name) }.map { $0 } + targetsToProcess = package.targets.filter { selectedTargets.contains($0.name) } } return targetsToProcess.compactMap { target in - guard let target = target as? SourceModuleTarget, case .generic = target.kind else { + guard let target = target as? SourceModuleTarget else { return nil } - return target + switch target.kind { + case .generic, .executable: + return target + case .test: + return nil + } } } + /// Returns the current version number from the version file at the given path. + /// + /// - Parameter path: The path to the version file. + /// - Returns: The current version number. func currentVersion(path: Path) throws -> String { let fileContents = try String(contentsOfFile: path.string) @@ -109,21 +143,38 @@ private extension VersionFile { return versionString } + /// Returns the contents of a version file for the given version number. + /// + /// - Parameter version: The version number. + /// - Returns: The version file contents func makeVersion(_ version: String) -> String { """ // This file was generated by the `VersionFile` package plugin. + /// Namespace for the current version of the target in which this file is contained. enum Version { + /// The current version number. static let number = "\(version)" } """ } + /// Writes a version file with the given version number to the given path. + /// + /// - Parameters: + /// - version: The version number. + /// - path: The path to the version file. func writeVersionFile(_ version: String, in path: Path) throws { let fileContents = makeVersion(version) try fileContents.write(toFile: path.string, atomically: true, encoding: .utf8) } + /// Runs the given tool with the given arguments and returns the output. + /// + /// - Parameters: + /// - tool: The tool to run. + /// - arguments: The arguments to pass to the tool. + /// - Returns: The output of the tool. func run(tool: PluginContext.Tool, with arguments: [String]) throws -> String { let outputPipe = Pipe()