在 firefly 上构建 spice-gtk-client 时出现了如下的错误(x86 上没有任何问题):
Compiler executable checksum: 6a3864a8c3fe8bbb972fb5dbcb1f67d4
In file included from ../src/channel-display.c:22:
/usr/include/glib-2.0/glib/gi18n-lib.h:27:2: error: #error You must define GETTEXT_PACKAGE before including gi18n-lib.h. Did you forget to include config.h?
27 | #error You must define GETTEXT_PACKAGE before including gi18n-lib.h. Did you forget to include config.h?
| ^~~~~
In file included from ../src/channel-display.c:22:
由于确认生成的 config.h 头文件中包含相应的宏定义,那么显然是 config.h 头文件包含顺序导致的问题,定位到出问题的命令如下:
$ cc -v -Isrc/libspice-client-glib-2.0.so.8.8.2.p -Isrc -I../src -I. -I.. -Isubprojects/spice-common -I../subprojects/spice-common -Isubprojects/spice-common/common -I/usr/local/include/spice-1 -I/usr/include/glib-2.0 -I/usr/lib/aarch64-linux-gnu/glib-2.0/include -I/usr/include/pixman-1 -I/usr/include/opus -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/json-glib-1.0 -I/usr/include/gio-unix-2.0 -I/usr/include/gstreamer-1.0 -I/usr/include/aarch64-linux-gnu -I/usr/include/orc-0.4 -fvisibility=hidden -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -O2 -g -DHAVE_CONFIG_H -DSPICE_COMPILATION '-DG_LOG_DOMAIN="GSpice"' -Wno-sign-compare -Wno-unused-parameter -Wno-cast-function-type -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_52 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_52 -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_22 -DGDK_VERSION_MAX_ALLOWED=GDK_VERSION_3_22 -fPIC -pthread -MD -MQ src/libspice-client-glib-2.0.so.8.8.2.p/channel-display.c.o -MF src/libspice-client-glib-2.0.so.8.8.2.p/channel-display.c.o.d -o src/libspice-client-glib-2.0.so.8.8.2.p/channel-display.c.o -c ../src/channel-display.c
头文件搜索路径如下:
ignoring nonexistent directory "/usr/include/libdrm"
ignoring nonexistent directory "/usr/local/include/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/9/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include"
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
ignoring duplicate directory "/usr/include/aarch64-linux-gnu"
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
src/libspice-client-glib-2.0.so.8.8.2.p
src
../src
..
subprojects/spice-common
../subprojects/spice-common
subprojects/spice-common/common
/usr/local/include/spice-1
/usr/include/glib-2.0
/usr/lib/aarch64-linux-gnu/glib-2.0/include
/usr/include/pixman-1
/usr/include/opus
/usr/include/libmount
/usr/include/blkid
/usr/include/json-glib-1.0
/usr/include/gio-unix-2.0
/usr/include/gstreamer-1.0
/usr/include/orc-0.4
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
/usr/local/include
/usr/include/aarch64-linux-gnu
/usr/include
End of search list.
可以看到当前路径 .
的顺序错了,来一个最简单的可复现的例子:
$ echo | cpp -v -I. -I..
ignoring nonexistent directory "/usr/include/libdrm"
ignoring nonexistent directory "/usr/local/include/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/9/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include"
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
/usr/local/include
/usr/include/aarch64-linux-gnu
/usr/include
End of search list.
显然 -I.
和 -I..
的顺序翻转了,.
被当成了系统头文件搜索路径:
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
这个打印是通过如下代码打印的,显然,.
被当作系统头文件处理了:
merge_include_chains
https://github.com/gcc-mirror/gcc/blob/releases/gcc-13.2.0/gcc/incpath.cc#L372
free_path
https://github.com/gcc-mirror/gcc/blob/releases/gcc-13.2.0/gcc/incpath.cc#L71
由于通过 unset CPLUS_INCLUDE_PATH
环境变量可以解决 gcc 构建过程出现的错误,同样猜测问题的根源应当是终端错误的设置了不应该设置的环境变量(firefly 使用的是 Ubuntu 提供的 gcc):
$ echo $C_INCLUDE_PATH
/usr/include/libdrm:
$ echo $CPLUS_INCLUDE_PATH
/usr/include/libdrm:
这些环境变量是在这里设置的:
$ cat /etc/profile.d/drm.sh
C_INCLUDE_PATH=/usr/include/libdrm:$C_INCLUDE_PATH
CPLUS_INCLUDE_PATH=/usr/include/libdrm:$CPLUS_INCLUDE_PATH
export C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH
通过 unset 这些环境变量,问题可以完美解决:
$ unset C_INCLUDE_PATH
$ unset CPLUS_INCLUDE_PATH
$ echo | cpp -v -I. -I..
ignoring nonexistent directory "/usr/local/include/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/9/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
.
..
/usr/lib/gcc/aarch64-linux-gnu/9/include
/usr/local/include
/usr/include/aarch64-linux-gnu
/usr/include
End of search list.
其实问题的关键在于 C_INCLUDE_PATH
中包含了冒号分隔的空路径,如 :
, path1:
, :path1
, path1::path2
等:
$ echo | C_INCLUDE_PATH=: cpp -v -I. -I..
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
$ echo | C_INCLUDE_PATH=path1: cpp -v -I. -I..
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
$ echo | C_INCLUDE_PATH=:path1 cpp -v -I. -I..
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
$ echo | C_INCLUDE_PATH=path1::path2 cpp -v -I. -I..
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
如果没有空路径就不会有问题:
$ echo | C_INCLUDE_PATH= cpp -v -I. -I..
#include "..." search starts here:
#include <...> search starts here:
.
..
/usr/lib/gcc/aarch64-linux-gnu/9/include
$ echo | C_INCLUDE_PATH=path1:path2 cpp -v -I. -I..
#include "..." search starts here:
#include <...> search starts here:
.
..
/usr/lib/gcc/aarch64-linux-gnu/9/include
实际上,gcc 内置的系统头文件除了有一部分是硬编码的,还有一部分是环境变量影响的:
add_env_var_paths(const char *env_var, incpath_kind chain) {
char *p, *q, *path;
q = getenv(env_var);
if (!q)
return;
for (p = q; *q; p = q + 1) {
q = p;
while (*q != 0 && *q != PATH_SEPARATOR)
q++;
// 一旦环境变量中存在多余的 `:` 就会出现 p == q 这种情况,
// 如:`path1::path2`, `:path1`, `path1:`, `:`
if (p == q)
path = xstrdup(".");
else {
path = XNEWVEC(char, q - p + 1);
memcpy(path, p, q - p);
path[q - p] = '\0';
}
add_path(path, chain, chain == INC_SYSTEM, false);
}
}
从代码逻辑来看,.
的引入是显而易见的,不过需要注意的是 C_INCLUDE_PATH
,CPLUS_INCLUDE_PATH
,OBJC_INCLUDE_PATH
,OBJCPLUS_INCLUDE_PATH
都能影响系统头文件的定义。
add_env_var_paths
https://github.com/gcc-mirror/gcc/blob/releases/gcc-13.2.0/gcc/incpath.cc#L116
register_include_chains
https://github.com/gcc-mirror/gcc/blob/releases/gcc-13.2.0/gcc/incpath.cc#L496
当然,从环境变量的命名可以看到,特定的环境变量只影响特定的语言(可以参考 register_include_chains
的实现):
$ echo | C_INCLUDE_PATH=: cpp -xc -v -I. -I..
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/lib/gcc/aarch64-linux-gnu/9/include
$ echo | CPLUS_INCLUDE_PATH=: cpp -xc++ -v -I. -I..
ignoring duplicate directory "."
as it is a non-system directory that duplicates a system directory
#include "..." search starts here:
#include <...> search starts here:
..
.
/usr/include/c++/9
需要注意的是 meson 并不支持(也包括 cmake)直接 source 或修改 shell 的环境变量,因此问题没法在 meson.build 脚本中进行规避:
Feature Request: Set environment variable in meson.build
https://github.com/mesonbuild/meson/issues/541
presets: sourcing environment scripts
https://gitlab.kitware.com/cmake/cmake/-/issues/21619
最后修改于 2023-11-15