Skip to content

Commit b8c8db2

Browse files
authored
Handle Corrupt Install Script Installs (#2235)
* Check if the dotnet install is valid The dotnet install script has a bug where it wont do the install if the folder is not empty. Sometimes it will leave a partially done install, like if killed, in which case we get to a bad state. This fixes that by cleaning up the directory, and or checking if dotnet.exe exists, whether it works. dotnet --list-runtimes is a fast way to do this since it should only invoke the host and not need to invoke the SDK. doing just 'dotnet' will not work as that does not return 0. Also, gets rid of the 'lock' exception since thats from the proper-lockfile lib which we don't use anymore. Resolves: #2059 First scrapped attempt: #2106 * Add missing await * Recursively delete the stuff wipe dir only wipes the files, and the script looks at the folders * Remove tests for deprecated code The Install Multiple versions tests fails bc the deprecated sdk code installs everything into the same folder, so this new cleanup logic will wipe the older folder. The uninstall logic fails because the original sdk code does not work with respect to finding a local sdk when a global sdk is on the machine (it finds a 7.0.4xx sdk), this code is not used by anything anymore so it is likely not worth investigating. * Update changelog * Changelog should mention min vscode version * Check if dotnet meets the requirement of the install
1 parent f5411d3 commit b8c8db2

File tree

5 files changed

+52
-47
lines changed

5 files changed

+52
-47
lines changed

vscode-dotnet-runtime-extension/CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ and this project adheres to [Semantic Versioning].
77

88
## [Unreleased]
99

10-
## [2.3.3] - 2025-4
10+
## [2.3.3] - 2025-4-13
1111

1212
- Performance improvements.
13-
- Fixes to locking issues, with listen errors.
13+
- No longer utilizes chcp to try to force dotnet --info to output in english for internal parsing (perf)
14+
- Checks dotnet from --list-runtimes before checking the PATH to locate dotnet faster in the common case.
15+
- Fixes for when the install script leaves behind a corrupt install.
16+
- Fixes to locking issues on darwin when the temp file system is locked.
17+
- Bumps minimum VS Code version requirement to ensure running on nodejs 20 -- solves previous crypto errors for older versions of vscode.
1418

1519
## [2.3.2] - 2025-4-10
1620

vscode-dotnet-runtime-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,23 @@ suite('DotnetCoreAcquisitionExtension End to End', function ()
326326
await installMultipleVersions(['2.2', '3.0', '3.1'], 'aspnetcore');
327327
}).timeout(standardTimeoutTime * 2);
328328

329+
test('Works With Prior Incomplete or Corrupted Install', async () =>
330+
{
331+
const installPath = await installRuntime('9.0', 'runtime');
332+
assert.isTrue(fs.existsSync(installPath), 'The path exists after install');
333+
// remove the install executable but not the folder to simulate a corrupt install
334+
rimraf.sync(installPath);
335+
assert.isFalse(fs.existsSync(installPath), 'The path does not exist after uninstall');
336+
// try to acquire again, and it should succeed
337+
const _ = await installRuntime('9.0', 'runtime');
338+
}).timeout(standardTimeoutTime);
339+
340+
test('It works if the install exists', async () =>
341+
{
342+
const installPath = await installRuntime('9.0', 'runtime');
343+
const samePath = await installRuntime('9.0', 'runtime');
344+
}).timeout(standardTimeoutTime);
345+
329346
test('Find dotnet PATH Command Met Condition', async () =>
330347
{
331348
// install 5.0 then look for 5.0 path

vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* The .NET Foundation licenses this file to you under the MIT license.
44
*--------------------------------------------------------------------------------------------*/
55
import * as cp from 'child_process';
6+
import * as fs from 'fs';
67
import * as os from 'os';
78
import path = require('path');
89

@@ -20,6 +21,7 @@ import
2021
EventBasedError,
2122
PowershellBadExecutionPolicy,
2223
PowershellBadLanguageMode,
24+
SuppressedAcquisitionError,
2325
} from '../EventStream/EventStreamEvents';
2426

2527
import { TelemetryUtilities } from '../EventStream/TelemetryUtilities';
@@ -29,9 +31,10 @@ import { FileUtilities } from '../Utils/FileUtilities';
2931
import { InstallScriptAcquisitionWorker } from './InstallScriptAcquisitionWorker';
3032

3133
import { IUtilityContext } from '../Utils/IUtilityContext';
32-
import { executeWithLock } from '../Utils/TypescriptUtilities';
34+
import { executeWithLock, getDotnetExecutable } from '../Utils/TypescriptUtilities';
3335
import { WebRequestWorkerSingleton } from '../Utils/WebRequestWorkerSingleton';
3436
import { LOCAL_LOCK_PING_DURATION_MS } from './CacheTimeConstants';
37+
import { DotnetConditionValidator } from './DotnetConditionValidator';
3538
import { DotnetInstall } from './DotnetInstall';
3639
import { DotnetInstallMode } from './DotnetInstallMode';
3740
import { IAcquisitionInvoker } from './IAcquisitionInvoker';
@@ -76,6 +79,30 @@ You will need to restart VS Code after these changes. If PowerShell is still not
7679
windowsFullCommand = windowsFullCommand.replace('powershell.exe', powershellReference);
7780
}
7881

