mirror of
https://github.com/xtool-org/xtool.git
synced 2026-10-12 23:49:20 +02:00
## What does this PR do? Adds support for SwiftBuild on Swift 6.4. We now point SwiftBuild to iPhoneOS.platform and rely on the real [SWBApplePlatform](https://github.com/swiftlang/swift-build/tree/b30ee3c00abf34857084c26bb873c3da06348134/Sources/SWBApplePlatform). We also do a little futzing to convince SWB to use our Darwin-compatible toolset (lld, librarian, resource dirs). We explicitly _don't_ pass in a Swift SDK because that would downgrade SWB to using one of the generic platform plugins instead of ApplePlatform. This also gets us a lot closer to parity with Xcode's behavior when building for Apple platforms; we get the same build flags and everything. We can probably achieve identical bundling too, if we take control of the PIF. One other change: since we need to pass custom env vars and such, we can't rely on sourcekit-lsp's built-in SwiftPM integration anymore. We thus create a custom BSP server that trampolines to SwiftPM's build server with the right flags set. This effectively makes SwiftPM an implementation detail, so we can start messing with the PIF. ## How was it tested? - [x] Build with Swift 6.3 on macOS - [x] Build with Swift 6.4 on macOS - [x] Build with Swift 6.3 on Linux - [x] Build with Swift 6.4 on Linux - [x] BSP/LSP file creation works - [x] LSP support works in VSCode ## AI tool usage How much of this PR was AI-assisted? (check one) - [ ] **0** - No AI was used to write code - [x] **1** - I was assisted by AI. I reviewed the finished result. - [ ] **2** - I set the AI going and left it to it; nobody has read the result - no review, or AI review only <!-- If an AI agent is filling this in: declare the level honestly, and open as a draft if it is #2. Do not lower the declared level to get the PR reviewed. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a `dev build-server` command for Build Server Protocol integration. - Projects automatically select the appropriate Swift build system when supported. - Packaging supports output layouts for Swift Package Manager and Swift Build. - Added options to skip language-server configuration when needed. - **Documentation** - Updated project templates and tutorials to use `.bsp/xtool.json` for IDE features such as syntax highlighting and documentation lookup. - **Improvements** - Enhanced SDK generation and compatibility across supported toolchains and platforms. - Improved automatic language-server configuration while preserving existing configuration files. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
180 lines
5.5 KiB
Swift
180 lines
5.5 KiB
Swift
import ArgumentParser
|
|
import Foundation
|
|
import PackLib
|
|
|
|
struct NewCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "new",
|
|
abstract: "Create a new xtool SwiftPM project"
|
|
)
|
|
|
|
@Argument var name: String?
|
|
|
|
@Flag(
|
|
help: ArgumentHelp(
|
|
"Skip setup steps. (default: false)",
|
|
discussion: """
|
|
By default, this command first invokes `xtool setup` to complete \
|
|
any missing setup steps, like authenticating and installing the SDK. Use \
|
|
this flag to always skip setup.
|
|
"""
|
|
)
|
|
) var skipSetup = false
|
|
|
|
// swiftlint:disable:next function_body_length
|
|
func run() async throws {
|
|
if !skipSetup {
|
|
// perform any remaining setup steps first
|
|
try await SetupOperation(quiet: true).run()
|
|
}
|
|
|
|
let name = try await Console.promptRequired("Package name: ", existing: self.name)
|
|
|
|
var allowedFirstCharacters: CharacterSet = ["_"]
|
|
allowedFirstCharacters.insert(charactersIn: "a"..."z")
|
|
allowedFirstCharacters.insert(charactersIn: "A"..."Z")
|
|
|
|
var allowedOtherCharacters = allowedFirstCharacters
|
|
allowedOtherCharacters.insert(charactersIn: "0"..."9")
|
|
allowedOtherCharacters.insert("-")
|
|
|
|
// promptRequired validates !isEmpty
|
|
let firstScalar = name.unicodeScalars.first!
|
|
guard allowedFirstCharacters.contains(firstScalar) else {
|
|
throw Console.Error("""
|
|
Package name '\(name)' is invalid. \
|
|
The package name must start with one of [a-z, A-Z, _]. Found '\(firstScalar)'.
|
|
""")
|
|
}
|
|
|
|
if let firstInvalid = name.rangeOfCharacter(from: allowedOtherCharacters.inverted) {
|
|
let invalidValue = name[firstInvalid]
|
|
throw Console.Error("""
|
|
Package name '\(name)' is invalid. \
|
|
The package name may only contain [a-z, A-Z, 0-9, _, -]. Found '\(invalidValue)'.
|
|
""")
|
|
}
|
|
|
|
let baseURL = URL(fileURLWithPath: name)
|
|
|
|
guard !baseURL.exists else {
|
|
throw Console.Error("Cannot create \(name): a file already exists at that path.")
|
|
}
|
|
|
|
print("Creating package: \(name)")
|
|
|
|
let moduleName = name.replacingOccurrences(of: "-", with: "_")
|
|
|
|
let files: [(String, String)] = [
|
|
(
|
|
"Package.swift",
|
|
"""
|
|
// swift-tools-version: 6.0
|
|
|
|
import PackageDescription
|
|
|
|
let package = Package(
|
|
name: "\(name)",
|
|
platforms: [
|
|
.iOS(.v17),
|
|
.macOS(.v14),
|
|
],
|
|
products: [
|
|
// An xtool project should contain exactly one library product,
|
|
// representing the main app.
|
|
.library(
|
|
name: "\(moduleName)",
|
|
targets: ["\(moduleName)"]
|
|
),
|
|
],
|
|
targets: [
|
|
.target(
|
|
name: "\(moduleName)"
|
|
),
|
|
]
|
|
)
|
|
"""
|
|
),
|
|
|
|
(
|
|
"xtool.yml",
|
|
"""
|
|
version: 1
|
|
bundleID: com.example.\(name)
|
|
"""
|
|
),
|
|
|
|
(
|
|
".gitignore",
|
|
"""
|
|
.DS_Store
|
|
/.build
|
|
/Packages
|
|
xcuserdata/
|
|
DerivedData/
|
|
.swiftpm/configuration/registries.json
|
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
.netrc
|
|
|
|
/xtool
|
|
"""
|
|
),
|
|
|
|
(
|
|
"Sources/\(moduleName)/\(moduleName)App.swift",
|
|
"""
|
|
import SwiftUI
|
|
|
|
@main
|
|
struct \(moduleName)App: App {
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ContentView()
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
),
|
|
|
|
(
|
|
"Sources/\(moduleName)/ContentView.swift",
|
|
"""
|
|
import SwiftUI
|
|
|
|
struct ContentView: View {
|
|
var body: some View {
|
|
VStack {
|
|
Image(systemName: "globe")
|
|
.imageScale(.large)
|
|
.foregroundStyle(.tint)
|
|
Text("Hello, world!")
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
"""
|
|
),
|
|
]
|
|
|
|
for (path, contents) in files {
|
|
let url = baseURL.appendingPathComponent(path)
|
|
try? FileManager.default.createDirectory(
|
|
at: url.deletingLastPathComponent(),
|
|
withIntermediateDirectories: true
|
|
)
|
|
print("Creating \(path)")
|
|
try "\(contents)\n".write(to: url, atomically: true, encoding: .utf8)
|
|
}
|
|
|
|
await LSPConfig.tryEnsure(
|
|
using: .default,
|
|
quiet: true,
|
|
currentDirectory: baseURL,
|
|
)
|
|
|
|
print("\nFinished generating project \(name). Next steps:")
|
|
print("- Enter the directory with `cd \(name)`")
|
|
print("- Build and run with `xtool dev`")
|
|
}
|
|
}
|