Can you use environment variables in C/C++ include directives?

时间:2023-01-13 10:03:24

Say I have a folder layout as such:

假设我有一个文件夹布局:

.
+-- Project
    +-- src
        +-- foo.h
        +-- foo.cpp
    +-- test
        +-- test_foo.c

And test_foo.c looks as such:

而test_foo.c看起来像这样:

#include "../src/foo.h"
#include <stdio.h>
#include <assert.h>

int main() {
    assert(foo() == true);
    printf("Test complete");
    return 0;
}

Is there a way to replace the line #include "../src/foo.h" with a variable that points to the source directory? For instance, say in my environment I have a have a variable:

有没有办法用一个指向源目录的变量替换行#include“../src/foo.h”?例如,在我的环境中说我有一个变量:

PROJECT_SRC="./Project/src/"

And then I could have the include directive as such:

然后我可以使用include指令:

#include "PROJECT_SRC/foo.h"

This would be nice as I could then have a bash script that exports all the paths I need for a certain project. Also, if the file is included in different test and build files I would have to set up the relative path for each (although not much work) which would be less robust than one absolute path.

这将是很好的,因为我可以有一个bash脚本导出我需要的某个项目的所有路径。此外,如果文件包含在不同的测试和构建文件中,我将不得不为每个文件设置相对路径(尽管工作量不大),这个路径不如一个绝对路径强大。

An alternative might be a tool like CMake that can do this. Or is this considered bad practice?

另一种选择可能是像CMake这样的工具。或者这被认为是不好的做法?

1 个解决方案

#1


6  

Weeelll...it is possible, sort of, but it's not pretty, and it has some pitfalls. It is usually better to add the include path in the build system, such as (assuming plain make):

Weeelll ......它有可能,但它并不漂亮,而且有一些陷阱。通常最好在构建系统中添加include路径,例如(假设普通make):

# C PreProcessor flags. This variable is used by make's implicit rules for 
# everything preprocessor-related.
CPPFLAGS += -I$(PROJECT_PATH)

and #include the headers without the path in the source file. This will make make call the compiler with -Iyour/project/path, which will make the compiler look for headers in your/project/path. That is to say, in the Makefile you can have

和#include包含源文件中的路径的标头。这将使make使用-Iyour / project / path调用编译器,这将使编译器在/ project / path中查找头文件。也就是说,在Makefile中你可以拥有

PROJECT_PATH = foo/bar
CPPFLAGS = -I$(PROJECT_PATH)

and in the sources

在消息来源中

#include "foobar.h"

to have the effect of #include "foo/bar/foobar.h".

具有#include“foo / bar / foobar.h”的效果。

...also, did I see you try to #include source files instead of headers? Do not go down that road; down that road madness lies. Compile the source files separately and link them together the usual way unless you have a really good reason to do otherwise.

...另外,我是否看到你尝试#include源文件而不是标题?不要走那条路;那道疯狂的谎言。单独编译源文件并以常规方式将它们链接在一起,除非您有充分的理由不这样做。

So, I don 't see a reason why you would want to reference the project path directly in #include directives in the code; the only change on the build system side is only that you have to pass -DPROJECT_PATH=foo/bar/ instead of -IPROJECT_PATH=foo/bar/ and that the construct is more brittle than the mechanisms that are actually designed for this sort of stuff. But if you really want to do it, then here is how:

所以,我没有看到你想要在代码中的#include指令中直接引用项目路径的原因;构建系统方面唯一的变化只是你必须传递-DPROJECT_PATH = foo / bar /而不是-IPROJECT_PATH = foo / bar /并且构造比实际为这类东西设计的机制更脆弱。但如果你真的想这样做,那么这里是如何:

The first problem you run into is that

你遇到的第一个问题是

#include "foo/bar/" "baz.h" // no dice.

is ill-formed, so the easy way is out. We have to try preprocessor magic, and it works like this:

形象不对,所以简单的方法就出来了。我们必须尝试预处理器魔术,它的工作原理如下:

#define HEADER_STRING(s) #s
#define HEADER_I(path, name) HEADER_STRING(path ## name)
#define HEADER(path, name) HEADER_I(path, name)

//                                v-- important: no spaces allowed here!
#include HEADER(PROJECT_PATH,foobar.h)

Perhaps start from the bottom up:

也许从下往上:

#define HEADER_STRING(s) #s

makes a string from its argument. That is to say, HEADER_STRING(foo/bar/baz.h) expands to "foo/bar/baz.h". Notably, macro parameters are not expanded, so HEADER_STRING(PROJECT_PATH) will expand to "PROJECT_PATH" even if a macro PROJECT_PATH is defined. This is one of the most common problems you run into when you try to do anything complicated with the preprocessor, and the solution is to add another layer in which the parameters can be expanded:

