diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index 961d3264f1..5d45c37d96 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -26,6 +26,17 @@ "Microsoft.mssql" ], "contributes": { + "configuration": [ + { + "title": "%sqlDatabaseProjects.Settings%", + "properties": { + "sqlDatabaseProjects.netCoreSDKLocation": { + "type": "string", + "description": "%sqlDatabaseProjects.netCoreInstallLocation%" + } + } + } + ], "commands": [ { "command": "sqlDatabaseProjects.new", diff --git a/extensions/sql-database-projects/package.nls.json b/extensions/sql-database-projects/package.nls.json index e681585477..12763f535f 100644 --- a/extensions/sql-database-projects/package.nls.json +++ b/extensions/sql-database-projects/package.nls.json @@ -12,5 +12,9 @@ "sqlDatabaseProjects.newView": "Add View", "sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure", "sqlDatabaseProjects.newItem": "Add Item...", - "sqlDatabaseProjects.newFolder": "Add Folder" + "sqlDatabaseProjects.newFolder": "Add Folder", + + + "sqlDatabaseProjects.Settings": "Database Projects", + "sqlDatabaseProjects.netCoreInstallLocation": "Full Path to .Net Core SDK on the machine." } diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts index b3bc70ba1f..57e0ce163f 100644 --- a/extensions/sql-database-projects/src/controllers/mainController.ts +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -12,6 +12,7 @@ import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewPro import { getErrorMessage } from '../common/utils'; import { ProjectsController } from './projectController'; import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; +import { NetCoreTool } from '../tools/netcoreTool'; const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView'; @@ -22,10 +23,12 @@ export default class MainController implements vscode.Disposable { protected _context: vscode.ExtensionContext; protected dbProjectTreeViewProvider: SqlDatabaseProjectTreeViewProvider = new SqlDatabaseProjectTreeViewProvider(); protected projectsController: ProjectsController; + protected netcoreTool: NetCoreTool; public constructor(context: vscode.ExtensionContext) { this._context = context; this.projectsController = new ProjectsController(this.dbProjectTreeViewProvider); + this.netcoreTool = new NetCoreTool(); } public get extensionContext(): vscode.ExtensionContext { @@ -56,6 +59,9 @@ export default class MainController implements vscode.Disposable { this.extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(SQL_DATABASE_PROJECTS_VIEW_ID, this.dbProjectTreeViewProvider)); await templates.loadTemplates(path.join(this._context.extensionPath, 'resources', 'templates')); + + // ensure .net core is installed + this.netcoreTool.findOrInstallNetCore(); } /** diff --git a/extensions/sql-database-projects/src/test/netCoreTool.test.ts b/extensions/sql-database-projects/src/test/netCoreTool.test.ts new file mode 100644 index 0000000000..92a086ad9d --- /dev/null +++ b/extensions/sql-database-projects/src/test/netCoreTool.test.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as should from 'should'; +import * as os from 'os'; +import * as vscode from 'vscode'; +import { NetCoreTool, DBProjectConfigurationKey, NetCoreInstallLocationKey, NextCoreNonWindowsDefaultPath } from '../tools/netcoreTool'; +import { isNullOrUndefined } from 'util'; + +describe('NetCoreTool: Net core install popup tests', function (): void { + + it('settings value should override default paths', async function (): Promise { + try { + // update settings and validate + await vscode.workspace.getConfiguration(DBProjectConfigurationKey).update(NetCoreInstallLocationKey, 'test value path', true); + const netcoreTool = new NetCoreTool(); + should(netcoreTool.netcoreInstallLocation).equal('test value path'); // the path in settings should be taken + should(netcoreTool.isNetCoreInstallationPresent).equal(false); // dotnet can not be present at dummy path in settings + } + finally { + // clean again + await vscode.workspace.getConfiguration(DBProjectConfigurationKey).update(NetCoreInstallLocationKey, '', true); + } + }); + + it('should find right default paths', async function (): Promise { + const netcoreTool = new NetCoreTool(); + netcoreTool.findOrInstallNetCore(); + + if (os.platform() === 'win32') { + // check that path should start with c:\program files + let result = isNullOrUndefined(netcoreTool.netcoreInstallLocation) || netcoreTool.netcoreInstallLocation.toLowerCase().startsWith('c:\\program files'); + should(result).true('dotnet is either not present or in pogramfiles by default'); + } + + if (os.platform() === 'linux' || os.platform() === 'darwin') { + //check that path should start with /usr/local/share + let result = isNullOrUndefined(netcoreTool.netcoreInstallLocation) || netcoreTool.netcoreInstallLocation.toLowerCase().startsWith(NextCoreNonWindowsDefaultPath); + should(result).true('dotnet is either not present or in /usr/local/share by default'); + } + }); +}); + +export async function sleep(ms: number): Promise<{}> { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/extensions/sql-database-projects/src/tools/netcoreTool.ts b/extensions/sql-database-projects/src/tools/netcoreTool.ts new file mode 100644 index 0000000000..17d4e51076 --- /dev/null +++ b/extensions/sql-database-projects/src/tools/netcoreTool.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { isNullOrUndefined } from 'util'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export const DBProjectConfigurationKey: string = 'sqlDatabaseProjects'; +export const NetCoreInstallLocationKey: string = 'netCoreSDKLocation'; +export const NextCoreNonWindowsDefaultPath = '/usr/local/share'; +export const NetCoreInstallationConfirmation: string = localize('sqlDatabaseProjects.NetCoreInstallationConfirmation', "The .NET Core SDK cannnot be located. Project build will not work. Please install the same or update the .Net Core SDK location in settings if already installed."); +export const UpdateNetCoreLocation: string = localize('sqlDatabaseProjects.UpdateNetCoreLocation', "Update .Net Core location"); +export const InstallNetCore: string = localize('sqlDatabaseProjects.InstallNetCore', "Install .Net Core SDK"); + +export class NetCoreTool { + + public findOrInstallNetCore(): void { + if (!this.isNetCoreInstallationPresent) { + this.showInstallDialog(); + } + } + + private showInstallDialog(): void { + vscode.window.showInformationMessage(NetCoreInstallationConfirmation, UpdateNetCoreLocation, InstallNetCore).then(async (result) => { + if (result === UpdateNetCoreLocation) { + //open settings + vscode.commands.executeCommand('workbench.action.openGlobalSettings'); + } + else if (result === InstallNetCore) { + //open install link + const dotnetcoreURL = 'https://dotnet.microsoft.com/download/dotnet-core/3.1'; + vscode.env.openExternal(vscode.Uri.parse(dotnetcoreURL)); + } + }); + } + + public get isNetCoreInstallationPresent(): Boolean { + return (!isNullOrUndefined(this.netcoreInstallLocation) && fs.existsSync(this.netcoreInstallLocation)); + } + + public get netcoreInstallLocation(): string { + return vscode.workspace.getConfiguration(DBProjectConfigurationKey)[NetCoreInstallLocationKey] || + this.defaultLocalInstallLocationByDistribution; + } + + private get defaultLocalInstallLocationByDistribution(): string | undefined { + const osPlatform: string = os.platform(); + return (osPlatform === 'win32') ? this.defaultWindowsLocation : + (osPlatform === 'darwin' || osPlatform === 'linux') ? this.defaultnonWindowsLocation : + undefined; + } + + private get defaultnonWindowsLocation(): string | undefined { + const defaultNonWindowsInstallLocation = NextCoreNonWindowsDefaultPath; //default folder for net core sdk + return this.getDotnetPathIfPresent(defaultNonWindowsInstallLocation) || + 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; + } +}