Skip to content
Open
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
20 changes: 20 additions & 0 deletions docs/cmake-presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,26 @@ You can specify the name of a compiler on your `PATH` instance or an environment

When you build with the Visual C++ toolset, CMake Tools automatically sources the environment from the latest version of the Visual Studio Build Tools installed on your system. You can specify a compiler version with the `toolset` option in `CMakePresets.json`. For more information, see [Configure Presets and Toolset Selection](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html).

CMake Tools can detect the need for the VS Developer Environment from several signals in your preset. You do **not** need to explicitly set `CMAKE_CXX_COMPILER` to `cl` if any of the following are true:

- Your preset uses a **Visual Studio generator** (e.g. `"generator": "Visual Studio 17 2022"`)
- Your preset specifies `"toolset": { "strategy": "external" }` (which means the IDE should set up the environment)
- Your preset specifies `"architecture": { "strategy": "external" }`

For example, a Ninja preset that relies on MSVC can use `strategy: "external"` without setting compilers explicitly:

```json
"generator": "Ninja",
"architecture": {
"value": "x64",
"strategy": "external"
},
"toolset": {
"value": "v143",
"strategy": "external"
}
```

A preset that builds for 64-bit Windows with `cl.exe` and a Visual Studio Generator might set compilers like this:

```json
Expand Down
46 changes: 38 additions & 8 deletions src/presets/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,25 @@ async function getVsDevEnv(opts: VsDevEnvOptions): Promise<EnvironmentWithNull |
}
}

/**
* Checks whether a configure preset has structural signals indicating that the VS Developer Environment
* should be activated, even without an explicit CMAKE_CXX_COMPILER or CMAKE_C_COMPILER setting.
*
* This detects:
* 1. A Visual Studio generator (e.g. "Visual Studio 17 2022")
* 2. A toolset with strategy "external" (meaning the IDE should set up the environment)
* 3. An architecture with strategy "external"
*
* @param preset The configure preset to inspect.
* @returns true if the preset has MSVC-intent signals, false otherwise.
*/
export function hasVsDevEnvSignals(preset: ConfigurePreset): boolean {
const hasVsGenerator = /Visual Studio \d+/.test(preset.generator ?? '');
const toolsetStrategy = typeof preset.toolset === 'object' ? (preset.toolset as ValueStrategy).strategy : undefined;
const archStrategy = typeof preset.architecture === 'object' ? (preset.architecture as ValueStrategy).strategy : undefined;
return hasVsGenerator || toolsetStrategy === 'external' || archStrategy === 'external';
}

/**
* This method tries to apply, based on the useVsDeveloperEnvironment setting value and, in "auto" mode, whether certain preset compilers/generators are used and not found, the VS Dev Env.
* @param preset Preset to modify the parentEnvironment of. If the developer environment should be applied, the preset.environment is modified by reference.
Expand All @@ -979,12 +998,17 @@ export async function tryApplyVsDevEnv(preset: ConfigurePreset, workspaceFolder:
// [Windows Only] We only support VS Dev Env on Windows.
if (!preset.__parentEnvironment && process.platform === "win32") {
if (useVsDeveloperEnvironmentMode === "auto") {
if (preset.cacheVariables) {
const cxxCompiler = getStringValueFromCacheVar(preset.cacheVariables['CMAKE_CXX_COMPILER'])?.toLowerCase();
const cCompiler = getStringValueFromCacheVar(preset.cacheVariables['CMAKE_C_COMPILER'])?.toLowerCase();
// The env variables for the supported compilers are the same.
const compilerName: string | undefined = util.isSupportedCompiler(cxxCompiler) || util.isSupportedCompiler(cCompiler);
const cxxCompiler = preset.cacheVariables ? getStringValueFromCacheVar(preset.cacheVariables['CMAKE_CXX_COMPILER'])?.toLowerCase() : undefined;
const cCompiler = preset.cacheVariables ? getStringValueFromCacheVar(preset.cacheVariables['CMAKE_C_COMPILER'])?.toLowerCase() : undefined;
// The env variables for the supported compilers are the same.
const compilerName: string | undefined = util.isSupportedCompiler(cxxCompiler) || util.isSupportedCompiler(cCompiler);

// Also check for structural MSVC signals in the preset (VS generator, external strategy)
const infersMsvc = !compilerName && hasVsDevEnvSignals(preset);
// If MSVC is inferred from preset signals, treat 'cl' as the effective compiler name for env setup
const effectiveCompilerName = compilerName ?? (infersMsvc ? 'cl' : undefined);

if (effectiveCompilerName) {
// find where.exe using process.env since we're on windows.
let whereExecutable;
// assume in this call that it exists
Expand All @@ -1004,7 +1028,7 @@ export async function tryApplyVsDevEnv(preset: ConfigurePreset, workspaceFolder:
}
}

if (compilerName && whereExecutable) {
if (whereExecutable) {
// We need to construct and temporarily expand the environment in order to accurately determine if this preset has the compiler / ninja on PATH.
// This puts the preset.environment on top of process.env, then expands with process.env as the penv and preset.environment as the envOverride
const env = EnvironmentUtils.mergePreserveNull([process.env, preset.environment]);
Expand All @@ -1019,7 +1043,7 @@ export async function tryApplyVsDevEnv(preset: ConfigurePreset, workspaceFolder:
}
}

const compilerLocation = await execute(whereExecutable, [compilerName], null, {
const compilerLocation = await execute(whereExecutable, [effectiveCompilerName], null, {
environment: EnvironmentUtils.create(presetEnv),
silent: true,
encoding: 'utf8',
Expand All @@ -1041,10 +1065,16 @@ export async function tryApplyVsDevEnv(preset: ConfigurePreset, workspaceFolder:
developerEnvironment = await getVsDevEnv({
preset,
shouldInterrogateForNinja,
compilerName
compilerName: effectiveCompilerName
});
}
}
} else {
log.info(localize(
'vs.dev.env.skipped.hint',
'Configure preset "{0}": VS Developer Environment was not set up automatically. To enable it, set CMAKE_CXX_COMPILER to "cl" in cacheVariables, use a Visual Studio generator, set toolset or architecture strategy to "external", or set cmake.useVsDeveloperEnvironment to "always".',
preset.name
));
}
} else if (useVsDeveloperEnvironmentMode === "always") {
developerEnvironment = await getVsDevEnv({
Expand Down
43 changes: 42 additions & 1 deletion test/unit-tests/presets/presets.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildArgs, Condition, configureArgs, evaluateCondition, getArchitecture, getToolset } from '@cmt/presets/preset';
import { buildArgs, Condition, configureArgs, evaluateCondition, getArchitecture, getToolset, hasVsDevEnvSignals } from '@cmt/presets/preset';
import { expect } from '@test/util';
import * as os from "os";

Expand Down Expand Up @@ -274,4 +274,45 @@ suite('Preset tests', () => {
expect(args).to.include('-j');
expect(args).to.not.include('--parallel');
});

test('hasVsDevEnvSignals detects Visual Studio generator', () => {
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Visual Studio 17 2022' })).to.eq(true);
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Visual Studio 16 2019' })).to.eq(true);
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Visual Studio 15 2017' })).to.eq(true);
});

test('hasVsDevEnvSignals detects external toolset strategy', () => {
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja', toolset: { value: 'v143', strategy: 'external' } })).to.eq(true);
});

test('hasVsDevEnvSignals detects external architecture strategy', () => {
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja', architecture: { value: 'x64', strategy: 'external' } })).to.eq(true);
});

test('hasVsDevEnvSignals returns false for Ninja with no MSVC signals', () => {
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja' })).to.eq(false);
});

test('hasVsDevEnvSignals returns false for no generator and no strategy', () => {
expect(hasVsDevEnvSignals({ name: 'test' })).to.eq(false);
});

test('hasVsDevEnvSignals returns false for set strategy (non-external)', () => {
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja', toolset: { value: 'v143', strategy: 'set' } })).to.eq(false);
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja', architecture: { value: 'x64', strategy: 'set' } })).to.eq(false);
});

test('hasVsDevEnvSignals returns false for string toolset and architecture', () => {
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja', toolset: 'v143' })).to.eq(false);
expect(hasVsDevEnvSignals({ name: 'test', generator: 'Ninja', architecture: 'x64' })).to.eq(false);
});

test('hasVsDevEnvSignals detects combined external toolset and architecture', () => {
expect(hasVsDevEnvSignals({
name: 'test',
generator: 'Ninja',
toolset: { value: 'v143', strategy: 'external' },
architecture: { value: 'x64', strategy: 'external' }
})).to.eq(true);
});
});