mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
add initial SDK-style project migration (#18827)
* add initial SDK-style project migration * addressing comments
This commit is contained in:
@@ -22,6 +22,7 @@ export const msdbDacpac = 'msdb.dacpac';
|
|||||||
export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql';
|
export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql';
|
||||||
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
|
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
|
||||||
export const sqlProjectSdk = 'Microsoft.Build.Sql';
|
export const sqlProjectSdk = 'Microsoft.Build.Sql';
|
||||||
|
export const sqlProjectSdkVersion = '0.1.3-preview';
|
||||||
|
|
||||||
// Project Provider
|
// Project Provider
|
||||||
export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj';
|
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 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 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 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
|
// Publish dialog strings
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* Adds a database reference to the project
|
||||||
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
|
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
|
||||||
|
|||||||
@@ -617,6 +617,45 @@ export class Project implements ISqlProject {
|
|||||||
await this.createCleanFileNode(beforeBuildNode);
|
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> {
|
private async createCleanFileNode(parentNode: Element): Promise<void> {
|
||||||
const deleteFileNode = this.projFileXmlDoc!.createElement(constants.Delete);
|
const deleteFileNode = this.projFileXmlDoc!.createElement(constants.Delete);
|
||||||
deleteFileNode.setAttribute(constants.Files, constants.ProjJsonToClean);
|
deleteFileNode.setAttribute(constants.Files, constants.ProjJsonToClean);
|
||||||
|
|||||||
@@ -1577,7 +1577,7 @@ describe('Project: round trip updates', function (): void {
|
|||||||
await testUpdateInRoundTrip(baselines.SSDTUpdatedProjectBaseline, baselines.SSDTUpdatedProjectAfterSystemDbUpdateBaseline);
|
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);
|
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');
|
should(stub.calledOnce).be.true('showWarningMessage should have been called exactly once');
|
||||||
sinon.restore();
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user