Skip to content

Windows support #263

@bguidolim

Description

@bguidolim

Summary

Make mcs build and run on Windows. This requires deeper changes than Linux due to fundamental platform differences in shell execution, terminal handling, file locking, and the absence of Homebrew. The existing Environment and ShellRunner abstractions are natural extension points for a platform abstraction layer.

Effort Estimate

High — requires a platform abstraction layer across shell execution, terminal I/O, file locking, and package management. No full rewrite, but significant targeted work.

Required Changes

Build Configuration

  • Remove or conditionalize .macOS(.v13) in Package.swift (shared with Linux issue)

Shell Execution Abstraction

  • Hardcoded POSIX paths in Constants.swift:36-42:
    • /usr/bin/env → no equivalent on Windows (not needed if using absolute paths)
    • /usr/bin/whichwhere.exe on Windows
    • /bin/bashcmd.exe /c or powershell.exe -Command
  • ShellRunner.shell() (ShellRunner.swift:82-95) — runs commands via /bin/bash -c. Needs Windows shell equivalent
  • Environment.resolveCommand() (Environment.swift:112-137) — uses /usr/bin/which via Process. Needs where.exe on Windows
  • ClaudeIntegration (ClaudeIntegration.swift:47-49) — invokes claude via /usr/bin/env. Needs direct PATH resolution on Windows
  • ScriptRunner.runCommand() (ScriptRunner.swift:97-122) — executes fix commands via /bin/bash -c

Terminal I/O

  • Darwin.read() in CLIOutput.swift:523 — no Darwin/Glibc on Windows. Use _read() from CRT or ReadConsoleInput
  • termios/tcgetattr/tcsetattr in CLIOutput.swift:232-245, 378-392 — no POSIX terminal on Windows. Use SetConsoleMode with ENABLE_LINE_INPUT / ENABLE_ECHO_INPUT flags
  • ioctl(TIOCGWINSZ) in CLIOutput.swift:52-53 — use GetConsoleScreenBufferInfo for terminal width
  • isatty(STDOUT_FILENO) in CLIOutput.swift:11,205,219 — use _isatty(_fileno(stdout)) on Windows
  • ANSI escape codes — Windows Terminal supports them natively, but legacy cmd.exe requires SetConsoleMode with ENABLE_VIRTUAL_TERMINAL_PROCESSING flag. Consider adding an initialization step

File Locking

  • POSIX flock() in FileLock.swift:76-82 — does not exist on Windows. Replace with LockFileEx/UnlockFileEx (Win32 API)
  • open()/close() in FileLock.swift:76,80 — use CreateFileW/CloseHandle or CRT _open/_close
  • strerror/errno in FileLock.swift:14 — available via CRT but error codes may differ

Apple-Only APIs

  • CryptoKit — add #if canImport(CryptoKit) / import Crypto fallback (shared with Linux issue) in 4 files
  • OSAllocatedUnfairLock in ScriptRunner.swift:174 — replace with Swift 6 Mutex or NSLock (shared with Linux issue)

File Permissions

  • .posixPermissions: 0o755 used in:

    • CoreDoctorChecks.swift:223
    • ScriptRunner.swift:131
    • ComponentExecutor.swift:157, 182, 262

    POSIX permissions are meaningless on Windows. Guard with #if !os(Windows) or use Windows ACL equivalent

Path Handling

  • Raw / path splits — e.g. Homebrew.swift:58: resolved.split(separator: "/"). Windows paths use \. Use Foundation URL APIs instead of raw string splitting where possible
  • PATH separatorEnvironment.swift:107: "\(brewBin):\(currentPath)" uses : as PATH separator. Windows uses ;

Package Management

  • Homebrew does not exist on Windows — the entire Homebrew struct and ComponentExecutor.installBrewPackage() need a platform guard
  • Consider abstracting package management behind a protocol to support alternatives (Scoop, Chocolatey, winget) in the future, or simply skip brew components on Windows with a warning
  • ClaudePrerequisite.swift — brew-based Claude CLI installation fallback won't work

Suggested Architecture

Platform (protocol)
├── macOS: DarwinPlatform
├── Linux: LinuxPlatform  
└── Windows: WindowsPlatform

Abstracts:
- shellCommand(_ command: String) -> ShellResult   // bash vs cmd/powershell
- resolveCommand(_ name: String) -> String?         // which vs where
- terminalWidth() -> Int                            // ioctl vs GetConsoleScreenBufferInfo
- withFileLock(at: URL, body:) throws -> T          // flock vs LockFileEx
- pathSeparator: Character                          // : vs ;
- isPackageManagerAvailable: Bool                   
- installPackage(_ name: String) -> Bool            

Files Affected

File Change
Package.swift Platform restriction
Sources/mcs/Core/Constants.swift Platform-conditional shell paths
Sources/mcs/Core/Environment.swift Command resolution, PATH separator, brew prefix
Sources/mcs/Core/ShellRunner.swift Shell execution abstraction
Sources/mcs/Core/CLIOutput.swift Terminal I/O (raw mode, read, width, isatty)
Sources/mcs/Core/FileLock.swift File locking mechanism
Sources/mcs/Core/Homebrew.swift Platform guard or abstraction
Sources/mcs/Core/ClaudePrerequisite.swift Brew install fallback
Sources/mcs/Core/FileHasher.swift CryptoKit import
Sources/mcs/Core/SettingsHasher.swift CryptoKit import
Sources/mcs/Doctor/SectionValidator.swift CryptoKit import
Sources/mcs/Doctor/CoreDoctorChecks.swift POSIX permissions guard
Sources/mcs/ExternalPack/PackTrustManager.swift CryptoKit import
Sources/mcs/ExternalPack/ScriptRunner.swift OSAllocatedUnfairLock, bash execution
Sources/mcs/Install/ComponentExecutor.swift POSIX permissions guard, brew abstraction
Sources/mcs/Core/ClaudeIntegration.swift /usr/bin/env usage

Dependencies

  • Linux support issue should be completed first — several fixes are shared (CryptoKit, OSAllocatedUnfairLock, Package.swift)
  • Swift for Windows toolchain maturity should be evaluated at implementation time (swift-corelibs-foundation Process support on Windows is still evolving)

Notes

  • Distribution: Homebrew is not available on Windows. Future distribution TBD (direct download, Scoop, Chocolatey, winget)
  • Claude Code CLI must also be available on Windows for mcs to be useful
  • The fallback (non-TTY) multi-select in CLIOutput already works without raw mode — this could be the initial Windows path while terminal support matures

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions