Windows: improve Git Bash detection
This commit is contained in:
@@ -14,6 +14,7 @@ Design rules:
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import ntpath
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -134,11 +135,40 @@ _BASH_CACHE: Optional[str] = None
|
||||
_BASH_PROBED = False
|
||||
|
||||
# Common Git-for-Windows install locations to probe when bash isn't on PATH.
|
||||
_WINDOWS_BASH_FALLBACKS = (
|
||||
r"C:\Program Files\Git\bin\bash.exe",
|
||||
r"C:\Program Files\Git\usr\bin\bash.exe",
|
||||
r"C:\Program Files (x86)\Git\bin\bash.exe",
|
||||
_WINDOWS_BASH_ROOT_ENV_VARS = (
|
||||
"ProgramFiles",
|
||||
"ProgramW6432",
|
||||
"ProgramFiles(x86)",
|
||||
"LocalAppData",
|
||||
)
|
||||
_WINDOWS_BASH_DEFAULT_ROOTS = (
|
||||
r"C:\Program Files\Git",
|
||||
r"C:\Program Files (x86)\Git",
|
||||
)
|
||||
_WINDOWS_BASH_RELATIVE_PATHS = (
|
||||
("bin", "bash.exe"),
|
||||
("usr", "bin", "bash.exe"),
|
||||
)
|
||||
|
||||
|
||||
def _windows_bash_fallbacks() -> List[str]:
|
||||
roots: List[str] = []
|
||||
for env_name in _WINDOWS_BASH_ROOT_ENV_VARS:
|
||||
base = os.environ.get(env_name)
|
||||
if base:
|
||||
roots.append(ntpath.join(base, "Git"))
|
||||
roots.extend(_WINDOWS_BASH_DEFAULT_ROOTS)
|
||||
|
||||
paths: List[str] = []
|
||||
seen = set()
|
||||
for root in roots:
|
||||
for rel in _WINDOWS_BASH_RELATIVE_PATHS:
|
||||
path = ntpath.join(root, *rel)
|
||||
key = path.lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
paths.append(path)
|
||||
return paths
|
||||
|
||||
|
||||
def find_bash() -> Optional[str]:
|
||||
@@ -153,9 +183,9 @@ def find_bash() -> Optional[str]:
|
||||
if _BASH_PROBED:
|
||||
return _BASH_CACHE
|
||||
_BASH_PROBED = True
|
||||
found = shutil.which("bash")
|
||||
found = which_tool("bash")
|
||||
if not found and IS_WINDOWS:
|
||||
for cand in _WINDOWS_BASH_FALLBACKS:
|
||||
for cand in _windows_bash_fallbacks():
|
||||
if os.path.exists(cand):
|
||||
found = cand
|
||||
break
|
||||
|
||||
@@ -30,6 +30,26 @@ function Fail($msg) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Find-GitBash {
|
||||
$cmd = Get-Command bash -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
|
||||
$roots = @()
|
||||
foreach ($name in @("ProgramFiles", "ProgramW6432", "ProgramFiles(x86)", "LocalAppData")) {
|
||||
$base = [Environment]::GetEnvironmentVariable($name)
|
||||
if ($base) { $roots += (Join-Path $base "Git") }
|
||||
}
|
||||
$roots += @("C:\Program Files\Git", "C:\Program Files (x86)\Git")
|
||||
|
||||
foreach ($root in ($roots | Select-Object -Unique)) {
|
||||
foreach ($relative in @("bin\bash.exe", "usr\bin\bash.exe")) {
|
||||
$candidate = Join-Path $root $relative
|
||||
if (Test-Path $candidate) { return $candidate }
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# 1. Locate a Python interpreter (3.11+ required)
|
||||
Write-Step "Checking for Python"
|
||||
function Get-PythonVersionText($launcher, $launcherArgs) {
|
||||
@@ -101,7 +121,7 @@ Write-Step "Running first-time setup"
|
||||
if ($LASTEXITCODE -ne 0) { Fail "setup.py failed." }
|
||||
|
||||
# 5. Friendly note about Git Bash (full Cookbook / agent-shell parity)
|
||||
if (-not (Get-Command bash -ErrorAction SilentlyContinue)) {
|
||||
if (-not (Find-GitBash)) {
|
||||
Write-Host ""
|
||||
Write-Host "NOTE: Git Bash (bash.exe) was not found on PATH." -ForegroundColor Yellow
|
||||
Write-Host " The core app works without it. For full Cookbook background" -ForegroundColor Yellow
|
||||
|
||||
37
tests/test_platform_compat.py
Normal file
37
tests/test_platform_compat.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Regression tests for cross-platform helper behavior."""
|
||||
|
||||
from core import platform_compat
|
||||
|
||||
|
||||
def _reset_bash_cache(monkeypatch):
|
||||
monkeypatch.setattr(platform_compat, "_BASH_CACHE", None)
|
||||
monkeypatch.setattr(platform_compat, "_BASH_PROBED", False)
|
||||
|
||||
|
||||
def test_find_bash_tries_windows_exe_suffix(monkeypatch):
|
||||
_reset_bash_cache(monkeypatch)
|
||||
monkeypatch.setattr(platform_compat, "IS_WINDOWS", True)
|
||||
|
||||
expected = r"C:\Program Files\Git\bin\bash.exe"
|
||||
|
||||
def fake_which(name):
|
||||
return expected if name == "bash.exe" else None
|
||||
|
||||
monkeypatch.setattr(platform_compat.shutil, "which", fake_which)
|
||||
monkeypatch.setattr(platform_compat.os.path, "exists", lambda _path: False)
|
||||
|
||||
assert platform_compat.find_bash() == expected
|
||||
|
||||
|
||||
def test_find_bash_checks_local_app_data_git_install(monkeypatch):
|
||||
_reset_bash_cache(monkeypatch)
|
||||
monkeypatch.setattr(platform_compat, "IS_WINDOWS", True)
|
||||
monkeypatch.setattr(platform_compat.shutil, "which", lambda _name: None)
|
||||
for env_name in platform_compat._WINDOWS_BASH_ROOT_ENV_VARS:
|
||||
monkeypatch.delenv(env_name, raising=False)
|
||||
monkeypatch.setenv("LocalAppData", r"C:\Users\alice\AppData\Local")
|
||||
|
||||
expected = r"C:\Users\alice\AppData\Local\Git\bin\bash.exe"
|
||||
monkeypatch.setattr(platform_compat.os.path, "exists", lambda path: path == expected)
|
||||
|
||||
assert platform_compat.find_bash() == expected
|
||||
Reference in New Issue
Block a user