Skip to content

Commit ba73b6b

Browse files
committed
fix: Windows venv initialization issues
1 parent bd29b69 commit ba73b6b

3 files changed

Lines changed: 120 additions & 32 deletions

File tree

electron/main/utils/process.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,22 @@ function fixPyvenvCfgPlaceholder(pyvenvCfgPath: string): boolean {
163163
if (content.includes('{{PREBUILT_PYTHON_DIR}}')) {
164164
const prebuiltPythonDir = getPrebuiltPythonDir();
165165
if (!prebuiltPythonDir) {
166-
log.warn('[VENV] Cannot fix pyvenv.cfg: prebuilt Python directory not found');
166+
log.warn(
167+
'[VENV] Cannot fix pyvenv.cfg: prebuilt Python directory not found'
168+
);
167169
return false;
168170
}
169171

170172
// Replace placeholder with actual path
171173
// On Windows, path.join returns paths with backslashes, which matches our placeholder format
172-
content = content.replace(/\{\{PREBUILT_PYTHON_DIR\}\}/g, prebuiltPythonDir);
174+
content = content.replace(
175+
/\{\{PREBUILT_PYTHON_DIR\}\}/g,
176+
prebuiltPythonDir
177+
);
173178
fs.writeFileSync(pyvenvCfgPath, content);
174-
log.info(`[VENV] Fixed pyvenv.cfg placeholder with: ${prebuiltPythonDir}`);
179+
log.info(
180+
`[VENV] Fixed pyvenv.cfg placeholder with: ${prebuiltPythonDir}`
181+
);
175182
return true;
176183
}
177184

@@ -195,20 +202,24 @@ function fixPyvenvCfgPlaceholder(pyvenvCfgPath: string): boolean {
195202
/**
196203
* Fix shebang lines in venv scripts by replacing placeholder with actual Python path
197204
* This ensures scripts can be executed directly (not just via `uv run`)
205+
* Note: Windows doesn't use shebangs - it uses .exe wrappers instead
198206
*/
199207
function fixVenvScriptShebangs(venvPath: string): boolean {
200208
const isWindows = process.platform === 'win32';
201-
const binDir = isWindows
202-
? path.join(venvPath, 'Scripts')
203-
: path.join(venvPath, 'bin');
209+
210+
// Windows doesn't use shebangs - skip this step
211+
if (isWindows) {
212+
log.info(`[VENV] Skipping shebang fixes on Windows (not needed)`);
213+
return true;
214+
}
215+
216+
const binDir = path.join(venvPath, 'bin');
204217

205218
if (!fs.existsSync(binDir)) {
206219
return false;
207220
}
208221

209-
const pythonExe = isWindows
210-
? path.join(binDir, 'python.exe')
211-
: path.join(binDir, 'python');
222+
const pythonExe = path.join(binDir, 'python');
212223

213224
if (!fs.existsSync(pythonExe)) {
214225
log.warn(`[VENV] Python executable not found: ${pythonExe}`);
@@ -373,7 +384,10 @@ export function getPrebuiltTerminalVenvPath(): string | null {
373384
);
374385
if (fs.existsSync(prebuiltTerminalVenvPath)) {
375386
const pyvenvCfgPath = path.join(prebuiltTerminalVenvPath, 'pyvenv.cfg');
376-
const installedMarker = path.join(prebuiltTerminalVenvPath, '.packages_installed');
387+
const installedMarker = path.join(
388+
prebuiltTerminalVenvPath,
389+
'.packages_installed'
390+
);
377391
if (fs.existsSync(pyvenvCfgPath) && fs.existsSync(installedMarker)) {
378392
// Fix placeholder in pyvenv.cfg if needed
379393
fixPyvenvCfgPlaceholder(pyvenvCfgPath);

scripts/fix-symlinks.js

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env node
2+
/* global console */
23
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
34
// Licensed under the Apache License, Version 2.0 (the "License");
45
// you may not use this file except in compliance with the License.
@@ -28,6 +29,7 @@
2829
* - Works on any machine after packaging
2930
*/
3031

32+
import { Buffer } from 'buffer';
3133
import fs from 'fs';
3234
import path from 'path';
3335
import { fileURLToPath } from 'url';
@@ -50,12 +52,22 @@ function findPythonExecutable(uvPythonDir) {
5052
// Find cpython directory
5153
for (const entry of entries) {
5254
if (entry.isDirectory() && entry.name.startsWith('cpython-')) {
53-
// Try different possible paths
5455
const possiblePaths = [
56+
// Unix paths
5557
path.join(uvPythonDir, entry.name, 'bin', 'python3.10'),
5658
path.join(uvPythonDir, entry.name, 'bin', 'python'),
5759
path.join(uvPythonDir, entry.name, 'install', 'bin', 'python3.10'),
5860
path.join(uvPythonDir, entry.name, 'install', 'bin', 'python'),
61+
// Windows paths
62+
path.join(uvPythonDir, entry.name, 'Scripts', 'python.exe'),
63+
path.join(
64+
uvPythonDir,
65+
entry.name,
66+
'install',
67+
'Scripts',
68+
'python.exe'
69+
),
70+
path.join(uvPythonDir, entry.name, 'python.exe'),
5971
];
6072

6173
for (const pythonPath of possiblePaths) {
@@ -64,7 +76,7 @@ function findPythonExecutable(uvPythonDir) {
6476
absolutePath: pythonPath,
6577
cpythonDir: entry.name,
6678
// Extract relative path from uv_python
67-
relativePath: path.relative(uvPythonDir, pythonPath)
79+
relativePath: path.relative(uvPythonDir, pythonPath),
6880
};
6981
}
7082
}
@@ -82,14 +94,29 @@ function findPythonExecutable(uvPythonDir) {
8294
*/
8395
function fixVenvSymlinks(venvPath, venvName) {
8496
const binDir = path.join(venvPath, 'bin');
97+
const scriptsDir = path.join(venvPath, 'Scripts');
98+
99+
// Windows uses Scripts directory and doesn't need symlink fixes
100+
if (fs.existsSync(scriptsDir) && !fs.existsSync(binDir)) {
101+
console.log(`\n📝 Processing ${venvName}`);
102+
console.log(
103+
` ℹ️ Windows venv detected - skipping symlink fixes (not needed)`
104+
);
105+
return true;
106+
}
85107

86108
if (!fs.existsSync(binDir)) {
87109
console.log(`⚠️ ${venvName} bin directory not found: ${binDir}`);
88110
return false;
89111
}
90112

91113
// Find Python in uv_python
92-
const uvPythonDir = path.join(projectRoot, 'resources', 'prebuilt', 'uv_python');
114+
const uvPythonDir = path.join(
115+
projectRoot,
116+
'resources',
117+
'prebuilt',
118+
'uv_python'
119+
);
93120
const pythonInfo = findPythonExecutable(uvPythonDir);
94121

95122
if (!pythonInfo) {
@@ -112,22 +139,34 @@ function fixVenvSymlinks(venvPath, venvName) {
112139
let needsFix = false;
113140
let currentTarget = null;
114141

115-
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
142+
if (
143+
fs.existsSync(symlinkPath) ||
144+
fs.lstatSync(symlinkPath).isSymbolicLink()
145+
) {
116146
try {
117147
currentTarget = fs.readlinkSync(symlinkPath);
118148

119149
// Check if it's a broken symlink or points to wrong location
120-
if (path.isAbsolute(currentTarget) || currentTarget.includes('cache')) {
150+
if (
151+
path.isAbsolute(currentTarget) ||
152+
currentTarget.includes('cache')
153+
) {
121154
needsFix = true;
122-
console.log(` ❌ ${symlinkName}: broken symlink -> ${currentTarget}`);
155+
console.log(
156+
` ❌ ${symlinkName}: broken symlink -> ${currentTarget}`
157+
);
123158
} else {
124-
console.log(` ✓ ${symlinkName}: already fixed -> ${currentTarget}`);
159+
console.log(
160+
` ✓ ${symlinkName}: already fixed -> ${currentTarget}`
161+
);
125162
continue;
126163
}
127164
} catch (err) {
128165
// Broken symlink
129166
needsFix = true;
130-
console.log(` ❌ ${symlinkName}: broken symlink (target doesn't exist)`);
167+
console.log(
168+
` ❌ ${symlinkName}: broken symlink (target doesn't exist), ${err.message}`
169+
);
131170
}
132171
} else {
133172
needsFix = true;
@@ -137,10 +176,16 @@ function fixVenvSymlinks(venvPath, venvName) {
137176
if (needsFix) {
138177
// Remove existing symlink
139178
try {
140-
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
179+
if (
180+
fs.existsSync(symlinkPath) ||
181+
fs.lstatSync(symlinkPath).isSymbolicLink()
182+
) {
141183
fs.unlinkSync(symlinkPath);
142184
}
143185
} catch (err) {
186+
console.error(
187+
` ❌ Failed to remove ${symlinkName}: ${err.message}`
188+
);
144189
// Ignore
145190
}
146191

@@ -171,9 +216,20 @@ function fixVenvSymlinks(venvPath, venvName) {
171216

172217
/**
173218
* Fix shebang lines in all executable scripts in venv/bin directory
219+
* Note: Windows doesn't use shebangs - it uses .exe wrappers instead
174220
*/
175221
function fixScriptShebangs(venvPath, venvName) {
176222
const binDir = path.join(venvPath, 'bin');
223+
const scriptsDir = path.join(venvPath, 'Scripts');
224+
225+
// Windows uses Scripts directory and doesn't need shebang fixes
226+
if (fs.existsSync(scriptsDir) && !fs.existsSync(binDir)) {
227+
console.log(`\n📝 Processing ${venvName}`);
228+
console.log(
229+
` ℹ️ Windows venv detected - skipping shebang fixes (not needed)`
230+
);
231+
return { fixed: 0, skipped: 0 };
232+
}
177233

178234
if (!fs.existsSync(binDir)) {
179235
console.log(`⚠️ ${venvName} bin directory not found: ${binDir}`);
@@ -198,6 +254,7 @@ function fixScriptShebangs(venvPath, venvName) {
198254
continue;
199255
}
200256
} catch (err) {
257+
console.error(` ❌ Failed to check ${entry}: ${err.message}`);
201258
continue;
202259
}
203260

@@ -222,7 +279,17 @@ function fixScriptShebangs(venvPath, venvName) {
222279
}
223280

224281
// Check if it contains an absolute path to the venv
225-
if (!path.isAbsolute(firstLine.substring(2).trim()) && !firstLine.includes('/resources/prebuilt/venv/')) {
282+
// Support both Unix (/resources/prebuilt/venv/) and Windows (\resources\prebuilt\venv\ or \resources\prebuilt\Scripts\)
283+
const shebangPath = firstLine.substring(2).trim();
284+
const hasVenvPath =
285+
firstLine.includes('/resources/prebuilt/venv/') ||
286+
firstLine.includes('\\resources\\prebuilt\\venv\\') ||
287+
firstLine.includes('/resources/prebuilt/terminal_venv/') ||
288+
firstLine.includes('\\resources\\prebuilt\\terminal_venv\\') ||
289+
firstLine.includes('/resources/prebuilt/Scripts/') ||
290+
firstLine.includes('\\resources\\prebuilt\\Scripts\\');
291+
292+
if (!path.isAbsolute(shebangPath) && !hasVenvPath) {
226293
skippedCount++;
227294
continue;
228295
}
@@ -244,21 +311,24 @@ function fixScriptShebangs(venvPath, venvName) {
244311
fs.chmodSync(filePath, 0o755);
245312
fixedCount++;
246313

247-
if (fixedCount <= 5) { // Only show first 5 for brevity
314+
if (fixedCount <= 5) {
315+
// Only show first 5 for brevity
248316
console.log(` ✅ Fixed: ${entry}`);
249317
}
250318
}
251319
} catch (err) {
252320
// Silently skip files that can't be processed
321+
console.error(` ❌ Failed to process ${entry}: ${err.message}`);
253322
continue;
254323
}
255324
}
256325

257326
if (fixedCount > 5) {
258327
console.log(` ✅ ... and ${fixedCount - 5} more files`);
259328
}
260-
console.log(` 📊 Total: ${fixedCount} fixed, ${skippedCount} already correct`);
261-
329+
console.log(
330+
` 📊 Total: ${fixedCount} fixed, ${skippedCount} already correct`
331+
);
262332
} catch (error) {
263333
console.error(`❌ Error processing ${venvName}: ${error.message}`);
264334
}
@@ -276,12 +346,12 @@ function main() {
276346
const venvDirs = [
277347
{
278348
path: path.join(projectRoot, 'resources', 'prebuilt', 'venv'),
279-
name: 'backend venv'
349+
name: 'backend venv',
280350
},
281351
{
282352
path: path.join(projectRoot, 'resources', 'prebuilt', 'terminal_venv'),
283-
name: 'terminal venv'
284-
}
353+
name: 'terminal venv',
354+
},
285355
];
286356

287357
let symlinkSuccessCount = 0;
@@ -307,9 +377,13 @@ function main() {
307377

308378
console.log('\n==========================================================');
309379
if (totalCount === 0) {
310-
console.log('⚠️ No venv directories found - this is OK for development builds');
380+
console.log(
381+
'⚠️ No venv directories found - this is OK for development builds'
382+
);
311383
} else {
312-
console.log(`✅ Fixed symlinks in ${symlinkSuccessCount}/${totalCount} venv(s)`);
384+
console.log(
385+
`✅ Fixed symlinks in ${symlinkSuccessCount}/${totalCount} venv(s)`
386+
);
313387
console.log(`✅ Fixed shebangs in ${totalShebangsFixed} script(s)`);
314388
console.log('✅ Venvs are now fully portable!');
315389
}

scripts/fix-venv-paths.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ function fixPyvenvCfg(venvPath, venvName) {
6464
console.log(` Original home: ${originalHome}`);
6565

6666
// Extract cpython directory name from the path
67-
// Example: /path/to/cpython-3.10.19-macos-aarch64-none/bin -> cpython-3.10.19-macos-aarch64-none
68-
const cpythonMatch = originalHome.match(/(cpython-[\w.-]+)/);
67+
// Example: /path/to/cpython-3.10.19-macos-aarch64-none/bin -> cpython-3.10.19-macos-aarch64-none + /bin
68+
const cpythonMatch = originalHome.match(/(cpython-[\w.-]+)(.*)/);
6969
if (!cpythonMatch) {
7070
console.log(
7171
`⚠️ Could not extract cpython directory from: ${originalHome}`
@@ -74,19 +74,19 @@ function fixPyvenvCfg(venvPath, venvName) {
7474
}
7575

7676
const cpythonDir = cpythonMatch[1];
77+
const pathAfterCpython = cpythonMatch[2];
7778

7879
// Determine if this is Windows or Unix path
7980
const isWindowsPath =
8081
/^[A-Za-z]:\\/.test(originalHome) ||
8182
originalHome.startsWith('\\\\') ||
8283
originalHome.includes('\\');
83-
const binDir = isWindowsPath ? 'Scripts' : 'bin';
8484

8585
// Replace with placeholder that will be substituted at runtime
8686
// {{PREBUILT_PYTHON_DIR}} will be replaced with the actual path on user's machine
8787
// Use appropriate path separator for the platform
8888
const pathSep = isWindowsPath ? '\\' : '/';
89-
const newHome = `{{PREBUILT_PYTHON_DIR}}${pathSep}${cpythonDir}${pathSep}${binDir}`;
89+
const newHome = `{{PREBUILT_PYTHON_DIR}}${pathSep}${cpythonDir}${pathAfterCpython}`;
9090
content = content.replace(/^home\s*=\s*.+$/m, `home = ${newHome}`);
9191

9292
// Only write if content changed

0 commit comments

Comments
 (0)