diff --git a/Package.swift b/Package.swift index e5fe50d9fd..fd22bd909d 100644 --- a/Package.swift +++ b/Package.swift @@ -52,7 +52,7 @@ let package = Package( .target( name: "SwiftLintCore", dependencies: [ - .product(name: "CryptoSwift", package: "CryptoSwift", condition: .when(platforms: [.linux])), + .product(name: "CryptoSwift", package: "CryptoSwift", condition: .when(platforms: [.linux, .windows])), .target(name: "DyldWarningWorkaround", condition: .when(platforms: [.macOS])), .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "SwiftIDEUtils", package: "swift-syntax"), diff --git a/Plugins/SwiftLintPlugin/Path+Helpers.swift b/Plugins/SwiftLintPlugin/Path+Helpers.swift index 0698dc6005..7e402060eb 100644 --- a/Plugins/SwiftLintPlugin/Path+Helpers.swift +++ b/Plugins/SwiftLintPlugin/Path+Helpers.swift @@ -3,6 +3,8 @@ import PackagePlugin #if os(Linux) import Glibc +#elseif os(Windows) +import ucrt #else import Darwin #endif @@ -33,9 +35,15 @@ extension Path { /// Safe way to check if the file is accessible from within the current process sandbox. private func isAccessible() -> Bool { +#if os(Windows) + let result = string.withCString(encodedAs: UTF16.self) { + _waccess($0, 04) + } +#else let result = string.withCString { pointer in access(pointer, R_OK) } +#endif return result == 0 } diff --git a/Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift b/Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift index aad9334ad9..a5281ce9fa 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift @@ -135,7 +135,7 @@ private extension String { var strippingURLs: String { let range = fullNSRange // Workaround for Linux until NSDataDetector is available - #if os(Linux) + #if os(Linux) || os(Windows) // Regex pattern from http://daringfireball.net/2010/07/improved_regex_for_matching_urls let pattern = "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)" + "(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*" + diff --git a/Source/SwiftLintCore/Extensions/Configuration+Remote.swift b/Source/SwiftLintCore/Extensions/Configuration+Remote.swift index a8a2a9ad89..ccd92403cd 100644 --- a/Source/SwiftLintCore/Extensions/Configuration+Remote.swift +++ b/Source/SwiftLintCore/Extensions/Configuration+Remote.swift @@ -4,6 +4,10 @@ import Foundation // swiftlint:disable:this file_name import FoundationNetworking #endif +#if os(Windows) +import func WinSDK.Sleep +#endif + internal extension Configuration.FileGraph.FilePath { // MARK: - Properties: Remote Cache /// This should never be touched. @@ -87,7 +91,11 @@ internal extension Configuration.FileGraph.FilePath { while true { if taskDone { break } if Date().timeIntervalSince(startDate) > timeout { task.cancel(); break } +#if os(Windows) + Sleep(50) +#else usleep(50_000) // Sleep for 50 ms +#endif } // Handle wrong data diff --git a/Source/SwiftLintCore/Helpers/Glob.swift b/Source/SwiftLintCore/Helpers/Glob.swift index d025da520e..4ba7bd5e00 100644 --- a/Source/SwiftLintCore/Helpers/Glob.swift +++ b/Source/SwiftLintCore/Helpers/Glob.swift @@ -8,10 +8,16 @@ private let globFunction = Darwin.glob import Glibc private let globFunction = Glibc.glob +#elseif canImport(ucrt) +import ucrt #else #error("Unsupported platform") #endif +#if os(Windows) +import WinSDK +#endif + // Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 struct Glob { @@ -23,12 +29,35 @@ struct Glob { return expandGlobstar(pattern: pattern) .reduce(into: [String]()) { paths, pattern in +#if os(Windows) + URL(fileURLWithPath: pattern).withUnsafeFileSystemRepresentation { + var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW() + + let hDirectory: HANDLE = String(cString: $0!).withCString(encodedAs: UTF16.self) { + FindFirstFileW($0, &ffd) + } + if hDirectory == INVALID_HANDLE_VALUE { return } + defer { FindClose(hDirectory) } + + repeat { + let path: String = withUnsafePointer(to: &ffd.cFileName) { + $0.withMemoryRebound(to: UInt16.self, capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout.size) { + String(decodingCString: $0, as: UTF16.self) + } + } + if path != "." && path != ".." { + paths.append(path) + } + } while FindNextFileW(hDirectory, &ffd) + } +#else var globResult = glob_t() defer { globfree(&globResult) } if globFunction(pattern, GLOB_TILDE | GLOB_BRACE | GLOB_MARK, nil, &globResult) == 0 { paths.append(contentsOf: populateFiles(globResult: globResult)) } +#endif } .unique .sorted() @@ -90,6 +119,7 @@ struct Glob { return isDirectory && isDirectoryBool.boolValue } +#if !os(Windows) private static func populateFiles(globResult: glob_t) -> [String] { #if os(Linux) let matchCount = globResult.gl_pathc @@ -100,4 +130,5 @@ struct Glob { globResult.gl_pathv[index].flatMap { String(validatingUTF8: $0) } } } +#endif } diff --git a/Source/SwiftLintCore/Helpers/Reachability.swift b/Source/SwiftLintCore/Helpers/Reachability.swift index 1731588826..7b9df916b1 100644 --- a/Source/SwiftLintCore/Helpers/Reachability.swift +++ b/Source/SwiftLintCore/Helpers/Reachability.swift @@ -1,4 +1,4 @@ -#if !os(Linux) +#if os(macOS) import SystemConfiguration #endif @@ -9,9 +9,9 @@ enum Reachability { } /// Returns whether the device is connected to a network, if known. - /// On Linux, this always evaluates to `nil`. + /// On Linux and Windows, this always evaluates to `nil`. static var connectivityStatus: ConnectivityStatus { -#if os(Linux) +#if os(Linux) || os(Windows) return .unknown #else var zeroAddress = sockaddr_in() diff --git a/Source/swiftlint/Commands/Rules.swift b/Source/swiftlint/Commands/Rules.swift index 52cff62992..be522c64a9 100644 --- a/Source/swiftlint/Commands/Rules.swift +++ b/Source/swiftlint/Commands/Rules.swift @@ -3,12 +3,17 @@ import ArgumentParser import Darwin #elseif canImport(Glibc) import Glibc +#elseif canImport(ucrt) +import ucrt #else #error("Unsupported platform") #endif import Foundation import SwiftLintFramework import SwiftyTextTable +#if os(Windows) +import WinSDK +#endif private typealias SortedRules = [(String, any Rule.Type)] @@ -149,6 +154,13 @@ private extension TextTable { private struct Terminal { static func currentWidth() -> Int { +#if os(Windows) + var csbi = CONSOLE_SCREEN_BUFFER_INFO() + guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else { + return 80 + } + return Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1 +#else var size = winsize() #if os(Linux) _ = ioctl(CInt(STDOUT_FILENO), UInt(TIOCGWINSZ), &size) @@ -156,5 +168,6 @@ private struct Terminal { _ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) #endif return Int(size.ws_col) +#endif } } diff --git a/Source/swiftlint/Extensions/Configuration+CommandLine.swift b/Source/swiftlint/Extensions/Configuration+CommandLine.swift index 2ec8a2c5b7..8ca6f5a115 100644 --- a/Source/swiftlint/Extensions/Configuration+CommandLine.swift +++ b/Source/swiftlint/Extensions/Configuration+CommandLine.swift @@ -40,7 +40,7 @@ private func scriptInputFiles() throws -> [SwiftLintFile] { } } -#if os(Linux) +#if os(Linux) || os(Windows) private func autoreleasepool(block: () -> T) -> T { return block() } #endif diff --git a/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift b/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift index 3e4e1ec986..335ac33d0a 100644 --- a/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift +++ b/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift @@ -330,7 +330,7 @@ private actor CorrectionsBuilder { } private func memoryUsage() -> String? { -#if os(Linux) +#if os(Linux) || os(Windows) return nil #else var info = mach_task_basic_info() diff --git a/Source/swiftlint/Helpers/ProgressBar.swift b/Source/swiftlint/Helpers/ProgressBar.swift index 0d50555df6..6a4c0402e5 100644 --- a/Source/swiftlint/Helpers/ProgressBar.swift +++ b/Source/swiftlint/Helpers/ProgressBar.swift @@ -53,7 +53,7 @@ actor ProgressBar { } } -#if os(Linux) +#if os(Linux) || os(Windows) // swiftlint:disable:next identifier_name private let NSEC_PER_SEC = 1_000_000_000 #endif