From f40bda2f85a13f3594fa0094221efdeb7fe40c78 Mon Sep 17 00:00:00 2001 From: Adrian Perez de Castro Date: Tue, 13 Jan 2026 16:50:52 +0200 Subject: [PATCH] Fix building on Linux Apply the needed changes to make builds succeed on Linux. Mainly this involves features missing on non-Apple systems: - URLResourceKey.volumeAvailableCapacityForImportantUsageKey is not available, so only .volumeAvailableCapacityKey is used. - The CryptoKit module is only available on Apple systems. The Crypto module can be used instead, which exposes the same API and wraps CryptoKit where available, and in the rest of cases uses a BoringSSL under the hood. - Linux Swift toolchains include a new FoundationNetworking module that contains some of the APIs previously available directly in Foundation. - URLError.downloadTaskResumeData is not available, download resumption is disabled on Linux. - The Hardware.registryProperty(for:) function always returns nil. - Using setbuf(__stdoutp, ...) does not build on Linux. - Shim autoreleasepool() functions are provided on Linux. --- .../Download/DownloadFirmwareCommand.swift | 15 ++++++++++++--- .../Download/DownloadInstallerCommand.swift | 15 ++++++++++++--- Mist/Extensions/URL+Extension.swift | 2 +- Mist/Helpers/Downloader.swift | 12 ++++++++++++ Mist/Helpers/Validator.swift | 2 +- Mist/Model/Hardware.swift | 5 +++++ Mist/main.swift | 15 +++++++++++++++ Package.swift | 2 ++ 8 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Mist/Commands/Download/DownloadFirmwareCommand.swift b/Mist/Commands/Download/DownloadFirmwareCommand.swift index 8aca5d4..7bf3da3 100644 --- a/Mist/Commands/Download/DownloadFirmwareCommand.swift +++ b/Mist/Commands/Download/DownloadFirmwareCommand.swift @@ -200,16 +200,25 @@ struct DownloadFirmwareCommand: ParsableCommand { let required: Int64 = firmware.size for url in [outputURL, temporaryURL] { - let values: URLResourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey, .volumeAvailableCapacityKey]) - let free: Int64 + var free: Int64 = 0 +#if os(Linux) + let values: URLResourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityKey]) + if let volumeAvailableCapacity: Int = values.volumeAvailableCapacity { + free = Int64(volumeAvailableCapacity) + } +#else + let values: URLResourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey, .volumeAvailableCapacityKey]) if let volumeAvailableCapacityForImportantUsage: Int64 = values.volumeAvailableCapacityForImportantUsage, volumeAvailableCapacityForImportantUsage > 0 { free = volumeAvailableCapacityForImportantUsage } else if let volumeAvailableCapacity: Int = values.volumeAvailableCapacity { free = Int64(volumeAvailableCapacity) - } else { + } +#endif + + if free == 0 { throw MistError.notEnoughFreeSpace(volume: url.path, free: 0, required: required) } diff --git a/Mist/Commands/Download/DownloadInstallerCommand.swift b/Mist/Commands/Download/DownloadInstallerCommand.swift index 42246a6..8e67614 100644 --- a/Mist/Commands/Download/DownloadInstallerCommand.swift +++ b/Mist/Commands/Download/DownloadInstallerCommand.swift @@ -397,16 +397,25 @@ struct DownloadInstallerCommand: ParsableCommand { for volume in volumes { let required: Int64 = installer.size * volume.count let url: URL = .init(fileURLWithPath: volume.path) - let values: URLResourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey, .volumeAvailableCapacityKey]) - let free: Int64 + var free: Int64 = 0 +#if os(Linux) + let values: URLResourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityKey]) + if let volumeAvailableCapacity: Int = values.volumeAvailableCapacity { + free = Int64(volumeAvailableCapacity) + } +#else + let values: URLResourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey, .volumeAvailableCapacityKey]) if let volumeAvailableCapacityForImportantUsage: Int64 = values.volumeAvailableCapacityForImportantUsage, volumeAvailableCapacityForImportantUsage > 0 { free = volumeAvailableCapacityForImportantUsage } else if let volumeAvailableCapacity: Int = values.volumeAvailableCapacity { free = Int64(volumeAvailableCapacity) - } else { + } +#endif + + if free == 0 { throw MistError.notEnoughFreeSpace(volume: url.path, free: 0, required: required) } diff --git a/Mist/Extensions/URL+Extension.swift b/Mist/Extensions/URL+Extension.swift index 91c6081..8098e0f 100644 --- a/Mist/Extensions/URL+Extension.swift +++ b/Mist/Extensions/URL+Extension.swift @@ -5,7 +5,7 @@ // Created by Nindi Gill on 15/8/2022. // -import CryptoKit +import Crypto import Foundation extension URL { diff --git a/Mist/Helpers/Downloader.swift b/Mist/Helpers/Downloader.swift index a909d66..34091b1 100644 --- a/Mist/Helpers/Downloader.swift +++ b/Mist/Helpers/Downloader.swift @@ -7,6 +7,10 @@ import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + /// Helper Class used to download macOS Firmwares and Installers. class Downloader: NSObject { private static let maximumWidth: Int = 95 @@ -70,12 +74,14 @@ class Downloader: NSObject { while urlError != nil { if retries >= options.retries { +#if !os(Linux) if let error: URLError = urlError, let data: Data = error.downloadTaskResumeData { !quiet ? PrettyPrint.print("Saving resume data to '\(resumeDataURL.path)'...", noAnsi: noAnsi) : Mist.noop() try data.write(to: resumeDataURL) } +#endif throw MistError.maximumRetriesReached } @@ -168,12 +174,14 @@ class Downloader: NSObject { while urlError != nil { if retries >= options.retries { +#if !os(Linux) if let error: URLError = urlError, let data: Data = error.downloadTaskResumeData { !quiet ? PrettyPrint.print("Saving resume data to '\(resumeDataURL.path)'...", noAnsi: noAnsi) : Mist.noop() try data.write(to: resumeDataURL) } +#endif throw MistError.maximumRetriesReached } @@ -225,6 +233,9 @@ class Downloader: NSObject { } private func retry(attempt retry: Int, of maximumRetries: Int, with delay: Int, using session: URLSession) { +#if os(Linux) + mistError = MistError.generalError("Cannot retry downloads on Linux") +#else guard let urlError: URLError = urlError, let data: Data = urlError.downloadTaskResumeData else { @@ -242,6 +253,7 @@ class Downloader: NSObject { updateProgress(replacing: false) task.resume() semaphore.wait() +#endif } private func updateProgress(replacing: Bool = true) { diff --git a/Mist/Helpers/Validator.swift b/Mist/Helpers/Validator.swift index 9837be1..97f3a07 100644 --- a/Mist/Helpers/Validator.swift +++ b/Mist/Helpers/Validator.swift @@ -5,7 +5,7 @@ // Created by Nindi Gill on 29/4/2022. // -import CryptoKit +import Crypto import Foundation /// Helper Struct used to validate macOS Firmware and Installer downloads. diff --git a/Mist/Model/Hardware.swift b/Mist/Model/Hardware.swift index 727e4a0..db535d1 100644 --- a/Mist/Model/Hardware.swift +++ b/Mist/Model/Hardware.swift @@ -49,6 +49,10 @@ enum Hardware { /// /// - Returns: The entity property for the provided key. private static func registryProperty(for key: String) -> String? { +#if os(Linux) + // Cannot use Darwin/XNU kernel APIs on Linux + return nil; +#else let entry: io_service_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")) defer { @@ -74,5 +78,6 @@ enum Hardware { let string: String = .init(decoding: data, as: UTF8.self) return string.trimmingCharacters(in: CharacterSet(["\0"])) +#endif } } diff --git a/Mist/main.swift b/Mist/main.swift index 60d8247..947800e 100644 --- a/Mist/main.swift +++ b/Mist/main.swift @@ -7,8 +7,23 @@ import Foundation +#if os(Linux) +/// A shim for Linux that runs the given block of code. +/// +/// The existence of this shim allows you the use of auto-release pools to optimize memory footprint on Darwin platforms while maintaining +/// compatibility with Linux where this API is not implemented. +@discardableResult +public func autoreleasepool(_ block: () throws -> Result) rethrows -> Result { + return try block() +} + +public func autoreleasepool(invoking body: () throws(E) -> Result) throws(E) -> Result where E: Error, Result : ~Copyable { + return try body() +} +#else // Disable stdout stream buffering for more immediate output. setbuf(__stdoutp, nil) +#endif Mist.main() exit(0) diff --git a/Package.swift b/Package.swift index 5fc1a7b..1e656d0 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,7 @@ let package: Package = .init( ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"), + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "5.0.0"), // .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.57.2"), .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "0.61.0"), .package(url: "https://github.com/jpsim/Yams", from: "6.1.0") @@ -23,6 +24,7 @@ let package: Package = .init( name: "Mist", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Crypto", package: "swift-crypto"), .product(name: "Yams", package: "Yams") ], path: "Mist"