add initial SDK-style project migration (#18827)

* add initial SDK-style project migration

* addressing comments
This commit is contained in:
Kim Santiago
2022-03-28 16:29:14 -07:00
committed by GitHub
parent aad20bc338
commit 2f825f8a97
4 changed files with 112 additions and 1 deletions

View File

@@ -22,6 +22,7 @@ export const msdbDacpac = 'msdb.dacpac';
export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql';
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
export const sqlProjectSdk = 'Microsoft.Build.Sql';
export const sqlProjectSdkVersion = '0.1.3-preview';
// Project Provider
export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj';
@@ -100,6 +101,7 @@ export function deleteConfirmationContents(toDelete: string) { return localize('
export function deleteReferenceConfirmation(toDelete: string) { return localize('deleteReferenceConfirmation', "Are you sure you want to delete the reference to {0}?", toDelete); }
export function selectTargetPlatform(currentTargetPlatform: string) { return localize('selectTargetPlatform', "Current target platform: {0}. Select new target platform", currentTargetPlatform); }
export function currentTargetPlatform(projectName: string, currentTargetPlatform: string) { return localize('currentTargetPlatform', "Target platform of the project {0} is now {1}", projectName, currentTargetPlatform); }
export function projectUpdatedToSdkStyle(projectName: string) { return localize('projectUpdatedToSdkStyle', "The project {0} has been updated to be an SDK-style project. Click 'Learn More' for details on the Microsoft.Build.Sql SDK and ways to simplify the project file.", projectName); }
// Publish dialog strings

View File

@@ -862,6 +862,24 @@ export class ProjectsController {
}
}
/**
* Converts a legacy style project to an SDK-style project
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/
public async convertToSdkStyleProject(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context);
await project.convertProjectToSdkStyle();
void this.reloadProject(context);
// show message that project file can be simplified
const result = await vscode.window.showInformationMessage(constants.projectUpdatedToSdkStyle(project.projectFileName), constants.learnMore);
if (result === constants.learnMore) {
void vscode.env.openExternal(vscode.Uri.parse(constants.sdkLearnMoreUrl!));
}
}
/**
* Adds a database reference to the project
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project

View File

@@ -617,6 +617,45 @@ export class Project implements ISqlProject {
await this.createCleanFileNode(beforeBuildNode);
}
public async convertProjectToSdkStyle(): Promise<void> {
// don't do anything if the project is already SDK style or it's an SSDT project that hasn't been updated to build in ADS
if (this.isSdkStyleProject || !this._importedTargets.includes(constants.NetCoreTargets)) {
return;
}
// make backup copy of project
await fs.copyFile(this._projectFilePath, this._projectFilePath + '_backup');
// remove SSDT and ADS SqlTasks imports
const importsToRemove = [];
for (let i = 0; i < this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Import).length; i++) {
const importTarget = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Import)[i];
const projectAttributeVal = importTarget.getAttribute(constants.Project);
if (projectAttributeVal === constants.NetCoreTargets || projectAttributeVal === constants.SqlDbTargets || projectAttributeVal === constants.MsBuildtargets) {
importsToRemove.push(importTarget);
}
}
const parent = importsToRemove[0]?.parentNode;
importsToRemove.forEach(i => { parent?.removeChild(i); });
// add SDK node
const sdkNode = this.projFileXmlDoc!.createElement(constants.Sdk);
sdkNode.setAttribute(constants.Name, constants.sqlProjectSdk);
sdkNode.setAttribute(constants.Version, constants.sqlProjectSdkVersion);
const projectNode = this.projFileXmlDoc!.documentElement;
projectNode.insertBefore(sdkNode, projectNode.firstChild);
// TODO: also update system dacpac path, but might as well wait for them to get included in the SDK since the path will probably change again
// TODO: remove Build includes and folder includes. Make sure the same files and folders are being included and there aren't extra files included by the default **/*.sql glob
await this.serializeToProjFile(this.projFileXmlDoc!);
await this.readProjFile();
}
private async createCleanFileNode(parentNode: Element): Promise<void> {
const deleteFileNode = this.projFileXmlDoc!.createElement(constants.Delete);
deleteFileNode.setAttribute(constants.Files, constants.ProjJsonToClean);

View File

@@ -1577,7 +1577,7 @@ describe('Project: round trip updates', function (): void {
await testUpdateInRoundTrip(baselines.SSDTUpdatedProjectBaseline, baselines.SSDTUpdatedProjectAfterSystemDbUpdateBaseline);
});
it('Should update SSDT project to work in ADS handling pre-exsiting targets', async function (): Promise<void> {
it('Should update SSDT project to work in ADS handling pre-existing targets', async function (): Promise<void> {
await testUpdateInRoundTrip(baselines.SSDTProjectBaselineWithBeforeBuildTarget, baselines.SSDTProjectBaselineWithBeforeBuildTargetAfterUpdate);
});
@@ -1646,3 +1646,55 @@ async function testUpdateInRoundTrip(fileBeforeupdate: string, fileAfterUpdate:
should(stub.calledOnce).be.true('showWarningMessage should have been called exactly once');
sinon.restore();
}
describe('Project: legacy to SDK-style updates', function (): void {
before(async function (): Promise<void> {
await baselines.loadBaselines();
});
beforeEach(function (): void {
sinon.restore();
});
it('Should update legacy style project to SDK-style', async function (): Promise<void> {
const projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
const project = await Project.openProject(projFilePath);
should(project.importedTargets.length).equal(3, 'SSDT and ADS imports should be in the project');
should(project.isSdkStyleProject).equal(false);
await project.convertProjectToSdkStyle();
should(await exists(projFilePath + '_backup')).equal(true, 'Backup file should have been generated before the project was updated');
should(project.importedTargets.length).equal(0, 'SSDT and ADS imports should have been removed');
should(project.isSdkStyleProject).equal(true);
});
it('Should not update project and no backup file should be created when project is already SDK-style', async function (): Promise<void> {
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.openSdkStyleSqlProjectBaseline, folderPath);
const project = await Project.openProject(Uri.file(sqlProjPath).fsPath);
should(project.isSdkStyleProject).equal(true);
await project.convertProjectToSdkStyle();
should(await exists(sqlProjPath + '_backup')).equal(false, 'No backup file should have been created');
should(project.isSdkStyleProject).equal(true);
});
it('Should not update project and no backup file should be created when it is an SSDT project that has not been updated to work in ADS', async function (): Promise<void> {
sinon.stub(window, 'showWarningMessage').returns(<any>Promise.resolve(constants.noString));
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
const project = await Project.openProject(Uri.file(sqlProjPath).fsPath);
should(project.isSdkStyleProject).equal(false);
should(project.importedTargets.length).equal(2, 'Project should have 2 SSDT imports');
await project.convertProjectToSdkStyle();
should(await exists(sqlProjPath + '_backup')).equal(false, 'No backup file should have been created');
should(project.importedTargets.length).equal(2, 'Project imports should not have been changed');
should(project.isSdkStyleProject).equal(false);
});
});