feat: auto-install Python 3 via winget on Windows

When Python 3 is not found during installation on Windows, the installer
now automatically attempts to install it using winget (Python.Python.3.12).
Falls back to manual install prompt if winget is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-03-09 18:22:05 +08:00
parent 3bc537df95
commit 67242de577

View File

@ -365,6 +365,58 @@ func (inst *Installer) extractDir(embedDir, destDir string) error {
})
}
// installPython3Windows attempts to install Python 3 via winget on Windows.
func (inst *Installer) installPython3Windows() error {
if runtime.GOOS != "windows" {
return fmt.Errorf("not windows")
}
// Check if winget is available
if _, err := exec.LookPath("winget"); err != nil {
return fmt.Errorf("winget not found — please install Python 3 manually from https://python.org")
}
inst.emitProgress(ProgressEvent{
Step: "python",
Message: "Installing Python 3 via winget...",
Percent: 73,
})
cmd := exec.Command("winget", "install", "Python.Python.3.12",
"--accept-source-agreements", "--accept-package-agreements", "--silent")
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("winget install Python failed: %s — %w", string(out), err)
}
// winget installs Python to %LOCALAPPDATA%\Programs\Python\Python312\
// We need to add it to PATH for the current process
pythonDir := filepath.Join(os.Getenv("LOCALAPPDATA"), "Programs", "Python", "Python312")
scriptsDir := filepath.Join(pythonDir, "Scripts")
os.Setenv("PATH", pythonDir+";"+scriptsDir+";"+os.Getenv("PATH"))
// Verify it works
if _, err := findPython3(); err != nil {
// Try common alternative paths
for _, ver := range []string{"Python313", "Python311", "Python310"} {
altDir := filepath.Join(os.Getenv("LOCALAPPDATA"), "Programs", "Python", ver)
if _, err := os.Stat(filepath.Join(altDir, "python.exe")); err == nil {
os.Setenv("PATH", altDir+";"+filepath.Join(altDir, "Scripts")+";"+os.Getenv("PATH"))
break
}
}
if _, err := findPython3(); err != nil {
return fmt.Errorf("Python installed but not found in PATH — please restart the installer")
}
}
inst.emitProgress(ProgressEvent{
Step: "python",
Message: "Python 3 installed successfully.",
Percent: 75,
})
return nil
}
// setupPythonVenv creates a Python virtual environment and installs requirements.
func (inst *Installer) setupPythonVenv(installDir string) error {
venvDir := filepath.Join(installDir, "venv")
@ -372,7 +424,18 @@ func (inst *Installer) setupPythonVenv(installDir string) error {
pythonPath, err := findPython3()
if err != nil {
return fmt.Errorf("python3 not found on PATH: %w", err)
// On Windows, try to install Python automatically
if runtime.GOOS == "windows" {
if installErr := inst.installPython3Windows(); installErr != nil {
return fmt.Errorf("python3 not found and auto-install failed: %w", installErr)
}
pythonPath, err = findPython3()
if err != nil {
return fmt.Errorf("python3 still not found after installation: %w", err)
}
} else {
return fmt.Errorf("python3 not found on PATH: %w", err)
}
}
inst.emitProgress(ProgressEvent{