Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.23

Features:
- Add `singleConfig`/`multiConfig` object form for `cmake.buildDirectory` to allow separate build directory templates for single- and multi-config generators. [#2426](https://github.com/microsoft/vscode-cmake-tools/issues/2426)
- triple: Add riscv32be riscv64be support. [#4648](https://github.com/microsoft/vscode-cmake-tools/pull/4648) [@lygstate](https://github.com/lygstate)
- Add command to clear build diagnostics from the Problems pane. [#4691](https://github.com/microsoft/vscode-cmake-tools/pull/4691)
- Clear build diagnostics from the Problems pane when a new build starts and populate them incrementally during the build. [#4608](https://github.com/microsoft/vscode-cmake-tools/issues/4608)
Expand Down
20 changes: 19 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2261,7 +2261,25 @@
"scope": "resource"
},
"cmake.buildDirectory": {
"type": "string",
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"singleConfig": {
"type": "string",
"description": "%cmake-tools.configuration.cmake.buildDirectory.singleConfig.description%"
},
"multiConfig": {
"type": "string",
"description": "%cmake-tools.configuration.cmake.buildDirectory.multiConfig.description%"
}
},
"additionalProperties": false
}
],
"default": "${workspaceFolder}/build",
"description": "%cmake-tools.configuration.cmake.buildDirectory.description%",
"scope": "resource"
Expand Down
4 changes: 3 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@
]
},
"cmake-tools.configuration.cmake.cmakePath.description": "Name/path of the CMake executable to use.",
"cmake-tools.configuration.cmake.buildDirectory.description": "The directory where CMake build files will go.",
"cmake-tools.configuration.cmake.buildDirectory.description": "The directory where CMake build files will go. Can be a string (used for all generators) or an object with `singleConfig` and/or `multiConfig` properties to specify separate build directories for single-config (e.g. Make, Ninja) and multi-config (e.g. Ninja Multi-Config, Visual Studio) generators.",
"cmake-tools.configuration.cmake.buildDirectory.singleConfig.description": "Build directory template for single-config generators (e.g. Make, Ninja).",
"cmake-tools.configuration.cmake.buildDirectory.multiConfig.description": "Build directory template for multi-config generators (e.g. Ninja Multi-Config, Visual Studio, Xcode).",
"cmake-tools.configuration.cmake.installPrefix.description": "The directory where CMake installed files will go.",
"cmake-tools.configuration.cmake.sourceDirectory.description": "Path or array of paths to the CMakeLists.txt root directory/directories.",
"cmake-tools.configuration.cmake.saveBeforeBuild.description": "Save open files before building.",
Expand Down
19 changes: 16 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export function defaultNumJobs (): number {

const log = logging.createLogger('config');

export const defaultBuildDirectoryValue = '${workspaceFolder}/build';

export type LogLevelKey = 'trace' | 'debug' | 'info' | 'note' | 'warning' | 'error' | 'fatal';
export type CMakeCommunicationMode = 'legacy' | 'serverApi' | 'fileApi' | 'automatic';
export type StatusBarOptionVisibility = "visible" | "compact" | "icon" | "hidden" | "inherit";
Expand Down Expand Up @@ -169,7 +171,7 @@ export interface ExtensionConfigurationSettings {
defaultActiveFolder: string | null;
exclude: string[];
cmakePath: string;
buildDirectory: string;
buildDirectory: string | { singleConfig?: string; multiConfig?: string };
installPrefix: string | null;
sourceDirectory: string | string[];
saveBeforeBuild: boolean;
Expand Down Expand Up @@ -341,11 +343,22 @@ export class ConfigurationReader implements vscode.Disposable {
return this.configData.exclude;
}

buildDirectory(multiProject: boolean, workspaceFolder?: vscode.ConfigurationScope): string {
buildDirectory(multiProject: boolean, workspaceFolder?: vscode.ConfigurationScope, isMultiConfig?: boolean): string {
if (multiProject && this.isDefaultValue('buildDirectory', workspaceFolder)) {
return '${sourceDirectory}/build';
}
return this.configData.buildDirectory;
const raw = this.configData.buildDirectory;
if (typeof raw === 'string') {
return raw;
}
// Object form: pick the branch based on the generator type.
// The isMultiConfig flag is typically derived from isMultiConfGeneratorFast(),
// a fast/pre-configure heuristic that may not be authoritative until after the first configure run.
if (isMultiConfig) {
return raw.multiConfig ?? raw.singleConfig ?? defaultBuildDirectoryValue;
} else {
return raw.singleConfig ?? raw.multiConfig ?? defaultBuildDirectoryValue;
}
}
get installPrefix(): string | null {
return this.configData.installPrefix;
Expand Down
4 changes: 2 additions & 2 deletions src/drivers/cmakeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ export abstract class CMakeDriver implements vscode.Disposable {
await this._setKit(kit, preferredGenerators);
await this._refreshExpansions();
const scope = this.workspaceFolder ? vscode.Uri.file(this.workspaceFolder) : undefined;
const newBinaryDir = util.lightNormalizePath(await expand.expandString(this.config.buildDirectory(this.isMultiProject, scope), this.expansionOptions));
const newBinaryDir = util.lightNormalizePath(await expand.expandString(this.config.buildDirectory(this.isMultiProject, scope, this.isMultiConfFast), this.expansionOptions));
if (needsCleanIfKitChange && (newBinaryDir === oldBinaryDir)) {
await this._cleanPriorConfiguration();
}
Expand Down Expand Up @@ -855,7 +855,7 @@ export abstract class CMakeDriver implements vscode.Disposable {

if (!this.useCMakePresets) {
const scope = this.workspaceFolder ? vscode.Uri.file(this.workspaceFolder) : undefined;
this._binaryDir = util.lightNormalizePath(await expand.expandString(this.config.buildDirectory(this.isMultiProject, scope), opts));
this._binaryDir = util.lightNormalizePath(await expand.expandString(this.config.buildDirectory(this.isMultiProject, scope, this.isMultiConfFast), opts));
}
});
}
Expand Down
12 changes: 9 additions & 3 deletions src/projectController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,15 @@ export class ProjectController implements vscode.Disposable {
if (sourceDirectories.length <= 1) {
return;
}
const unresolvedBuildDirectory: string = config.buildDirectory(sourceDirectories.length > 1);

if (unresolvedBuildDirectory.includes("${sourceDirectory}") || unresolvedBuildDirectory.includes("${sourceDir}")) {
// When the object form is used, check both branches since the generator is not yet known.
const singleConfDir: string = config.buildDirectory(sourceDirectories.length > 1, undefined, false);
const multiConfDir: string = config.buildDirectory(sourceDirectories.length > 1, undefined, true);
const dirsToCheck = new Set([singleConfDir, multiConfDir]);

const allContainSourceDir = [...dirsToCheck].every(
d => d.includes("${sourceDirectory}") || d.includes("${sourceDir}")
);
if (allContainSourceDir) {
return;
} else {
const sameBinaryDir = localize('duplicate.build.directory.1', 'Multiple CMake projects in this folder are using the same CMAKE_BINARY_DIR.');
Expand Down
67 changes: 67 additions & 0 deletions test/unit-tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,71 @@ suite('Configuration', () => {
conf.updatePartial({ buildDirectory: 'Foo' });
expect(conf.parallelJobs).to.eq(5);
});

test('buildDirectory plain string form returns the string', () => {
const conf = createConfig({ buildDirectory: '/my/build/path' });
expect(conf.buildDirectory(false)).to.eq('/my/build/path');
});

test('buildDirectory object form returns singleConfig when isMultiConfig is false', () => {
const conf = createConfig({
buildDirectory: {
singleConfig: '/build/single-${buildType}',
multiConfig: '/build/multi'
}
});
expect(conf.buildDirectory(false, undefined, false)).to.eq('/build/single-${buildType}');
});

test('buildDirectory object form returns multiConfig when isMultiConfig is true', () => {
const conf = createConfig({
buildDirectory: {
singleConfig: '/build/single-${buildType}',
multiConfig: '/build/multi'
}
});
expect(conf.buildDirectory(false, undefined, true)).to.eq('/build/multi');
});

test('buildDirectory object form with only singleConfig falls back for multi-config generator', () => {
const conf = createConfig({
buildDirectory: { singleConfig: '/build/single' }
});
// No multiConfig set, should fall back to singleConfig
expect(conf.buildDirectory(false, undefined, true)).to.eq('/build/single');
});

test('buildDirectory object form with only multiConfig falls back for single-config generator', () => {
const conf = createConfig({
buildDirectory: { multiConfig: '/build/multi' }
});
// No singleConfig set, should fall back to multiConfig
expect(conf.buildDirectory(false, undefined, false)).to.eq('/build/multi');
});

test('buildDirectory object form with empty object falls back to default', () => {
const conf = createConfig({
buildDirectory: {}
});
expect(conf.buildDirectory(false, undefined, false)).to.eq('${workspaceFolder}/build');
});

test('buildDirectory object form defaults to singleConfig when isMultiConfig is undefined', () => {
const conf = createConfig({
buildDirectory: {
singleConfig: '/build/single',
multiConfig: '/build/multi'
}
});
// When isMultiConfig is not provided, defaults to false (single-config)
expect(conf.buildDirectory(false)).to.eq('/build/single');
});

test('buildDirectory plain string form works with multiProject=false', () => {
// Note: we cannot test multiProject=true with a non-default value here because
// isDefaultValue() checks the real vscode.workspace.getConfiguration (not configData),
// which always reports the default in the test host environment.
const conf = createConfig({ buildDirectory: '/custom/build' });
expect(conf.buildDirectory(false)).to.eq('/custom/build');
});
});