From 036b57144d46a30f1975a60c64194b8a78f10c10 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 13:57:20 -0700 Subject: [PATCH 1/7] build: add CryptoSwift dependency for Windows --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"), From 484e5d08141ed6e6ec72d82277314cdc70014474 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 13:58:05 -0700 Subject: [PATCH 2/7] SwiftLintPlugin: implement Windows support for `isAccessible` --- Plugins/SwiftLintPlugin/Path+Helpers.swift | 8 ++++++++ 1 file changed, 8 insertions(+) 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 } From 52bfa347c3e7d0b7d4091381785247c447b1e38f Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 13:59:03 -0700 Subject: [PATCH 3/7] SwiftLintBuiltInRules: treat Windows similar to Linux wrt `NSDataDetector` --- Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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()<>]+\\)))*" + From 3f79c42ffd0adf3d9580f830fd2d285a187ddaf6 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 14:39:52 -0700 Subject: [PATCH 4/7] SwiftLintCore: initial pass for Windows support Add some Windows specific handling for the paths in SwiftLintCore. The one piece that this change does not cover is the handling of `glob` as that is not an ISO C standard function and as such there is no `glob` on Windows. This will be worked through separately. --- .../SwiftLintCore/Extensions/Configuration+Remote.swift | 8 ++++++++ Source/SwiftLintCore/Helpers/Reachability.swift | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) 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/Reachability.swift b/Source/SwiftLintCore/Helpers/Reachability.swift index 1731588826..238a5c0c95 100644 --- a/Source/SwiftLintCore/Helpers/Reachability.swift +++ b/Source/SwiftLintCore/Helpers/Reachability.swift @@ -1,4 +1,4 @@ -#if !os(Linux) +#if !os(Linux) && !os(Windows) 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() From 5537b4e11576ce9de6f828ef4702f5f429fc17d1 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 14:41:12 -0700 Subject: [PATCH 5/7] swiftlint: add a Windows port This enables building the swiftlint command on Windows. There is no system ioctl for terminal access, instead, we can use the Win32 Console API surface to query the console size. In the case of a failure, assume the width to be 80-columns (as the standard VGA console is 80x25). --- Source/swiftlint/Commands/Rules.swift | 13 +++++++++++++ .../Extensions/Configuration+CommandLine.swift | 2 +- Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift | 2 +- Source/swiftlint/Helpers/ProgressBar.swift | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Source/swiftlint/Commands/Rules.swift b/Source/swiftlint/Commands/Rules.swift index 52cff62992..fa7a2b67b0 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 = 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 From e8763aa99be201b5b7b9accc1d454ce3515a66c2 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 14:42:44 -0700 Subject: [PATCH 6/7] WIP/SwiftLintCore: port the `glob` function to Windows Windows does not support `glob` as a standard C library function as that is not part of the C standard. Attempt to emulate that through the use of `FindFirstFileW` and `FindNextFile` to iterate the matching files given a pattern. This should allow us to start enumerating the files as if we had `glob` available. --- Source/SwiftLintCore/Helpers/Glob.swift | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) 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 } From ead3381c04459ce40660e80051328c62701cf1af Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 8 Jan 2024 10:51:05 -0800 Subject: [PATCH 7/7] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danny Mösch --- Source/SwiftLintCore/Helpers/Reachability.swift | 2 +- Source/swiftlint/Commands/Rules.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/SwiftLintCore/Helpers/Reachability.swift b/Source/SwiftLintCore/Helpers/Reachability.swift index 238a5c0c95..7b9df916b1 100644 --- a/Source/SwiftLintCore/Helpers/Reachability.swift +++ b/Source/SwiftLintCore/Helpers/Reachability.swift @@ -1,4 +1,4 @@ -#if !os(Linux) && !os(Windows) +#if os(macOS) import SystemConfiguration #endif diff --git a/Source/swiftlint/Commands/Rules.swift b/Source/swiftlint/Commands/Rules.swift index fa7a2b67b0..be522c64a9 100644 --- a/Source/swiftlint/Commands/Rules.swift +++ b/Source/swiftlint/Commands/Rules.swift @@ -155,7 +155,7 @@ private extension TextTable { private struct Terminal { static func currentWidth() -> Int { #if os(Windows) - var csbi: CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO() + var csbi = CONSOLE_SCREEN_BUFFER_INFO() guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else { return 80 }