Skip to content

Commit 86ebba7

Browse files
committed
fix(provider/python): bypass Windows symlink DLL resolution issues (0xc0000135)
1 parent d434417 commit 86ebba7

1 file changed

Lines changed: 40 additions & 18 deletions

File tree

internal/provider/python.go

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,26 @@ func (p *PythonProvider) Install(ctx context.Context, tool string, installPath s
3535
return p.generic.Install(ctx, tool, installPath, artifactPath, version)
3636
}
3737

38-
// PostInstall creates a virtual environment.
39-
func (p *PythonProvider) PostInstall(ctx context.Context, tool string, installPath string, version string) error {
40-
pythonPath := filepath.Join(installPath, "bin", "python3")
38+
// getRealPythonPath resolves the actual Python binary path.
39+
// This is necessary on Windows because generic.Install creates symlinks in bin/,
40+
// and executing a symlink on Windows causes DLL resolution failures (0xc0000135)
41+
// since vcruntime140.dll is next to the real binary, not the symlink.
42+
func (p *PythonProvider) getRealPythonPath(installPath string) string {
4143
if runtime.GOOS == "windows" {
42-
pythonPath = filepath.Join(installPath, "bin", "python.exe")
44+
// python-build-standalone on Windows often extracts python.exe to the root
45+
rootPy := filepath.Join(installPath, "python.exe")
46+
if _, err := os.Stat(rootPy); err == nil {
47+
return rootPy
48+
}
49+
// Try bin just in case
50+
return filepath.Join(installPath, "bin", "python.exe")
4351
}
52+
return filepath.Join(installPath, "bin", "python3")
53+
}
54+
55+
// PostInstall creates a virtual environment.
56+
func (p *PythonProvider) PostInstall(ctx context.Context, tool string, installPath string, version string) error {
57+
pythonPath := p.getRealPythonPath(installPath)
4458

4559
venvDir := filepath.Join(installPath, "venv")
4660
cmd := exec.CommandContext(ctx, pythonPath, "-m", "venv", venvDir)
@@ -57,9 +71,14 @@ func (p *PythonProvider) GenerateShims(tool string, installPath string, version
5771

5872
executables := []string{"python", "python3", "pip", "pip3"}
5973
for _, exe := range executables {
60-
exePath := filepath.Join(installPath, "bin", exe)
74+
var exePath string
75+
// Point the shim directly to the venv executables.
76+
// This natively solves the Windows symlink DLL resolution issue
77+
// and ensures the tool inherently uses its isolated environment.
6178
if runtime.GOOS == "windows" {
62-
exePath += ".exe"
79+
exePath = filepath.Join(installPath, "venv", "Scripts", exe+".exe")
80+
} else {
81+
exePath = filepath.Join(installPath, "venv", "bin", exe)
6382
}
6483

6584
shimContent := p.generatePythonShim(exe, exePath, installPath, version)
@@ -71,10 +90,7 @@ func (p *PythonProvider) GenerateShims(tool string, installPath string, version
7190

7291
// DetectVersion detects Python version.
7392
func (p *PythonProvider) DetectVersion(ctx context.Context, tool string, installPath string) (string, error) {
74-
pythonPath := filepath.Join(installPath, "bin", "python3")
75-
if runtime.GOOS == "windows" {
76-
pythonPath = filepath.Join(installPath, "bin", "python.exe")
77-
}
93+
pythonPath := p.getRealPythonPath(installPath)
7894

7995
cmd := exec.CommandContext(ctx, pythonPath, "--version")
8096
output, err := cmd.Output()
@@ -89,15 +105,21 @@ func (p *PythonProvider) DetectVersion(ctx context.Context, tool string, install
89105

90106
// ListExecutables returns Python executables relative to installPath.
91107
func (p *PythonProvider) ListExecutables(tool string, installPath string, version string) ([]string, error) {
92-
executables := []string{
93-
filepath.Join("bin", "python"),
94-
filepath.Join("bin", "python3"),
95-
filepath.Join("bin", "pip"),
96-
filepath.Join("bin", "pip3"),
97-
}
108+
// Expose the venv executables so they can be discovered by generic logic if needed
109+
var executables []string
98110
if runtime.GOOS == "windows" {
99-
for i := range executables {
100-
executables[i] += ".exe"
111+
executables = []string{
112+
filepath.Join("venv", "Scripts", "python.exe"),
113+
filepath.Join("venv", "Scripts", "python3.exe"),
114+
filepath.Join("venv", "Scripts", "pip.exe"),
115+
filepath.Join("venv", "Scripts", "pip3.exe"),
116+
}
117+
} else {
118+
executables = []string{
119+
filepath.Join("venv", "bin", "python"),
120+
filepath.Join("venv", "bin", "python3"),
121+
filepath.Join("venv", "bin", "pip"),
122+
filepath.Join("venv", "bin", "pip3"),
101123
}
102124
}
103125
return executables, nil

0 commit comments

Comments
 (0)