1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
public enum Xcode: Sendable {
/// Get the path to the active developer directory, if possible.
public static func getActiveDeveloperDirectoryPath() async throws -> Path {
if let env = getEnvironmentVariable("DEVELOPER_DIR")?.nilIfEmpty {
return Path(env)
}
let xcodeSelectPath = Path("/usr/bin/xcode-select")
if !localFS.exists(xcodeSelectPath) {
throw StubError.error("\(xcodeSelectPath.str) does not exist")
}
let environment = Environment.current.filter { $0.key == .developerDir }
let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: xcodeSelectPath.str), arguments: ["-p"], environment: environment)
if !executionResult.exitStatus.isSuccess {
throw RunProcessNonZeroExitError(args: [xcodeSelectPath.str, "-p"], workingDirectory: nil, environment: environment, status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr))
}
// If we got the location, extract the path.
return Path(String(decoding: executionResult.stdout, as: UTF8.self).trimmingCharacters(in: .newlines)).normalize()
}
}
public struct XcodeVersionInfo: Sendable {
public let shortVersion: Version
public let productBuildVersion: ProductBuildVersion?
/// Extracts the version info from a version.plist in an Xcode or Playgrounds installation at `versionPath`.
///
/// - Returns: A tuple of the short version and ProductBuildVersion, or `nil` if the Xcode at `appPath` does not have a version.plist. Note that the ProductBuildVersion may also be `nil` even if the version.plist was present, as it can be missing in some cases.
/// - Throws: If there was an error reading the version.plist file or parsing its contents.
public static func versionInfo(versionPath: Path, fs: any FSProxy = localFS) throws -> XcodeVersionInfo? {
struct VersionPlist: Decodable {
let shortVersionString: String
let productBuildVersion: String?
enum CodingKeys: String, CodingKey {
case shortVersionString = "CFBundleShortVersionString"
case productBuildVersion = "ProductBuildVersion"
}
}
guard fs.exists(versionPath) else {
return nil
}
let versionStrings: VersionPlist
do {
versionStrings = try PropertyListDecoder().decode(VersionPlist.self, from: Data(fs.read(versionPath).bytes))
} catch {
throw StubError.error("Failed to decode version plist at '\(versionPath.str)': \(error.localizedDescription)")
}
let (shortVersion, productBuildVersion) = try (Version(versionStrings.shortVersionString), versionStrings.productBuildVersion.map(ProductBuildVersion.init))
return XcodeVersionInfo(shortVersion: shortVersion, productBuildVersion: productBuildVersion)
}
}
|