mirror of
https://github.com/xtool-org/xtool.git
synced 2026-09-29 13:12:14 +02:00
## What does this PR do? Allows xtool to auto-update the Darwin SDK format when there's a new Darwin toolset, or when we change the SDK format. Downstream this also allows us to auto-update when there's changes to OpenAppleMacros (#101). One callout: the disk footprint of `darwin.artifactbundle` is now ~7 GB instead of ~3 GB. Reason: `xtool sdk install /path/to/Xcode.[app|xip]` now performs a "normal" install by default, which preserves the entirety of `Xcode.app/Contents/Developer` — even the bits we don't currently use, like `AppleTVOS.platform`. This makes it so if we start using more components in the future, the user doesn't need to download Xcode again and rebuild the SDK themselves. You can still build an old-style SDK (containing just the components we need) using `xtool sdk install --slim` or the dedicated `xtool sdk build`. ## How was it tested? Tested all of the install modes. Confirmed that auto-updating works. Added integration tests in #252. ## 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 SDK flavor reporting for slim, normal, and legacy installations. - Added `sdk update` support to rebuild and install SDKs from an installed Xcode bundle. - Added a `--slim` option for SDK installation. - SDK status now displays installation path, flavor, and version. - SDK setup and build operations now ensure a suitable SDK is available automatically. - **Bug Fixes** - Improved temporary SDK bundle handling, validation, replacement, and cross-filesystem copying. - Copy failures now provide detailed diagnostic information. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
124 lines
4.9 KiB
Swift
124 lines
4.9 KiB
Swift
import Foundation
|
|
import XUtils
|
|
import Subprocess
|
|
import Superutils
|
|
|
|
public struct DarwinSDK {
|
|
package struct TemporaryBundle: ~Copyable {
|
|
package let url: URL
|
|
|
|
fileprivate init(url: URL) {
|
|
self.url = url
|
|
}
|
|
|
|
package consuming func install() async throws {
|
|
guard DarwinSDK(bundle: url) != nil else {
|
|
throw StringError("Invalid Darwin SDK at '\(url.path)'")
|
|
}
|
|
|
|
try await DarwinSDK.addHostClangResourceDir(to: url)
|
|
|
|
let destination = url.deletingLastPathComponent()
|
|
.appendingPathComponent("darwin.artifactbundle", isDirectory: true)
|
|
try FileManager.default.moveItem(at: url, to: destination)
|
|
}
|
|
|
|
deinit {
|
|
try? FileManager.default.removeItem(at: url)
|
|
}
|
|
}
|
|
|
|
public enum Flavor {
|
|
// can't be updated in place
|
|
case slim
|
|
// can be updated in place, includes a whole copy of Xcode.app
|
|
case normal
|
|
// from before the slim/normal split existed (version "develop")
|
|
case legacy
|
|
}
|
|
|
|
public let bundle: URL
|
|
public let version: String
|
|
public let flavor: Flavor
|
|
|
|
static func swiftPMDirectory(
|
|
environment: [String: String] = ProcessInfo.processInfo.environment,
|
|
homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser
|
|
) throws -> URL {
|
|
if let configurationDirectory = environment["XDG_CONFIG_HOME"] {
|
|
guard (configurationDirectory as NSString).isAbsolutePath else {
|
|
throw StringError("XDG_CONFIG_HOME must be an absolute path: '\(configurationDirectory)'")
|
|
}
|
|
return URL(fileURLWithPath: configurationDirectory, isDirectory: true)
|
|
.appendingPathComponent("swiftpm", isDirectory: true)
|
|
} else {
|
|
return homeDirectory.appendingPathComponent(".swiftpm", isDirectory: true)
|
|
}
|
|
}
|
|
|
|
private static func swiftSDKsDirectory(
|
|
environment: [String: String] = ProcessInfo.processInfo.environment,
|
|
homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser
|
|
) throws -> URL {
|
|
try swiftPMDirectory(environment: environment, homeDirectory: homeDirectory)
|
|
.appendingPathComponent("swift-sdks", isDirectory: true)
|
|
}
|
|
|
|
public init?(bundle: URL) {
|
|
self.bundle = bundle
|
|
if let version = try? Data(contentsOf: bundle.appendingPathComponent("darwin-sdk-version.txt")) {
|
|
self.version = String(decoding: version, as: UTF8.self)
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
} else if ["darwin.xtoolsdk", "darwin.artifactbundle", "darwin.artifactbundle.tmp"].contains(bundle.lastPathComponent) {
|
|
self.version = "develop"
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
if version == "develop" {
|
|
self.flavor = .legacy
|
|
} else if bundle.appendingPathComponent("Xcode.app").dirExists {
|
|
self.flavor = .normal
|
|
} else {
|
|
self.flavor = .slim
|
|
}
|
|
}
|
|
|
|
package static func prepareTemporaryBundle(
|
|
environment: [String: String] = ProcessInfo.processInfo.environment,
|
|
homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser
|
|
) throws -> TemporaryBundle {
|
|
let sdksDirectory = try swiftSDKsDirectory(environment: environment, homeDirectory: homeDirectory)
|
|
try FileManager.default.createDirectory(at: sdksDirectory, withIntermediateDirectories: true)
|
|
let bundle = sdksDirectory.appendingPathComponent("darwin.artifactbundle.tmp", isDirectory: true)
|
|
if FileManager.default.fileExists(atPath: bundle.path) {
|
|
try FileManager.default.removeItem(at: bundle)
|
|
}
|
|
return TemporaryBundle(url: bundle)
|
|
}
|
|
|
|
private static func addHostClangResourceDir(to sdk: URL) async throws {
|
|
let clangURL = try await ToolRegistry.locate("clang")
|
|
let process = try await Subprocess.run(
|
|
.path(FilePath(clangURL.path)),
|
|
arguments: ["-print-resource-dir"],
|
|
output: .string(limit: .max)
|
|
).checkSuccess()
|
|
let output = process.standardOutput ?? ""
|
|
let hostClangResources = URL(filePath: output.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
let hostInclude = hostClangResources.appending(path: "include")
|
|
let sdkInclude = sdk.appending(path: "Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/clang/include")
|
|
try await FileManager.default.copyItem(at: hostInclude, to: sdkInclude, preserveOwner: false)
|
|
}
|
|
|
|
public static func current() throws -> DarwinSDK? {
|
|
let bundle = try swiftSDKsDirectory().appendingPathComponent("darwin.artifactbundle", isDirectory: true)
|
|
guard bundle.dirExists else { return nil }
|
|
return DarwinSDK(bundle: bundle)
|
|
}
|
|
|
|
public func remove() throws {
|
|
try FileManager.default.removeItem(at: bundle)
|
|
}
|
|
}
|