Skip to content

Commit 15d2f9d

Browse files
committed
poc: pure-Go replacement for cygpath.exe shell-out
Proof-of-concept for the LFX 2026 Term 2 project "Improve Windows support (host and guest)" (#4907). Replaces the single cygpath.exe subprocess at pkg/ioutilx/ioutilx.go:54 with deterministic pure-Go path translation. Three output styles (native / msys / cygwin) selected from MSYSTEM, CYGWIN, and the resolved ssh binary path. Path parsing uses pure string logic so the package builds and tests on any host, not just GOOS=windows. Lives under poc/ as a self-contained Go module so it is reviewable in isolation. A real PR would inline pkg/winpath into pkg/ioutilx/ and rewrite the call site; see poc/README.md for the diff sketch. Includes 17 table-driven sub-tests covering style detection, drive letter and UNC conversion, and the end-to-end entry point. Signed-off-by: mn-ram <235066282+mn-ram@users.noreply.github.com>
1 parent 5a20c6d commit 15d2f9d

5 files changed

Lines changed: 574 additions & 0 deletions

File tree

poc/README.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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.

poc/cmd/winpath-demo/main.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// winpath-demo is a tiny CLI to show that the pure-Go replacement for the
5+
// cygpath shell-out at pkg/ioutilx/ioutilx.go:54 produces the expected
6+
// string in every relevant environment, without invoking any subprocess.
7+
//
8+
// $ go run ./cmd/winpath-demo 'C:\Users\me\.lima'
9+
// detected style: native
10+
// converted : C:/Users/me/.lima
11+
//
12+
// $ MSYSTEM=MINGW64 go run ./cmd/winpath-demo 'C:\Users\me\.lima'
13+
// detected style: msys
14+
// converted : /c/Users/me/.lima
15+
//
16+
// $ CYGWIN=1 go run ./cmd/winpath-demo 'C:\Users\me\.lima'
17+
// detected style: cygwin
18+
// converted : /cygdrive/c/Users/me/.lima
19+
package main
20+
21+
import (
22+
"fmt"
23+
"os"
24+
"os/exec"
25+
26+
"github.com/mn-ram/lima-windows-poc/pkg/winpath"
27+
)
28+
29+
func main() {
30+
if len(os.Args) != 2 {
31+
fmt.Fprintf(os.Stderr, "usage: %s <windows-path>\n", os.Args[0])
32+
os.Exit(2)
33+
}
34+
35+
env := winpath.EnvFromOS(exec.LookPath)
36+
style := winpath.DetectStyle(env)
37+
38+
out, err := winpath.WindowsSubsystemPath(env, os.Args[1])
39+
if err != nil {
40+
fmt.Fprintln(os.Stderr, "error:", err)
41+
os.Exit(1)
42+
}
43+
44+
fmt.Printf("detected style: %s\n", style)
45+
fmt.Printf("converted : %s\n", out)
46+
}

poc/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/mn-ram/lima-windows-poc
2+
3+
go 1.25.7

0 commit comments

Comments
 (0)