Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 104 additions & 19 deletions Sources/XToolSupport/SDKBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import XKit // HTTPClient, stdoutSafe
import XUtils // System.File{Path,Descriptor}

struct SDKBuilder {

Check warning on line 8 in Sources/XToolSupport/SDKBuilder.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Type body should span 500 lines or less excluding comments and whitespace: currently spans 582 lines (type_body_length)

Check warning on line 8 in Sources/XToolSupport/SDKBuilder.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Type body should span 500 lines or less excluding comments and whitespace: currently spans 582 lines (type_body_length)
enum Arch: String {
case x86_64
case aarch64
Expand Down Expand Up @@ -96,9 +96,11 @@
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)
Expand Down Expand Up @@ -374,7 +376,49 @@
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: ld64.path)
}

// swiftlint:disable:next cyclomatic_complexity
private func installMacros(in output: URL) async throws {
let oamVersion = "1.1.0"

@Dependency(\.httpClient) var httpClient
let url = URL(string: """
https://github.com/xtool-org/OpenAppleMacros/releases/download/\
v\(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")

Expand Down Expand Up @@ -493,43 +537,84 @@

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 platformDir = dev.appending(path: "Platforms/\(platform).platform")
let platformDir = dev.appendingPathComponent("Platforms/\(platform).platform")

try patchPlatformManifest(path: platformDir.appending(path: "Info.plist"))

let dest = platformDir.appending(path: "Developer/SDKs/\(platform).sdk/System/Library/Frameworks").path
/*
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.appendingPathComponent("""
Developer/SDKs/\(platform).sdk/System/Library/Frameworks
""").path

let lib = "../../../../../Library"

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 {
let plugins = try FileManager.default.contentsOfDirectory(at: pluginsDir, includingPropertiesForKeys: nil)
// remove the lib*.dylib plugins and create lib*.so files, which is what swiftc expects
// on Linux hosts. 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 plugin in plugins {
try FileManager.default.removeItem(at: plugin)
let name = plugin.deletingPathExtension().lastPathComponent
try Data().write(to: pluginsDir.appendingPathComponent("\(name).so"))
}
}
}

// we copy over the host's clang resorurce dir during install, because intrinsics can differ
Expand Down
Loading