Files
azuredatastudio/extensions/sql-database-projects/src/tools/netcoreTool.ts
Kim Santiago 43d235ea42 update .net sdk location strings now that .net 6 is supported (#17757)
* update .net install strings now that .net 6 is supported

* copy old setting value to new setting

* update strings

* add link to issue
2021-12-02 15:32:07 -08:00

219 lines
8.9 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as child_process from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as semver from 'semver';
import { isNullOrUndefined } from 'util';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { DoNotAskAgain, Install, DotnetInstallationConfirmation, NetCoreSupportedVersionInstallationConfirmation, UpdateDotnetLocation } from '../common/constants';
import * as utils from '../common/utils';
import { ShellCommandOptions, ShellExecutionHelper } from './shellExecutionHelper';
const localize = nls.loadMessageBundle();
export const DBProjectConfigurationKey: string = 'sqlDatabaseProjects';
export const NetCoreInstallLocationKey: string = 'netCoreSDKLocation';
export const DotnetInstallLocationKey: string = 'dotnetSDK Location';
export const NetCoreDoNotAskAgainKey: string = 'netCoreDoNotAsk';
export const NetCoreNonWindowsDefaultPath = '/usr/local/share';
export const winPlatform: string = 'win32';
export const macPlatform: string = 'darwin';
export const linuxPlatform: string = 'linux';
export const minSupportedNetCoreVersionForBuild: string = '3.1.0'; // TODO: watch out for EOL support in Dec 2022 https://github.com/microsoft/azuredatastudio/issues/17800
export const enum netCoreInstallState {
netCoreNotPresent,
netCoreVersionNotSupported,
netCoreVersionSupported
}
const dotnet = os.platform() === 'win32' ? 'dotnet.exe' : 'dotnet';
export class NetCoreTool extends ShellExecutionHelper {
private osPlatform: string = os.platform();
private netCoreSdkInstalledVersion: string | undefined;
private netCoreInstallState: netCoreInstallState = netCoreInstallState.netCoreVersionSupported;
/**
* This method presents the installation dialog for .NET Core, if not already present/supported
* @param skipVersionSupportedCheck If true then skip the check to determine whether the .NET version is supported (for commands that work on all versions)
* @returns True if .NET version was found and is supported
* False if .NET version isn't present or present but not supported
*/
public async findOrInstallNetCore(skipVersionSupportedCheck = false): Promise<boolean> {
if (!this.isNetCoreInstallationPresent || (this.isNetCoreInstallationPresent && !skipVersionSupportedCheck)) {
if ((!this.isNetCoreInstallationPresent || !await this.isNetCoreVersionSupportedForBuild())) {
if (vscode.workspace.getConfiguration(DBProjectConfigurationKey)[NetCoreDoNotAskAgainKey] !== true) {
void this.showInstallDialog(); // Removing await so that Build and extension load process doesn't wait on user input
}
return false;
}
}
this.netCoreInstallState = netCoreInstallState.netCoreVersionSupported;
return true;
}
constructor(_outputChannel: vscode.OutputChannel) {
super(_outputChannel);
}
public async showInstallDialog(): Promise<void> {
let result;
if (this.netCoreInstallState === netCoreInstallState.netCoreNotPresent) {
result = await vscode.window.showErrorMessage(DotnetInstallationConfirmation, UpdateDotnetLocation, Install, DoNotAskAgain);
} else {
result = await vscode.window.showErrorMessage(NetCoreSupportedVersionInstallationConfirmation(this.netCoreSdkInstalledVersion!), UpdateDotnetLocation, Install, DoNotAskAgain);
}
if (result === UpdateDotnetLocation) {
//open settings
await vscode.commands.executeCommand('workbench.action.openGlobalSettings');
} else if (result === Install) {
//open install link
const dotnetcoreURL = 'https://dotnet.microsoft.com/download/dotnet-core/3.1';
await vscode.env.openExternal(vscode.Uri.parse(dotnetcoreURL));
} else if (result === DoNotAskAgain) {
const config = vscode.workspace.getConfiguration(DBProjectConfigurationKey);
await config.update(NetCoreDoNotAskAgainKey, true, vscode.ConfigurationTarget.Global);
}
}
private get isNetCoreInstallationPresent(): boolean {
const netCoreInstallationPresent = (!isNullOrUndefined(this.netcoreInstallLocation) && fs.existsSync(this.netcoreInstallLocation));
if (!netCoreInstallationPresent) {
this.netCoreInstallState = netCoreInstallState.netCoreNotPresent;
}
return netCoreInstallationPresent;
}
public get netcoreInstallLocation(): string {
return vscode.workspace.getConfiguration(DBProjectConfigurationKey)[DotnetInstallLocationKey] ||
this.defaultLocalInstallLocationByDistribution;
}
private get defaultLocalInstallLocationByDistribution(): string | undefined {
switch (this.osPlatform) {
case winPlatform: return this.defaultWindowsLocation;
case macPlatform:
case linuxPlatform: return this.defaultnonWindowsLocation;
default: return undefined;
}
}
private get defaultnonWindowsLocation(): string | undefined {
return this.getDotnetPathIfPresent(NetCoreNonWindowsDefaultPath) || //default folder for net core sdk
this.getDotnetPathIfPresent(os.homedir()) ||
undefined;
}
private get defaultWindowsLocation(): string | undefined {
return this.getDotnetPathIfPresent(process.env['ProgramW6432']) ||
this.getDotnetPathIfPresent(process.env['ProgramFiles(x86)']) ||
this.getDotnetPathIfPresent(process.env['ProgramFiles']);
}
private getDotnetPathIfPresent(folderPath: string | undefined): string | undefined {
if (!isNullOrUndefined(folderPath) && fs.existsSync(path.join(folderPath, 'dotnet'))) {
return path.join(folderPath, 'dotnet');
}
return undefined;
}
/**
* This function checks if the installed dotnet version is at least minSupportedNetCoreVersionForBuild.
* Versions lower than minSupportedNetCoreVersionForBuild aren't supported for building projects.
* Returns: True if installed dotnet version is supported, false otherwise.
* Undefined if dotnet isn't installed.
*/
private async isNetCoreVersionSupportedForBuild(): Promise<boolean | undefined> {
try {
const spawn = child_process.spawn;
let child: child_process.ChildProcessWithoutNullStreams;
let isSupported: boolean = false;
const stdoutBuffers: Buffer[] = [];
child = spawn('dotnet --version', [], {
shell: true
});
child.stdout.on('data', (b: Buffer) => stdoutBuffers.push(b));
await new Promise((resolve, reject) => {
child.on('exit', () => {
this.netCoreSdkInstalledVersion = Buffer.concat(stdoutBuffers).toString('utf8').trim();
try {
if (semver.gte(this.netCoreSdkInstalledVersion, minSupportedNetCoreVersionForBuild)) { // Net core version greater than or equal to minSupportedNetCoreVersion are supported for Build
isSupported = true;
} else {
isSupported = false;
}
resolve({ stdout: this.netCoreSdkInstalledVersion });
} catch (err) {
console.log(err);
reject(err);
}
});
child.on('error', (err) => {
console.log(err);
this.netCoreInstallState = netCoreInstallState.netCoreNotPresent;
reject(err);
});
});
if (isSupported) {
this.netCoreInstallState = netCoreInstallState.netCoreVersionSupported;
} else {
this.netCoreInstallState = netCoreInstallState.netCoreVersionNotSupported;
}
return isSupported;
} catch (err) {
console.log(err);
this.netCoreInstallState = netCoreInstallState.netCoreNotPresent;
return undefined;
}
}
/**
* Runs the specified dotnet command
* @param options The options to use when launching the process
* @param skipVersionSupportedCheck If true then skip the check to determine whether the .NET version is supported (for commands that work on all versions)
* @returns
*/
public async runDotnetCommand(options: ShellCommandOptions, skipVersionSupportedCheck = false): Promise<string> {
if (options && options.commandTitle !== undefined && options.commandTitle !== null) {
this._outputChannel.appendLine(`\t[ ${options.commandTitle} ]`);
}
if (!(await this.findOrInstallNetCore(skipVersionSupportedCheck))) {
if (this.netCoreInstallState === netCoreInstallState.netCoreNotPresent) {
throw new DotNetError(DotnetInstallationConfirmation);
} else {
throw new DotNetError(NetCoreSupportedVersionInstallationConfirmation(this.netCoreSdkInstalledVersion!));
}
}
const dotnetPath = utils.getQuotedPath(path.join(this.netcoreInstallLocation, dotnet));
const command = dotnetPath + ' ' + options.argument;
try {
return await this.runStreamedCommand(command, options);
} catch (error) {
this._outputChannel.append(localize('sqlDatabaseProject.RunCommand.ErroredOut', "\t>>> {0} … errored out: {1}", command, utils.getErrorMessage(error))); //errors are localized in our code where emitted, other errors are pass through from external components that are not easily localized
throw error;
}
}
}
export class DotNetError extends Error {
}