Ever since we shipped support for opening a folder of C++ code, the community has been asking for more control over their build and editing environments. To achieve this, we have added new ways to customize your environment with CppProperties.json in the latest version of Visual Studio 2017.
This new customization surface enables you to use a broader variety of tools, write more succinct CppProperties files, and have powerful per-configuration customization similar to MSBuild. The topics below expand on several concepts described in the original C++ Open Folder post. If you are not familiar with editing CppProperties.json, Launch.vs.json, and Tasks.vs.json it might be worth reading that post first.
This post is a companion to our previous post on customizing your environment for CMake projects so if you have already read it, you may find that some of this content will be similar since we strive to keep the experiences consistent. The most important differences are under “What about Launch.vs.json and Tasks.vs.json” regarding how to use configuration-specific variables.
What’s new in CppProperties.json
The heart of this new flexibility is inside your project’s CppProperties.json file and it stems from two new concepts:
- The ability to inherit a set of default environment variables globally or per configuration using the “inheritEnvironments” property.
- The ability to define custom environment variables and their values globally or per configuration by defining an “environments” block.
Combining these new concepts with the existing ability to consume environment variables in CppProperties.json, launch.vs.json, and tasks.vs.json using the “${env.VAR}” syntax, provides a powerful mechanism for creating rich development environments.
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": [ { "INCLUDE": "${workspaceRoot}\\src\\includes" } ], "configurations": [ { "inheritEnvironments": [ // Inherit the MSVC 32-bit environment and toolchain. "msvc_x86" ], "name": "x86", "includePath": [ // Use the include path defined above. "${env.INCLUDE}" ], "defines": [ "WIN32", "_DEBUG", "UNICODE", "_UNICODE" ], "intelliSenseMode": "msvc-x86" }, { "inheritEnvironments": [ // Inherit the MSVC 64-bit environment and toolchain. "msvc_x64" ], "name": "x64", "includePath": [ // Use the include path defined above. "${env.INCLUDE}" ], "defines": [ "WIN32", "_DEBUG", "UNICODE", "_UNICODE" ], "intelliSenseMode": "msvc-x64" } ] }
To unpack this a bit, this example defines two configurations that build with Microsoft’s Visual C++ toolchain. The first builds for x86 (since it inherits the “msvc_x86” environment) while the other builds for x64. It also defines an environment variable “INCLUDE” (line 6) that is used by both configurations.
Keep in mind, both the “environments” (line 4) and “inheritEnvironments” (lines 12 and 25) properties can be defined globally for all configurations, per configuration, or both. In the example above, the “INCLUDE” variable will be global and the “inheritEnvironment” property will only apply to each individual configuration.
The following environments are available today:
- Target x86 Windows with MSVC (msvc_x86)
- Target x64 Windows with MSVC (msvc_x64)
- Target x86 Windows with the 64-bit MSVC (msvc_x64_x86)
- Target x64 Windows with the 64-bit MSVC (msvc_x64_x64)
Additionally, if you have the Linux Workload installed, the following environments are available for remotely targeting Linux and WSL:
- Target x86 Linux remotely (linux_x86)
- Target x64 Linux remotely (linux_x64)
- Target ARM Linux remotely (linux_arm)
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": [ { "INCLUDE": "${workspaceRoot}\\src\\includes" } ], "configurations": [ { "inheritEnvironments": [ // Inherit the MSVC 32-bit environment and toolchain. "msvc_x86" ], "name": "x86", "includePath": [ // Use the include path defined above. "${env.INCLUDE}" ], "defines": [ "WIN32", "_DEBUG", "UNICODE", "_UNICODE" ], "intelliSenseMode": "msvc-x86" }, { // The "environments" property is an array of key value pairs of the form // { "EnvVar1": "Value1", "EnvVar2": "Value2" } "environments": [ { // Append 64-bit specific include path to env.INCLUDE. "INCLUDE": "${env.INCLUDE};${workspaceRoot}\\src\\includes64" } ], "inheritEnvironments": [ // Inherit the MSVC 64-bit environment and toolchain. "msvc_x64" ], "name": "x64", "includePath": [ // Use the include path defined above. "${env.INCLUDE}" ], "defines": [ "WIN32", "_DEBUG", "UNICODE", "_UNICODE" ], "intelliSenseMode": "msvc-x64" } ] }
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 CppProperties.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 CppProperties.json file, the answer is yes! All the environment variables you declare in your CppProperties.json can be consumed in launch.vs.json and tasks.vs.json as well. Just embed the same “${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 16.
{ "version": "0.2.1", "tasks": [ { "taskName": "build-helloworld", "appliesTo": "*.cpp", "contextType": "build", "type": "launch", "command": "${env.comspec}", "workingDirectory": "${workspaceRoot}", // Use environment from selected configuration, you can omit this // to only use globally defined variables instead. "inheritEnvironments": [ "${cpp.activeConfiguration}" ], "output": "${workspaceRoot}\\bin\\helloworld.exe", "args": [ "build.bat ${env.BUILD_ARGS}" ] } ] }
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 if you include this in your task or launch configuration:
"inheritEnvironments": [ "${cpp.activeConfiguration}" ]
If you do not include this, only globally defined variables will be available.
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 automatically. The example below shows how to explicitly pass environment variables to a launched process.
{ "version": "0.2.1", "defaults": {}, "configurations": [ { "type": "native", "name": "helloworld.exe", // Use environment from selected configuration, you can omit this // to only use globally defined variables instead. "inheritEnvironments": [ "${cpp.activeConfiguration}" ], "project": "bin\\helloworld.exe", "args": [ // Use arguments defined in CppProperties.json. "${env.PROG_ARGS}" ] , "env": "var1=${env.var1}\u0000var2=hardcodedvalue" } ] }
You can see on line 14 that it is possible to reference variables defined in your CppProperties.json file. The “\u0000” on line 17 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 CppProperties.json syntax. It is possible to declare and inherit from multiple environments. For typical build 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 would be to declare a few variables that you can reference in any CppProperties, Launch, or Tasks 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 create a custom namespace:
{ // The "environments" property is an array of key value pairs of the form // { "EnvVar1": "Value1", "EnvVar2": "Value2" } "environments": [ { "INCLUDE": "${workspaceRoot}\\src\\includes" }, { // "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": [ { "inheritEnvironments": [ // Inherit the MSVC 32-bit environment and toolchain. "msvc_x86" ], "name": "x86", "includePath": [ // Use the include path defined above. "${env.INCLUDE}" ], "defines": [ // You can use alternative namespaces (such as special defined above) // just like "${env.VAR}" "${special.specialVar}", "WIN32", "_DEBUG", "UNICODE", "_UNICODE" ], "intelliSenseMode": "msvc-x86" } ] }
You can access “SpecialVar” in any CppProperties, Launch, or Tasks JSON file with the syntax “${special.SpecialVar}”, as seen on line 32.
Send Us Feedback
To try out the latest and greatest C++ 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 visualcpp@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.
0 comments