Hi everyone. I'm trying to become more familiar with SPM and have started exploring the idea of building a small "SMP modularized project" command-line tool. I got stuck on a step where I am trying to use Process to basically execute swift package tools-version --set
and swift package add-target
.
The add-target command hangs when I run it from the command line, but it works as expected when I include the add-target launch argument and run the same command from Xcode.
The tools version is working well in both Xcode and the command line. I've created a sample repository GitHub - JozefLipovsky/CmdToolExample that hopefully better demonstrates what I'm trying to explain here. I would appreciate any advice.
I have a simple extension on ParsableCommand:
import ArgumentParser
import Foundation
extension ParsableCommand {
func execute(arguments: [String]) throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/swift")
process.arguments = arguments
print("\(String(describing: type(of: self))) executing arguments: \(arguments.joined(separator: " "))")
try process.run()
process.waitUntilExit()
print("\(String(describing: type(of: self))) finished executing")
}
}
And then these 2 sub-commands as an example:
import ArgumentParser
import Foundation
struct UpdateToolsVersion: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "update-tools-version",
abstract: "Updates Swift.package swift-tools-version"
)
mutating func run() throws {
try execute(arguments: ["package", "tools-version", "--set", "5.9"])
}
}
import ArgumentParser
import Foundation
struct AddTarget: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "add-target",
abstract: "Adds a new target in Swift.package"
)
mutating func run() throws {
try execute(arguments: ["package", "add-target", "NewTarget"])
}
}
With the main command:
import ArgumentParser
public struct Commands: ParsableCommand {
public static let configuration = CommandConfiguration(
commandName: "cmd-tool",
abstract: "A Swift command-line tool example.",
shouldDisplay: true,
subcommands: [
UpdateToolsVersion.self,
AddTarget.self
]
)
public init() {}
}
Package manifest:
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CmdTool",
platforms: [
.macOS(.v13)
],
products: [
.plugin(
name: "CmdToolPlugin",
targets: [
"CmdToolPlugin"
]
),
.executable(
name: "CmdToolExecutable",
targets: [
"CmdToolExecutable"
]
),
],
dependencies: [
.package(
url: "https://github.com/apple/swift-argument-parser.git",
from: "1.2.0"
)
],
targets: [
.plugin(
name: "CmdToolPlugin",
capability: .command(
intent: .custom(
verb: "cmd-tool",
description: "A CMD tool example."
),
permissions: [
.writeToPackageDirectory(reason: "Update SMP project config.")
]
),
dependencies: [
"CmdToolExecutable"
]
),
.executableTarget(
name: "CmdToolExecutable",
dependencies: [
"Commands"
]
),
.target(
name: "Commands",
dependencies: [
.product(
name: "ArgumentParser",
package: "swift-argument-parser"
),
]
),
]
)
I am assuming the issue is how the commands are executed directly when I build from Xcode with the scheme launch argument vs how they are executed via CmdToolPlugin
from the command line
import PackagePlugin
import Foundation
@main
struct CmdToolPlugin: CommandPlugin {
func performCommand(context: PluginContext, arguments: [String]) async throws {
let tool = try context.tool(named: "CmdToolExecutable")
let process = Process()
process.executableURL = tool.url
process.arguments = arguments
print("\(String(describing: type(of: self))) performCommand arguments: \(arguments.joined(separator: " "))")
try process.run()
process.waitUntilExit()
let gracefulExit = process.terminationReason == .exit && process.terminationStatus == 0
if !gracefulExit {
let reason = process.terminationReason.rawValue
let status = process.terminationStatus
throw "\(String(describing: type(of: self))) failed - reason: \(reason) - status: \(status)"
}
print("\(String(describing: type(of: self))) finished performCommand")
}
}
I'm still confused about how UpdateToolsVersion
works in both cases, and AddTarget
always gets stuck in the command line. Do you have any idea what I might be doing wrong?