From 0e9aa119bc00b6c47c976aa6ee69b4ee530e79c6 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Fri, 4 Oct 2024 03:49:29 +0900 Subject: [PATCH 1/6] feat(wasi): add support for the `.skipHiddenFiles` option --- Sources/WASIHelpers/FileManager.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/WASIHelpers/FileManager.swift b/Sources/WASIHelpers/FileManager.swift index 5de8168f8..72ccc4f74 100644 --- a/Sources/WASIHelpers/FileManager.swift +++ b/Sources/WASIHelpers/FileManager.swift @@ -21,7 +21,7 @@ extension FileManager { errorHandler handler: (/* @escaping */ (URL, Error) -> Bool)? = nil ) -> FileManager.WASIDirectoryEnumerator? { // TODO: Use arguments - .init(at: url) + .init(at: url, options: mask) } } @@ -29,8 +29,10 @@ extension FileManager { /// Thread-unsafe directory enumerator. public class WASIDirectoryEnumerator: Sequence, IteratorProtocol { private var dpStack = [(URL, OpaquePointer)]() + private var options: FileManager.DirectoryEnumerationOptions - fileprivate init(at url: URL) { + fileprivate init(at url: URL, options: FileManager.DirectoryEnumerationOptions) { + self.options = options appendDirectoryPointer(of: url) } @@ -54,6 +56,9 @@ extension FileManager { return String(cString: d_namePtr.assumingMemoryBound(to: CChar.self)) } guard filename != "." && filename != ".." else { continue } + if options.contains(.skipsHiddenFiles) { + guard !filename.hasPrefix(".") else { continue } + } let child = url.appendingPathComponent(filename) var status = stat() if child.withUnsafeFileSystemRepresentation({ stat($0, &status) }) == 0, (status.st_mode & S_IFMT) == S_IFDIR { From 21a3b454ef13f873c5c0151b40fbda0fc58a4691 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Sun, 6 Oct 2024 06:52:02 +0900 Subject: [PATCH 2/6] ci: rename Swift.yml to swiftwasm.yml The upstream of swift-format now has GitHub Actions, so I will rename Swift.yml to avoid confusion. --- .github/workflows/{Swift.yml => swiftwasm.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{Swift.yml => swiftwasm.yml} (100%) diff --git a/.github/workflows/Swift.yml b/.github/workflows/swiftwasm.yml similarity index 100% rename from .github/workflows/Swift.yml rename to .github/workflows/swiftwasm.yml From e0b34ac810ae7c25c9adfde015192a4efe8307b7 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:10:13 +0900 Subject: [PATCH 3/6] refactor(wasi): refactor `FileManager.DirectoryEnumerator` --- Sources/WASIHelpers/FileManager.swift | 137 ++++++++++++++---- .../swift-format/Utilities/FileIterator.swift | 8 +- 2 files changed, 113 insertions(+), 32 deletions(-) diff --git a/Sources/WASIHelpers/FileManager.swift b/Sources/WASIHelpers/FileManager.swift index 72ccc4f74..313ea8b08 100644 --- a/Sources/WASIHelpers/FileManager.swift +++ b/Sources/WASIHelpers/FileManager.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import _FoundationCShims #if os(WASI) extension FileManager { @@ -18,22 +19,41 @@ extension FileManager { at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: FileManager.DirectoryEnumerationOptions = [], - errorHandler handler: (/* @escaping */ (URL, Error) -> Bool)? = nil - ) -> FileManager.WASIDirectoryEnumerator? { - // TODO: Use arguments - .init(at: url, options: mask) + errorHandler handler: ( /* @escaping */(URL, Error) -> Bool)? = nil + ) -> DirectoryEnumerator? { + NSURLDirectoryEnumerator(url: url, options: mask, errorHandler: handler) } } extension FileManager { /// Thread-unsafe directory enumerator. - public class WASIDirectoryEnumerator: Sequence, IteratorProtocol { - private var dpStack = [(URL, OpaquePointer)]() - private var options: FileManager.DirectoryEnumerationOptions + class NSURLDirectoryEnumerator: DirectoryEnumerator { + private(set) var dpStack = [(URL, OpaquePointer)]() + var url: URL + var options: FileManager.DirectoryEnumerationOptions + var errorHandler: ((URL, any Error) -> Bool)? + var rootError: (any Error)? = nil - fileprivate init(at url: URL, options: FileManager.DirectoryEnumerationOptions) { + init( + url: URL, + options: FileManager.DirectoryEnumerationOptions, + errorHandler: ( /* @escaping */(URL, Error) -> Bool)? + ) { + self.url = url self.options = options - appendDirectoryPointer(of: url) + self.errorHandler = errorHandler + super.init() + + let fm = FileManager.default + do { + guard fm.fileExists(atPath: url.path) else { throw _NSErrorWithErrno(ENOENT, reading: true, url: url) } + guard let dp = fm.withFileSystemRepresentation(for: url.path, opendir) else { + throw _NSErrorWithErrno(errno, reading: true, url: url) + } + dpStack.append((url, dp)) + } catch { + rootError = error + } } deinit { @@ -47,36 +67,103 @@ extension FileManager { dpStack.append((url, dp)) } - public func next() -> Any? { + override func nextObject() -> Any? { + func match(filename: String, to options: DirectoryEnumerationOptions, isDir: Bool) -> (Bool, Bool) { + var showFile = true + var skipDescendants = false + + if isDir { + if options.contains(.skipsSubdirectoryDescendants) { + skipDescendants = true + } + // Ignore .skipsPackageDescendants + } + if options.contains(.skipsHiddenFiles) && filename.hasPrefix(".") { + showFile = false + skipDescendants = true + } + + return (showFile, skipDescendants) + } + while let (url, dp) = dpStack.last { while let ep = readdir(dp) { - let filename = withUnsafeBytes(of: &ep.pointee.d_type) { rawPtr in - // UnsafeRawPointer of d_name - let d_namePtr = rawPtr.baseAddress! + MemoryLayout.stride - return String(cString: d_namePtr.assumingMemoryBound(to: CChar.self)) - } + guard ep.pointee.d_ino != 0 else { continue } + let filename = String(cString: _platform_shims_dirent_d_name(ep)) guard filename != "." && filename != ".." else { continue } - if options.contains(.skipsHiddenFiles) { - guard !filename.hasPrefix(".") else { continue } - } let child = url.appendingPathComponent(filename) - var status = stat() - if child.withUnsafeFileSystemRepresentation({ stat($0, &status) }) == 0, (status.st_mode & S_IFMT) == S_IFDIR { - appendDirectoryPointer(of: child) - return child + var isDirectory = false + if ep.pointee.d_type == _platform_shims_DT_DIR() { + isDirectory = true + } else if ep.pointee.d_type == _platform_shims_DT_UNKNOWN() { + var status = stat() + if stat(child.path, &status) == 0, (status.st_mode & S_IFMT) == S_IFDIR { + isDirectory = true + } + } + if isDirectory { + let (showFile, skipDescendants) = match(filename: filename, to: options, isDir: true) + if !skipDescendants { + appendDirectoryPointer(of: child) + } + if showFile { + return child + } } else { - return child + let (showFile, _) = match(filename: filename, to: options, isDir: false) + if showFile { + return child + } } } closedir(dp) dpStack.removeLast() } + if let error = rootError, let handler = errorHandler { + let _ = handler(url, error) + } return nil } + } +} - public func nextObject() -> Any? { - next() +func _NSErrorWithErrno( + _ posixErrno: Int32, + reading: Bool, + path: String? = nil, + url: URL? = nil, + extraUserInfo: [String: Any]? = nil +) -> NSError { + var cocoaError: CocoaError.Code + if reading { + switch posixErrno { + case EFBIG: cocoaError = .fileReadTooLarge + case ENOENT: cocoaError = .fileReadNoSuchFile + case EPERM, EACCES: cocoaError = .fileReadNoPermission + case ENAMETOOLONG: cocoaError = .fileReadUnknown + default: cocoaError = .fileReadUnknown } + } else { + switch posixErrno { + case ENOENT: cocoaError = .fileNoSuchFile + case EPERM, EACCES: cocoaError = .fileWriteNoPermission + case ENAMETOOLONG: cocoaError = .fileWriteInvalidFileName + case EDQUOT, ENOSPC: cocoaError = .fileWriteOutOfSpace + case EROFS: cocoaError = .fileWriteVolumeReadOnly + case EEXIST: cocoaError = .fileWriteFileExists + default: cocoaError = .fileWriteUnknown + } + } + + var userInfo = extraUserInfo ?? [String: Any]() + if let path = path { + userInfo[NSFilePathErrorKey] = path + } else if let url = url { + userInfo[NSURLErrorKey] = url } + + userInfo[NSUnderlyingErrorKey] = NSError(domain: NSPOSIXErrorDomain, code: Int(posixErrno)) + + return NSError(domain: NSCocoaErrorDomain, code: cocoaError.rawValue, userInfo: userInfo) } #endif diff --git a/Sources/swift-format/Utilities/FileIterator.swift b/Sources/swift-format/Utilities/FileIterator.swift index f7b307e95..5c28c0533 100644 --- a/Sources/swift-format/Utilities/FileIterator.swift +++ b/Sources/swift-format/Utilities/FileIterator.swift @@ -15,12 +15,6 @@ import Foundation import WASIHelpers #endif -#if !os(WASI) -private typealias DirectoryEnumerator = FileManager.DirectoryEnumerator -#else -private typealias DirectoryEnumerator = FileManager.WASIDirectoryEnumerator -#endif - /// Iterator for looping over lists of files and directories. Directories are automatically /// traversed recursively, and we check for files with a ".swift" extension. @_spi(Testing) @@ -37,7 +31,7 @@ public struct FileIterator: Sequence, IteratorProtocol { private var urlIterator: Array.Iterator /// Iterator for recursing through directories. - private var dirIterator: DirectoryEnumerator? = nil + private var dirIterator: FileManager.DirectoryEnumerator? = nil /// The current working directory of the process, which is used to relativize URLs of files found /// during iteration. From 969ce3d5eaeb39c895233001437767c6a07e19d9 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:19:45 +0900 Subject: [PATCH 4/6] ci: bump wasmtime to 25.0.2 https://github.com/bytecodealliance/wasmtime/releases/tag/v25.0.2 --- .github/workflows/swiftwasm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swiftwasm.yml b/.github/workflows/swiftwasm.yml index 4d6ddfaf6..9f0030ce8 100644 --- a/.github/workflows/swiftwasm.yml +++ b/.github/workflows/swiftwasm.yml @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v4 - uses: bytecodealliance/actions/wasmtime/setup@v1 with: - version: "25.0.1" + version: "25.0.2" - run: swift --version - run: wasmtime -V - run: swift sdk install $SWIFT_SDK_URL --checksum $SWIFT_SDK_CHECKSUM From 0e0d6fdd6dccdc713069be657859b8a49025a984 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:44:22 +0900 Subject: [PATCH 5/6] ci: change workflow name from Swift to SwiftWasm --- .github/workflows/swiftwasm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swiftwasm.yml b/.github/workflows/swiftwasm.yml index 9f0030ce8..b0f2ac346 100644 --- a/.github/workflows/swiftwasm.yml +++ b/.github/workflows/swiftwasm.yml @@ -1,4 +1,4 @@ -name: Swift +name: SwiftWasm on: push: branches: ["wasm32-wasi-release/6.0"] From 08a84d7095982b6e4a32f5ae826ac32475ef2b33 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:25:31 +0900 Subject: [PATCH 6/6] docs: update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ee250ea9..c844a1f48 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # kkebo/swift-format -The WebAssembly (WASI) version of `swift-format`. +The WebAssembly (WASI) version of `swift-format`. This project's goal is to be merged into [swiftlang/swift-format](https://github.com/swiftlang/swift-format). ## Download @@ -17,8 +17,9 @@ The checked items are currently supported. The others are planned to be supporte - [x] [a-Shell (`wasm` and `wasm3`)](https://github.com/holzschu/a-shell?tab=readme-ov-file) (iOS, iPadOS) - [x] [Wasmtime](https://wasmtime.dev) -- [ ] [Wasmer](https://wasmer.io) -- [ ] [WasmKit](https://github.com/swiftwasm/WasmKit) +- [x] [Wasmer](https://wasmer.io) +- [x] [WasmKit](https://github.com/swiftwasm/WasmKit) +- [x] [WasmEdge](https://wasmedge.org) ## Restrictions