From 841d12ba2616f22acffed6a8dfc1bd00c6804d44 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 13:57:20 -0700 Subject: [PATCH 1/5] 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 cb777696a0..7e1d4e050a 100644 --- a/Package.swift +++ b/Package.swift @@ -94,7 +94,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 d51535f7fd1fdf7d2b7cc78b5007fce459f2b75f Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 13:59:03 -0700 Subject: [PATCH 2/5] 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 0fdef2d9ef..91765f8e9c 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift @@ -233,7 +233,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 67f29a2b553be3a4d0d3fb03d7ca402b48770c7e Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 14:39:52 -0700 Subject: [PATCH 3/5] 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. --- .../Configuration/Configuration+Remote.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/SwiftLintFramework/Configuration/Configuration+Remote.swift b/Source/SwiftLintFramework/Configuration/Configuration+Remote.swift index c083290c8a..761e8de314 100644 --- a/Source/SwiftLintFramework/Configuration/Configuration+Remote.swift +++ b/Source/SwiftLintFramework/Configuration/Configuration+Remote.swift @@ -5,6 +5,10 @@ import SourceKittenFramework 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. @@ -83,7 +87,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 From 6f429ad15c803725d1f501edf9b624a6312d626b Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 14:41:12 -0700 Subject: [PATCH 4/5] 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). --- .../Configuration+CommandLine.swift | 2 +- Source/SwiftLintFramework/LintOrAnalyzeCommand.swift | 2 +- Source/SwiftLintFramework/ProgressBar.swift | 2 +- Source/swiftlint/Commands/Rules.swift | 11 +++++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Configuration+CommandLine.swift b/Source/SwiftLintFramework/Configuration+CommandLine.swift index 2935f2fc79..b875a0e888 100644 --- a/Source/SwiftLintFramework/Configuration+CommandLine.swift +++ b/Source/SwiftLintFramework/Configuration+CommandLine.swift @@ -69,7 +69,7 @@ private func fileCount(from envVar: String) throws(SwiftLintError) -> Int { return count } -#if os(Linux) +#if os(Linux) || os(Windows) private func autoreleasepool(block: () -> T) -> T { block() } #endif diff --git a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift index 4af32083a4..4c458942dc 100644 --- a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift +++ b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift @@ -444,7 +444,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/SwiftLintFramework/ProgressBar.swift b/Source/SwiftLintFramework/ProgressBar.swift index b5a08ab62f..32253a9da5 100644 --- a/Source/SwiftLintFramework/ProgressBar.swift +++ b/Source/SwiftLintFramework/ProgressBar.swift @@ -52,7 +52,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 diff --git a/Source/swiftlint/Commands/Rules.swift b/Source/swiftlint/Commands/Rules.swift index 816551a0e5..6c812c411b 100644 --- a/Source/swiftlint/Commands/Rules.swift +++ b/Source/swiftlint/Commands/Rules.swift @@ -2,6 +2,9 @@ import ArgumentParser import Foundation import SwiftLintFramework import SwiftyTextTable +#if os(Windows) +import WinSDK +#endif private typealias SortedRules = [(String, any Rule.Type)] @@ -141,6 +144,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) @@ -148,5 +158,6 @@ private struct Terminal { _ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) #endif return Int(size.ws_col) +#endif } } From 83b358c666b05cddbb4e0f92935f23f8046024c8 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 22 May 2023 14:42:44 -0700 Subject: [PATCH 5/5] 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/SwiftLintFramework/Helpers/Glob.swift | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Source/SwiftLintFramework/Helpers/Glob.swift b/Source/SwiftLintFramework/Helpers/Glob.swift index 953819fd15..0d67228d3f 100644 --- a/Source/SwiftLintFramework/Helpers/Glob.swift +++ b/Source/SwiftLintFramework/Helpers/Glob.swift @@ -9,6 +9,10 @@ import func Musl.glob #endif #endif +#if os(Windows) +import WinSDK +#endif + // Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 struct Glob { @@ -20,6 +24,28 @@ struct Glob { return expandGlobstar(pattern: pattern) .reduce(into: [String]()) { paths, pattern in +#if os(Windows) + URL(fileURLWithPath: pattern).withUnsafeFileSystemRepresentation { + var ffd = 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 == ".." { continue } + paths.append(path) + } while FindNextFileW(hDirectory, &ffd) + } +#else var globResult = glob_t() defer { globfree(&globResult) } @@ -31,6 +57,7 @@ struct Glob { if glob(pattern, flags, nil, &globResult) == 0 { paths.append(contentsOf: populateFiles(globResult: globResult)) } +#endif } .unique .sorted() @@ -92,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 @@ -102,4 +130,5 @@ struct Glob { globResult.gl_pathv[index].flatMap { String(validatingUTF8: $0) } } } +#endif }