-
Notifications
You must be signed in to change notification settings - Fork 72
[Hotfix] Enhance Windows environment detection in BallerinaExtension #1549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: hotfix/windows-fix
Are you sure you want to change the base?
Changes from 3 commits
58181fa
e52df09
9b4a302
7833789
9e6dc84
c6da561
0dd458d
2549849
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1791,6 +1791,18 @@ export class BallerinaExtension { | |||||||||||||||||||||||||||||||||||||||||||
| if (distPath) { break; } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } else if (isWindows() && !ballerinaHome) { | ||||||||||||||||||||||||||||||||||||||||||||
| // On Windows, if syncEnvironment() already merged the User+Machine PATH the | ||||||||||||||||||||||||||||||||||||||||||||
| // 'bal.bat version' call below will just work via PATH lookup (distPath stays | ||||||||||||||||||||||||||||||||||||||||||||
| // empty). But for restricted environments (where even User | ||||||||||||||||||||||||||||||||||||||||||||
| // PATH is locked, or where VSCode's inherited PATH is still stale), we run a | ||||||||||||||||||||||||||||||||||||||||||||
| // proactive directory search here so that we can use an absolute path instead | ||||||||||||||||||||||||||||||||||||||||||||
| // of relying on PATH resolution. | ||||||||||||||||||||||||||||||||||||||||||||
| const detectedBinPath = findWindowsBallerinaPath(); | ||||||||||||||||||||||||||||||||||||||||||||
| if (detectedBinPath) { | ||||||||||||||||||||||||||||||||||||||||||||
| distPath = detectedBinPath; | ||||||||||||||||||||||||||||||||||||||||||||
| debug(`[VERSION] Windows fallback search found Ballerina bin: ${distPath}`); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1801
to
+1805
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| let exeExtension = ""; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2679,6 +2691,83 @@ function updateProcessEnv(newEnv: NodeJS.ProcessEnv): void { | |||||||||||||||||||||||||||||||||||||||||||
| debug("[UPDATE_ENV] Process environment update completed"); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||
| * Searches for the Ballerina bin directory on Windows using two strategies: | ||||||||||||||||||||||||||||||||||||||||||||
| * 1. Read the User-scope and Machine-scope PATH entries from the registry and look | ||||||||||||||||||||||||||||||||||||||||||||
| * for a directory that contains bal.bat. | ||||||||||||||||||||||||||||||||||||||||||||
| * 2. Check well-known installation directories (LOCALAPPDATA, ProgramFiles, etc.). | ||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||
| * Returns the bin directory path (with trailing separator) or an empty string when | ||||||||||||||||||||||||||||||||||||||||||||
| * nothing is found. This is used as a last-resort fallback for environments where the | ||||||||||||||||||||||||||||||||||||||||||||
| * process PATH was not updated (e.g. company laptops with restricted System PATH, or | ||||||||||||||||||||||||||||||||||||||||||||
| * VS Code opened before the installer ran). | ||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
| function findWindowsBallerinaPath(): string { | ||||||||||||||||||||||||||||||||||||||||||||
| debug('[WIN_BAL_FIND] Searching for Ballerina installation on Windows...'); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // --- Strategy 1: scan PATH entries from User + Machine registry scopes --- | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| const psCommand = | ||||||||||||||||||||||||||||||||||||||||||||
| '[Environment]::GetEnvironmentVariable(\'Path\',\'Machine\') + \';\' + ' + | ||||||||||||||||||||||||||||||||||||||||||||
| '[Environment]::GetEnvironmentVariable(\'Path\',\'User\')'; | ||||||||||||||||||||||||||||||||||||||||||||
| const rawPaths = execSync( | ||||||||||||||||||||||||||||||||||||||||||||
| `powershell.exe -NoProfile -Command "${psCommand}"`, | ||||||||||||||||||||||||||||||||||||||||||||
| { encoding: 'utf8', timeout: 10000 } | ||||||||||||||||||||||||||||||||||||||||||||
| ).trim(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| debug(`[WIN_BAL_FIND] Registry PATH (Machine+User) length: ${rawPaths.length} chars`); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const pathEntries = rawPaths.split(';').map(p => p.trim()).filter(Boolean); | ||||||||||||||||||||||||||||||||||||||||||||
| for (const entry of pathEntries) { | ||||||||||||||||||||||||||||||||||||||||||||
| const candidate = path.join(entry, 'bal.bat'); | ||||||||||||||||||||||||||||||||||||||||||||
| if (fs.existsSync(candidate)) { | ||||||||||||||||||||||||||||||||||||||||||||
| debug(`[WIN_BAL_FIND] Found bal.bat in registry PATH entry: ${entry}`); | ||||||||||||||||||||||||||||||||||||||||||||
| return entry + path.sep; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2723
to
+2726
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| debug('[WIN_BAL_FIND] bal.bat not found in registry PATH entries'); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||
| debug(`[WIN_BAL_FIND] Failed to read registry PATH: ${err}`); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // --- Strategy 2: check well-known Ballerina installation directories --- | ||||||||||||||||||||||||||||||||||||||||||||
| const localAppData = process.env.LOCALAPPDATA || ''; | ||||||||||||||||||||||||||||||||||||||||||||
| const programFiles = process.env.ProgramFiles || 'C:\\Program Files'; | ||||||||||||||||||||||||||||||||||||||||||||
| const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const searchRoots = [ | ||||||||||||||||||||||||||||||||||||||||||||
| localAppData ? path.join(localAppData, 'Programs', 'Ballerina') : '', | ||||||||||||||||||||||||||||||||||||||||||||
| path.join(programFiles, 'Ballerina'), | ||||||||||||||||||||||||||||||||||||||||||||
| path.join(programFilesX86, 'Ballerina'), | ||||||||||||||||||||||||||||||||||||||||||||
| 'C:\\Ballerina', | ||||||||||||||||||||||||||||||||||||||||||||
| ].filter(Boolean); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for (const root of searchRoots) { | ||||||||||||||||||||||||||||||||||||||||||||
| const directBin = path.join(root, 'bin'); | ||||||||||||||||||||||||||||||||||||||||||||
| if (fs.existsSync(path.join(directBin, 'bal.bat'))) { | ||||||||||||||||||||||||||||||||||||||||||||
| debug(`[WIN_BAL_FIND] Found bal.bat in common directory: ${directBin}`); | ||||||||||||||||||||||||||||||||||||||||||||
| return directBin + path.sep; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| // Handle versioned subdirectory layout, e.g. Ballerina\ballerina-2.x.x\bin | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| const children = fs.readdirSync(root); | ||||||||||||||||||||||||||||||||||||||||||||
| for (const child of children) { | ||||||||||||||||||||||||||||||||||||||||||||
| const versionedBin = path.join(root, child, 'bin'); | ||||||||||||||||||||||||||||||||||||||||||||
| if (fs.existsSync(path.join(versionedBin, 'bal.bat'))) { | ||||||||||||||||||||||||||||||||||||||||||||
| debug(`[WIN_BAL_FIND] Found bal.bat in versioned directory: ${versionedBin}`); | ||||||||||||||||||||||||||||||||||||||||||||
| return versionedBin + path.sep; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||
| // Directory doesn't exist or isn't readable — skip | ||||||||||||||||||||||||||||||||||||||||||||
| debug(`[WIN_BAL_FIND] Failed to read directory "${root}" for versioned Ballerina installations: ${err}`); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| debug('[WIN_BAL_FIND] Ballerina installation not found via fallback search'); | ||||||||||||||||||||||||||||||||||||||||||||
| return ''; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| function getShellEnvironment(): Promise<NodeJS.ProcessEnv> { | ||||||||||||||||||||||||||||||||||||||||||||
| return new Promise((resolve, reject) => { | ||||||||||||||||||||||||||||||||||||||||||||
| debug('[SHELL_ENV] Starting shell environment retrieval...'); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2688,8 +2777,19 @@ function getShellEnvironment(): Promise<NodeJS.ProcessEnv> { | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (isWindowsPlatform) { | ||||||||||||||||||||||||||||||||||||||||||||
| debug('[SHELL_ENV] Windows platform detected'); | ||||||||||||||||||||||||||||||||||||||||||||
| // Windows: use PowerShell to get environment | ||||||||||||||||||||||||||||||||||||||||||||
| command = 'powershell.exe -Command "[Environment]::GetEnvironmentVariables(\'Process\') | ConvertTo-Json"'; | ||||||||||||||||||||||||||||||||||||||||||||
| // Windows: read from registry (Machine + User scopes) so that paths added by | ||||||||||||||||||||||||||||||||||||||||||||
| // a fresh Ballerina install (which goes to the User PATH registry key) are | ||||||||||||||||||||||||||||||||||||||||||||
| // picked up even when VS Code's process was launched before the installation. | ||||||||||||||||||||||||||||||||||||||||||||
| // We start with the current Process environment so that VS Code-internal | ||||||||||||||||||||||||||||||||||||||||||||
| // variables are preserved, but we override Path with the merged registry value. | ||||||||||||||||||||||||||||||||||||||||||||
| command = 'powershell.exe -NoProfile -Command "' + | ||||||||||||||||||||||||||||||||||||||||||||
| '$e=[Environment]::GetEnvironmentVariables(\'Process\');' + | ||||||||||||||||||||||||||||||||||||||||||||
| '$mp=[Environment]::GetEnvironmentVariable(\'Path\',\'Machine\');' + | ||||||||||||||||||||||||||||||||||||||||||||
| '$up=[Environment]::GetEnvironmentVariable(\'Path\',\'User\');' + | ||||||||||||||||||||||||||||||||||||||||||||
| 'if($mp -and $up){$e[\'Path\']=$mp+\';\'+$up}' + | ||||||||||||||||||||||||||||||||||||||||||||
| 'elseif($mp){$e[\'Path\']=$mp}' + | ||||||||||||||||||||||||||||||||||||||||||||
| 'elseif($up){$e[\'Path\']=$up};' + | ||||||||||||||||||||||||||||||||||||||||||||
| '$e | ConvertTo-Json"'; | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2780
to
+2792
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overriding The new PowerShell command replaces Consider merging the registry values with the current process PATH instead of replacing it, so that VS Code-internal tooling remains functional: Proposed fix: merge registry PATH with existing process PATH command = 'powershell.exe -NoProfile -Command "' +
'$e=[Environment]::GetEnvironmentVariables(\'Process\');' +
'$mp=[Environment]::GetEnvironmentVariable(\'Path\',\'Machine\');' +
'$up=[Environment]::GetEnvironmentVariable(\'Path\',\'User\');' +
- 'if($mp -and $up){$e[\'Path\']=$mp+\';\'+$up}' +
- 'elseif($mp){$e[\'Path\']=$mp}' +
- 'elseif($up){$e[\'Path\']=$up};' +
+ '$pp=$e[\'Path\'];' +
+ '$merged=@($mp,$up,$pp|Where-Object{$_})-join\';\';' +
+ 'if($merged){$e[\'Path\']=$merged};' +
'$e | ConvertTo-Json"';This keeps existing process 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| debug(`[SHELL_ENV] Windows command: ${command}`); | ||||||||||||||||||||||||||||||||||||||||||||
| } else if (isWSL()) { | ||||||||||||||||||||||||||||||||||||||||||||
| debug("[SHELL_ENV] Windows WSL platform, using non-interactive shell"); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fallback search runs unconditionally whenever
isWindows() && !ballerinaHome, even though the comment suggests PATH-based resolution will often already work aftersyncEnvironment(). SincefindWindowsBallerinaPath()does synchronous PowerShell + filesystem scans (up to 10s timeout), consider only running it after the initialbal(.bat) versionattempt fails (e.g. ENOENT / "not recognized"), and/or caching the detected result so repeated version checks don’t repeatedly invoke PowerShell.