diff --git a/extensions/sql-database-projects/README.md b/extensions/sql-database-projects/README.md index 23ee2daff1..5d3c22f738 100644 --- a/extensions/sql-database-projects/README.md +++ b/extensions/sql-database-projects/README.md @@ -35,7 +35,7 @@ Learn more about the SQL Database Projects extension in the documentation: https ### General Settings - `sqlDatabaseProjects.dotnetSDK Location`: The path to the folder containing the `dotnet` folder for the .NET SDK. If not set, the extension will attempt to find the .NET SDK on the system. -- `sqlDatabaseProjects.microsoftBuildSqlVersion`: Version of Microsoft.Build.Sql binaries used when building SQL projects that are not SDK-style SQL projects. If not set, the extension will use Microsoft.Build.Sql 0.1.9-preview. +- `sqlDatabaseProjects.microsoftBuildSqlVersion`: Version of Microsoft.Build.Sql binaries used when building SQL projects that are not SDK-style SQL projects. If not set, the extension will use Microsoft.Build.Sql 0.1.10-preview. - `sqlDatabaseProjects.netCoreDoNotAsk`: When true, no longer prompts to install .NET SDK when a supported installation is not found. - `sqlDatabaseProjects.collapseProjectNodes`: Option to set the default state of the project nodes in the database projects view to collapsed. If not set, the extension will default to expanded. diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index dc3f385853..21f1f86d60 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -674,7 +674,7 @@ export const downloading = localize('downloading', "Downloading"); //#endregion //#region buildHelper -export const downloadingDacFxDlls = localize('downloadingDacFxDlls', "Downloading Microsoft.Build.Sql nuget to get build DLLs"); +export function downloadingNuget(nuget: string) { return localize('downloadingNuget', "Downloading {0} nuget to get build DLLs ", nuget); } export function downloadingFromTo(from: string, to: string) { return localize('downloadingFromTo', "Downloading from {0} to {1}", from, to); } export function extractingDacFxDlls(location: string) { return localize('extractingDacFxDlls', "Extracting DacFx build DLLs to {0}", location); } export function errorDownloading(url: string, error: string) { return localize('errorDownloading', "Error downloading {0}. Error: {1}", url, error); } diff --git a/extensions/sql-database-projects/src/tools/buildHelper.ts b/extensions/sql-database-projects/src/tools/buildHelper.ts index 781a16e151..b2998f375a 100644 --- a/extensions/sql-database-projects/src/tools/buildHelper.ts +++ b/extensions/sql-database-projects/src/tools/buildHelper.ts @@ -17,23 +17,6 @@ import * as mssql from 'mssql'; import * as vscodeMssql from 'vscode-mssql'; const buildDirectory = 'BuildDirectory'; -const sdkName = 'Microsoft.Build.Sql'; -const microsoftBuildSqlDefaultVersion = '0.1.9-preview'; // default version of Microsoft.Build.Sql nuget to use for building legacy style projects, update in README when updating this - -const buildFiles: string[] = [ - 'Microsoft.Data.SqlClient.dll', - 'Microsoft.Data.Tools.Schema.Sql.dll', - 'Microsoft.Data.Tools.Schema.Tasks.Sql.dll', - 'Microsoft.Data.Tools.Utilities.dll', - 'Microsoft.SqlServer.Dac.dll', - 'Microsoft.SqlServer.Dac.Extensions.dll', - 'Microsoft.SqlServer.TransactSql.ScriptDom.dll', - 'Microsoft.SqlServer.Types.dll', - 'System.ComponentModel.Composition.dll', - 'System.IO.Packaging.dll', - 'Microsoft.Data.Tools.Schema.SqlTasks.targets', - 'Microsoft.SqlServer.Server.dll' -]; export class BuildHelper { @@ -52,7 +35,6 @@ export class BuildHelper { * @param outputChannel */ public async createBuildDirFolder(outputChannel: vscode.OutputChannel): Promise { - if (this.initialized) { return true; } @@ -61,60 +43,103 @@ export class BuildHelper { await fs.mkdir(this.extensionBuildDir); } - // check if the settings has a version specified for Microsoft.Build.Sql, otherwise use default - const microsoftBuildSqlVersionConfig = vscode.workspace.getConfiguration(DBProjectConfigurationKey)[constants.microsoftBuildSqlVersionKey]; - const sdkVersion = !!microsoftBuildSqlVersionConfig ? microsoftBuildSqlVersionConfig : microsoftBuildSqlDefaultVersion; - const fullSdkName = `${sdkName}.${sdkVersion}`; + const dacFxDllsExist = await this.ensureDacFxDllsPresence(outputChannel); + const scriptDomExists = await this.ensureScriptDomDllPresence(outputChannel); - // check if this if the nuget needs to be downloaded - const nugetPath = path.join(this.extensionBuildDir, `${fullSdkName}.nupkg`); - - if (await utils.exists(nugetPath)) { - // if it does exist, make sure all the necessary files are also in the BuildDirectory - let missingFiles = false; - for (const fileName of buildFiles) { - if (!await (utils.exists(path.join(this.extensionBuildDir, fileName)))) { - missingFiles = true; - break; - } - } - - // if all the files are there, no need to continue - if (!missingFiles) { - return true; - } - } - - // TODO: check nuget cache locations first to avoid downloading if those exist - // download the Microsoft.Build.Sql sdk nuget - outputChannel.appendLine(constants.downloadingDacFxDlls); - - const microsoftBuildSqlUrl = `https://www.nuget.org/api/v2/package/${sdkName}/${sdkVersion}`; - - try { - const httpClient = new HttpClient(); - outputChannel.appendLine(constants.downloadingFromTo(microsoftBuildSqlUrl, nugetPath)); - await httpClient.download(microsoftBuildSqlUrl, nugetPath, outputChannel); - } catch (e) { - void vscode.window.showErrorMessage(constants.errorDownloading(microsoftBuildSqlUrl, utils.getErrorMessage(e))); + if (!dacFxDllsExist || !scriptDomExists) { return false; } - // extract the files from the nuget - const extractedFolderPath = path.join(this.extensionDir, buildDirectory, sdkName); - outputChannel.appendLine(constants.extractingDacFxDlls(extractedFolderPath)); + this.initialized = true; + return true; + } + + public async ensureDacFxDllsPresence(outputChannel: vscode.OutputChannel): Promise { + const sdkName = 'Microsoft.Build.Sql'; + const microsoftBuildSqlDefaultVersion = '0.1.10-preview'; // default version of Microsoft.Build.Sql nuget to use for building legacy style projects, update in README when updating this + + const dacFxBuildFiles: string[] = [ + 'Microsoft.Data.SqlClient.dll', + 'Microsoft.Data.Tools.Schema.Sql.dll', + 'Microsoft.Data.Tools.Schema.Tasks.Sql.dll', + 'Microsoft.Data.Tools.Utilities.dll', + 'Microsoft.SqlServer.Dac.dll', + 'Microsoft.SqlServer.Dac.Extensions.dll', + 'Microsoft.SqlServer.Types.dll', + 'System.ComponentModel.Composition.dll', + 'System.IO.Packaging.dll', + 'Microsoft.Data.Tools.Schema.SqlTasks.targets', + 'Microsoft.SqlServer.Server.dll' + ]; + + // check if the settings has a version specified for Microsoft.Build.Sql, otherwise use default + const microsoftBuildSqlVersionConfig = vscode.workspace.getConfiguration(DBProjectConfigurationKey)[constants.microsoftBuildSqlVersionKey]; + const sdkVersion = !!microsoftBuildSqlVersionConfig ? microsoftBuildSqlVersionConfig : microsoftBuildSqlDefaultVersion; + + const microsoftBuildSqlDllLocation = path.join('tools', 'netstandard2.1'); + return this.ensureNugetAndFilesPresence(sdkName, sdkVersion, dacFxBuildFiles, microsoftBuildSqlDllLocation, outputChannel); + } + + public async ensureScriptDomDllPresence(outputChannel: vscode.OutputChannel): Promise { + const scriptdomNugetPkgName = 'Microsoft.SqlServer.TransactSql.ScriptDom'; + const scriptDomDll = 'Microsoft.SqlServer.TransactSql.ScriptDom.dll'; + const scriptDomNugetVersion = '161.8817.2'; // TODO: make this a configurable setting, like the Microsoft.Build.Sql version + const scriptDomDllLocation = path.join('lib', 'netstandard2.1'); + + return this.ensureNugetAndFilesPresence(scriptdomNugetPkgName, scriptDomNugetVersion, [scriptDomDll], scriptDomDllLocation, outputChannel); + } + + /** + * Ensures a nuget package and expected files exist in the BuildDirectory + * @param nugetName Name of the nuget package + * @param nugetVersion versiion of the nuget files + * @param expectedFiles array of expected files from the nuget in the BuildDirectory + * @param nugetFolderWithExpectedfiles folder in the nuget containing the expected files + * @param outputChannel + * @returns true if expected files exist in the BuildDirectory + */ + public async ensureNugetAndFilesPresence(nugetName: string, nugetVersion: string, expectedFiles: string[], nugetFolderWithExpectedfiles: string, outputChannel: vscode.OutputChannel): Promise { + let missingNuget = false; + + const fullNugetName = `${nugetName}.${nugetVersion}`; + const fullNugetPath = path.join(this.extensionBuildDir, `${fullNugetName}.nupkg`); + + // check if the correct nuget version has been previously downloaded before checking if the files exist. + // TODO: handle when multiple nugets are in the BuildDirectory and a user wants to switch back to an older one - probably should + // remove other versions of this nuget when a new one is downloaded + if (await utils.exists(fullNugetPath)) { + // if it does exist, make sure all the necessary files are also in the BuildDirectory + for (const fileName of expectedFiles) { + if (!await (utils.exists(path.join(this.extensionBuildDir, fileName)))) { + missingNuget = true; + break; + } + } + } else { + // if the nuget isn't there, it needs to be downloaded and the build dlls extracted + missingNuget = true; + } + + if (!missingNuget) { + return true; + } + + outputChannel.appendLine(constants.downloadingNuget(fullNugetName)); + + const nugetUrl = `https://www.nuget.org/api/v2/package/${nugetName}/${nugetVersion}`; + const extractedFolderPath = path.join(this.extensionDir, buildDirectory, nugetName); try { - await extractZip(nugetPath, { dir: extractedFolderPath }); + await this.downloadAndExtractNuget(nugetUrl, fullNugetPath, extractedFolderPath, outputChannel); } catch (e) { - void vscode.window.showErrorMessage(constants.errorExtracting(nugetPath, utils.getErrorMessage(e))); + void vscode.window.showErrorMessage(e); return false; } // copy the dlls and targets file to the BuildDirectory folder - const buildfilesPath = path.join(extractedFolderPath, 'tools', 'netstandard2.1'); + const buildfilesPath = path.join(extractedFolderPath, nugetFolderWithExpectedfiles); - for (const fileName of buildFiles) { + for (const fileName of expectedFiles) { if (await (utils.exists(path.join(buildfilesPath, fileName)))) { await fs.copyFile(path.join(buildfilesPath, fileName), path.join(this.extensionBuildDir, fileName)); } @@ -123,10 +148,32 @@ export class BuildHelper { // cleanup extracted folder await fs.rm(extractedFolderPath, { recursive: true }); - this.initialized = true; return true; } + /** + * Downloads and extracts a nuget package + * @param downloadUrl Url to download the nuget package from + * @param nugetPath Path to download the nuget package to + * @param extractFolderPath Folder path to extract the nuget package contents to + * @param outputChannel + */ + public async downloadAndExtractNuget(downloadUrl: string, nugetPath: string, extractFolderPath: string, outputChannel: vscode.OutputChannel): Promise { + try { + const httpClient = new HttpClient(); + outputChannel.appendLine(constants.downloadingFromTo(downloadUrl, nugetPath)); + await httpClient.download(downloadUrl, nugetPath, outputChannel); + } catch (e) { + throw constants.errorDownloading(extractFolderPath, utils.getErrorMessage(e)); + } + + try { + await extractZip(nugetPath, { dir: extractFolderPath }); + } catch (e) { + throw constants.errorExtracting(nugetPath, utils.getErrorMessage(e)); + } + } + public get extensionBuildDirPath(): string { return this.extensionBuildDir; }