mirror of
https://github.com/xtool-org/xtool.git
synced 2026-11-15 12:04:55 +03:00
2f2336cb5f
## What does this PR do? Small utils refactors to make subsequent PRs cleaner. - Minor tweaks to how we determine / handle the xtool build version - Factor out TemporaryDirectory cache location ## How was it tested? - [x] Cache directory logic is unchanged - [x] Checked `xtool --version` ## 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 is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Version information now distinguishes clean builds from builds with uncommitted changes, displaying a `-dirty` suffix when applicable. * Nonblocking file-locking operations are now available to external consumers. * **Changes** * Version reporting uses `XTOOL_VERSION` when set; otherwise, it uses available Git tag or commit information. * Temporary files now use the tool’s cache directory, honoring `XTL_TMPDIR` and platform cache settings. On Linux, the location follows `XDG_CACHE_HOME` when it is an absolute path; otherwise, the user cache directory is used. * `TMPDIR` is no longer used to select the temporary-file location. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
81 lines
2.4 KiB
Swift
81 lines
2.4 KiB
Swift
import Foundation
|
|
|
|
#if canImport(System)
|
|
import System
|
|
|
|
public typealias FilePath = System.FilePath
|
|
public typealias FileDescriptor = System.FileDescriptor
|
|
public typealias Errno = System.Errno
|
|
#else
|
|
import SystemPackage
|
|
|
|
public typealias FilePath = SystemPackage.FilePath
|
|
public typealias FileDescriptor = SystemPackage.FileDescriptor
|
|
public typealias Errno = SystemPackage.Errno
|
|
|
|
extension URL {
|
|
public init?(filePath: FilePath) {
|
|
self.init(filePath: filePath.string)
|
|
}
|
|
}
|
|
|
|
extension FilePath {
|
|
public init?(_ url: URL) {
|
|
guard url.isFileURL else { return nil }
|
|
self.init(url.path)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
extension FileDescriptor {
|
|
/// Writes every byte, waiting for capacity if the descriptor is nonblocking.
|
|
package func writeAllWaiting(_ bytes: UnsafeRawBufferPointer) throws {
|
|
var offset = 0
|
|
while offset < bytes.count {
|
|
do {
|
|
// Swift System retries EINTR; retain the offset when a write is partial.
|
|
let count = try write(UnsafeRawBufferPointer(rebasing: bytes[offset...]))
|
|
guard count > 0 else { throw Errno.ioError }
|
|
offset += count
|
|
} catch let error as Errno where error == .resourceTemporarilyUnavailable || error == .wouldBlock {
|
|
var descriptor = pollfd(fd: rawValue, events: Int16(POLLOUT), revents: 0)
|
|
while true {
|
|
try Task.checkCancellation()
|
|
// periodically wake up to check for cancellation
|
|
let result = poll(&descriptor, 1, 100)
|
|
if result > 0 { break }
|
|
if result == -1 {
|
|
let error = Errno(rawValue: errno)
|
|
guard error != .interrupted else { continue }
|
|
throw error
|
|
}
|
|
}
|
|
// Retry write to report terminal errors (including POLLERR/POLLNVAL).
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum LockMode {
|
|
case shared
|
|
case exclusive
|
|
|
|
fileprivate var raw: CInt {
|
|
switch self {
|
|
case .shared: LOCK_SH
|
|
case .exclusive: LOCK_EX
|
|
}
|
|
}
|
|
}
|
|
|
|
public func tryLock(mode: LockMode) throws -> Bool {
|
|
if flock(rawValue, mode.raw | LOCK_NB) == 0 {
|
|
return true
|
|
}
|
|
let err = errno
|
|
if err == EWOULDBLOCK {
|
|
return false
|
|
}
|
|
throw Errno(rawValue: err)
|
|
}
|
|
}
|