从其参数中生成一个字符串。也就是说,HEADER_STRING(foo / bar / baz.h)扩展为“foo / bar / baz.h”。值得注意的是,宏参数未扩展,因此即使定义了宏PROJECT_PATH,HEADER_STRING(PROJECT_PATH)也将扩展为“PROJECT_PATH”。当您尝试使用预处理器执行任何复杂操作时,这是您遇到的最常见问题之一,解决方案是添加另一个可以扩展参数的层:

#define HEADER_STRING_I(s) #s
#define HEADER_STRING(s) HEADER_STRING_I(s)

...we do not need this for HEADER_STRING, but it is used in HEADER, so keep the trick in mind. I'm afraid the precise preprocessor substitution rules are somewhat arcane, and explaining them in detail goes beyond the scope of a SO answer. In a nutshell, macros are expanded in layers, and when macros don't get expanded, the trick is usually to give them a place to expand, i.e., to add another layer.

...我们不需要这个用于HEADER_STRING,但它在HEADER中使用,所以记住这个诀窍。我担心精确的预处理器替换规则有点晦涩难懂,详细解释它们超出了SO答案的范围。简而言之,宏是在层中扩展的,当宏不扩展时,诀窍通常是为它们提供扩展的位置,即添加另一层。

HEADER_I then,

#define HEADER_I(path, name) HEADER_STRING(path ## name)

tapes its arguments together and passes them to HEADER_STRING. HEADER_I(foo,bar) expands to HEADER_STRING(foobar). Because of the problem I mentioned above, HEADER_I(PROJECT_PATH,foobar.h) expands to HEADER_STRING(PROJECT_PATHfoobar.h), which in turn expands to "PROJECT_PATHfoobar.h", so we need another layer for the expansion of PROJECT_PATH:

将其参数绑定在一起并将它们传递给HEADER_STRING。 HEADER_I(foo,bar)扩展为HEADER_STRING(foobar)。由于我上面提到的问题,HEADER_I(PROJECT_PATH,foobar.h)扩展为HEADER_STRING(PROJECT_PATHfoobar.h),后者又扩展为“PROJECT_PATHfoobar.h”,因此我们需要另一层来扩展PROJECT_PATH:

#define HEADER(path, name) HEADER_I(path, name)

This just adds the place for the path and name parameters to be expanded. Finally, with PROJECT_PATH #defined to foo/bar/, HEADER(PROJECT_PATH,foobar.h) expands to "foo/bar/foobar.h", and then we can say

这只是添加要扩展的路径和名称参数的位置。最后,使用PROJECT_PATH #defined到foo / bar /,HEADER(PROJECT_PATH,foobar.h)扩展为“foo / bar / foobar.h”,然后我们可以说

#include HEADER(PROJECT_PATH,foobar.h)

to #include "foo/bar/foobar.h". PROJECT_PATH can then be set in the makefile and passed with -DPROJECT_PATH=$(some_make_variable).

#include“foo / bar / foobar.h”。然后可以在makefile中设置PROJECT_PATH并使用-DPROJECT_PATH = $(some_make_variable)传递。

The last pitfall is that you must take care to not let any spaces slip between the tokens.

最后一个陷阱是你必须注意不要让任何空间在令牌之间滑动。

#include HEADER(PROJECT_PATH,foobar.h)

ultimately expands to "foo/bar/ foobar.h" (note the space), which does not work.

最终扩展到“foo / bar / foobar.h”(注意空格),这是行不通的。

#1


6  

Weeelll...it is possible, sort of, but it's not pretty, and it has some pitfalls. It is usually better to add the include path in the build system, such as (assuming plain make):

Weeelll ......它有可能,但它并不漂亮,而且有一些陷阱。通常最好在构建系统中添加include路径,例如(假设普通make):

# C PreProcessor flags. This variable is used by make's implicit rules for 
# everything preprocessor-related.
CPPFLAGS += -I$(PROJECT_PATH)

and #include the headers without the path in the source file. This will make make call the compiler with -Iyour/project/path, which will make the compiler look for headers in your/project/path. That is to say, in the Makefile you can have

和#include包含源文件中的路径的标头。这将使make使用-Iyour / project / path调用编译器,这将使编译器在/ project / path中查找头文件。也就是说,在Makefile中你可以拥有

PROJECT_PATH = foo/bar
CPPFLAGS = -I$(PROJECT_PATH)

and in the sources

在消息来源中

#include "foobar.h"

to have the effect of #include "foo/bar/foobar.h".

具有#include“foo / bar / foobar.h”的效果。

...also, did I see you try to #include source files instead of headers? Do not go down that road; down that road madness lies. Compile the source files separately and link them together the usual way unless you have a really good reason to do otherwise.

...另外,我是否看到你尝试#include源文件而不是标题?不要走那条路;那道疯狂的谎言。单独编译源文件并以常规方式将它们链接在一起,除非您有充分的理由不这样做。

So, I don 't see a reason why you would want to reference the project path directly in #include directives in the code; the only change on the build system side is only that you have to pass -DPROJECT_PATH=foo/bar/ instead of -IPROJECT_PATH=foo/bar/ and that the construct is more brittle than the mechanisms that are actually designed for this sort of stuff. But if you really want to do it, then here is how:

