diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts
index 7b27a300a4..9f014315b2 100644
--- a/extensions/sql-database-projects/src/common/constants.ts
+++ b/extensions/sql-database-projects/src/common/constants.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
+import * as path from 'path';
import { SqlTargetPlatform } from 'sqldbproj';
import * as utils from '../common/utils';
@@ -457,6 +458,10 @@ export const Sdk: string = 'Sdk';
export const DatabaseSource = 'DatabaseSource';
export const VisualStudioVersion = 'VisualStudioVersion';
export const SSDTExists = 'SSDTExists';
+export const OutputPath = 'OutputPath';
+export const Configuration = 'Configuration';
+export const Platform = 'Platform';
+export const AnyCPU = 'AnyCPU';
export const BuildElements = localize('buildElements', "Build Elements");
export const FolderElements = localize('folderElements', "Folder Elements");
@@ -494,6 +499,11 @@ export const RoundTripSqlDbPresentCondition = '\'$(NetCoreBuild)\' != \'true\' A
export const RoundTripSqlDbNotPresentCondition = '\'$(NetCoreBuild)\' != \'true\' AND \'$(SQLDBExtensionsRefPath)\' == \'\'';
export const DacpacRootPath = '$(DacPacRootPath)';
export const ProjJsonToClean = '$(BaseIntermediateOutputPath)\\project.assets.json';
+export const EmptyConfigurationCondition = '\'$(Configuration)\' == \'\'';
+export const EmptyPlatformCondition = '\'$(Platform)\' == \'\'';
+export function ConfigurationPlatformCondition(configuration: string, platform: string) { return `'$(Configuration)|$(Platform)' == '${configuration}|${platform}'`; }
+
+export function defaultOutputPath(configuration: string) { return path.join('.', 'bin', configuration); }
// Sqlproj VS property conditions
export const VSVersionCondition = '\'$(VisualStudioVersion)\' == \'\'';
diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts
index ce317aa992..03575f1090 100644
--- a/extensions/sql-database-projects/src/models/project.ts
+++ b/extensions/sql-database-projects/src/models/project.ts
@@ -20,6 +20,15 @@ import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings, IProjectRef
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { DacpacReferenceProjectEntry, FileProjectEntry, ProjectEntry, SqlCmdVariableProjectEntry, SqlProjectReferenceProjectEntry, SystemDatabase, SystemDatabaseReferenceProjectEntry } from './projectEntry';
+/**
+ * Represents the configuration based on the Configuration property in the sqlproj
+ */
+enum Configuration {
+ Debug = 'Debug', // default used if the Configuration property is not specified
+ Release = 'Release',
+ Output = 'Output' // if a string besides debug or release is used, then Output is used as the configuration
+}
+
/**
* Class representing a Project, and providing functions for operating on it
*/
@@ -36,9 +45,11 @@ export class Project implements ISqlProject {
private _postDeployScripts: FileProjectEntry[] = [];
private _noneDeployScripts: FileProjectEntry[] = [];
private _isSdkStyleProject: boolean = false; // https://docs.microsoft.com/en-us/dotnet/core/project-sdk/overview
+ private _outputPath: string = '';
+ private _configuration: Configuration = Configuration.Debug;
public get dacpacOutputPath(): string {
- return path.join(this.projectFolderPath, 'bin', 'Debug', `${this._projectFileName}.dacpac`);
+ return path.join(this.outputPath, `${this._projectFileName}.dacpac`);
}
public get projectFolderPath() {
@@ -93,6 +104,14 @@ export class Project implements ISqlProject {
return this._isSdkStyleProject;
}
+ public get outputPath(): string {
+ return this._outputPath;
+ }
+
+ public get configuration(): Configuration {
+ return this._configuration;
+ }
+
private projFileXmlDoc: Document | undefined = undefined;
constructor(projectFilePath: string) {
@@ -155,6 +174,67 @@ export class Project implements ISqlProject {
this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.PropertyGroup)[0]?.appendChild(newProjectGuidNode);
await this.serializeToProjFile(this.projFileXmlDoc);
}
+
+ // get configuration
+ const configurationNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Configuration);
+ if (configurationNodes.length > 0) {
+ const configuration = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Configuration)[0].childNodes[0].nodeValue!;
+ switch (configuration.toLowerCase()) {
+ case Configuration.Debug.toString().toLowerCase():
+ this._configuration = Configuration.Debug;
+ break;
+ case Configuration.Release.toString().toLowerCase():
+ this._configuration = Configuration.Release;
+ break;
+ default:
+ // if the configuration doesn't match release or debug, the dacpac will get created in ./bin/Output
+ this._configuration = Configuration.Output;
+ }
+ } else {
+ // If configuration isn't specified in .sqlproj, set it to the default debug
+ this._configuration = Configuration.Debug;
+ }
+
+ // get platform
+ const platformNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Platform);
+ let platform = '';
+ if (platformNodes.length > 0) {
+ for (let i = 0; i < platformNodes.length; i++) {
+ const condition = platformNodes[i].getAttribute(constants.Condition);
+ if (condition?.trim() === constants.EmptyPlatformCondition.trim()) {
+ platform = platformNodes[i].childNodes[0].nodeValue ?? '';
+ break;
+ }
+ }
+ } else {
+ platform = constants.AnyCPU;
+ }
+
+ // get output path
+ let outputPath;
+ const outputPathNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.OutputPath);
+ if (outputPathNodes.length > 0) {
+ // go through all the OutputPath nodes and use the last one in the .sqlproj that the condition matches
+ for (let i = 0; i < outputPathNodes.length; i++) {
+ // check if parent has a condition
+ const parent = outputPathNodes[i].parentNode as Element;
+ const condition = parent?.getAttribute(constants.Condition);
+
+ // only handle the default conditions format that are there when creating a sqlproj in VS or ADS
+ if (condition?.toLowerCase().trim() === constants.ConfigurationPlatformCondition(this.configuration.toString(), platform).toLowerCase()) {
+ outputPath = outputPathNodes[i].childNodes[0].nodeValue;
+ } else if (!condition) {
+ outputPath = outputPathNodes[i].childNodes[0].nodeValue;
+ }
+ }
+ }
+
+ if (outputPath) {
+ this._outputPath = path.join(utils.getPlatformSafeFileEntryPath(this.projectFolderPath), utils.getPlatformSafeFileEntryPath(outputPath));
+ } else {
+ // If output path isn't specified in .sqlproj, set it to the default output path .\bin\Debug\
+ this._outputPath = path.join(utils.getPlatformSafeFileEntryPath(this.projectFolderPath), utils.getPlatformSafeFileEntryPath(constants.defaultOutputPath(this.configuration.toString())));
+ }
}
/**
@@ -539,6 +619,8 @@ export class Project implements ISqlProject {
this._postDeployScripts = [];
this._noneDeployScripts = [];
this.projFileXmlDoc = undefined;
+ this._outputPath = '';
+ this._configuration = Configuration.Debug;
}
/**
diff --git a/extensions/sql-database-projects/src/test/baselines/baselines.ts b/extensions/sql-database-projects/src/test/baselines/baselines.ts
index 73cf5e1786..b0a1da1d8c 100644
--- a/extensions/sql-database-projects/src/test/baselines/baselines.ts
+++ b/extensions/sql-database-projects/src/test/baselines/baselines.ts
@@ -11,6 +11,10 @@ export let newProjectFileBaseline: string;
export let newProjectFileWithScriptBaseline: string;
export let newProjectFileNoPropertiesFolderBaseline: string;
export let openProjectFileBaseline: string;
+export let openProjectFileReleaseConfigurationBaseline: string;
+export let openProjectFileUnknownConfigurationBaseline: string;
+export let openProjectFileSingleOutputPathBaseline: string;
+export let openProjectFileMultipleOutputPathBaseline: string;
export let openDataSourcesBaseline: string;
export let SSDTProjectFileBaseline: string;
export let SSDTProjectAfterUpdateBaseline: string;
@@ -44,6 +48,10 @@ export async function loadBaselines() {
newProjectFileWithScriptBaseline = await loadBaseline(baselineFolderPath, 'newSqlProjectWithScriptBaseline.xml');
newProjectFileNoPropertiesFolderBaseline = await loadBaseline(baselineFolderPath, 'newSqlProjectNoPropertiesFolderBaseline.xml');
openProjectFileBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectBaseline.xml');
+ openProjectFileReleaseConfigurationBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectReleaseConfigurationBaseline.xml');
+ openProjectFileUnknownConfigurationBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectUnknownConfigurationBaseline.xml');
+ openProjectFileSingleOutputPathBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectSingleOutputPathBaseline.xml');
+ openProjectFileMultipleOutputPathBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectMultipleOutputPathBaseline.xml');
openDataSourcesBaseline = await loadBaseline(baselineFolderPath, 'openDataSourcesBaseline.json');
SSDTProjectFileBaseline = await loadBaseline(baselineFolderPath, 'SSDTProjectBaseline.xml');
SSDTProjectAfterUpdateBaseline = await loadBaseline(baselineFolderPath, 'SSDTProjectAfterUpdateBaseline.xml');
diff --git a/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectBaseline.xml
index dbf2406a1a..a956694190 100644
--- a/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectBaseline.xml
+++ b/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectBaseline.xml
@@ -6,6 +6,7 @@
{2C283C5D-9E4A-4313-8FF9-4E0CEE20B063}
Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider
1033, CI
+ ..\otherFolder
diff --git a/extensions/sql-database-projects/src/test/baselines/openSqlProjectMultipleOutputPathBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSqlProjectMultipleOutputPathBaseline.xml
new file mode 100644
index 0000000000..78bcb8405a
--- /dev/null
+++ b/extensions/sql-database-projects/src/test/baselines/openSqlProjectMultipleOutputPathBaseline.xml
@@ -0,0 +1,114 @@
+
+
+
+ Debug
+ AnyCPU
+ TestProjectName
+ 2.0
+ 4.1
+ {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575}
+ Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider
+ Database
+
+
+ TestProjectName
+ TestProjectName
+ 1033, CI
+ BySchemaAndSchemaType
+ True
+ v4.5
+ CS
+ Properties
+ False
+ True
+ True
+
+
+ bin\Release\
+ $(MSBuildProjectName).sql
+ False
+ pdbonly
+ true
+ false
+ true
+ prompt
+ 4
+
+
+ bin\Debug\
+ $(MSBuildProjectName).sql
+ false
+ true
+ full
+ false
+ true
+ true
+ prompt
+ 4
+
+
+ bin\other
+
+
+ 11.0
+
+ True
+ 11.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MyProdDatabase
+ $(SqlCmdVar__1)
+
+
+ MyBackupDatabase
+ $(SqlCmdVar__2)
+
+
+
+
+ False
+ master
+
+
+ False
+ master
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/sql-database-projects/src/test/baselines/openSqlProjectReleaseConfigurationBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSqlProjectReleaseConfigurationBaseline.xml
new file mode 100644
index 0000000000..19324c0b31
--- /dev/null
+++ b/extensions/sql-database-projects/src/test/baselines/openSqlProjectReleaseConfigurationBaseline.xml
@@ -0,0 +1,111 @@
+
+
+
+ Release
+ AnyCPU
+ TestProjectName
+ 2.0
+ 4.1
+ {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575}
+ Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider
+ Database
+
+
+ TestProjectName
+ TestProjectName
+ 1033, CI
+ BySchemaAndSchemaType
+ True
+ v4.5
+ CS
+ Properties
+ False
+ True
+ True
+
+
+ bin\Release\
+ $(MSBuildProjectName).sql
+ False
+ pdbonly
+ true
+ false
+ true
+ prompt
+ 4
+
+
+ bin\Debug\
+ $(MSBuildProjectName).sql
+ false
+ true
+ full
+ false
+ true
+ true
+ prompt
+ 4
+
+
+ 11.0
+
+ True
+ 11.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MyProdDatabase
+ $(SqlCmdVar__1)
+
+
+ MyBackupDatabase
+ $(SqlCmdVar__2)
+
+
+
+
+ False
+ master
+
+
+ False
+ master
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/sql-database-projects/src/test/baselines/openSqlProjectSingleOutputPathBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSqlProjectSingleOutputPathBaseline.xml
new file mode 100644
index 0000000000..a8587678d2
--- /dev/null
+++ b/extensions/sql-database-projects/src/test/baselines/openSqlProjectSingleOutputPathBaseline.xml
@@ -0,0 +1,89 @@
+
+
+
+ Debug
+ AnyCPU
+ TestProjectName
+ 2.0
+ 4.1
+ {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575}
+ Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider
+ Database
+
+
+ TestProjectName
+ TestProjectName
+ 1033, CI
+ BySchemaAndSchemaType
+ True
+ v4.5
+ CS
+ Properties
+ False
+ True
+ True
+ ..\otherFolder
+
+
+ 11.0
+
+ True
+ 11.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MyProdDatabase
+ $(SqlCmdVar__1)
+
+
+ MyBackupDatabase
+ $(SqlCmdVar__2)
+
+
+
+
+ False
+ master
+
+
+ False
+ master
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/sql-database-projects/src/test/baselines/openSqlProjectUnknownConfigurationBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSqlProjectUnknownConfigurationBaseline.xml
new file mode 100644
index 0000000000..3bd55a2436
--- /dev/null
+++ b/extensions/sql-database-projects/src/test/baselines/openSqlProjectUnknownConfigurationBaseline.xml
@@ -0,0 +1,111 @@
+
+
+
+ Unknown
+ AnyCPU
+ TestProjectName
+ 2.0
+ 4.1
+ {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575}
+ Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider
+ Database
+
+
+ TestProjectName
+ TestProjectName
+ 1033, CI
+ BySchemaAndSchemaType
+ True
+ v4.5
+ CS
+ Properties
+ False
+ True
+ True
+
+
+ bin\Release\
+ $(MSBuildProjectName).sql
+ False
+ pdbonly
+ true
+ false
+ true
+ prompt
+ 4
+
+
+ bin\Debug\
+ $(MSBuildProjectName).sql
+ false
+ true
+ full
+ false
+ true
+ true
+ prompt
+ 4
+
+
+ 11.0
+
+ True
+ 11.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MyProdDatabase
+ $(SqlCmdVar__1)
+
+
+ MyBackupDatabase
+ $(SqlCmdVar__2)
+
+
+
+
+ False
+ master
+
+
+ False
+ master
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts
index 69d0650a78..fadbcafc6a 100644
--- a/extensions/sql-database-projects/src/test/project.test.ts
+++ b/extensions/sql-database-projects/src/test/project.test.ts
@@ -13,7 +13,7 @@ import * as constants from '../common/constants';
import { promises as fs } from 'fs';
import { Project } from '../models/project';
-import { exists, convertSlashesForSqlProj, getWellKnownDatabaseSources } from '../common/utils';
+import { exists, convertSlashesForSqlProj, getWellKnownDatabaseSources, getPlatformSafeFileEntryPath } from '../common/utils';
import { Uri, window } from 'vscode';
import { IDacpacReferenceSettings, IProjectReferenceSettings, ISystemDatabaseReferenceSettings } from '../models/IDatabaseReferenceSettings';
import { EntryType, ItemType, SqlTargetPlatform } from 'sqldbproj';
@@ -858,6 +858,51 @@ describe('Project: sqlproj content operations', function (): void {
should(projFileText.includes('')).equal(true, projFileText);
should(projFileText.includes('')).equal(true, projFileText);
});
+
+ it('Should read OutputPath from sqlproj if there is one for legacy-style project with Debug configuration', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
+ const project: Project = await Project.openProject(projFilePath);
+
+ should(project.configuration).equal('Debug');
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\Debug\\')));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\Debug\\'), `${project.projectFileName}.dacpac`));
+ });
+
+ it('Should read OutputPath from sqlproj if there is one for legacy-style project with Release configuration', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileReleaseConfigurationBaseline);
+ const project: Project = await Project.openProject(projFilePath);
+
+ should(project.configuration).equal('Release');
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\Release\\')));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\Release\\'), `${project.projectFileName}.dacpac`));
+ });
+
+ it('Should set configuration to Output for legacy-style project with unknown configuration', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileUnknownConfigurationBaseline);
+ const project: Project = await Project.openProject(projFilePath);
+
+ should(project.configuration).equal('Output');
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\Output')));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\Output\\'), `${project.projectFileName}.dacpac`));
+ });
+
+ it('Should set configuration to Output for legacy-style project with unknown configuration', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileSingleOutputPathBaseline);
+ const project: Project = await Project.openProject(projFilePath);
+
+ should(project.configuration).equal('Debug');
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('..\\otherFolder')));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('..\\otherFolder'), `${project.projectFileName}.dacpac`));
+ });
+
+ it('Should use the last OutputPath in the .sqlproj that matches the conditions', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileMultipleOutputPathBaseline);
+ const project: Project = await Project.openProject(projFilePath);
+
+ should(project.configuration).equal('Debug');
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\other')));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('bin\\other'), `${project.projectFileName}.dacpac`));
+ });
});
describe('Project: sdk style project content operations', function (): void {
@@ -1430,6 +1475,30 @@ describe('Project: sdk style project content operations', function (): void {
should(projFileText.includes(constants.ProjectGuid)).equal(true);
});
+ it('Should read OutputPath from sqlproj if there is one for SDK-style project', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openSdkStyleSqlProjectBaseline);
+ const projFileText = (await fs.readFile(projFilePath)).toString();
+
+ // Verify sqlproj has OutputPath
+ should(projFileText.includes(constants.OutputPath)).equal(true);
+
+ const project: Project = await Project.openProject(projFilePath);
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('..\\otherFolder')));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath('..\\otherFolder'), `${project.projectFileName}.dacpac`));
+ });
+
+ it('Should use default output path if OutputPath is not specified in sqlproj', async function (): Promise {
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.openSdkStyleSqlProjectWithGlobsSpecifiedBaseline);
+ const projFileText = (await fs.readFile(projFilePath)).toString();
+
+ // Verify sqlproj doesn't have OutputPath
+ should(projFileText.includes(constants.OutputPath)).equal(true);
+
+ const project: Project = await Project.openProject(projFilePath);
+ should(project.outputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath(constants.defaultOutputPath(project.configuration.toString()))));
+ should(project.dacpacOutputPath).equal(path.join(getPlatformSafeFileEntryPath(project.projectFolderPath), getPlatformSafeFileEntryPath(constants.defaultOutputPath(project.configuration.toString())), `${project.projectFileName}.dacpac`));
+ });
+
it('Should handle adding existing items to project', async function (): Promise {
projFilePath = await testUtils.createTestSqlProjFile(baselines.openSdkStyleSqlProjectBaseline);
const projectFolder = path.dirname(projFilePath);