diff --git a/Sources/Commands/PackageCommands/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift index a5221e8dfe2..ecc3a2eabbd 100644 --- a/Sources/Commands/PackageCommands/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -26,7 +26,8 @@ import Workspace struct DeprecatedAPIDiff: ParsableCommand { static let configuration = CommandConfiguration(commandName: "experimental-api-diff", abstract: "Deprecated - use `swift package diagnose-api-breaking-changes` instead", - shouldDisplay: false) + shouldDisplay: false, + helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) @Argument(parsing: .captureForPassthrough) var args: [String] = [] @@ -49,7 +50,9 @@ struct APIDiff: AsyncSwiftCommand { behavior may be undesirable as the comparison can be slow. \ The `--products` and `--targets` options may be used to restrict the scope of \ the comparison. - """) + """, + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/AddDependency.swift b/Sources/Commands/PackageCommands/AddDependency.swift index 03c49fc9661..8a5381fd130 100644 --- a/Sources/Commands/PackageCommands/AddDependency.swift +++ b/Sources/Commands/PackageCommands/AddDependency.swift @@ -27,7 +27,8 @@ import class PackageModel.Manifest extension SwiftPackageCommand { struct AddDependency: SwiftCommand { package static let configuration = CommandConfiguration( - abstract: "Add a package dependency to the manifest." + abstract: "Add a package dependency to the manifest.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @Argument(help: "The URL or directory of the package to add.") diff --git a/Sources/Commands/PackageCommands/AddProduct.swift b/Sources/Commands/PackageCommands/AddProduct.swift index 288e691797b..2a0ab921b28 100644 --- a/Sources/Commands/PackageCommands/AddProduct.swift +++ b/Sources/Commands/PackageCommands/AddProduct.swift @@ -35,7 +35,9 @@ extension SwiftPackageCommand { } package static let configuration = CommandConfiguration( - abstract: "Add a new product to the manifest.") + abstract: "Add a new product to the manifest.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/AddSetting.swift b/Sources/Commands/PackageCommands/AddSetting.swift index 4589bfa50f9..1f1de738b07 100644 --- a/Sources/Commands/PackageCommands/AddSetting.swift +++ b/Sources/Commands/PackageCommands/AddSetting.swift @@ -35,7 +35,8 @@ extension SwiftPackageCommand { } package static let configuration = CommandConfiguration( - abstract: "Add a new setting to the manifest." + abstract: "Add a new setting to the manifest.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup(visibility: .hidden) diff --git a/Sources/Commands/PackageCommands/AddTarget.swift b/Sources/Commands/PackageCommands/AddTarget.swift index 089f148675f..58cfb4dd996 100644 --- a/Sources/Commands/PackageCommands/AddTarget.swift +++ b/Sources/Commands/PackageCommands/AddTarget.swift @@ -42,7 +42,8 @@ extension SwiftPackageCommand { } package static let configuration = CommandConfiguration( - abstract: "Add a new target to the manifest." + abstract: "Add a new target to the manifest.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup(visibility: .hidden) diff --git a/Sources/Commands/PackageCommands/AddTargetDependency.swift b/Sources/Commands/PackageCommands/AddTargetDependency.swift index c5daf524361..47cded60850 100644 --- a/Sources/Commands/PackageCommands/AddTargetDependency.swift +++ b/Sources/Commands/PackageCommands/AddTargetDependency.swift @@ -26,7 +26,9 @@ import Workspace extension SwiftPackageCommand { struct AddTargetDependency: SwiftCommand { package static let configuration = CommandConfiguration( - abstract: "Add a new target dependency to the manifest.") + abstract: "Add a new target dependency to the manifest.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/ArchiveSource.swift b/Sources/Commands/PackageCommands/ArchiveSource.swift index c5229c1177e..82ee0d4e52f 100644 --- a/Sources/Commands/PackageCommands/ArchiveSource.swift +++ b/Sources/Commands/PackageCommands/ArchiveSource.swift @@ -21,7 +21,8 @@ extension SwiftPackageCommand { struct ArchiveSource: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "archive-source", - abstract: "Create a source archive for the package." + abstract: "Create a source archive for the package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup(visibility: .hidden) diff --git a/Sources/Commands/PackageCommands/AuditBinaryArtifact.swift b/Sources/Commands/PackageCommands/AuditBinaryArtifact.swift index ac290eea4c0..9e49db339c6 100644 --- a/Sources/Commands/PackageCommands/AuditBinaryArtifact.swift +++ b/Sources/Commands/PackageCommands/AuditBinaryArtifact.swift @@ -24,7 +24,8 @@ import struct TSCBasic.StringError struct AuditBinaryArtifact: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "experimental-audit-binary-artifact", - abstract: "Audit a static library binary artifact for undefined symbols." + abstract: "Audit a static library binary artifact for undefined symbols.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup(visibility: .hidden) diff --git a/Sources/Commands/PackageCommands/CompletionCommand.swift b/Sources/Commands/PackageCommands/CompletionCommand.swift index 14a61a08c2a..268efe3921c 100644 --- a/Sources/Commands/PackageCommands/CompletionCommand.swift +++ b/Sources/Commands/PackageCommands/CompletionCommand.swift @@ -22,7 +22,8 @@ extension SwiftPackageCommand { struct CompletionCommand: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "completion-tool", - abstract: "Command to generate shell completions." + abstract: "Command to generate shell completions.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) enum Mode: String, CaseIterable, ExpressibleByArgument { @@ -93,4 +94,4 @@ extension SwiftPackageCommand { } } } -} \ No newline at end of file +} diff --git a/Sources/Commands/PackageCommands/ComputeChecksum.swift b/Sources/Commands/PackageCommands/ComputeChecksum.swift index db417080f3c..5cc57dc7fc0 100644 --- a/Sources/Commands/PackageCommands/ComputeChecksum.swift +++ b/Sources/Commands/PackageCommands/ComputeChecksum.swift @@ -19,7 +19,9 @@ import struct TSCBasic.SHA256 struct ComputeChecksum: SwiftCommand { static let configuration = CommandConfiguration( - abstract: "Compute the checksum for a binary artifact.") + abstract: "Compute the checksum for a binary artifact.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Config.swift b/Sources/Commands/PackageCommands/Config.swift index d5243a4503b..ef2720f2682 100644 --- a/Sources/Commands/PackageCommands/Config.swift +++ b/Sources/Commands/PackageCommands/Config.swift @@ -22,7 +22,8 @@ extension SwiftPackageCommand { struct Config: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Manipulate configuration of the package", - subcommands: [SetMirror.self, UnsetMirror.self, GetMirror.self] + subcommands: [SetMirror.self, UnsetMirror.self, GetMirror.self], + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) } } diff --git a/Sources/Commands/PackageCommands/Describe.swift b/Sources/Commands/PackageCommands/Describe.swift index 99025ef41e6..cdf9c1b7165 100644 --- a/Sources/Commands/PackageCommands/Describe.swift +++ b/Sources/Commands/PackageCommands/Describe.swift @@ -23,7 +23,9 @@ import struct TSCBasic.StringError extension SwiftPackageCommand { struct Describe: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Describe the current package.") + abstract: "Describe the current package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index a46630d1d0f..f0b5aa81da1 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -23,7 +23,9 @@ import XCBuildSupport struct DumpSymbolGraph: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Dump symbol graphs.") + abstract: "Dump symbol graphs.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) static let defaultMinimumAccessLevel = SymbolGraphExtract.AccessLevel.public @OptionGroup(visibility: .hidden) @@ -146,7 +148,9 @@ enum ExtensionBlockSymbolBehavior: String, EnumerableFlag { struct DumpPackage: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Print parsed Package.swift as JSON.") + abstract: "Print parsed Package.swift as JSON.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -174,7 +178,10 @@ struct DumpPackage: AsyncSwiftCommand { struct DumpPIF: AsyncSwiftCommand { // hides this command from CLI `--help` output - static let configuration = CommandConfiguration(shouldDisplay: false) + static let configuration = CommandConfiguration( + shouldDisplay: false, + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .private) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift index 5509c2ee51f..332e7f6b572 100644 --- a/Sources/Commands/PackageCommands/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -19,7 +19,9 @@ import Workspace extension SwiftPackageCommand { struct Edit: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Put a package in editable mode.") + abstract: "Put a package in editable mode.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -53,7 +55,9 @@ extension SwiftPackageCommand { struct Unedit: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Remove a package from editable mode.") + abstract: "Remove a package from editable mode.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Format.swift b/Sources/Commands/PackageCommands/Format.swift index 1c2341fb4a6..e005ccd1c4e 100644 --- a/Sources/Commands/PackageCommands/Format.swift +++ b/Sources/Commands/PackageCommands/Format.swift @@ -25,7 +25,9 @@ import enum TSCUtility.Diagnostics extension SwiftPackageCommand { struct Format: AsyncSwiftCommand { static let configuration = CommandConfiguration( - commandName: "_format", shouldDisplay: false) + commandName: "_format", shouldDisplay: false, + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .private) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Init.swift b/Sources/Commands/PackageCommands/Init.swift index 7057845a3df..5758fff800b 100644 --- a/Sources/Commands/PackageCommands/Init.swift +++ b/Sources/Commands/PackageCommands/Init.swift @@ -23,7 +23,9 @@ import SPMBuildCore extension SwiftPackageCommand { struct Init: SwiftCommand { public static let configuration = CommandConfiguration( - abstract: "Initialize a new package.") + abstract: "Initialize a new package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Install.swift b/Sources/Commands/PackageCommands/Install.swift index daea088980a..5a6b833a260 100644 --- a/Sources/Commands/PackageCommands/Install.swift +++ b/Sources/Commands/PackageCommands/Install.swift @@ -24,7 +24,8 @@ extension SwiftPackageCommand { struct Install: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "experimental-install", - abstract: "Offers the ability to install executable products of the current package." + abstract: "Offers the ability to install executable products of the current package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup() @@ -102,7 +103,8 @@ extension SwiftPackageCommand { struct Uninstall: SwiftCommand { static let configuration = CommandConfiguration( commandName: "experimental-uninstall", - abstract: "Offers the ability to uninstall executable products previously installed by `swift package experimental-install`." + abstract: "Offers the ability to uninstall executable products previously installed by `swift package experimental-install`.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup diff --git a/Sources/Commands/PackageCommands/Learn.swift b/Sources/Commands/PackageCommands/Learn.swift index b8a0a614d8a..ea10cdc7775 100644 --- a/Sources/Commands/PackageCommands/Learn.swift +++ b/Sources/Commands/PackageCommands/Learn.swift @@ -23,7 +23,10 @@ extension SwiftPackageCommand { @OptionGroup() var globalOptions: GlobalOptions - static let configuration = CommandConfiguration(abstract: "Learn about Swift and this package.") + static let configuration = CommandConfiguration( + abstract: "Learn about Swift and this package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) func files(fileSystem: FileSystem, in directory: AbsolutePath, fileExtension: String? = nil) throws -> [AbsolutePath] { guard fileSystem.isDirectory(directory) else { diff --git a/Sources/Commands/PackageCommands/Migrate.swift b/Sources/Commands/PackageCommands/Migrate.swift index fca4dd4b3fe..d7485164f6e 100644 --- a/Sources/Commands/PackageCommands/Migrate.swift +++ b/Sources/Commands/PackageCommands/Migrate.swift @@ -57,7 +57,8 @@ struct MigrateOptions: ParsableArguments { extension SwiftPackageCommand { struct Migrate: AsyncSwiftCommand { package static let configuration = CommandConfiguration( - abstract: "Migrate a package or its individual targets to use the given set of features." + abstract: "Migrate a package or its individual targets to use the given set of features.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup(visibility: .hidden) diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index 8bc238af22a..31502178e6c 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -25,7 +25,8 @@ import Workspace struct PluginCommand: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "plugin", - abstract: "Invoke a command plugin or perform other actions on command plugins." + abstract: "Invoke a command plugin or perform other actions on command plugins.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) @OptionGroup(visibility: .hidden) diff --git a/Sources/Commands/PackageCommands/ResetCommands.swift b/Sources/Commands/PackageCommands/ResetCommands.swift index fdd491d7db3..62aa6bb8c0e 100644 --- a/Sources/Commands/PackageCommands/ResetCommands.swift +++ b/Sources/Commands/PackageCommands/ResetCommands.swift @@ -17,7 +17,9 @@ import Workspace extension SwiftPackageCommand { struct Clean: SwiftCommand { static let configuration = CommandConfiguration( - abstract: "Delete build artifacts.") + abstract: "Delete build artifacts.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -29,7 +31,9 @@ extension SwiftPackageCommand { struct PurgeCache: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Purge the global repository cache.") + abstract: "Purge the global repository cache.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -41,7 +45,9 @@ extension SwiftPackageCommand { struct Reset: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Reset the complete cache/build directory.") + abstract: "Reset the complete cache/build directory.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index 10c5dff9039..ea34131b4bb 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -35,7 +35,9 @@ extension SwiftPackageCommand { struct Resolve: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Resolve package dependencies.") + abstract: "Resolve package dependencies.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -66,7 +68,10 @@ extension SwiftPackageCommand { } struct Fetch: AsyncSwiftCommand { - static let configuration = CommandConfiguration(shouldDisplay: false) + static let configuration = CommandConfiguration( + shouldDisplay: false, + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .private) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/ShowDependencies.swift b/Sources/Commands/PackageCommands/ShowDependencies.swift index 6fe6f3940f3..2998dc950f7 100644 --- a/Sources/Commands/PackageCommands/ShowDependencies.swift +++ b/Sources/Commands/PackageCommands/ShowDependencies.swift @@ -23,7 +23,9 @@ import var TSCBasic.stdoutStream extension SwiftPackageCommand { struct ShowDependencies: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Print the resolved dependency graph.") + abstract: "Print the resolved dependency graph.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/ShowExecutables.swift b/Sources/Commands/PackageCommands/ShowExecutables.swift index c1e50248b19..09c74f30c79 100644 --- a/Sources/Commands/PackageCommands/ShowExecutables.swift +++ b/Sources/Commands/PackageCommands/ShowExecutables.swift @@ -20,7 +20,9 @@ import Workspace struct ShowExecutables: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "List the available executables from this package.") + abstract: "List the available executables from this package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/ShowTraits.swift b/Sources/Commands/PackageCommands/ShowTraits.swift index 128415ebff1..edd8aaf3d99 100644 --- a/Sources/Commands/PackageCommands/ShowTraits.swift +++ b/Sources/Commands/PackageCommands/ShowTraits.swift @@ -20,7 +20,9 @@ import Workspace struct ShowTraits: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "List the available traits for a package.") + abstract: "List the available traits for a package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift index 473bb6e54ed..6c7437fe991 100644 --- a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift +++ b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift @@ -30,7 +30,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand { commandName: "package", _superCommandName: "swift", abstract: "Perform operations on Swift packages.", - discussion: "SEE ALSO: swift build, swift run, swift test", + discussion: "SEE ALSO: swift build, swift run, swift test \n(Run this command without --help to see possible dynamic plugin commands.)", version: SwiftVersion.current.completeDisplayString, subcommands: [ AddDependency.self, @@ -77,7 +77,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand { ] + (ProcessInfo.processInfo.environment["SWIFTPM_ENABLE_SNIPPETS"] == "1" ? [Learn.self] : []), defaultSubcommand: DefaultCommand.self, - helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + helpNames: [] ) @OptionGroup() @@ -106,10 +106,25 @@ extension SwiftPackageCommand { @Argument(parsing: .captureForPassthrough) var remaining: [String] = [] + @Flag(name: [.short, .long, .customLong("help", withSingleDash: true)]) + var help = false + func run(_ swiftCommandState: SwiftCommandState) async throws { // See if have a possible plugin command. - guard let command = remaining.first else { + guard !self.help, let command = remaining.first else { print(SwiftPackageCommand.helpMessage()) + do { + let pluginCommands = try await fetchAvailablePluginCommands(swiftCommandState: swiftCommandState) + if !pluginCommands.isEmpty { + print("\nAVAILABLE PLUGIN COMMANDS:") + for cmd in pluginCommands { + let formattedDescription = "\(cmd.name)" + .padding(toLength: 24, withPad: " ", startingAt: 0) + cmd.description + print(" " + formattedDescription) + } + } + } catch {} // fail silently as user does not need to know we could not fetch plugin command's for the + // help screen return } @@ -128,6 +143,38 @@ extension SwiftPackageCommand { swiftCommandState: swiftCommandState ) } + + private func fetchAvailablePluginCommands(swiftCommandState: SwiftCommandState) async throws -> [( + name: String, + description: String + )] { + let packageGraph = try await swiftCommandState.loadPackageGraph() + let allPlugins = PluginCommand.availableCommandPlugins( + in: packageGraph, + limitedTo: self.pluginOptions.packageIdentity + ).map { + $0.underlying as! PluginModule + } + + var result: [(String, String)] = [] + + for plugin in allPlugins.sorted(by: { $0.name < $1.name }) { + guard case .command(let intent, _) = plugin.capability else { continue } + let commandName = intent.invocationVerb + var commandDescription = "(plugin ‘\(plugin.name)’" + + if let package = packageGraph.packages + .first(where: { $0.modules.contains(where: { $0.name == plugin.name }) }) + { + commandDescription += " in package ‘\(package.manifest.displayName)’" + } + commandDescription += ")" + + result.append((commandName, commandDescription)) + } + + return result + } } } diff --git a/Sources/Commands/PackageCommands/ToolsVersionCommand.swift b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift index 02044f71564..aaff41334c6 100644 --- a/Sources/Commands/PackageCommands/ToolsVersionCommand.swift +++ b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift @@ -21,7 +21,9 @@ import Workspace struct ToolsVersionCommand: SwiftCommand { static let configuration = CommandConfiguration( commandName: "tools-version", - abstract: "Manipulate tools version of the current package.") + abstract: "Manipulate tools version of the current package.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Update.swift b/Sources/Commands/PackageCommands/Update.swift index 2bea09a4500..1757da18657 100644 --- a/Sources/Commands/PackageCommands/Update.swift +++ b/Sources/Commands/PackageCommands/Update.swift @@ -21,7 +21,9 @@ import Workspace extension SwiftPackageCommand { struct Update: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Update package dependencies.") + abstract: "Update package dependencies.", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)] + ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index e7b1936c4b5..cbbb41ca4e8 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -133,7 +133,7 @@ struct PackageCommandTests { @Test func seeAlso() async throws { let stdout = try await SwiftPM.Package.execute(["--help"]).stdout - #expect(stdout.contains("SEE ALSO: swift build, swift run, swift test")) + #expect(stdout.contains("SEE ALSO: swift build, swift run, swift test \n(Run this command without --help to see possible dynamic plugin commands.)")) } @Test @@ -153,6 +153,130 @@ struct PackageCommandTests { #expect(stdout.contains(expectedRegex)) } + @Test( + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), + ) + func commandFailsSilentlyWhenFetchingPluginFails( + data: BuildData, + ) async throws { + try await fixture(name: "Miscellaneous/Plugins/MySourceGenPlugin") { fixturePath in // Contains only build-tool-plugins, therefore would not appear in available plugin commands. + let (stdout, _) = try await execute( + ["--help"], + packagePath: fixturePath, + configuration: data.config, + buildSystem: data.buildSystem, + ) + + #expect(!stdout.contains("AVAILABLE PLUGIN COMMANDS:")) + #expect(!stdout.contains("MySourceGenBuildToolPlugin")) + #expect(!stdout.contains("MySourceGenPrebuildPlugin")) + } + } + + // Have to create empty package, as in CI, --help is invoked on swiftPM, causing test to fail + @Test( + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), + ) + func commandDisplaysNoAvailablePluginCommands( + data: BuildData + ) async throws { + try await testWithTemporaryDirectory { tmpPath in + + let packageDir = tmpPath.appending(components: "MyPackage") + + try localFileSystem.writeFileContents( + packageDir.appending(components: "Package.swift"), + string: + """ + // swift-tools-version: 5.9 + // The swift-tools-version declares the minimum version of Swift required to build this package. + + import PackageDescription + + let package = Package( + name: "foo" + ) + """ + ) + let (stdout, _) = try await execute( + ["--help"], + packagePath: packageDir, + configuration: data.config, + buildSystem: data.buildSystem, + ) + #expect(!stdout.contains("AVAILABLE PLUGIN COMMANDS:")) + } + } + + @Test( + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), + ) + func commandDisplaysAvailablePluginCommands( + data: BuildData + ) async throws { + try await testWithTemporaryDirectory { tmpPath in + // Create a sample package with a library target, a plugin, and a local tool. It depends on a sample package which also has a tool. + let packageDir = tmpPath.appending(components: "MyPackage") + try localFileSystem.writeFileContents( + packageDir.appending(components: "Package.swift"), + string: + """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "MyPackage", + targets: [ + .plugin( + name: "MyPlugin", + capability: .command( + intent: .custom(verb: "mycmd", description: "What is mycmd anyway?") + ), + dependencies: [ + .target(name: "LocalBuiltTool"), + ] + ), + .executableTarget( + name: "LocalBuiltTool" + ) + ] + ) + """ + ) + + try localFileSystem.writeFileContents( + packageDir.appending(components: "Sources", "LocalBuiltTool", "main.swift"), + string: #"print("Hello")"# + ) + try localFileSystem.writeFileContents( + packageDir.appending(components: "Plugins", "MyPlugin", "plugin.swift"), + string: """ + import PackagePlugin + import Foundation + @main + struct MyCommandPlugin: CommandPlugin { + func performCommand( + context: PluginContext, + arguments: [String] + ) throws { + print("This is MyCommandPlugin.") + } + } + """ + ) + + let (stdout, _) = try await execute( + ["--help"], + packagePath: packageDir, + configuration: data.config, + buildSystem: data.buildSystem, + ) + + #expect(stdout.contains("AVAILABLE PLUGIN COMMANDS:")) + #expect(stdout.contains("mycmd")) + #expect(stdout.contains("(plugin ‘MyPlugin’ in package ‘MyPackage’)")) + } + } + @Test( .tags( .Feature.Command.Package.CompletionTool,