I need to conditionally compile C++ source files based on either environment variables or other input parameters. Is there a mechanism in VS2019 to do this?
This solution works with VS2017 but I don't know of any reason why it wouldn't also work with VS2019.
You can "import" environment variables as preprocessor definitions. In your Visual Studio project's properties go to Configuration Properties -> C/C++ -> Preprocessor. Click in the Preprocessor Definitions field, hit the down arrow at the far right and select Edit.
Here, you can add preprocessor definitions that include environment variables. Each line represents a definition with the notation [name]=[value] which defines a preprocessor definition named [name] which will be substituted by [value]. Environment variables should be wrapped in a $() to be resolved. So, for example, to import the environment variable MY_ENV_VAR you would add the definition MY_ENV_VAR=$(MY_ENV_VAR). If MY_ENV_VAR had say 5 at the time of compilation, this definition would be equivalent to having a #define MY_ENV_VAR 5 available across the project.
In your source file, you can then wrap your code with a #if/#endif guard to conditionally compile that code. Using the same example, to only compile a source file if MY_ENV_VAR is exactly 1, you would write :
#if MY_ENV_VAR == 1
// Entire source file
#endif // #if MY_ENV_VAR == 1
Note that environment variables are loaded when Visual Studio launches. You may need to restart Visual Studio if you want recent changes to the environment variables to be visible.
This can actually be accomplished directly by editing the project's .vcxproj file by adding a "Condition" attribute to the CLCompile element for the file in question.
Note that if you do add a Condition attribute then change the properties specifically for that file VS may remove the attribute (I am not sure whether VS does so or not, but it is something to keep in mind).
Related
In Visual Studio 2019 I have two C++ projects contained in the same Solution: ProjectA and ProjectB.
How to define a C/C++ Preprocessor Definition in ProjectB ( let's call it: THE_OTHER_FILE_NAME ), which is equal to the string returned by the VS macro $(TargetFileName) in the ProjectA ?
NOTE: Hardcoding paths in the C/C++ source files is outside the scope of this question.
To access another project's VS macro $(TargetFileName) create the following XML file:
<Project>
<PropertyGroup>
<TargetFileName_Global>__UNDEFINED__</TargetFileName_Global>
<TargetFileName_Global Condition="Exists('$(SolutionDir)TargetFileName.vsmacro')">"$([System.IO.File]::ReadAllText('$(SolutionDir)TargetFileName.vsmacro').trim())"</TargetFileName_Global>
</PropertyGroup>
</Project>
...and name it Directory.Build.props and place it into your Solution Directory (where your *.sln file resides).
Next, in Solution Explorer right-click on the ProjectA's Properties --> Configuration Properties --> Build Events --> Pre-Build Event --> Command Line and set it to:
set /P "_VSdmy=$(TargetFileName.Replace('"','').trim())" <nul> "$(SolutionDir)TargetFileName.vsmacro" ||(call,)
Next, in Solution Explorer right-click on the ProjectB's Properties --> Configuration Properties --> C/C++ --> Preprocessor --> Preprocessor Definitions and add the following code there:
THE_OTHER_FILE_NAME=$(TargetFileName_Global);
Restart Visual Studio.
Now, you can use the THE_OTHER_FILE_NAME preprocessor macro in in ProjectB's *.h, *.c and *.cpp source files at will. This macro will be defined as a string of the ProjectA's target file name.
Note, that the THE_OTHER_FILE_NAME preprocessor macro in ProjectB will have meaningful contents only after you build the ProjectA. Before that happens, this macro will be defined as __UNDEFINED__. After building ProjectA you may have to refresh the Intellisense and the syntax highlighting of this macro in your source files by performing Rescan --> Rescan Solution from the context menu. Forgetting to rescan the solution this way, affects only the Intellisense display - it does not affect the building of any of the projects.
It is possible to do this with any other macro in Visual Studio and with multiple macros in multiple projects as well.
However, this method of passing variables between projects will not work with MsBuild versions earlier than v15.0 (included in Visual Studio starting from version v2017), because older versions ignore the Directory.Build.props file. See the "Updates" section, in this article.
This method will also not work when the MsBuild property ImportDirectoryBuildTargets is set to false.
P.S.
It is also possible to instantly pass a value of an MSBuild variable between projects using the Windows Registry (even through volatile registry keys), however it is not possible to pass it so through an environment variable set by the set command, because environment variables set in this manner do not survive the termination of the process that had set them. ...and each project is built by a separate MsBuild process. The <SetEnv... task has the ability to permanently set an environment variable in the User Profile or the Local Machine through the <SetEnv Target=... attribute, and environment variables set in this manner survive even the reboot, but they are not visible to existing processes (i.e. already running VS) - only to newly spawned processes. Environment variables set by the <SetEnv... task without the Target=... attribute, do not survive the termination of the process that had set them, just like the set command.
You can include file witch you need example
my second project name is p2
path in same folder repo
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>
#include "../p2/Header.h"
int main(){
print();
system("PAUSE");
}
header file code
#pragma once
#include <stdio.h>
void print() {
printf("Hello\n");
}
And you can cheek this question
I have a large solution (100 projects) and a lot of them rely on a preprocessor definition (NEWGUI) in a header file (shared.h) to switch between two valid states of code (old gui and new gui).
I'm trying to add a compile guard around this macro
#ifndef OLDGUI
#define NEWGUI
#endif
so that we don't have to comment out or delete that line in shared.h to build the old gui, which slows down incremental builds. Then, I'm hoping to find a way to set OLDGUI outside cf the configuration. With CMake we could do this with cmake .. -DOLD_GUI
I'm doing the building in an MSBuild task in azure pipelines.
My first attempt to get OLDGUI set on the solution was use the -p flag to set the preprocessor definition, but I realized that that overrides all existing preprocessor definitions.
My second attempt was to create a new configuration that just overrode the project that has the shared.h and add a preprocessor definition there, but that doesn't affect all the other projects that depend on shared.h, and the build ends up producing a chimera of old gui and new gui.
I'm not at all sure what the proper way to do this is, and I'm hoping I don't have to manually add configurations to 100 projects to add OLDGUI to each one individually. With MSBuild this looks increasingly cumbersome. So my question is: how can I do this? Initial thoughts:
Is there some way to use something like -p:PreprocessorDefinitions=OLDGUI to add to existing preprocessor definitions at compile time?
Is there some way to add a global property page to all the projects just under a single configuration so that I can define OLDGUI there?
Is there some way to add a global property page to all the projects at compile time to do the same thing as (2)?
The solution was found in this answer from 2013. It's a hack, but it'll do.
https://stackoverflow.com/a/14206134/9691276
The approach I took was this:
Add $(ExternalPreprocessorDefinitions) to C++>Preprocessor in a global property page inherited by every project (this is pre-existing).
In the build pipeline, just set the environment variable ExternalPreprocessorDefinitions=OLDGUI.
Voila.
This question already has an answer here:
Hide a C++ code block from Intellisense
(1 answer)
Closed 3 years ago.
Is it possible to have a macro be set to true when building but otherwise is set to false? I realize there are macros to detect build configurations (e.g. debug/release) but I am looking for a macro that is set when the compiler starts building. It's okay if the macro is very specific to a particular compiler.
The use case is to help intellisense with some complex macros by simplifying them. The autocomplete does not work with the complex macros. However the simplified macros are very slow at runtime.
This one would probably work:
#define ONLY_TRUE_AT_COMPILE_TIME true
You can set a macro in your IDE and don't use it in the build process
#ifdef IDE_MACRO
#define SIMPLE_MACROS
#else
#define COMPLEX_MACROS
#endif
In Visual Studio Code you can set
"configurations": [
{
...
"defines":[
"IDE_MACRO"
]
}
]
in
.vscode/c_cpp_properties.json
defines A list of preprocessor definitions for the IntelliSense engine
to use while parsing files. Optionally, use = to set a value, for
example VERSION=1.
This only works if compileCommands is not set
compileCommands (optional) The full path to the compile_commands.json
file for the workspace. The include paths and defines discovered in
this file will be used instead of the values set for includePath and
defines settings. If the compile commands database does not contain an
entry for the translation unit that corresponds to the file you opened
in the editor, then a warning message will appear and the extension
will use the includePath and defines settings instead.
As you can read in the quote you can also use compile_commands.json to set macros and IntelliSense will consider them.
In Eclipse CDT it's in C/C++ Build -> Build Variables. There you can set a Variable IDE_MACRO. Now Eclipse CDT uses SIMPLE_MACROS instead of COMPLEX_MACROS. In the build process the macro IDE_MACRO is not defined and the compiler uses COMPLEX_MACROS.
In my current project I have multiple ATL projects that depend on each other. One of them is called "Common" and defines a trace category, other projects might use to print out trace information.
I defined the category from the IDL file like so:
cpp_quote("static ATL::CTraceCategory DATA_LAYER(_T(\"Data Layer\"), 1);")
Basically this translates to the following definition inside the common header file, other projects include to get informed about the interfaces of the "Common" project.
static ATL::CTraceCategory DATA_LAYER(_T("Data Layer"), 1);
Now since Visual Studio 2013 there appears to be a change in how tracing works.
This does cause source-breaking changes in some uses of the ATL::CTraceCategory class, which will require changes in source code when migrating to Visual Studio 2013.
And indeed, I had to change the line above by removing the second parameter:
cpp_quote("static ATL::CTraceCategory DATA_LAYER(_T(\"Data Layer\"));")
Now everything builds again, but the problem raises as soon as I try to rebuild any project that uses the trace category. After the build successfully finished, the compiler automatically registers the component. And during regsvr32 /s "C:\...\Common.dll" I always receive an debug assertation like this:
Microsoft Visual C++ Runtime Library
Debug Assertion Failed!
Program: ...\x64\Debug\Common.dll
File: c:\program files (x86)\microsoft visual studio 14.0\vc\atlmfc\include\atltrace.h
Line: 337
Expression: false && "Too many categories defined"
This also happens when I try to register the component manually. Only projects that do not depend on the common project and therefor not use any trace category are registered successfully.
Does anybody have a solution for this? I would also accept a solution that shows another way of tracing in ATL, since there does not appear to be any difference to using DebugOutputString instead (If I understood the linked blog correctly).
Okay, I finally figured this out. The problem was related to the declaration of the trace category as static. I have no idea why this built in previous versions of Visual Studio. Anyway, here's the fix: First of all, I changed the definition of the trace category inside my Common.idl file:
cpp_quote("#ifdef DEFINE_EXPORTS")
cpp_quote("__declspec(dllexport) extern ATL::CTraceCategory DATA_LAYER;")
cpp_quote("#else // DEFINE_EXPORTS")
cpp_quote("__declspec(dllimport) ATL::CTraceCategory DATA_LAYER;")
cpp_quote("#endif // DEFINE_EXPORTS")
As you can see, the trace category now exports to the library, if DEFINE_EXPORTS is defined, which is true for the common project. All projects referencing this library are importing the definition (by including Common.h, which get's created from the idl file). If you define the trace category as static, each library defines a category on it's own. I think that was the cause for the error I was facing.
Now inside the dllmain.cpp file of the common project I define the trace categories:
#if (_MSC_VER >= 1800)
ATL::CTraceCategory DATA_LAYER(_T("Data Layer"));
#else
ATL::CTraceCategory DATA_LAYER(_T("Data Layer"), 1);
#endif
Note that the code switches between two constructors based on the VC++ version it is compiled with. It should be possible to get rid of this by using the CTraceCategoryEx template, but I stick to this method for now.
Finally all I had to do was adding the Common.lib reference to the Additional Dependencies of the projects referencing it.
Now I am building a .dll project with VC 10. Depending on whether certain libraries are included or not, the output .dll as well as its corresponding lib file names should be different. For example, if the project uses the native STL library provided by Mirosoft, the .dll name is *_native_stl.dll; if the project uses STLport, then the output .dll name is *_stlport.dll. Of course I can do it manually, but I am now considering to let VC 10 do it for me automatically. My plan is that if I use a certain library, I define a variable to represent it in the Preprocessor Definitions (located in C/C++ Preprocessor)section. After that, I define a .bat file in the Post-Build Event (located in Build Events) section: call "$(ProjectDir)\script.bat". In the script.bat file, I will changed the default .dll output name $(OutDir)$(TargetName)$(TargetExt) according to the variable definition in the Preprocessor Definitions section. My main problem is that for in batch file how I can check a certain variable is defined or not. Thanks! Any suggestions on changing output file name are welcome as well.
My main problem is that for in batch file how I can check a certain variable is defined or not. Thanks!
You can check this with a statement like this:
if "%MYVAR%" == "" set undefined=1
The variable undefined will be set to 1 only if MYVAR is not defined.