CMake Support in Visual Studio: Customizing your Environment

In Visual Studio 15.3, there are new ways for you to apply advanced configuration to your CMake environments.

Please download the latest and try out the latest CMake features.  If you are just getting started with CMake, follow the link to learn more about CMake support in Visual Studio.  We are looking forward to your feedback.

What’s new in CMakeSettings.json

The heart of this new flexibility is inside your project’s CMakeSettings.json file and it stems from two new concepts:

  1. The ability to inherit a set of default environment variables globally or per configuration.
  2. The ability to define custom environment variables and their values globally or per configuration.

Let’s start with a quick example of how using this feature might look:

{
  // The "environments" property is an array of key value pairs of the form
  // { "EnvVar1": "Value1", "EnvVar2": "Value2" }
  "environments": [
    {
      "BuildDir": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build",
    }
  ],

  "configurations": [
    {
      "name": "x86-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      // Inherit the defaults for using the MSVC x86 compiler.
      "inheritEnvironments": [ "msvc_x86" ],
      "buildRoot": "${env.BuildDir}\\${name}"
    },
    {
      "name": "x64-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      // Inherit the defaults for using the MSVC x64 compiler.
      "inheritEnvironments": [ "msvc_x64" ],
      "buildRoot": "${env.BuildDir}\\${name}"
    }
  ]
}

To unpack this a bit, this example defines two configurations that use Ninja to build this CMake project.  The first builds x86 debug while the other builds x64 debug.  It also defines an environment variable “BuildDir” that it makes use of in both configurations.

Keep in mind, both the “environments” (line 4) and “inheritEnvironments” (lines 16 and 24) properties can be defined globally for all configurations, per configuration, or both.  In the example above, the “BuildDir” variable will be global and the “inheritEnvironment” property will only apply to each individual configuration.

Configuration-specific environment variables are evaluated last, so they override global ones.  The example below explains the override behavior in the comments:

{
  // The "environments" property is an array of key value pairs of the form
  // { "EnvVar1": "Value1", "EnvVar2": "Value2" }
  "environments": [
    {
      "BuildDir": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}",
    }
  ],

  "configurations": [
    {
      "name": "x86-Debug",

      // The syntax for this property is the same as the global one above.
      "environments": [
        {
          // Replace the global property entirely.
          "BuildDir": "D:\\custom-builddir",
        }
      ],

      "generator": "Ninja",
      "configurationType": "Debug",
      // Inherit the defaults for using the MSVC x86 compiler.
      "inheritEnvironments": [ "msvc_x86" ],
      // This will evaluate to "D:\custom-builddir\x86-Debug" due to the
      // configuration-specific override.
      "buildRoot": "${env.BuildDir}\\${name}"
    },
    {
      "name": "x64-Debug",

      "generator": "Ninja",
      "configurationType": "Debug",
      // Inherit the defaults for using the MSVC x64 compiler.
      "inheritEnvironments": [ "msvc_x64" ],

      // Since this configuration doesn’t modify BuildDir, it inherits
      // from the one defined globally.
      "buildRoot": "${env.BuildDir}\\${name}"
    }
  ]
}

If you need to declare a lot of variables for your build environment and then make only minor modifications to them for each configuration, this override behavior can condense your project’s CMakeSettings.json file considerably.

What about Launch.vs.json and Tasks.vs.json

In case you are wondering if you can use these variables outside of the CMakeSettings.json file, the answer is yes!  All the environment variables you declare in your CMakeSettings.json file are available to Tasks and Launch configuration using the same syntax.  Just embed the “${env.VarName}” syntax into any property’s value in a task or launch configuration.  The macro syntax will be expanded into its actual value, as it is on line 12 in the example below.

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "cppdbg",
      "name": "helloworld.exe",
      "project": "bin\\helloworld.exe",
      "cwd": "${workspaceRoot}",
      "program": "${debugInfo.target}",
      "MIMode": "gdb",
      "miDebuggerPath": "${env.MINGW_PREFIX}\\bin\\gdb.exe",
      "externalConsole": true
    }
  ]
}

If the value of an environment variable is configuration-specific, the value for the currently selected configuration when you try to run a task or debug your program will be used.
The environment variables you declare will also be inherited by the processes launched by tasks. Programs being debugged, on the other hand, will not inherit the build environment.

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "default",
      "project": "ConsoleApp2\\PrintEnv.csproj",
      "name": "PrintEnv.csproj",
      "env": "var1=${env.var1}\u0000var2=hardcodedvalue"
    }
  ]
}

You can see on line 0 that it is possible to reference variables defined in your CMakeSettings.json file. The “\u0000” is a null character used to separate variables.

Advanced Features

Those of you with a keen eye might have noticed that “environments” and “inheritEnvironments” are arrays in the CMakeSettings.json syntax.  It is possible to declare and inherit from multiple environments.  For typical usage in CMake scenarios it is unlikely that you would want to inherit from more than one environment but there are some cases where you might want to declare more than one environment block.  The prime use case for this, for CMake, would be to declare a few variables that you can reference in CMakeSettings/launch.vs/tasks.vs JSON but don’t want added to the build environment itself – e.g. not inherited by spawned build processes.

The following example shows how you accomplish this:

  // The "environments" property is an array of key value pairs of the form
  // { "EnvVar1": "Value1", "EnvVar2": "Value2" }
  "environments": [
    {
      "BuildDir": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build",
    },
    {
      // “namespace” is a reserved key that lets you put variables
      // in namespaces other than $env.
      "namespace": "special",
      // SpecialVar will not be added to the environment.
      "SpecialVar": "special",
    }
  ],

  "configurations": [
    {
      "name": "x86-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      // Inherit the defaults for using the MSVC x86 compiler.
      "inheritEnvironments": [ "msvc_x86" ],
      // You can use alternative namespaces (such as special defined above)
      // just like "$env."
      "buildRoot": "${env.BuildDir}\\${special.SpecialVar}\\${name}"
    },
    {
      "name": "x64-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      // Inherit the defaults for using the MSVC x64 compiler.
      "inheritEnvironments": [ "msvc_x64" ],
      "buildRoot": "${env.BuildDir}\\${name}"
    }
  ]
}

You can access “SpecialVar” in any CMakeSettings, Launch, or Tasks JSON file with the syntax “${special.SpecialVar}”, as seen on line 25.

Send Us Feedback

To try out the latest and greatest CMake features and give us some early feedback, please download and install the latest Visual Studio 2017 Preview.  As always, we welcome your feedback.  Feel free to send any comments through e-mail at cmake@microsoft.com, through Twitter @visualc, or Facebook at Microsoft Visual Cpp.

If you encounter other problems with Visual Studio 2017 please let us know via Report a Problem, which is available in both the installer and the IDE itself.  For suggestions, let us know through UserVoice. We look forward to your feedback!