Files
xtool/Sources/XUtils/System+Utils.swift
Kabir Oberai 2f2336cb5f utils: tweaks (#387)
## 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 -->
2026-10-28 19:22:26 -04:00

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)
}
}