mirror of
https://github.com/xtool-org/xtool.git
synced 2026-10-23 08:15:10 +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 -->
288 lines
8.9 KiB
Swift
288 lines
8.9 KiB
Swift
import Foundation
|
|
import XKit
|
|
import Version
|
|
import ArgumentParser
|
|
import Dependencies
|
|
import PackLib
|
|
import XUtils
|
|
import Subprocess
|
|
|
|
struct SDKCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "sdk",
|
|
abstract: "Manage the Darwin Swift SDK",
|
|
subcommands: [
|
|
DevSDKInstallCommand.self,
|
|
DevSDKUpdateCommand.self,
|
|
DevSDKRemoveCommand.self,
|
|
DevSDKBuildCommand.self,
|
|
DevSDKStatusCommand.self,
|
|
],
|
|
defaultSubcommand: DevSDKInstallCommand.self
|
|
)
|
|
}
|
|
|
|
struct DevSDKBuildCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "build",
|
|
abstract: "Build the Darwin SDK from Xcode.xip"
|
|
)
|
|
|
|
@Argument(
|
|
help: "Path to Xcode.xip or Xcode.app",
|
|
completion: .file(extensions: ["xip", "app"])
|
|
)
|
|
var path: String
|
|
|
|
@Argument(
|
|
help: "Output directory"
|
|
)
|
|
var outputDir: String
|
|
|
|
@Option(
|
|
help: ArgumentHelp(
|
|
"The architecture of the Linux host the SDK is being built for.",
|
|
discussion: "Defaults to 'auto', which attempts to match the current host architecture."
|
|
)
|
|
) var arch: ArchSelection = .auto
|
|
|
|
func run() async throws {
|
|
let builderArch = try arch.sdkBuilderArch
|
|
let input = try SDKBuilder.Input(path: path)
|
|
let output = URL(fileURLWithPath: outputDir, isDirectory: true).appending(path: "darwin.xtoolsdk")
|
|
let builder = SDKBuilder(input: input, output: output, arch: builderArch, mode: .buildSlim)
|
|
try await builder.buildSDK()
|
|
print("Built SDK at \(output.path). You can install it with `xtool sdk install`.")
|
|
}
|
|
}
|
|
|
|
enum ArchSelection: String, ExpressibleByArgument {
|
|
case auto
|
|
case x86_64
|
|
case arm64
|
|
|
|
var sdkBuilderArch: SDKBuilder.Arch {
|
|
get throws {
|
|
switch self {
|
|
case .auto:
|
|
#if arch(arm64)
|
|
.aarch64
|
|
#elseif arch(x86_64)
|
|
.x86_64
|
|
#else
|
|
throw Console.Error("Could not auto-detect target architecture. Please specify one with '--arch'.")
|
|
#endif
|
|
case .arm64: .aarch64
|
|
case .x86_64: .x86_64
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DevSDKInstallCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "install",
|
|
abstract: "Install the Darwin Swift SDK"
|
|
)
|
|
|
|
@Argument(
|
|
help: "Path to Xcode.xip, Xcode.app, or darwin.xtoolsdk",
|
|
completion: .file(extensions: ["xip", "app", "xtoolsdk"])
|
|
)
|
|
var path: String
|
|
|
|
@Flag(
|
|
help: "Install a slim SDK (uses less disk space). Slim SDKs cannot be updated in place."
|
|
)
|
|
var slim = false
|
|
|
|
func run() async throws {
|
|
try await InstallSDKOperation(path: path, slim: slim).run()
|
|
}
|
|
}
|
|
|
|
struct DevSDKUpdateCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "update",
|
|
abstract: "Update the installed Darwin Swift SDK"
|
|
)
|
|
|
|
func run() async throws {
|
|
try await UpdateSDKOperation().run()
|
|
}
|
|
}
|
|
|
|
struct DevSDKRemoveCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "remove",
|
|
abstract: "Remove the Darwin Swift SDK"
|
|
)
|
|
|
|
func run() async throws {
|
|
guard let sdk = try DarwinSDK.current() else {
|
|
throw Console.Error("Cannot remove SDK: no Darwin SDK installed")
|
|
}
|
|
try sdk.remove()
|
|
print("Uninstalled SDK")
|
|
}
|
|
}
|
|
|
|
struct DevSDKStatusCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "status",
|
|
abstract: "Get the status of the Darwin Swift SDK"
|
|
)
|
|
|
|
func run() async throws {
|
|
if let sdk = try DarwinSDK.current() {
|
|
print("Darwin SDK is installed")
|
|
print(" Path: \(sdk.bundle.path)")
|
|
print(" Flavor: \(sdk.flavor)")
|
|
print(" Version: \(sdk.version)")
|
|
} else {
|
|
print("Not installed")
|
|
}
|
|
}
|
|
}
|
|
|
|
extension DarwinSDK {
|
|
func isUpToDate() -> Bool {
|
|
version == SDKBuilder.currentSDKVersion
|
|
}
|
|
}
|
|
|
|
struct EnsureSDKOperation {
|
|
let quiet: Bool
|
|
|
|
func run() async throws {
|
|
#if os(macOS)
|
|
if !quiet {
|
|
print("Skipping Darwin SDK setup since we're on macOS.")
|
|
}
|
|
#else
|
|
let sdk = try DarwinSDK.current()
|
|
switch sdk.map({ ($0.isUpToDate(), $0.flavor) }) {
|
|
case (true, _)?: // swiftlint:disable:this optional_enum_case_matching
|
|
if !quiet {
|
|
print("Darwin SDK is up to date.")
|
|
}
|
|
case (false, .slim)?: // swiftlint:disable:this optional_enum_case_matching
|
|
throw Console.Error("""
|
|
Darwin SDK is out of date, and was installed in 'slim' mode.
|
|
|
|
Slim SDKs take less disk space, but can't be auto-updated.
|
|
Please install a new SDK with
|
|
xtool sdk install [--slim]
|
|
""")
|
|
case (false, .normal)?: // swiftlint:disable:this optional_enum_case_matching
|
|
print("Darwin SDK is out of date. Rebuilding...")
|
|
try await UpdateSDKOperation().run()
|
|
case (false, .legacy)?: // swiftlint:disable:this optional_enum_case_matching
|
|
print("""
|
|
Darwin SDK is incompatible: built with an older version of xtool.
|
|
|
|
Requesting re-install. This is a one-time rebuild; after this,
|
|
xtool will be able to resolve incompatibilities automatically.
|
|
|
|
""")
|
|
try await generateSDK()
|
|
case nil:
|
|
print("Now generating the Darwin SDK.\n")
|
|
try await generateSDK()
|
|
}
|
|
|
|
func generateSDK() async throws {
|
|
let path = try await Console.prompt("""
|
|
Please download Xcode from http://developer.apple.com/download/all/?q=Xcode
|
|
and enter the path to the downloaded Xcode.xip.
|
|
|
|
Path to Xcode.xip: \("" /* pacify swiftlint trailing_whitespace */)
|
|
""")
|
|
|
|
let expanded = (path as NSString).expandingTildeInPath
|
|
|
|
try await InstallSDKOperation(path: expanded).run()
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
struct InstallSDKOperation {
|
|
let path: String
|
|
let slim: Bool
|
|
|
|
init(path: String, slim: Bool = false) {
|
|
self.path = path
|
|
self.slim = slim
|
|
}
|
|
|
|
func run() async throws {
|
|
#if os(macOS)
|
|
print("Skipping SDK install; the iOS SDK ships with Xcode on macOS")
|
|
#else
|
|
|
|
let temporaryBundle = try DarwinSDK.prepareTemporaryBundle()
|
|
|
|
if path.hasSuffix(".xtoolsdk") {
|
|
print("Installing prebuilt SDK...")
|
|
try await FileManager.default.copyItem(at: URL(filePath: path), to: temporaryBundle.url, preserveOwner: false)
|
|
} else {
|
|
// validate input before removing existing SDK
|
|
let input = try SDKBuilder.Input(path: path)
|
|
let arch = try ArchSelection.auto.sdkBuilderArch
|
|
|
|
let mode: SDKBuilder.Mode = slim ? .buildSlim : .buildNormal
|
|
let builder = SDKBuilder(input: input, output: temporaryBundle.url, arch: arch, mode: mode)
|
|
try await builder.buildSDK()
|
|
}
|
|
|
|
if let sdk = try DarwinSDK.current() {
|
|
print("Removing existing SDK...")
|
|
try sdk.remove()
|
|
}
|
|
|
|
try await temporaryBundle.install()
|
|
#endif
|
|
}
|
|
}
|
|
|
|
struct UpdateSDKOperation {
|
|
func run() async throws {
|
|
#if os(macOS)
|
|
print("Skipping SDK install; the iOS SDK ships with Xcode on macOS")
|
|
#else
|
|
guard let existing = try DarwinSDK.current() else {
|
|
throw Console.Error("Could not locate existing SDK; cannot perform update.")
|
|
}
|
|
let xcode = existing.bundle.appendingPathComponent("Xcode.app")
|
|
guard xcode.dirExists else {
|
|
// This includes prebuilt .xtoolsdk installs and installs created with --slim.
|
|
throw Console.Error("""
|
|
The installed SDK was built in 'slim' mode and cannot be updated in place. \
|
|
Please install a new copy with `xtool sdk install`.
|
|
""")
|
|
}
|
|
|
|
let input = try SDKBuilder.Input(path: xcode.path)
|
|
let arch = try ArchSelection.auto.sdkBuilderArch
|
|
|
|
let temporaryBundle = try DarwinSDK.prepareTemporaryBundle()
|
|
let builder = SDKBuilder(input: input, output: temporaryBundle.url, arch: arch, mode: .update)
|
|
try await builder.buildSDK()
|
|
|
|
guard DarwinSDK(bundle: temporaryBundle.url) != nil else {
|
|
throw Console.Error("Invalid Darwin SDK at '\(temporaryBundle.url.path)'")
|
|
}
|
|
|
|
try FileManager.default.moveItem(
|
|
at: xcode,
|
|
to: temporaryBundle.url.appendingPathComponent("Xcode.app")
|
|
)
|
|
try existing.remove()
|
|
try await temporaryBundle.install()
|
|
|
|
print("Updated SDK")
|
|
#endif
|
|
}
|
|
}
|