From 6b1ad8ff01bb537964f5b61424e30d71ac3961f6 Mon Sep 17 00:00:00 2001 From: Mathieu Post Date: Sun, 11 Oct 2026 15:11:51 +0200 Subject: [PATCH 1/3] SDKBuilder: allow local toolset and OpenAppleMacrosServer (offline sdk build) xtool sdk build downloads darwin-tools-linux-llvm's toolset and the OpenAppleMacrosServer binary from GitHub releases. Builds without network (Nix and other sandboxed package builds, air-gapped machines) cannot do that. Two optional environment variables point at local copies instead: - XTOOL_TOOLSET_TARBALL: a toolset-.tar.gz, unpacked with tar - XTOOL_OAM_SERVER: an OpenAppleMacrosServer executable, copied (written as a new file, so a read-only source does not make the SDK copy read-only) Without them, nothing changes. --- Sources/XToolSupport/SDKBuilder.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/XToolSupport/SDKBuilder.swift b/Sources/XToolSupport/SDKBuilder.swift index 09af2dd7..fb58c39b 100644 --- a/Sources/XToolSupport/SDKBuilder.swift +++ b/Sources/XToolSupport/SDKBuilder.swift @@ -242,6 +242,15 @@ struct SDKBuilder { withIntermediateDirectories: false ) + if let local = ProcessInfo.processInfo.environment["XTOOL_TOOLSET_TARBALL"] { + let tar = Process() + // /bin/sh finds tar on PATH (the Nix build sandbox has no /usr/bin/env) + tar.executableURL = URL(fileURLWithPath: "/bin/sh") + tar.arguments = ["-c", "exec tar xzf \"$0\" -C \"$1\"", local, toolsetDir.path] + try tar.run() + tar.waitUntilExit() + guard tar.terminationStatus == 0 else { throw Console.Error("Could not unpack toolset \(local)") } + } else { @Dependency(\.httpClient) var httpClient let url = URL(string: """ https://github.com/xtool-org/darwin-tools-linux-llvm/releases/download/\ @@ -280,6 +289,7 @@ struct SDKBuilder { try await execution.standardInputWriter.finish() } .checkSuccess() + } // 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) @@ -386,6 +396,13 @@ struct SDKBuilder { } private func installMacros(in output: URL) async throws { + if let local = ProcessInfo.processInfo.environment["XTOOL_OAM_SERVER"] { + let dest = output.appendingPathComponent("OpenAppleMacrosServer") + // write a new file: a copy would keep the read-only mode of the Nix store + try Data(contentsOf: URL(fileURLWithPath: local)).write(to: dest) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: dest.path) + return + } @Dependency(\.httpClient) var httpClient let url = URL(string: """ https://github.com/xtool-org/OpenAppleMacros/releases/download/\ From fbe451d82994e566e6f2453921a5eb041bffd008 Mon Sep 17 00:00:00 2001 From: Mathieu Post Date: Tue, 13 Oct 2026 12:18:36 +0000 Subject: [PATCH 2/3] SDKBuilder: address review (Subprocess for tar, copy + chmod, no re-indent) - local toolset: Subprocess.run(tar) like the download path, no blocking Process wait - move the download into downloadToolset(into:), so the existing code keeps its indentation - local macro server: copyItem + setAttributes; resolve symlinks first, because copyItem copies a symlink as a symlink (a relative link then dangles in the SDK) - hoist outputPath and reuse it --- Sources/XToolSupport/SDKBuilder.swift | 50 +++++++++++++++------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Sources/XToolSupport/SDKBuilder.swift b/Sources/XToolSupport/SDKBuilder.swift index fb58c39b..93d4a537 100644 --- a/Sources/XToolSupport/SDKBuilder.swift +++ b/Sources/XToolSupport/SDKBuilder.swift @@ -234,23 +234,7 @@ struct SDKBuilder { } // swiftlint:disable:next function_body_length - private func installToolset(in output: URL) async throws { - let toolsetDir = output.appendingPathComponent("toolset") - - try FileManager.default.createDirectory( - at: toolsetDir, - withIntermediateDirectories: false - ) - - if let local = ProcessInfo.processInfo.environment["XTOOL_TOOLSET_TARBALL"] { - let tar = Process() - // /bin/sh finds tar on PATH (the Nix build sandbox has no /usr/bin/env) - tar.executableURL = URL(fileURLWithPath: "/bin/sh") - tar.arguments = ["-c", "exec tar xzf \"$0\" -C \"$1\"", local, toolsetDir.path] - try tar.run() - tar.waitUntilExit() - guard tar.terminationStatus == 0 else { throw Console.Error("Could not unpack toolset \(local)") } - } else { + private func downloadToolset(into toolsetDir: URL) async throws { @Dependency(\.httpClient) var httpClient let url = URL(string: """ https://github.com/xtool-org/darwin-tools-linux-llvm/releases/download/\ @@ -289,6 +273,27 @@ struct SDKBuilder { try await execution.standardInputWriter.finish() } .checkSuccess() + } + + private func installToolset(in output: URL) async throws { + let toolsetDir = output.appendingPathComponent("toolset") + + try FileManager.default.createDirectory( + at: toolsetDir, + withIntermediateDirectories: false + ) + + if let local = ProcessInfo.processInfo.environment["XTOOL_TOOLSET_TARBALL"] { + try await Subprocess.run( + .name("tar"), + arguments: ["xzf", local], + workingDirectory: FilePath(toolsetDir), + output: .discarded, + error: .discarded, + ) + .checkSuccess() + } else { + try await downloadToolset(into: toolsetDir) } // swift-build assumes we have an Apple-flavored librarian for Apple platforms @@ -396,11 +401,13 @@ struct SDKBuilder { } private func installMacros(in output: URL) async throws { + let outputPath = output.appendingPathComponent("OpenAppleMacrosServer").path if let local = ProcessInfo.processInfo.environment["XTOOL_OAM_SERVER"] { - let dest = output.appendingPathComponent("OpenAppleMacrosServer") - // write a new file: a copy would keep the read-only mode of the Nix store - try Data(contentsOf: URL(fileURLWithPath: local)).write(to: dest) - try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: dest.path) + // copyItem copies a symlink as a symlink (it may be relative): copy its target + let source = URL(fileURLWithPath: local).resolvingSymlinksInPath().path + try FileManager.default.copyItem(atPath: source, toPath: outputPath) + // the source may be read-only (e.g. in /nix/store); the copy keeps its mode + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: outputPath) return } @Dependency(\.httpClient) var httpClient @@ -417,7 +424,6 @@ struct SDKBuilder { case .unknown: nil } - let outputPath = output.appendingPathComponent("OpenAppleMacrosServer").path do { let handle = try FileDescriptor.open( FilePath(outputPath), From acc11fc7354fa856024146024cdea96437e53f17 Mon Sep 17 00:00:00 2001 From: Mathieu Post Date: Tue, 13 Oct 2026 12:52:56 +0000 Subject: [PATCH 3/3] SDKBuilder: make XTOOL_TOOLSET_TARBALL absolute (tar runs in toolsetDir) --- Sources/XToolSupport/SDKBuilder.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/XToolSupport/SDKBuilder.swift b/Sources/XToolSupport/SDKBuilder.swift index 93d4a537..59042163 100644 --- a/Sources/XToolSupport/SDKBuilder.swift +++ b/Sources/XToolSupport/SDKBuilder.swift @@ -284,9 +284,11 @@ struct SDKBuilder { ) if let local = ProcessInfo.processInfo.environment["XTOOL_TOOLSET_TARBALL"] { + // absolute: tar runs in toolsetDir, where a relative path would point + let tarball = URL(fileURLWithPath: local).standardizedFileURL.path try await Subprocess.run( .name("tar"), - arguments: ["xzf", local], + arguments: ["xzf", tarball], workingDirectory: FilePath(toolsetDir), output: .discarded, error: .discarded,