diff --git a/.gitignore b/.gitignore index 0fefab93..6eee1c45 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ DerivedData/ /openapi /.tmp +/xtool diff --git a/Documentation/Contributing/Debugging.md b/Documentation/Contributing/Debugging.md index 710dbcd5..5e77d0d5 100644 --- a/Documentation/Contributing/Debugging.md +++ b/Documentation/Contributing/Debugging.md @@ -11,7 +11,7 @@ Some environment variables that help with debugging. Use these as e.g. `XTL_FOO= ### Disk - `XTL_DEBUG_TMP`: Set to `1` to log details when xtool creates/destroys temporary directories. -- `XTL_TMPDIR`: Control the location where xtool stores temporary directories. We also respect `TMPDIR` and (on Linux) `XDG_CACHE_HOME`. +- `XTL_TMPDIR`: Control the location where xtool stores temporary directories. We also respect `XDG_CACHE_HOME` on Linux. ### Networking diff --git a/Package.swift b/Package.swift index a19a667c..5385c1d6 100644 --- a/Package.swift +++ b/Package.swift @@ -9,12 +9,14 @@ let xtoolVersion: String? = { let gitCommit = info.currentCommit let gitTag = info.currentTag let gitRef = gitTag ?? "commit \(gitCommit)" - return "\(gitRef)\(info.hasUncommittedChanges ? " (dirty)" : "")" + return gitRef } else { return nil } }() +let hasUncommittedChanges = Context.gitInformation?.hasUncommittedChanges ?? false + let cSettings: [CSetting] = [ .define("_GNU_SOURCE", .when(platforms: [.linux])), ] @@ -90,7 +92,9 @@ let package = Package( dependencies: [ .product(name: "OpenSSL", package: "xtool-core") ], - cSettings: cSettings + ( + cSettings: cSettings + [ + .define("XTOOL_HAS_UNCOMMITTED_CHANGES", to: hasUncommittedChanges ? "1" : "0"), + ] + ( xtoolVersion .map { [.define("XTOOL_VERSION", to: "\"\($0)\"")] } ?? [] diff --git a/Sources/CXKit/include/version.h b/Sources/CXKit/include/version.h index f58c6cca..57a4048d 100644 --- a/Sources/CXKit/include/version.h +++ b/Sources/CXKit/include/version.h @@ -1 +1,4 @@ +#include + const char * _Nonnull xtl_version(void); +bool xtl_has_uncommitted_changes(void); diff --git a/Sources/CXKit/version.c b/Sources/CXKit/version.c index 26637a44..d3e7986a 100644 --- a/Sources/CXKit/version.c +++ b/Sources/CXKit/version.c @@ -1,4 +1,5 @@ #include "version.h" +#include const char *xtl_version(void) { #ifdef XTOOL_VERSION @@ -7,3 +8,15 @@ const char *xtl_version(void) { return "unversioned"; #endif } + +bool xtl_has_uncommitted_changes(void) { +#ifndef XTOOL_VERSION + return true; +#else +#ifdef XTOOL_HAS_UNCOMMITTED_CHANGES + return XTOOL_HAS_UNCOMMITTED_CHANGES != 0; +#else + return false; +#endif +#endif +} diff --git a/Sources/XCLIUtils/CommandVersion.swift b/Sources/XCLIUtils/CommandVersion.swift new file mode 100644 index 00000000..bd3e240e --- /dev/null +++ b/Sources/XCLIUtils/CommandVersion.swift @@ -0,0 +1,19 @@ +public struct CommandVersion: Sendable, Hashable { + public var version: String + public var hasUncommittedChanges: Bool + + public init(version: String, hasUncommittedChanges: Bool) { + self.version = version + self.hasUncommittedChanges = hasUncommittedChanges + } + + public static var unversioned: Self { + .init(version: "unversioned", hasUncommittedChanges: true) + } +} + +extension CommandVersion: CustomStringConvertible { + public var description: String { + version + (hasUncommittedChanges ? "-dirty" : "") + } +} diff --git a/Sources/XKit/Utilities/KeyValueStorage.swift b/Sources/XKit/Utilities/KeyValueStorage.swift index ff2801e5..4e2c5b04 100644 --- a/Sources/XKit/Utilities/KeyValueStorage.swift +++ b/Sources/XKit/Utilities/KeyValueStorage.swift @@ -9,6 +9,7 @@ import Foundation import ConcurrencyExtras import Dependencies +import XUtils public enum KeyValueStorageError: Error { case stringConversionFailure diff --git a/Sources/XToolSupport/XTool+Version.swift b/Sources/XToolSupport/XTool+Version.swift index 9ad80e2d..e5edec03 100644 --- a/Sources/XToolSupport/XTool+Version.swift +++ b/Sources/XToolSupport/XTool+Version.swift @@ -1,5 +1,9 @@ import CXKit +import XCLIUtils extension XTool { - public static let version = String(cString: xtl_version()) + public static let version = CommandVersion( + version: String(cString: xtl_version()), + hasUncommittedChanges: xtl_has_uncommitted_changes(), + ) } diff --git a/Sources/XKit/Utilities/DirectoryProvider.swift b/Sources/XUtils/DirectoryProvider.swift similarity index 100% rename from Sources/XKit/Utilities/DirectoryProvider.swift rename to Sources/XUtils/DirectoryProvider.swift diff --git a/Sources/XUtils/System+Utils.swift b/Sources/XUtils/System+Utils.swift index bc6cc5da..ade85bfa 100644 --- a/Sources/XUtils/System+Utils.swift +++ b/Sources/XUtils/System+Utils.swift @@ -55,7 +55,7 @@ extension FileDescriptor { } } - enum LockMode { + public enum LockMode { case shared case exclusive @@ -67,7 +67,7 @@ extension FileDescriptor { } } - func tryLock(mode: LockMode) throws -> Bool { + public func tryLock(mode: LockMode) throws -> Bool { if flock(rawValue, mode.raw | LOCK_NB) == 0 { return true } diff --git a/Sources/XUtils/TemporaryDirectory.swift b/Sources/XUtils/TemporaryDirectory.swift index fdd32952..144f86bc 100644 --- a/Sources/XUtils/TemporaryDirectory.swift +++ b/Sources/XUtils/TemporaryDirectory.swift @@ -71,24 +71,7 @@ private struct TemporaryDirectoryRoot { } private init() { - let url: URL - let env = ProcessInfo.processInfo.environment - if let tmpdir = env["XTL_TMPDIR"] ?? env["TMPDIR"] { - url = URL(fileURLWithPath: tmpdir).appendingPathComponent("sh.xtool") - } else { - #if os(Linux) - // On Linux, /tmp is commonly a tmpfs mount while ~/.swiftpm lives on ext4. - // Foundation's FileManager.copyItem uses sendfile(2) internally, which returns - // EINVAL when copying between different filesystem types (e.g. tmpfs → ext4). - // This causes `swift sdk install` to fail on distros like Linux Mint (#181). - // Using a cache dir on the home filesystem avoids the cross-fs copy entirely. - let xdgCache = env["XDG_CACHE_HOME"].map { URL(fileURLWithPath: $0) } - ?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".cache") - url = xdgCache.appendingPathComponent("xtool") - #else - url = FileManager.default.temporaryDirectory.appendingPathComponent("sh.xtool") - #endif - } + let url = XToolCache.directory Self.pruneOrphans(in: url) diff --git a/Sources/XUtils/XToolCache.swift b/Sources/XUtils/XToolCache.swift new file mode 100644 index 00000000..455563ec --- /dev/null +++ b/Sources/XUtils/XToolCache.swift @@ -0,0 +1,22 @@ +import Foundation + +package enum XToolCache { + package static var directory: URL { + let env = ProcessInfo.processInfo.environment + if let tmpdir = env["XTL_TMPDIR"] { + return URL(fileURLWithPath: tmpdir).appendingPathComponent("sh.xtool") + } + #if os(Linux) + let xdgCache: URL + if let xdgCachePath = env["XDG_CACHE_HOME"], + (xdgCachePath as NSString).isAbsolutePath { + xdgCache = URL(filePath: xdgCachePath) + } else { + xdgCache = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".cache") + } + return xdgCache.appending(component: "xtool") + #else + return URL.cachesDirectory.appending(component: "sh.xtool") + #endif + } +}