Skip to content

Commit 95e3a6d

Browse files
committed
Add support for SPM packages
1 parent a7b1971 commit 95e3a6d

File tree

16 files changed

+804
-15
lines changed

16 files changed

+804
-15
lines changed

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [v1.7.0] - 2025-06-04
4+
- Added support for Swift Package Manager (SPM)
5+
- New tools for Swift Package Manager:
6+
- `swift_package_build`
7+
- `swift_package_clean`
8+
- `swift_package_test`
9+
- `swift_package_run`
10+
- `swift_package_list_processes`
11+
- `swift_package_stop`
12+
13+
## [v1.6.1] - 2025-06-03
14+
- Improve UI tool hints
15+
16+
## [v1.6.0] - 2025-06-03
417
- Moved project templates to external GitHub repositories for independent versioning
518
- Added support for downloading templates from GitHub releases
619
- Added local template override support via environment variables

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A Model Context Protocol (MCP) server that provides Xcode-related tools for inte
1212
- [Why?](#why)
1313
- [Features](#features)
1414
- [Xcode project management](#xcode-project-management)
15+
- [Swift Package Manager](#swift-package-manager)
1516
- [Simulator management](#simulator-management)
1617
- [App utilities](#app-utilities)
1718
- [Getting started](#getting-started)
@@ -64,6 +65,13 @@ The XcodeBuildMCP server provides the following tool capabilities:
6465
- **Incremental build support**: Lightning fast builds using incremental build support (experimental, opt-in required)
6566
- **Project Scaffolding**: Create new iOS and macOS projects from modern templates with workspace + SPM package architecture, customizable bundle identifiers, deployment targets, and device families
6667

68+
### Swift Package Manager
69+
- **Build Packages**: Build Swift packages with configuration and architecture options
70+
- **Run Tests**: Execute Swift package test suites with filtering and parallel execution
71+
- **Run Executables**: Execute package binaries with timeout handling and background execution support
72+
- **Process Management**: List and stop long-running executables started with Swift Package tools
73+
- **Clean Artifacts**: Remove build artifacts and derived data for fresh builds
74+
6775
### Simulator management
6876
- **Simulator Control**: List, boot, and open iOS simulators
6977
- **App Deployment**: Install and launch apps on iOS simulators

TOOL_OPTIONS.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ These groups organise tools based on common developer workflows, making it easie
3131
- _e.g., Building and deploying to physical iOS devices._
3232
- **XCODEBUILDMCP_GROUP_MACOS_WORKFLOW=true** - macOS application development workflow tools
3333
- _e.g., Building, running, debugging macOS applications._
34+
- **XCODEBUILDMCP_GROUP_SWIFT_PACKAGE_WORKFLOW=true** - Swift Package Manager development workflow tools
35+
- _e.g., Building, testing, and running Swift packages._
3436
- **XCODEBUILDMCP_GROUP_SIMULATOR_MANAGEMENT=true** - Simulator device management tools
3537
- _e.g., Managing simulator lifecycle (boot, open, set appearance)._
3638
- **XCODEBUILDMCP_GROUP_APP_DEPLOYMENT=true** - Application deployment tools
@@ -58,8 +60,13 @@ To enable specific tools rather than entire groups, use the following environmen
5860
- **XCODEBUILDMCP_TOOL_CLEAN_WORKSPACE=true** - Clean build products for an Xcode workspace.
5961
- **XCODEBUILDMCP_TOOL_CLEAN_PROJECT=true** - Clean build products for an Xcode project.
6062

61-
#### Swift Package Build
62-
- **XCODEBUILDMCP_TOOL_BUILD_SWIFT_PACKAGE=true** - Build a Swift package using `swift build`.
63+
#### Swift Package Tools
64+
- **XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_BUILD=true** - Build a Swift package using `swift build`.
65+
- **XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_TEST=true** - Run tests for a Swift package using `swift test`.
66+
- **XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_RUN=true** - Run an executable target from a Swift package using `swift run`.
67+
- **XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_STOP=true** - Stop a running Swift package executable by PID.
68+
- **XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_LIST=true** - List currently running Swift package processes.
69+
- **XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_CLEAN=true** - Clean Swift package build artifacts and derived data.
6370

6471
#### macOS Build & Run
6572
- **XCODEBUILDMCP_TOOL_MACOS_BUILD_WORKSPACE=true** - Build a macOS application from a workspace.

example_projects/spm/Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example_projects/spm/Package.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,36 @@ import PackageDescription
55

66
let package = Package(
77
name: "spm",
8+
platforms: [
9+
.macOS(.v15),
10+
],
11+
dependencies: [
12+
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.1"),
13+
],
814
targets: [
9-
// Targets are the basic building blocks of a package, defining a module or a test suite.
10-
// Targets can depend on other targets in this package and products from dependencies.
1115
.executableTarget(
12-
name: "spm"),
16+
name: "spm"
17+
),
18+
.executableTarget(
19+
name: "quick-task",
20+
dependencies: [
21+
"TestLib",
22+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
23+
]
24+
),
25+
.executableTarget(
26+
name: "long-server",
27+
dependencies: [
28+
"TestLib",
29+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
30+
]
31+
),
32+
.target(
33+
name: "TestLib"
34+
),
35+
.testTarget(
36+
name: "TestLibTests",
37+
dependencies: ["TestLib"]
38+
),
1339
]
1440
)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Foundation
2+
3+
public class TaskManager {
4+
private var isServerRunning = false
5+
6+
public init() {}
7+
8+
public func executeQuickTask(name: String, duration: Int, verbose: Bool) async {
9+
if verbose {
10+
print("📝 Task '\(name)' started at \(Date())")
11+
}
12+
13+
// Simulate work with periodic output using Swift Concurrency
14+
for i in 1...duration {
15+
if verbose {
16+
print("⚙️ Working... step \(i)/\(duration)")
17+
}
18+
try? await Task.sleep(for: .seconds(1))
19+
}
20+
21+
if verbose {
22+
print("🎉 Task '\(name)' completed at \(Date())")
23+
} else {
24+
print("Task '\(name)' completed in \(duration)s")
25+
}
26+
}
27+
28+
public func startLongRunningServer(port: Int, verbose: Bool, autoShutdown: Int) async {
29+
if verbose {
30+
print("🔧 Initializing server on port \(port)...")
31+
}
32+
33+
var secondsRunning = 0
34+
let startTime = Date()
35+
isServerRunning = true
36+
37+
// Simulate server startup
38+
try? await Task.sleep(for: .milliseconds(500))
39+
print("✅ Server running on port \(port)")
40+
41+
// Main server loop using Swift Concurrency
42+
while isServerRunning {
43+
try? await Task.sleep(for: .seconds(1))
44+
secondsRunning += 1
45+
46+
if verbose && secondsRunning % 5 == 0 {
47+
print("📊 Server heartbeat: \(secondsRunning)s uptime")
48+
}
49+
50+
// Handle auto-shutdown
51+
if autoShutdown > 0 && secondsRunning >= autoShutdown {
52+
if verbose {
53+
print("⏰ Auto-shutdown triggered after \(autoShutdown)s")
54+
}
55+
break
56+
}
57+
}
58+
59+
let uptime = Date().timeIntervalSince(startTime)
60+
print("🛑 Server stopped after \(String(format: "%.1f", uptime))s uptime")
61+
isServerRunning = false
62+
}
63+
64+
public func stopServer() {
65+
isServerRunning = false
66+
}
67+
68+
public func calculateSum(_ a: Int, _ b: Int) -> Int {
69+
return a + b
70+
}
71+
72+
public func validateInput(_ input: String) -> Bool {
73+
return !input.isEmpty && input.count <= 100
74+
}
75+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Foundation
2+
import TestLib
3+
import ArgumentParser
4+
5+
@main
6+
struct LongServer: AsyncParsableCommand {
7+
static let configuration = CommandConfiguration(
8+
commandName: "long-server",
9+
abstract: "A long-running server that runs indefinitely until stopped"
10+
)
11+
12+
@Option(name: .shortAndLong, help: "Port to listen on (default: 8080)")
13+
var port: Int = 8080
14+
15+
@Flag(name: .shortAndLong, help: "Enable verbose logging")
16+
var verbose: Bool = false
17+
18+
@Option(name: .shortAndLong, help: "Auto-shutdown after N seconds (0 = run forever)")
19+
var autoShutdown: Int = 0
20+
21+
func run() async throws {
22+
let taskManager = TaskManager()
23+
24+
if verbose {
25+
print("🚀 Starting long-running server...")
26+
print("🌐 Port: \(port)")
27+
if autoShutdown > 0 {
28+
print("⏰ Auto-shutdown: \(autoShutdown) seconds")
29+
} else {
30+
print("♾️ Running indefinitely (use SIGTERM to stop)")
31+
}
32+
}
33+
34+
// Set up signal handling for graceful shutdown
35+
let signalSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: .main)
36+
signalSource.setEventHandler {
37+
if verbose {
38+
print("\n🛑 Received SIGTERM, shutting down gracefully...")
39+
}
40+
taskManager.stopServer()
41+
}
42+
signalSource.resume()
43+
signal(SIGTERM, SIG_IGN)
44+
45+
await taskManager.startLongRunningServer(
46+
port: port,
47+
verbose: verbose,
48+
autoShutdown: autoShutdown
49+
)
50+
}
51+
}

example_projects/spm/Sources/main.swift

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
import TestLib
3+
import ArgumentParser
4+
5+
@main
6+
struct QuickTask: AsyncParsableCommand {
7+
static let configuration = CommandConfiguration(
8+
commandName: "quick-task",
9+
abstract: "A quick task that finishes within 5 seconds",
10+
version: "1.0.0"
11+
)
12+
13+
@Option(name: .shortAndLong, help: "Number of seconds to work (default: 3)")
14+
var duration: Int = 3
15+
16+
@Flag(name: .shortAndLong, help: "Enable verbose output")
17+
var verbose: Bool = false
18+
19+
@Option(name: .shortAndLong, help: "Task name to display")
20+
var taskName: String = "DefaultTask"
21+
22+
func run() async throws {
23+
let taskManager = TaskManager()
24+
25+
if verbose {
26+
print("🚀 Starting quick task: \(taskName)")
27+
print("⏱️ Duration: \(duration) seconds")
28+
}
29+
30+
await taskManager.executeQuickTask(name: taskName, duration: duration, verbose: verbose)
31+
32+
if verbose {
33+
print("✅ Quick task completed successfully!")
34+
}
35+
}
36+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("Hello, world!")

0 commit comments

Comments
 (0)