diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index e9a1830222..d3e7ae2470 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -21,6 +21,7 @@ export const msdb = 'msdb'; export const msdbDacpac = 'msdb.dacpac'; export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql'; export const databaseSchemaProvider = 'DatabaseSchemaProvider'; +export const sqlMsbuildSdk = 'Microsoft.Build.Sql'; // Project Provider export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj'; @@ -360,6 +361,7 @@ export const Private = 'Private'; export const ProjectGuid = 'ProjectGuid'; export const Type = 'Type'; export const ExternalStreamingJob: string = 'ExternalStreamingJob'; +export const Sdk: string = 'Sdk'; /** Name of the property item in the project file that defines default database collation. */ export const DefaultCollationProperty = 'DefaultCollation'; diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index de2c02190f..e23eacd5ae 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -1057,25 +1057,6 @@ export class ProjectsController { return new AddDatabaseReferenceDialog(project); } - public async updateProjectForRoundTrip(project: Project) { - if (project.importedTargets.includes(constants.NetCoreTargets) && !project.containsSSDTOnlySystemDatabaseReferences()) { - return; - } - - if (!project.importedTargets.includes(constants.NetCoreTargets)) { - const result = await vscode.window.showWarningMessage(constants.updateProjectForRoundTrip, constants.yesString, constants.noString); - if (result === constants.yesString) { - await project.updateProjectForRoundTrip(); - await project.updateSystemDatabaseReferencesInProjFile(); - } - } else if (project.containsSSDTOnlySystemDatabaseReferences()) { - const result = await vscode.window.showWarningMessage(constants.updateProjectDatabaseReferencesForRoundTrip, constants.yesString, constants.noString); - if (result === constants.yesString) { - await project.updateSystemDatabaseReferencesInProjFile(); - } - } - } - private async addTemplateFiles(newProjFilePath: string, projectTypeId: string): Promise { if (projectTypeId === constants.emptySqlDatabaseProjectTypeId || newProjFilePath === '') { return; diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index b93a0a9e31..84bf638280 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -33,6 +33,7 @@ export class Project implements ISqlProject { private _preDeployScripts: FileProjectEntry[] = []; private _postDeployScripts: FileProjectEntry[] = []; private _noneDeployScripts: FileProjectEntry[] = []; + private _isMsbuildSdkStyleProject: boolean = false; public get dacpacOutputPath(): string { return path.join(this.projectFolderPath, 'bin', 'Debug', `${this._projectFileName}.dacpac`); @@ -86,6 +87,10 @@ export class Project implements ISqlProject { return this._noneDeployScripts; } + public get isMsbuildSdkStyleProject(): boolean { + return this._isMsbuildSdkStyleProject; + } + private projFileXmlDoc: any = undefined; constructor(projectFilePath: string) { @@ -113,6 +118,8 @@ export class Project implements ISqlProject { const projFileText = await fs.readFile(this._projectFilePath); this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString()); + // check if this is a new msbuild sdk style project + this._isMsbuildSdkStyleProject = this.CheckForMsbuildSdkStyleProject(); // get projectGUID this._projectGuid = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ProjectGuid)[0].childNodes[0].nodeValue; @@ -236,8 +243,38 @@ export class Project implements ISqlProject { this.projFileXmlDoc = undefined; } + /** + * Checks for the 3 possible ways a project can reference the sql msbuild sdk + * https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2019 + * @returns true if the project is an msbuild sdk style project, false if it isn't + */ + public CheckForMsbuildSdkStyleProject(): boolean { + // type 1: Sdk node like + const sdkNodes = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Sdk); + if (sdkNodes.length > 0) { + return sdkNodes[0].getAttribute(constants.Name) === constants.sqlMsbuildSdk; + } + + // type 2: Project node has Sdk attribute like + const sdkAttribute: string = this.projFileXmlDoc.documentElement.getAttribute(constants.Sdk); + if (sdkAttribute) { + return sdkAttribute.includes(constants.sqlMsbuildSdk); + } + + // type 3: Import node with Sdk attribute like + const importNodes = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import); + for (let i = 0; i < importNodes.length; i++) { + if (importNodes[i].getAttribute(constants.Sdk) === constants.sqlMsbuildSdk) { + return true; + } + } + + return false; + } + public async updateProjectForRoundTrip(): Promise { - if (this._importedTargets.includes(constants.NetCoreTargets) && !this.containsSSDTOnlySystemDatabaseReferences()) { + if (this._importedTargets.includes(constants.NetCoreTargets) && !this.containsSSDTOnlySystemDatabaseReferences() // old style project check + || this.isMsbuildSdkStyleProject) { // new style project check return; } diff --git a/extensions/sql-database-projects/src/test/baselines/baselines.ts b/extensions/sql-database-projects/src/test/baselines/baselines.ts index 191303ecf3..e72202918b 100644 --- a/extensions/sql-database-projects/src/test/baselines/baselines.ts +++ b/extensions/sql-database-projects/src/test/baselines/baselines.ts @@ -26,6 +26,9 @@ export let sqlProjectMissingVersionBaseline: string; export let sqlProjectInvalidVersionBaseline: string; export let sqlProjectCustomCollationBaseline: string; export let sqlProjectInvalidCollationBaseline: string; +export let newStyleProjectSdkNodeBaseline: string; +export let newStyleProjectSdkProjectAttributeBaseline: string; +export let newStyleProjectSdkImportAttributeBaseline: string; const baselineFolderPath = __dirname; @@ -49,6 +52,9 @@ export async function loadBaselines() { sqlProjectInvalidVersionBaseline = await loadBaseline(baselineFolderPath, 'sqlProjectInvalidVersionBaseline.xml'); sqlProjectCustomCollationBaseline = await loadBaseline(baselineFolderPath, 'sqlProjectCustomCollationBaseline.xml'); sqlProjectInvalidCollationBaseline = await loadBaseline(baselineFolderPath, 'sqlProjectInvalidCollationBaseline.xml'); + newStyleProjectSdkNodeBaseline = await loadBaseline(baselineFolderPath, 'newStyleSqlProjectSdkNodeBaseline.xml'); + newStyleProjectSdkProjectAttributeBaseline = await loadBaseline(baselineFolderPath, 'newStyleSqlProjectSdkProjectAttributeBaseline.xml'); + newStyleProjectSdkImportAttributeBaseline = await loadBaseline(baselineFolderPath, 'newStyleSqlProjectSdkImportAttributeBaseline.xml'); } async function loadBaseline(baselineFolderPath: string, fileName: string): Promise { diff --git a/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkImportAttributeBaseline.xml b/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkImportAttributeBaseline.xml new file mode 100644 index 0000000000..1a07b70218 --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkImportAttributeBaseline.xml @@ -0,0 +1,14 @@ + + + + TestProjectName + {2C283C5D-9E4A-4313-8FF9-4E0CEE20B063} + Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider + 1033, CI + + + + + + + diff --git a/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkNodeBaseline.xml b/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkNodeBaseline.xml new file mode 100644 index 0000000000..1fe96dc91d --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkNodeBaseline.xml @@ -0,0 +1,13 @@ + + + + + TestProjectName + {2C283C5D-9E4A-4313-8FF9-4E0CEE20B063} + Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider + 1033, CI + + + + + diff --git a/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkProjectAttributeBaseline.xml b/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkProjectAttributeBaseline.xml new file mode 100644 index 0000000000..37ff0eb34c --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/newStyleSqlProjectSdkProjectAttributeBaseline.xml @@ -0,0 +1,12 @@ + + + + TestProjectName + {2C283C5D-9E4A-4313-8FF9-4E0CEE20B063} + Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider + 1033, CI + + + + + diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts index 9c7bac30b2..b28cd8b163 100644 --- a/extensions/sql-database-projects/src/test/project.test.ts +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -906,6 +906,10 @@ describe('Project: round trip updates', function (): void { await baselines.loadBaselines(); }); + beforeEach(function (): void { + sinon.restore(); + }); + it('Should update SSDT project to work in ADS', async function (): Promise { await testUpdateInRoundTrip(baselines.SSDTProjectFileBaseline, baselines.SSDTProjectAfterUpdateBaseline); }); @@ -943,6 +947,29 @@ describe('Project: round trip updates', function (): void { should(project.importedTargets.length).equal(3); // additional target should exist by default }); + + it('Should not show update project warning message when opening msbuild sdk style project using Sdk node', async function (): Promise { + await shouldNotShowUpdateWarning(baselines.newStyleProjectSdkNodeBaseline); + }); + + it('Should not show update project warning message when opening msbuild sdk style project using Project node with Sdk attribute', async function (): Promise { + await shouldNotShowUpdateWarning(baselines.newStyleProjectSdkProjectAttributeBaseline); + }); + + it('Should not show update project warning message when opening msbuild sdk style project using Import node with Sdk attribute', async function (): Promise { + await shouldNotShowUpdateWarning(baselines.newStyleProjectSdkImportAttributeBaseline); + }); + + async function shouldNotShowUpdateWarning(baselineFile: string): Promise { + // setup test files + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProjFile(baselineFile, folderPath); + const spy = sinon.spy(window, 'showWarningMessage'); + + const project = await Project.openProject(Uri.file(sqlProjPath).fsPath); + should(spy.notCalled).be.true(); + should(project.isMsbuildSdkStyleProject).be.true(); + } }); async function testUpdateInRoundTrip(fileBeforeupdate: string, fileAfterUpdate: string): Promise { @@ -960,5 +987,3 @@ async function testUpdateInRoundTrip(fileBeforeupdate: string, fileAfterUpdate: should(stub.calledOnce).be.true('showWarningMessage should have been called exactly once'); sinon.restore(); } - -