|
| 1 | +# Lima — Pure-Go `cygpath` Replacement (PoC) |
| 2 | + |
| 3 | +A proof-of-concept for the LFX 2026 Term 2 project |
| 4 | +[*Improve Windows support (host and guest)*](https://mentorship.lfx.linuxfoundation.org/project/f8bb0ffd-0c84-4cb9-8b66-ea63be76b6e2), |
| 5 | +upstream [lima-vm/lima#4907](https://github.com/lima-vm/lima/issues/4907). |
| 6 | + |
| 7 | +This PoC implements the smallest, most-concrete primary deliverable from the |
| 8 | +plan: **eliminate the `cygpath.exe` dependency** at |
| 9 | +[`pkg/ioutilx/ioutilx.go:54`](../pkg/ioutilx/ioutilx.go#L54) by replacing the |
| 10 | +subprocess shell-out with deterministic pure-Go path translation. |
| 11 | + |
| 12 | +## Why this slice |
| 13 | + |
| 14 | +Lima invokes `cygpath` in exactly one place to translate Windows paths into |
| 15 | +the form a Git-Bash / MSYS2 / Cygwin SSH client will accept. The shell-out: |
| 16 | + |
| 17 | +- pulls in an implicit Cygwin/MSYS2 dependency on every Windows host, |
| 18 | +- silently fails when `cygpath.exe` is missing from `PATH`, |
| 19 | +- adds a per-call subprocess on a hot code path. |
| 20 | + |
| 21 | +It is also the single highest-signal change a maintainer can review for this |
| 22 | +project — it proves the contributor (a) located the actual call site, (b) |
| 23 | +understands what `cygpath -u` does for each path-style namespace, and (c) |
| 24 | +can land a drop-in replacement that the existing call site can adopt with a |
| 25 | +one-line edit. |
| 26 | + |
| 27 | +## What the PoC does |
| 28 | + |
| 29 | +`pkg/winpath` exposes a drop-in replacement for `ioutilx.WindowsSubsystemPath`: |
| 30 | + |
| 31 | +```go |
| 32 | +out, err := winpath.WindowsSubsystemPath(winpath.EnvFromOS(exec.LookPath), |
| 33 | + `C:\Users\me\.lima`) |
| 34 | +``` |
| 35 | + |
| 36 | +Three styles, picked by environment: |
| 37 | + |
| 38 | +| Detected style | Trigger | Output for `C:\Users\me\.lima` | |
| 39 | +| -------------- | ---------------------------------------------- | -------------------------------- | |
| 40 | +| `native` | Win32-OpenSSH at `C:\Windows\System32\OpenSSH` | `C:/Users/me/.lima` | |
| 41 | +| `msys` | `MSYSTEM` env set, or ssh under Git-for-Windows | `/c/Users/me/.lima` | |
| 42 | +| `cygwin` | `CYGWIN` env set, or ssh under `cygwin64` | `/cygdrive/c/Users/me/.lima` | |
| 43 | + |
| 44 | +UNC paths (`\\server\share\...`) pass through with slashes normalized, |
| 45 | +matching `cygpath -u`'s behavior for UNC inputs. |
| 46 | + |
| 47 | +Detection precedence (deliberate): |
| 48 | + |
| 49 | +1. `MSYSTEM` env var (user-asserted intent) |
| 50 | +2. `CYGWIN` env var |
| 51 | +3. `SSH` env var pointing at the ssh binary |
| 52 | +4. `exec.LookPath("ssh")` fallback |
| 53 | +5. Default: `native` |
| 54 | + |
| 55 | +Path parsing uses pure string logic, not `path/filepath`, so the package is |
| 56 | +runnable and testable on any host — the production code path runs only on |
| 57 | +Windows, but every branch is exercised in CI on Linux/macOS. |
| 58 | + |
| 59 | +## Layout |
| 60 | + |
| 61 | +``` |
| 62 | +poc/ |
| 63 | +├── go.mod |
| 64 | +├── README.md |
| 65 | +├── cmd/winpath-demo/main.go # tiny CLI to demo the conversion |
| 66 | +└── pkg/winpath/ |
| 67 | + ├── winpath.go # Style, Env, DetectStyle, Convert, WindowsSubsystemPath |
| 68 | + └── winpath_test.go # 17 table-driven sub-tests |
| 69 | +``` |
| 70 | + |
| 71 | +Conventions mirror upstream Lima: |
| 72 | + |
| 73 | +- SPDX header (`Apache-2.0`) on every Go file. |
| 74 | +- Package layout under `pkg/` with a sibling `cmd/` binary, matching |
| 75 | + `cmd/lima-driver-*` and `pkg/driver/*` in the parent repo. |
| 76 | +- Table-driven tests in `_test.go` next to source, standard `testing` only — |
| 77 | + no extra test deps for this PoC. |
| 78 | +- Zero non-stdlib imports. |
| 79 | + |
| 80 | +## Run locally |
| 81 | + |
| 82 | +```bash |
| 83 | +cd poc |
| 84 | + |
| 85 | +# build everything |
| 86 | +go build ./... |
| 87 | + |
| 88 | +# 17 sub-tests across DetectStyle, Convert, and the end-to-end entry point |
| 89 | +go test ./... -v |
| 90 | + |
| 91 | +# demo CLI — three runs to exercise each detected style |
| 92 | +go run ./cmd/winpath-demo 'C:\Users\me\.lima' |
| 93 | +MSYSTEM=MINGW64 go run ./cmd/winpath-demo 'C:\Users\me\.lima' |
| 94 | +CYGWIN=1 go run ./cmd/winpath-demo 'C:\Users\me\.lima' |
| 95 | +``` |
| 96 | + |
| 97 | +Expected output (last three commands): |
| 98 | + |
| 99 | +``` |
| 100 | +detected style: native |
| 101 | +converted : C:/Users/me/.lima |
| 102 | +
|
| 103 | +detected style: msys |
| 104 | +converted : /c/Users/me/.lima |
| 105 | +
|
| 106 | +detected style: cygwin |
| 107 | +converted : /cygdrive/c/Users/me/.lima |
| 108 | +``` |
| 109 | + |
| 110 | +Requires Go ≥ 1.25 (matches the parent repo's `go.mod`). |
| 111 | + |
| 112 | +## How this would land in `lima-vm/lima` |
| 113 | + |
| 114 | +A real PR would inline `winpath.go` into `pkg/ioutilx/` (the package where |
| 115 | +`WindowsSubsystemPath` lives today) and rewrite the call site: |
| 116 | + |
| 117 | +```diff |
| 118 | + // pkg/ioutilx/ioutilx.go |
| 119 | +-func WindowsSubsystemPath(ctx context.Context, orig string) (string, error) { |
| 120 | +- out, err := exec.CommandContext(ctx, "cygpath", filepath.ToSlash(orig)).CombinedOutput() |
| 121 | +- if err != nil { |
| 122 | +- logrus.WithError(err).Errorf("failed to convert path to mingw, maybe not using Git ssh?") |
| 123 | +- return "", err |
| 124 | +- } |
| 125 | +- return strings.TrimSpace(string(out)), nil |
| 126 | +-} |
| 127 | ++func WindowsSubsystemPath(_ context.Context, orig string) (string, error) { |
| 128 | ++ return convertWindowsSubsystemPath(envFromOS(), orig) |
| 129 | ++} |
| 130 | +``` |
| 131 | + |
| 132 | +The `WindowsSubsystemPathForLinux` helper on line 62 stays as-is — that one |
| 133 | +shells out to `wsl --exec wslpath`, which is correct and not a Cygwin |
| 134 | +dependency. |
| 135 | + |
| 136 | +CI gains one new assertion: `strings limactl.exe | grep -c cygpath == 0`. |
| 137 | + |
| 138 | +## Out of scope for this PoC |
| 139 | + |
| 140 | +Everything else in the implementation plan — Windows guest template, |
| 141 | +Cloudbase-Init data path, `limactl doctor`, the HCS external driver, WSL2 |
| 142 | +multi-instance — is intentionally not in this PoC. The point of this slice |
| 143 | +is to show one real, runnable, drop-in change end-to-end. The rest of the |
| 144 | +plan builds on the same conventions demonstrated here. |
0 commit comments