环境变量导致的 gcc 头文件查找顺序错乱
在 firefly 上构建 spice-gtk-client 时出现了头文件查找顺序出错的情况,最终定位发现是环境变量导致的。

在 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_PATHCPLUS_INCLUDE_PATHOBJC_INCLUDE_PATHOBJCPLUS_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