82+
// The install script can leave behind a directory in an invalid install state. Make sure the executable is present at the very least.
83+
if (await this.fileUtilities.exists(installContext.installDir))
84+
{
85+
const dotnetPath = path.join(installContext.installDir, getDotnetExecutable());
86+
if (await this.fileUtilities.exists(dotnetPath))
87+
{
88+
const validator = new DotnetConditionValidator(this.workerContext, this.utilityContext);
89+
const meetsRequirement = await validator.dotnetMeetsRequirement(dotnetPath, { acquireContext: installContext, versionSpecRequirement: 'equal' });
90+
if (meetsRequirement)
91+
{
92+
return resolve();
93+
}
94+
}
95+
96+
try
97+
{
98+
await fs.promises.rm(installContext.installDir, { recursive: true, force: true });
99+
}
100+
catch (err: any)
101+
{
102+
this.eventStream.post(new SuppressedAcquisitionError(err, `${installContext.installDir} could not be not removed, and it has a corrupted install. Please remove it manually.`));
103+
}
104+
}
105+
79106
cp.exec(winOS ? windowsFullCommand : installCommand,
80107
{ cwd: process.cwd(), maxBuffer: 500 * 1024, timeout: 1000 * installContext.timeoutSeconds, killSignal: 'SIGKILL' },
81108
async (error, stdout, stderr) =>

vscode-dotnet-runtime-library/src/Utils/FileUtilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class FileUtilities extends IFileUtilities
8181
try
8282
{
8383
eventStream?.post(new FileToWipe(`The file ${f} may be deleted.`))
84-
if (!fileExtensionsToDelete || fileExtensionsToDelete?.includes(path.extname(f).toLocaleLowerCase()) && !(f?.includes('lock')))
84+
if (!fileExtensionsToDelete || fileExtensionsToDelete?.includes(path.extname(f).toLocaleLowerCase()))
8585
{
8686
eventStream?.post(new FileToWipe(`The file ${f} is being deleted -- if no error is reported, it should be wiped.`))
8787
await fs.promises.rm(path.join(directoryToWipe, f));

vscode-dotnet-sdk-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -304,49 +304,6 @@ suite('DotnetCoreAcquisitionExtension End to End', function ()
304304
await vscode.commands.executeCommand('dotnet-sdk.uninstallAll');
305305
}).timeout(standardTimeoutTime);
306306

307-
test('Uninstall Command', async () =>
308-
{
309-
const context: IDotnetAcquireContext = { version: '3.1', requestingExtensionId: 'ms-dotnettools.sample-extension' };
310-
const result = await vscode.commands.executeCommand<IDotnetAcquireResult>('dotnet-sdk.acquire', context);
311-
assert.exists(result);
312-
assert.exists(result!.dotnetPath);
313-
const sdkDir = fs.readdirSync(path.join(path.dirname(result!.dotnetPath), 'sdk'))[0];
314-
assert.include(sdkDir, context.version);
315-
assert.isTrue(fs.existsSync(result!.dotnetPath!));
316-
await vscode.commands.executeCommand('dotnet-sdk.uninstallAll');
317-
assert.isFalse(fs.existsSync(result!.dotnetPath));
318-
}).timeout(standardTimeoutTime);
319-
320-
test('Install Multiple Versions', async () =>
321-
{
322-
// Install 6.0
323-
let version = currentSDKVersion;
324-
let result = await vscode.commands.executeCommand<IDotnetAcquireResult>('dotnet-sdk.acquire', { version, requestingExtensionId: 'ms-dotnettools.sample-extension' });
325-
assert.exists(result, 'basic install works');
326-
assert.exists(result!.dotnetPath, 'basic install has path');
327-
let sdkDirs = fs.readdirSync(path.join(path.dirname(result!.dotnetPath), 'sdk'));
328-
assert.isNotEmpty(sdkDirs.filter(dir => dir.includes(version)), `sdk directories include version?
329-
PATH: ${result!.dotnetPath}
330-
PATH SUBDIRECTORIES: ${fs.readdirSync(path.dirname(result!.dotnetPath))}
331-
SDK SUBDIRECTORIES: ${sdkDirs}
332-
VERSION: ${version}`);
333-
334-
// Install 5.0
335-
version = '5.0';
336-
result = await vscode.commands.executeCommand<IDotnetAcquireResult>('dotnet-sdk.acquire', { version, requestingExtensionId: 'ms-dotnettools.sample-extension' });
337-
assert.exists(result, 'acquire works a 2nd time');
338-
assert.exists(result!.dotnetPath, 'acquire returns a path a 2nd time');
339-
sdkDirs = fs.readdirSync(path.join(path.dirname(result!.dotnetPath), 'sdk'));
340-
assert.isNotEmpty(sdkDirs.filter(dir => dir.includes(version)), 'acquire sdk twice does not overwrite');
341-
342-
sdkDirs = fs.readdirSync(path.join(path.dirname(result!.dotnetPath), 'sdk'));
343-
assert.isNotEmpty(sdkDirs.filter(dir => dir.includes(currentSDKVersion)), 'directories include a version.');
344-
assert.isNotEmpty(sdkDirs.filter(dir => dir.includes('5.0')), 'old directories are preserved');
345-
346-
// Clean up storage
347-
await vscode.commands.executeCommand('dotnet-sdk.uninstallAll');
348-
}).timeout(standardTimeoutTime * 6);
349-
350307
test('Extension Uninstall Removes SDKs', async () =>
351308
{
352309
const context: IDotnetAcquireContext = { version: currentSDKVersion, requestingExtensionId: 'ms-dotnettools.sample-extension' };

0 commit comments

Comments
 (0)