diff --git a/.github/workflows/Swift.yml b/.github/workflows/swiftwasm.yml similarity index 97% rename from .github/workflows/Swift.yml rename to .github/workflows/swiftwasm.yml index 4d6ddfaf6..b0f2ac346 100644 --- a/.github/workflows/Swift.yml +++ b/.github/workflows/swiftwasm.yml @@ -1,4 +1,4 @@ -name: Swift +name: SwiftWasm on: push: branches: ["wasm32-wasi-release/6.0"] @@ -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 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 diff --git a/Sources/WASIHelpers/FileManager.swift b/Sources/WASIHelpers/FileManager.swift index 5de8168f8..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,20 +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) + 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)]() + 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) { - appendDirectoryPointer(of: url) + init( + url: URL, + options: FileManager.DirectoryEnumerationOptions, + errorHandler: ( /* @escaping */(URL, Error) -> Bool)? + ) { + self.url = url + self.options = options + 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 { @@ -45,33 +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 } 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.