所以,我没有看到你想要在代码中的#include指令中直接引用项目路径的原因;构建系统方面唯一的变化只是你必须传递-DPROJECT_PATH = foo / bar /而不是-IPROJECT_PATH = foo / bar /并且构造比实际为这类东西设计的机制更脆弱。但如果你真的想这样做,那么这里是如何:

The first problem you run into is that

你遇到的第一个问题是

#include "foo/bar/" "baz.h" // no dice.

is ill-formed, so the easy way is out. We have to try preprocessor magic, and it works like this:

形象不对,所以简单的方法就出来了。我们必须尝试预处理器魔术,它的工作原理如下:

#define HEADER_STRING(s) #s
#define HEADER_I(path, name) HEADER_STRING(path ## name)
#define HEADER(path, name) HEADER_I(path, name)

//                                v-- important: no spaces allowed here!
#include HEADER(PROJECT_PATH,foobar.h)

Perhaps start from the bottom up:

也许从下往上:

#define HEADER_STRING(s) #s

makes a string from its argument. That is to say, HEADER_STRING(foo/bar/baz.h) expands to "foo/bar/baz.h". Notably, macro parameters are not expanded, so HEADER_STRING(PROJECT_PATH) will expand to "PROJECT_PATH" even if a macro PROJECT_PATH is defined. This is one of the most common problems you run into when you try to do anything complicated with the preprocessor, and the solution is to add another layer in which the parameters can be expanded:

从其参数中生成一个字符串。也就是说,HEADER_STRING(foo / bar / baz.h)扩展为“foo / bar / baz.h”。值得注意的是,宏参数未扩展,因此即使定义了宏PROJECT_PATH,HEADER_STRING(PROJECT_PATH)也将扩展为“PROJECT_PATH”。当您尝试使用预处理器执行任何复杂操作时,这是您遇到的最常见问题之一,解决方案是添加另一个可以扩展参数的层:

#define HEADER_STRING_I(s) #s
#define HEADER_STRING(s) HEADER_STRING_I(s)

...we do not need this for HEADER_STRING, but it is used in HEADER, so keep the trick in mind. I'm afraid the precise preprocessor substitution rules are somewhat arcane, and explaining them in detail goes beyond the scope of a SO answer. In a nutshell, macros are expanded in layers, and when macros don't get expanded, the trick is usually to give them a place to expand, i.e., to add another layer.

...我们不需要这个用于HEADER_STRING,但它在HEADER中使用,所以记住这个诀窍。我担心精确的预处理器替换规则有点晦涩难懂,详细解释它们超出了SO答案的范围。简而言之,宏是在层中扩展的,当宏不扩展时,诀窍通常是为它们提供扩展的位置,即添加另一层。

HEADER_I then,

#define HEADER_I(path, name) HEADER_STRING(path ## name)

tapes its arguments together and passes them to HEADER_STRING. HEADER_I(foo,bar) expands to HEADER_STRING(foobar). Because of the problem I mentioned above, HEADER_I(PROJECT_PATH,foobar.h) expands to HEADER_STRING(PROJECT_PATHfoobar.h), which in turn expands to "PROJECT_PATHfoobar.h", so we need another layer for the expansion of PROJECT_PATH:

将其参数绑定在一起并将它们传递给HEADER_STRING。 HEADER_I(foo,bar)扩展为HEADER_STRING(foobar)。由于我上面提到的问题,HEADER_I(PROJECT_PATH,foobar.h)扩展为HEADER_STRING(PROJECT_PATHfoobar.h),后者又扩展为“PROJECT_PATHfoobar.h”,因此我们需要另一层来扩展PROJECT_PATH:

#define HEADER(path, name) HEADER_I(path, name)

This just adds the place for the path and name parameters to be expanded. Finally, with PROJECT_PATH #defined to foo/bar/, HEADER(PROJECT_PATH,foobar.h) expands to "foo/bar/foobar.h", and then we can say

这只是添加要扩展的路径和名称参数的位置。最后,使用PROJECT_PATH #defined到foo / bar /,HEADER(PROJECT_PATH,foobar.h)扩展为“foo / bar / foobar.h”,然后我们可以说

#include HEADER(PROJECT_PATH,foobar.h)

to #include "foo/bar/foobar.h". PROJECT_PATH can then be set in the makefile and passed with -DPROJECT_PATH=$(some_make_variable).

#include“foo / bar / foobar.h”。然后可以在makefile中设置PROJECT_PATH并使用-DPROJECT_PATH = $(some_make_variable)传递。

The last pitfall is that you must take care to not let any spaces slip between the tokens.

最后一个陷阱是你必须注意不要让任何空间在令牌之间滑动。

#include HEADER(PROJECT_PATH,foobar.h)

ultimately expands to "foo/bar/ foobar.h" (note the space), which does not work.

最终扩展到“foo / bar / foobar.h”(注意空格),这是行不通的。