From 7966aa72083b96ee04a3a65839a1c9d500d5b9be Mon Sep 17 00:00:00 2001 From: Kabir Oberai Date: Tue, 27 May 2025 18:14:29 +0530 Subject: [PATCH] Integrate OpenAppleMacros --- Sources/XToolSupport/SDKBuilder.swift | 126 ++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 20 deletions(-) diff --git a/Sources/XToolSupport/SDKBuilder.swift b/Sources/XToolSupport/SDKBuilder.swift index 8f2deed5..be4fb0b3 100644 --- a/Sources/XToolSupport/SDKBuilder.swift +++ b/Sources/XToolSupport/SDKBuilder.swift @@ -80,9 +80,16 @@ struct SDKBuilder { // tag from https://github.com/xtool-org/darwin-tools-linux-llvm static let darwinToolsVersion = "1.0.1" + // tag from https://github.com/xtool-org/OpenAppleMacros + static let oamVersion = "1.2.0" + static let oamLibraries: [String] = [ + "PreviewsMacros", + "SwiftUIMacros", + ] + static var currentSDKVersion: String { """ - epoch=\(sdkEpoch),darwinTools=\(darwinToolsVersion) + epoch=\(sdkEpoch),darwinTools=\(darwinToolsVersion),oam=\(oamVersion) """ } @@ -95,9 +102,11 @@ struct SDKBuilder { withIntermediateDirectories: true ) - // TODO: parallelize these two steps + // TODO: parallelize these three steps // we need to synchronize progress reporting though + try await installMacros(in: output) + try await installToolset(in: output) let dev = try await installDeveloper(in: output) @@ -246,6 +255,46 @@ struct SDKBuilder { .checkSuccess() } + private func installMacros(in output: URL) async throws { + @Dependency(\.httpClient) var httpClient + let url = URL(string: """ + https://github.com/xtool-org/OpenAppleMacros/releases/download/\ + v\(Self.oamVersion)/OpenAppleMacrosServer-\(arch.rawValue) + """)! + + let (response, body) = try await httpClient.send(HTTPRequest(url: url)) + guard response.status == 200, let body else { throw Console.Error("Could not fetch toolset") } + + let length: Int64? = switch body.length { + case .known(let known): known + case .unknown: nil + } + + let outputPath = output.appendingPathComponent("OpenAppleMacrosServer").path + do { + let handle = try FileDescriptor.open( + FilePath(outputPath), + .writeOnly, + options: .create, + permissions: [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute] + ) + defer { try? handle.close() } + + var written: Int64 = 0 + for try await chunk in body { + try handle.writeAll(chunk) + written += Int64(chunk.count) + + if let length { + let progress = Int(Double(written) / Double(length) * 100) + print("\r[Downloading OpenAppleMacros] \(progress)%", terminator: "") + fflush(stdoutSafe) + } + } + } + print() + } + // swiftlint:disable:next cyclomatic_complexity function_body_length private func installDeveloper(in output: URL) async throws -> URL { let dev = output.appendingPathComponent("Developer") @@ -365,42 +414,79 @@ struct SDKBuilder { print("[Finalizing SDKs]") - /* - XCTest and Testing.framework are located in *.platform/Developer/{Library/Frameworks,usr/lib} rather than inside - the SDK. These search paths are explicitly included when building tests, which presumably ensures that normal - applications don't accidentally link against them in production. - - SwiftPM makes no such affordances outside of macOS, so we add the usr/lib path as include/library search paths - in the SDK config, and symlink the frameworks into the SDKs (since there's no frameworkSearchPaths option). - While this drops a safeguard it's better than not having the testing libs at all. - */ - for platform in ["iPhoneOS", "MacOSX", "iPhoneSimulator"] { let lib = "../../../../../Library" - let dest = dev.appendingPathComponent(""" - Platforms/\(platform).platform/Developer/SDKs/\(platform).sdk\ - /System/Library/Frameworks - """).path + let platformDir = dev.appending(path: "Platforms/\(platform).platform") + + /* + XCTest and Testing.framework are located in *.platform/Developer/{Library/Frameworks,usr/lib} rather than inside + the SDK. These search paths are explicitly included when building tests, which presumably ensures that normal + applications don't accidentally link against them in production. + + SwiftPM makes no such affordances outside of macOS, so we add the usr/lib path as include/library search paths + in the SDK config, and symlink the frameworks into the SDKs (since there's no frameworkSearchPaths option). + While this drops a safeguard it's better than not having the testing libs at all. + */ + + let fwk = platformDir.appending(path: "Developer/SDKs/\(platform).sdk/System/Library/Frameworks").path try FileManager.default.createSymbolicLink( - atPath: "\(dest)/Testing.framework", + atPath: "\(fwk)/Testing.framework", withDestinationPath: "\(lib)/Frameworks/Testing.framework" ) try FileManager.default.createSymbolicLink( - atPath: "\(dest)/XCTest.framework", + atPath: "\(fwk)/XCTest.framework", withDestinationPath: "\(lib)/Frameworks/XCTest.framework" ) try FileManager.default.createSymbolicLink( - atPath: "\(dest)/XCUIAutomation.framework", + atPath: "\(fwk)/XCUIAutomation.framework", withDestinationPath: "\(lib)/Frameworks/XCUIAutomation.framework" ) try FileManager.default.createSymbolicLink( - atPath: "\(dest)/XCTestCore.framework", + atPath: "\(fwk)/XCTestCore.framework", withDestinationPath: "\(lib)/PrivateFrameworks/XCTestCore.framework" ) + + /* + Apply patches to load OpenAppleMacros as the swift-plugin-server + */ + + let bin = platformDir.appendingPathComponent("Developer/usr/bin") + let pluginServer = bin.appendingPathComponent("swift-plugin-server") + try? FileManager.default.createDirectory(at: bin, withIntermediateDirectories: true) + try? FileManager.default.removeItem(at: pluginServer) + try FileManager.default.createSymbolicLink( + atPath: pluginServer.path, + withDestinationPath: "../../../../../../OpenAppleMacrosServer" + ) + + let hostDir = platformDir.appendingPathComponent("Developer/usr/lib/swift/host") + let pluginsDir = hostDir.appendingPathComponent("plugins") + + if platform == "iPhoneSimulator" { + // The iPhoneSimulator platform doesn't contain plugins; Xcode seems to be hardcoded + // to look at the ones from iPhoneOS.platform. We can symlink the iPhoneOS ones too. + try? FileManager.default.createDirectory(at: hostDir, withIntermediateDirectories: true) + try? FileManager.default.removeItem(at: pluginsDir) + try FileManager.default.createSymbolicLink( + atPath: pluginsDir.path, + withDestinationPath: "../../../../../../iPhoneOS.platform/Developer/usr/lib/swift/host/plugins" + ) + } else { + try? FileManager.default.removeItem(at: pluginsDir) + try FileManager.default.createDirectory(at: pluginsDir, withIntermediateDirectories: true) + // create a lib*.so file for each macro 'library' that we support. + // the files are stubs (empty) because we just need them to convince swiftc that + // yes we can handle said modules. the actual logic lives in the OpenAppleMacros + // server. + for library in Self.oamLibraries { + let target = pluginsDir.appendingPathComponent("lib\(library).so") + try Data().write(to: target) + } + } } // we copy over the host's clang resorurce dir during install, because intrinsics can differ