diff --git a/Examples/DemoSimple/Sources/DemoSimple/App.swift b/Examples/DemoSimple/Sources/DemoSimple/App.swift index 1ecb12cf..29e95508 100644 --- a/Examples/DemoSimple/Sources/DemoSimple/App.swift +++ b/Examples/DemoSimple/Sources/DemoSimple/App.swift @@ -1,4 +1,26 @@ import ExampleSupport import SwiftUI -@main struct DemoApp: ExampleApp {} +@main struct DemoApp: App { + public var body: some Scene { + WindowGroup { + ContentView() + } + } +} + +private struct ContentView: View { + @State var counter = 0 + + var body: some View { + VStack { + Button("Counter = \(counter)") { + counter += 1 + } + } + .padding() + .task { + Example.pass() + } + } +} diff --git a/Sources/XToolSupport/SDKBuilder.swift b/Sources/XToolSupport/SDKBuilder.swift index 3e0fb83d..245c5075 100644 --- a/Sources/XToolSupport/SDKBuilder.swift +++ b/Sources/XToolSupport/SDKBuilder.swift @@ -80,14 +80,8 @@ struct SDKBuilder { // tag from https://github.com/xtool-org/darwin-tools-linux-llvm static let darwinToolsVersion = "1.1.0" - // tag from https://github.com/xtool-org/OpenAppleMacros - static let oamVersion = "1.3.0" - static let oamLibraries: [String] = [ - "FoundationModelsMacros", - "PreviewsMacros", - "SwiftUIMacros", - ] + static let oamVersion = "1.4.1" static var currentSDKVersion: String { """ @@ -238,22 +232,21 @@ struct SDKBuilder { .write(to: output.appendingPathComponent("darwin-sdk-version.txt")) } - // swiftlint:disable:next function_body_length - private func installToolset(in output: URL) async throws { - let toolsetDir = output.appendingPathComponent("toolset") - + private func fetchTarball( + url: URL, + destination directory: URL, + displayName: String, + ) async throws { try FileManager.default.createDirectory( - at: toolsetDir, + at: directory, withIntermediateDirectories: false ) @Dependency(\.httpClient) var httpClient - let url = URL(string: """ - https://github.com/xtool-org/darwin-tools-linux-llvm/releases/download/\ - v\(Self.darwinToolsVersion)/toolset-\(arch.rawValue).tar.gz - """)! let (response, body) = try await httpClient.send(HTTPRequest(url: url)) - guard response.status == 200, let body else { throw Console.Error("Could not fetch toolset") } + guard response.status == 200, let body else { + throw Console.Error("Could not fetch tarball at \(url)") + } let length: Int64? = switch body.length { case .known(let known): known case .unknown: nil @@ -263,7 +256,7 @@ struct SDKBuilder { try await Subprocess.run( .name("tar"), arguments: ["xzf", "-"], - workingDirectory: FilePath(toolsetDir), + workingDirectory: FilePath(directory), input: .inputWriter, output: .discarded, error: .discarded, @@ -278,13 +271,26 @@ struct SDKBuilder { guard let length else { continue } let progress = Int(Double(totalWritten) / Double(length) * 100) - print("\r[Downloading toolset] \(progress)%", terminator: "") + print("\r[Downloading \(displayName)] \(progress)%", terminator: "") fflush(stdoutSafe) } } try await execution.standardInputWriter.finish() } .checkSuccess() + } + + private func installToolset(in output: URL) async throws { + let toolsetDir = output.appendingPathComponent("toolset") + let url = URL(string: """ + https://github.com/xtool-org/darwin-tools-linux-llvm/releases/download/\ + v\(Self.darwinToolsVersion)/toolset-\(arch.rawValue).tar.gz + """)! + try await fetchTarball( + url: url, + destination: toolsetDir, + displayName: "toolset", + ) // swift-build assumes we have an Apple-flavored librarian for Apple platforms // (https://github.com/swiftlang/swift-build/blob/b2433e74e/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift#L1735) @@ -391,43 +397,40 @@ struct SDKBuilder { } 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) + v\(Self.oamVersion)/OpenAppleMacrosServer-\(arch.rawValue).tar.gz """)! + let macrosDir = output.appendingPathComponent("OpenAppleMacros") + try await fetchTarball( + url: url, + destination: macrosDir, + displayName: "OpenAppleMacros", + ) - 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 + struct MacrosInfo: Decodable { + var plugins: [String] } - - 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) - } - } + let macrosInfo = try JSONDecoder().decode( + MacrosInfo.self, + from: Data(contentsOf: macrosDir.appendingPathComponent("info.json")) + ) + try FileManager.default.moveItem( + at: macrosDir.appendingPathComponent("OpenAppleMacrosServer"), + to: output.appendingPathComponent("OpenAppleMacrosServer") + ) + try FileManager.default.removeItem(at: macrosDir) + + let pluginsDir = output.appendingPathComponent("OpenAppleMacrosPlugins") + try FileManager.default.createDirectory(at: pluginsDir, withIntermediateDirectories: false) + // 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 macrosInfo.plugins { + let target = pluginsDir.appendingPathComponent("lib\(library).so") + try Data().write(to: target) } - print() } private static let expectedXcodeChildren = ["Info.plist", "version.plist", "Developer"] @@ -611,27 +614,12 @@ struct SDKBuilder { 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) - } - } + try FileManager.default.createDirectory(at: hostDir, withIntermediateDirectories: true) + try? FileManager.default.removeItem(at: pluginsDir) + try FileManager.default.createSymbolicLink( + atPath: pluginsDir.path, + withDestinationPath: "../../../../../../../../OpenAppleMacrosPlugins" + ) } // we copy over the host's clang resorurce dir during install, because intrinsics can differ