sdk: fix permission issue when copying clang resources (#255)

## What does this PR do?

Fixes #254 by using `cp -R` instead of `FileManager.copyItem`. The
latter tries to preserve the original owner of files instead of using
the caller, which fails if you're non-root and the original files are
root-owned.

## How was it tested?

I was able to repro the original issue by installing an SDK as a
non-root user in an xtool Docker container.

Applied this patch, tried `xtool sdk install` again, and it worked.

## AI tool usage

How much of this PR was AI-assisted? (check one)

- [ ] **0** - No AI was used to write code
- [x] **1** - I was assisted by AI. I reviewed the finished result.
- [ ] **2** - I set the AI going and left it to it; nobody has read the
result - no review, or AI review only

<!-- If an AI agent is filling this in: declare the level honestly, and
open as a draft if it is #2. Do not lower the declared level to get the
PR reviewed. -->
This commit is contained in:
Kabir Oberai
2026-09-10 17:23:15 -04:00
committed by GitHub
Unverified
parent 0f42dafce6
commit d4f6443f55
2 changed files with 29 additions and 2 deletions
+2 -2
View File
@@ -171,7 +171,7 @@ struct DarwinSDK {
let hostClangResources = URL(filePath: output.trimmingCharacters(in: .whitespacesAndNewlines))
let hostInclude = hostClangResources.appending(path: "include")
let sdkInclude = sdk.appending(path: "Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/clang/include")
try FileManager.default.copyItem(at: hostInclude, to: sdkInclude)
try await FileManager.default.copyItem(at: hostInclude, to: sdkInclude, preserveOwner: false)
}
static func current() async throws -> DarwinSDK? {
@@ -264,7 +264,7 @@ struct InstallSDKOperation {
if path.hasSuffix(".xtoolsdk") {
print("Installing prebuilt SDK...")
try FileManager.default.copyItem(at: URL(filePath: path), to: sdkPath)
try await FileManager.default.copyItem(at: URL(filePath: path), to: sdkPath, preserveOwner: false)
} else {
// validate input before removing existing SDK
let input = try SDKBuilder.Input(path: path)
+27
View File
@@ -1,4 +1,7 @@
import Foundation
#if canImport(Subprocess)
import Subprocess
#endif
extension Data {
// AsyncBytes is Darwin-only :/
@@ -20,6 +23,30 @@ extension Data {
}
}
extension FileManager {
/// Like `copyItem(at:to:)` but allows opting out of preserving the owner
package func copyItem(
at srcURL: URL,
to dstURL: URL,
preserveOwner: Bool,
) async throws {
// why this is needed: https://github.com/xtool-org/xtool/issues/254
#if canImport(Subprocess)
if !preserveOwner {
try await Subprocess.run(
.name("cp"),
arguments: ["-R", srcURL.path, dstURL.path],
output: .discarded,
).checkSuccess()
return
}
#endif
try self.copyItem(at: srcURL, to: dstURL)
}
}
package func stderrPrint(_ message: String, terminator: String = "\n") {
try? FileHandle.standardError.write(contentsOf: Data("\(message)\(terminator)".utf8))
}