mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Round trip with SSDT (#10563)
* Initial changes * Round trip feature implementation * Addressed PR comments * Addressed comments
This commit is contained in:
@@ -23,6 +23,8 @@ export const dataSourcesNodeName = localize('dataSourcesNodeName', "Data Sources
|
|||||||
export const sqlConnectionStringFriendly = localize('sqlConnectionStringFriendly', "SQL connection string");
|
export const sqlConnectionStringFriendly = localize('sqlConnectionStringFriendly', "SQL connection string");
|
||||||
export const newDatabaseProjectName = localize('newDatabaseProjectName', "New database project name:");
|
export const newDatabaseProjectName = localize('newDatabaseProjectName', "New database project name:");
|
||||||
export const sqlDatabaseProject = localize('sqlDatabaseProject', "SQL database project");
|
export const sqlDatabaseProject = localize('sqlDatabaseProject', "SQL database project");
|
||||||
|
export const yesString = localize('yesString', "Yes");
|
||||||
|
export const noString = localize('noString', "No");
|
||||||
export const extractTargetInput = localize('extractTargetInput', "Target for extraction:");
|
export const extractTargetInput = localize('extractTargetInput', "Target for extraction:");
|
||||||
export const selectFileFolder = localize('selectFileFolder', "Select");
|
export const selectFileFolder = localize('selectFileFolder', "Select");
|
||||||
export function newObjectNamePrompt(objectType: string) { return localize('newObjectNamePrompt', 'New {0} name:', objectType); }
|
export function newObjectNamePrompt(objectType: string) { return localize('newObjectNamePrompt', 'New {0} name:', objectType); }
|
||||||
@@ -60,6 +62,7 @@ export const projectLocationNotEmpty = localize('projectLocationNotEmpty', "Curr
|
|||||||
export const extractTargetRequired = localize('extractTargetRequired', "Target information for extract is required to import database to project.");
|
export const extractTargetRequired = localize('extractTargetRequired', "Target information for extract is required to import database to project.");
|
||||||
export const schemaCompareNotInstalled = localize('schemaCompareNotInstalled', "Schema compare extension installation is required to run schema compare");
|
export const schemaCompareNotInstalled = localize('schemaCompareNotInstalled', "Schema compare extension installation is required to run schema compare");
|
||||||
export const buildDacpacNotFound = localize('buildDacpacNotFound', "Dacpac created from build not found");
|
export const buildDacpacNotFound = localize('buildDacpacNotFound', "Dacpac created from build not found");
|
||||||
|
export const updateProjectForRoundTrip = localize('updateProjectForRoundTrip', "To build this project, Azure Data Studio needs to update targets and references. If the project is created in SSDT, it will continue to work in both tools. Do you want Azure Data Studio to update the project?");
|
||||||
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
|
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
|
||||||
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
|
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
|
||||||
export function noFileExist(fileName: string) { return localize('noFileExist', "File {0} doesn't exist", fileName); }
|
export function noFileExist(fileName: string) { return localize('noFileExist', "File {0} doesn't exist", fileName); }
|
||||||
@@ -82,6 +85,28 @@ export const ItemGroup = 'ItemGroup';
|
|||||||
export const Build = 'Build';
|
export const Build = 'Build';
|
||||||
export const Folder = 'Folder';
|
export const Folder = 'Folder';
|
||||||
export const Include = 'Include';
|
export const Include = 'Include';
|
||||||
|
export const Import = 'Import';
|
||||||
|
export const Project = 'Project';
|
||||||
|
export const Condition = 'Condition';
|
||||||
|
export const PackageReference = 'PackageReference';
|
||||||
|
export const Version = 'Version';
|
||||||
|
export const PrivateAssets = 'PrivateAssets';
|
||||||
|
|
||||||
|
|
||||||
|
// SqlProj File targets
|
||||||
|
export const NetCoreTargets = '$(NETCoreTargetsPath)\\Microsoft.Data.Tools.Schema.SqlTasks.targets';
|
||||||
|
export const SqlDbTargets = '$(SQLDBExtensionsRefPath)\\Microsoft.Data.Tools.Schema.SqlTasks.targets';
|
||||||
|
export const MsBuildtargets = '$(MSBuildExtensionsPath)\\Microsoft\\VisualStudio\\v$(VisualStudioVersion)\\SSDT\\Microsoft.Data.Tools.Schema.SqlTasks.targets';
|
||||||
|
export const NetCoreCondition = '\'$(NetCoreBuild)\' == \'true\'';
|
||||||
|
export const SqlDbPresentCondition = '\'$(SQLDBExtensionsRefPath)\' != \'\'';
|
||||||
|
export const SqlDbNotPresentCondition = '\'$(SQLDBExtensionsRefPath)\' == \'\'';
|
||||||
|
export const RoundTripSqlDbPresentCondition = '\'$(NetCoreBuild)\' != \'true\' AND \'$(SQLDBExtensionsRefPath)\' != \'\'';
|
||||||
|
export const RoundTripSqlDbNotPresentCondition = '\'$(NetCoreBuild)\' != \'true\' AND \'$(SQLDBExtensionsRefPath)\' == \'\'';
|
||||||
|
|
||||||
|
// SqlProj Reference Assembly Information
|
||||||
|
export const NETFrameworkAssembly = 'Microsoft.NETFramework.ReferenceAssemblies';
|
||||||
|
export const VersionNumber = '1.0.0';
|
||||||
|
export const All = 'All';
|
||||||
|
|
||||||
// SQL connection string components
|
// SQL connection string components
|
||||||
export const initialCatalogSetting = 'Initial Catalog';
|
export const initialCatalogSetting = 'Initial Catalog';
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ export class ProjectsController {
|
|||||||
await newProject.readProjFile();
|
await newProject.readProjFile();
|
||||||
this.projects.push(newProject);
|
this.projects.push(newProject);
|
||||||
|
|
||||||
|
// Update for round tripping as needed
|
||||||
|
await newProject.updateProjectForRoundTrip();
|
||||||
|
|
||||||
// Read datasources.json (if present)
|
// Read datasources.json (if present)
|
||||||
const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName);
|
const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as xmldom from 'xmldom';
|
|||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
|
|
||||||
import { Uri } from 'vscode';
|
import { Uri, window } from 'vscode';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { DataSource } from './dataSources/dataSources';
|
import { DataSource } from './dataSources/dataSources';
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ export class Project {
|
|||||||
public projectFileName: string;
|
public projectFileName: string;
|
||||||
public files: ProjectEntry[] = [];
|
public files: ProjectEntry[] = [];
|
||||||
public dataSources: DataSource[] = [];
|
public dataSources: DataSource[] = [];
|
||||||
|
public importedTargets: string[] = [];
|
||||||
public sqlCmdVariables: Record<string, string> = {};
|
public sqlCmdVariables: Record<string, string> = {};
|
||||||
|
|
||||||
public get projectFolderPath() {
|
public get projectFolderPath() {
|
||||||
@@ -41,7 +42,6 @@ export class Project {
|
|||||||
this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString());
|
this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString());
|
||||||
|
|
||||||
// find all folders and files to include
|
// find all folders and files to include
|
||||||
|
|
||||||
for (let ig = 0; ig < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
|
for (let ig = 0; ig < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
|
||||||
const itemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
|
const itemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
|
||||||
|
|
||||||
@@ -53,6 +53,45 @@ export class Project {
|
|||||||
this.files.push(this.createProjectEntry(itemGroup.getElementsByTagName(constants.Folder)[f].getAttribute(constants.Include), EntryType.Folder));
|
this.files.push(this.createProjectEntry(itemGroup.getElementsByTagName(constants.Folder)[f].getAttribute(constants.Include), EntryType.Folder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find all import statements to include
|
||||||
|
for (let i = 0; i < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import).length; i++) {
|
||||||
|
const importTarget = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import)[i];
|
||||||
|
this.importedTargets.push(importTarget.getAttribute(constants.Project));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateProjectForRoundTrip() {
|
||||||
|
if (this.importedTargets.includes(constants.NetCoreTargets)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showWarningMessage(constants.updateProjectForRoundTrip, constants.yesString, constants.noString).then(async (result) => {
|
||||||
|
if (result === constants.yesString) {
|
||||||
|
await fs.copyFile(this.projectFilePath, this.projectFilePath + '_backup');
|
||||||
|
await this.updateImportToSupportRoundTrip();
|
||||||
|
await this.updatePackageReferenceInProjFile();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateImportToSupportRoundTrip(): Promise<void> {
|
||||||
|
// update an SSDT project to include Net core target information
|
||||||
|
for (let i = 0; i < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import).length; i++) {
|
||||||
|
const importTarget = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import)[i];
|
||||||
|
|
||||||
|
let condition = importTarget.getAttribute(constants.Condition);
|
||||||
|
let project = importTarget.getAttribute(constants.Project);
|
||||||
|
|
||||||
|
if (condition === constants.SqlDbPresentCondition && project === constants.SqlDbTargets) {
|
||||||
|
await this.updateImportedTargetsToProjFile(constants.RoundTripSqlDbPresentCondition, project, importTarget);
|
||||||
|
}
|
||||||
|
if (condition === constants.SqlDbNotPresentCondition && project === constants.MsBuildtargets) {
|
||||||
|
await this.updateImportedTargetsToProjFile(constants.RoundTripSqlDbNotPresentCondition, project, importTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateImportedTargetsToProjFile(constants.NetCoreCondition, constants.NetCoreTargets, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,16 +148,19 @@ export class Project {
|
|||||||
private findOrCreateItemGroup(containedTag?: string): any {
|
private findOrCreateItemGroup(containedTag?: string): any {
|
||||||
let outputItemGroup = undefined;
|
let outputItemGroup = undefined;
|
||||||
|
|
||||||
|
// search for a particular item goup if a child type is provided
|
||||||
|
if (containedTag) {
|
||||||
// find any ItemGroup node that contains files; that's where we'll add
|
// find any ItemGroup node that contains files; that's where we'll add
|
||||||
for (let i = 0; i < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; i++) {
|
for (let ig = 0; ig < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
|
||||||
const currentItemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[i];
|
const currentItemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
|
||||||
|
|
||||||
// if we're not hunting for a particular child type, or if we are and we find it, use the ItemGroup
|
// if we find the tag, use the ItemGroup
|
||||||
if (!containedTag || currentItemGroup.getElementsByTagName(containedTag).length > 0) {
|
if (currentItemGroup.getElementsByTagName(containedTag).length > 0) {
|
||||||
outputItemGroup = currentItemGroup;
|
outputItemGroup = currentItemGroup;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if none already exist, make a new ItemGroup for it
|
// if none already exist, make a new ItemGroup for it
|
||||||
if (!outputItemGroup) {
|
if (!outputItemGroup) {
|
||||||
@@ -143,6 +185,34 @@ export class Project {
|
|||||||
this.findOrCreateItemGroup(constants.Folder).appendChild(newFolderNode);
|
this.findOrCreateItemGroup(constants.Folder).appendChild(newFolderNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateImportedTargetsToProjFile(condition: string, project: string, oldImportNode?: any): Promise<any> {
|
||||||
|
const importNode = this.projFileXmlDoc.createElement(constants.Import);
|
||||||
|
importNode.setAttribute(constants.Condition, condition);
|
||||||
|
importNode.setAttribute(constants.Project, project);
|
||||||
|
|
||||||
|
if (oldImportNode) {
|
||||||
|
this.projFileXmlDoc.documentElement.replaceChild(importNode, oldImportNode);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.projFileXmlDoc.documentElement.appendChild(importNode, oldImportNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.serializeToProjFile(this.projFileXmlDoc);
|
||||||
|
return importNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updatePackageReferenceInProjFile(): Promise<void> {
|
||||||
|
const packageRefNode = this.projFileXmlDoc.createElement(constants.PackageReference);
|
||||||
|
packageRefNode.setAttribute(constants.Condition, constants.NetCoreCondition);
|
||||||
|
packageRefNode.setAttribute(constants.Include, constants.NETFrameworkAssembly);
|
||||||
|
packageRefNode.setAttribute(constants.Version, constants.VersionNumber);
|
||||||
|
packageRefNode.setAttribute(constants.PrivateAssets, constants.All);
|
||||||
|
|
||||||
|
this.findOrCreateItemGroup(constants.PackageReference).appendChild(packageRefNode);
|
||||||
|
|
||||||
|
await this.serializeToProjFile(this.projFileXmlDoc);
|
||||||
|
}
|
||||||
|
|
||||||
private async addToProjFile(entry: ProjectEntry) {
|
private async addToProjFile(entry: ProjectEntry) {
|
||||||
switch (entry.type) {
|
switch (entry.type) {
|
||||||
case EntryType.File:
|
case EntryType.File:
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export class NetCoreTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// spawns the dotnet command with aruments and redirects the error and output to ADS output channel
|
// spawns the dotnet command with arguments and redirects the error and output to ADS output channel
|
||||||
public async runStreamedCommand(command: string, outputChannel: vscode.OutputChannel, options?: DotNetCommandOptions): Promise<string> {
|
public async runStreamedCommand(command: string, outputChannel: vscode.OutputChannel, options?: DotNetCommandOptions): Promise<string> {
|
||||||
const stdoutData: string[] = [];
|
const stdoutData: string[] = [];
|
||||||
outputChannel.appendLine(` > ${command}`);
|
outputChannel.appendLine(` > ${command}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user