Skip to content

Commit 339d250

Browse files
authored
Port to Windows (#5030)
At least ensure it compiles just fine on Windows. * build: add CryptoSwift dependency for Windows * SwiftLintBuiltInRules: treat Windows similar to Linux wrt `NSDataDetector` * 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. * 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). * 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.
1 parent c5ad695 commit 339d250

File tree

8 files changed

+53
-5
lines changed

8 files changed

+53
-5
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ let package = Package(
9494
.target(
9595
name: "SwiftLintCore",
9696
dependencies: [
97-
.product(name: "CryptoSwift", package: "CryptoSwift", condition: .when(platforms: [.linux])),
97+
.product(name: "CryptoSwift", package: "CryptoSwift", condition: .when(platforms: [.linux, .windows])),
9898
.target(name: "DyldWarningWorkaround", condition: .when(platforms: [.macOS])),
9999
.product(name: "SourceKittenFramework", package: "SourceKitten"),
100100
.product(name: "SwiftIDEUtils", package: "swift-syntax"),

Source/SwiftLintBuiltInRules/Rules/Metrics/LineLengthRule.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ private extension String {
233233
var strippingURLs: String {
234234
let range = fullNSRange
235235
// Workaround for Linux until NSDataDetector is available
236-
#if os(Linux)
236+
#if os(Linux) || os(Windows)
237237
// Regex pattern from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
238238
let pattern = "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)" +
239239
"(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*" +

Source/SwiftLintFramework/Configuration+CommandLine.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private func fileCount(from envVar: String) throws(SwiftLintError) -> Int {
6969
return count
7070
}
7171

72-
#if os(Linux)
72+
#if os(Linux) || os(Windows)
7373
private func autoreleasepool<T>(block: () -> T) -> T { block() }
7474
#endif
7575

Source/SwiftLintFramework/Configuration/Configuration+Remote.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import SourceKittenFramework
55
import FoundationNetworking
66
#endif
77

8+
#if os(Windows)
9+
import func WinSDK.Sleep
10+
#endif
11+
812
internal extension Configuration.FileGraph.FilePath {
913
// MARK: - Properties: Remote Cache
1014
/// This should never be touched.
@@ -83,7 +87,11 @@ internal extension Configuration.FileGraph.FilePath {
8387
while true {
8488
if taskDone { break }
8589
if Date().timeIntervalSince(startDate) > timeout { task.cancel(); break }
90+
#if os(Windows)
91+
Sleep(50)
92+
#else
8693
usleep(50_000) // Sleep for 50 ms
94+
#endif
8795
}
8896

8997
// Handle wrong data

Source/SwiftLintFramework/Helpers/Glob.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import func Musl.glob
99
#endif
1010
#endif
1111

12+
#if os(Windows)
13+
import WinSDK
14+
#endif
15+
1216
// Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3
1317

1418
struct Glob {
@@ -20,6 +24,28 @@ struct Glob {
2024

2125
return expandGlobstar(pattern: pattern)
2226
.reduce(into: [String]()) { paths, pattern in
27+
#if os(Windows)
28+
URL(fileURLWithPath: pattern).withUnsafeFileSystemRepresentation {
29+
var ffd = WIN32_FIND_DATAW()
30+
31+
let hDirectory: HANDLE = String(cString: $0!).withCString(encodedAs: UTF16.self) {
32+
FindFirstFileW($0, &ffd)
33+
}
34+
if hDirectory == INVALID_HANDLE_VALUE { return }
35+
defer { FindClose(hDirectory) }
36+
37+
repeat {
38+
let path: String = withUnsafePointer(to: &ffd.cFileName) {
39+
$0.withMemoryRebound(to: UInt16.self,
40+
capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout<WCHAR>.size) {
41+
String(decodingCString: $0, as: UTF16.self)
42+
}
43+
}
44+
if path == "." || path == ".." { continue }
45+
paths.append(path)
46+
} while FindNextFileW(hDirectory, &ffd)
47+
}
48+
#else
2349
var globResult = glob_t()
2450
defer { globfree(&globResult) }
2551

@@ -31,6 +57,7 @@ struct Glob {
3157
if glob(pattern, flags, nil, &globResult) == 0 {
3258
paths.append(contentsOf: populateFiles(globResult: globResult))
3359
}
60+
#endif
3461
}
3562
.unique
3663
.sorted()
@@ -92,6 +119,7 @@ struct Glob {
92119
return isDirectory && isDirectoryBool.boolValue
93120
}
94121

122+
#if !os(Windows)
95123
private static func populateFiles(globResult: glob_t) -> [String] {
96124
#if os(Linux)
97125
let matchCount = globResult.gl_pathc
@@ -102,4 +130,5 @@ struct Glob {
102130
globResult.gl_pathv[index].flatMap { String(validatingUTF8: $0) }
103131
}
104132
}
133+
#endif
105134
}

Source/SwiftLintFramework/LintOrAnalyzeCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ private actor CorrectionsBuilder {
444444
}
445445

446446
private func memoryUsage() -> String? {
447-
#if os(Linux)
447+
#if os(Linux) || os(Windows)
448448
return nil
449449
#else
450450
var info = mach_task_basic_info()

Source/SwiftLintFramework/ProgressBar.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ actor ProgressBar {
5252
}
5353
}
5454

55-
#if os(Linux)
55+
#if os(Linux) || os(Windows)
5656
// swiftlint:disable:next identifier_name
5757
private let NSEC_PER_SEC = 1_000_000_000
5858
#endif

Source/swiftlint/Commands/Rules.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import ArgumentParser
22
import Foundation
33
import SwiftLintFramework
44
import SwiftyTextTable
5+
#if os(Windows)
6+
import WinSDK
7+
#endif
58

69
private typealias SortedRules = [(String, any Rule.Type)]
710

@@ -141,12 +144,20 @@ private extension TextTable {
141144

142145
private struct Terminal {
143146
static func currentWidth() -> Int {
147+
#if os(Windows)
148+
var csbi = CONSOLE_SCREEN_BUFFER_INFO()
149+
guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else {
150+
return 80
151+
}
152+
return Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1
153+
#else
144154
var size = winsize()
145155
#if os(Linux)
146156
_ = ioctl(CInt(STDOUT_FILENO), UInt(TIOCGWINSZ), &size)
147157
#else
148158
_ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)
149159
#endif
150160
return Int(size.ws_col)
161+
#endif
151162
}
152163
}

0 commit comments

Comments
 (0)