Skip to content

[zig cc] command line -isystem paths are incorrectly searched after zig's system paths, instead of beforeΒ #24243

Open

Description

@henderkes

Zig Version

0.15-dev

Steps to Reproduce and Observed Behavior

I figured out why it happens. zig cc is not behaving like gcc and clang do with -isystem and -I directories.

zig cc

[m@M-TH build]$ zig cc -E -H iconv.c -isystem /home/m/static-php-cli/buildroot/include | grep iconv
. /usr/lib/zig/libc/include/generic-glibc/iconv.h
.. /usr/lib/zig/libc/include/generic-glibc/features.h
... /usr/lib/zig/libc/include/generic-glibc/features-time64.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/timesize.h
..... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
... /usr/lib/zig/libc/include/generic-glibc/stdc-predef.h
... /usr/lib/zig/libc/include/generic-glibc/sys/cdefs.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/long-double.h
... /usr/lib/zig/libc/include/x86_64-linux-gnu/gnu/stubs.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/gnu/stubs-64.h
...

clang:

[m@M-TH build]$ clang -E -H iconv.c -isystem /home/m/static-php-cli/buildroot/include | grep iconv
. /home/m/static-php-cli/buildroot/include/iconv.h
.. /usr/bin/../lib/clang/19/include/stddef.h
... /usr/bin/../lib/clang/19/include/__stddef_header_macro.h
... /usr/bin/../lib/clang/19/include/__stddef_ptrdiff_t.h
... /usr/bin/../lib/clang/19/include/__stddef_size_t.h
... /usr/bin/../lib/clang/19/include/__stddef_wchar_t.h
... /usr/bin/../lib/clang/19/include/__stddef_null.h
... /usr/bin/../lib/clang/19/include/__stddef_max_align_t.h
... /usr/bin/../lib/clang/19/include/__stddef_offsetof.h
...

gcc:

[m@M-TH build]$ gcc -E -H iconv.c -isystem /home/m/static-php-cli/buildroot/include | grep iconv
. /home/m/static-php-cli/buildroot/include/iconv.h
.. /usr/lib/gcc/x86_64-redhat-linux/14/include/stddef.h
.. /usr/include/errno.h
... /usr/include/features.h
.... /usr/include/features-time64.h
..... /usr/include/bits/wordsize.h
..... /usr/include/bits/timesize.h
...... /usr/include/bits/wordsize.h
.... /usr/include/sys/cdefs.h
..... /usr/include/bits/wordsize.h
..... /usr/include/bits/long-double.h
.... /usr/include/gnu/stubs.h
..... /usr/include/gnu/stubs-64.h
...

But the real issue is this:

[m@M-TH build]$ zig cc -E -H iconv.c -isystem /home/m/static-php-cli/buildroot/include -I/home/m/static-php-cli/buildroot/include | grep iconv
. /usr/lib/zig/libc/include/generic-glibc/iconv.h
.. /usr/lib/zig/libc/include/generic-glibc/features.h
... /usr/lib/zig/libc/include/generic-glibc/features-time64.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/timesize.h
..... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
... /usr/lib/zig/libc/include/generic-glibc/stdc-predef.h

# or 

[m@M-TH build]$ zig cc -E -H iconv.c -I/home/m/static-php-cli/buildroot/include -isystem /home/m/static-php-cli/buildroot/include | grep iconv
. /usr/lib/zig/libc/include/generic-glibc/iconv.h
.. /usr/lib/zig/libc/include/generic-glibc/features.h
... /usr/lib/zig/libc/include/generic-glibc/features-time64.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
.... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/timesize.h
..... /usr/lib/zig/libc/include/x86_64-linux-gnu/bits/wordsize.h
... /usr/lib/zig/libc/include/generic-glibc/stdc-predef.h

Zig ignores the -I directory, if it is also specified as a -isystem dir.

zig cc without isystem:

[m@M-TH build]$ zig cc -E -H iconv.c -I/home/m/static-php-cli/buildroot/include | grep iconv
. /home/m/static-php-cli/buildroot/include/iconv.h
.. /usr/lib/zig/include/stddef.h
... /usr/lib/zig/include/__stddef_header_macro.h
... /usr/lib/zig/include/__stddef_ptrdiff_t.h
... /usr/lib/zig/include/__stddef_size_t.h
... /usr/lib/zig/include/__stddef_wchar_t.h
... /usr/lib/zig/include/__stddef_null.h
... /usr/lib/zig/include/__stddef_max_align_t.h
... /usr/lib/zig/include/__stddef_offsetof.h
.. /usr/lib/zig/libc/include/generic-glibc/errno.h

Since the php build unconditionally adds all our directories as isystem directories to supress warnings during extension compilation, this cannot be worked around short of patching zig.

Activity

  1. added
    bugObserved behavior contradicts documented or intended behavior
    on Jun 22, 2025
  2. henderkes commented on Jun 23, 2025

    @henderkes
    ContributorAuthor

    FYI, deleting the generic-glibc/iconv.h file also works as a workaround, but obviously the root cause should be identified and fixed.

  3. added this to the 0.15.0 milestone on Jun 24, 2025
  4. added
    contributor friendlyThis issue is limited in scope and/or knowledge of Zig internals.
    zig ccZig as a drop-in C compiler feature
    on Jun 24, 2025
  5. henderkes commented on Jun 24, 2025

    @henderkes
    ContributorAuthor

    Conflicting command:

    Image

    yellow are the -I directories added by the extension itself, grey are the -isystem directories added by the extension, the first green are necessary CFLAGS passed by php-src and the last green. Only the next paragraph are the CFLAGS and EXTRA_CFLAGS passed by us.

  6. changed the title [-][zig cc] php-cli fails to build with zig cc, includes zig/libc/include/generic-glibc/iconv.h before -I/path/to/iconv.h[/-] [+][zig cc] command line -isystem paths are incorrectly searched after zig's system paths, instead of before[/+] on Jun 24, 2025
  7. The-Briel-Deal commented on Jul 1, 2025

    @The-Briel-Deal

    Hey @henderkes!

    I'm trying to reproduce this in a smaller environment outside of php currently, I am getting the behavior where if you specify an include path as -isystem and -I, then it will make that include dir lower priority than a -I path that comes later. This behavior is odd but for some reason this behavior is consistent on gcc, clang, and zig cc.

    [I] gf@arch:~/C/zig_cc_isystem_bug=>tree
    .
    β”œβ”€β”€ include1
    β”‚Β Β  └── foo.h
    β”œβ”€β”€ include2
    β”‚Β Β  └── foo.h
    └── test.c

    test.c

    #include "foo.h"

    include1/foo.h

    int im_from_include1 = 1;

    include2/foo.h

    int im_from_include2 = 2;

    Usually I would expect the include path that comes first to be searched first. This behavior is as expected with clang here:

    [N] gf@arch:~/C/zig_cc_isystem_bug=>clang -E test.c -Iinclude2 -Iinclude1
    # 1 "test.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 411 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "test.c" 2
    # 1 "include2/foo.h" 1
    
    int im_from_include2 = 2;
    # 2 "test.c" 2

    But as you showed above, if we add -isystem include2 then the ordered
    precendence is completely ignored and include2 is treated like a -isystem
    include.

    [I] gf@arch:~/C/zig_cc_isystem_bug=>clang -E test.c -isystem include2 -Iinclude2 -Iinclude1
    # 1 "test.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 411 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "test.c" 2
    # 1 "include1/foo.h" 1
    
    int im_from_include1 = 1;
    # 2 "test.c" 2
    

    Now if I do this with zig cc, I get the exact same results.

    Without -isystem:

    [I] gf@arch:~/C/zig_cc_isystem_bug=>zig cc -E test.c -Iinclude2 -Iinclude1
    # 1 "test.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 465 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "test.c" 2
    # 1 "include2/foo.h" 1
    
    int im_from_include2 = 2;
    # 2 "test.c" 2

    With -isystem:

    [I] gf@arch:~/C/zig_cc_isystem_bug=>zig cc -E test.c -isystem include2 -Iinclude2 -Iinclude1
    # 1 "test.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 465 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "test.c" 2
    # 1 "include1/foo.h" 1
    
    int im_from_include1 = 1;
    # 2 "test.c" 2

    Do you know if I'm doing something wrong here? I'm not able to get the behavior to differ from gcc or clang. I agree this behavior is unexpected, but it seems to be uniform with other compilers.

  8. henderkes commented on Jul 1, 2025

    @henderkes
    ContributorAuthor

    Do you know if I'm doing something wrong here? I'm not able to get the behavior to differ from gcc or clang. I agree this behavior is unexpected, but it seems to be uniform with other compilers.

    You're not doing anything wrong! The difference is that while gcc and clang process -isystem includes passed by the user before their own system paths, zig prefers to search its own system paths before.

    Try to include "iconv.h" like I did in the first post - while gcc and clang will include your own file passed as -isystem, zig will instead include generic-glib/iconv.h.

  9. The-Briel-Deal commented on Jul 2, 2025

    @The-Briel-Deal

    That makes sense, I'll try this again against iconv.h in a bit! That iconv.h header is part of the php interpreter's source code right?

  10. henderkes commented on Jul 3, 2025

    @henderkes
    ContributorAuthor

    Iconv.h is part of libc and part of the installation of libiconv.

    When you compile it yourself as a static library, you get extra functionality and definitions compared to the system library. Php's iconv extension requires a full libiconv installation and not just the system one.

    But that's not really important to the issue here - just create a iconv.h file yourself and pass it as -isystem - zig will use the generic-glibc/iconv.h, while gcc and clang will use the one you passed in -isystem.

  11. The-Briel-Deal commented on Jul 3, 2025

    @The-Briel-Deal

    Alright @henderkes, I think i've got this reproduced, let me know if this is the same issue. In this below example I've got the same directory structure as my above comment:

    [I] gf@arch:~/C/zig_cc_isystem_bug=>export C_INCLUDE_PATH=$PWD/include2/
    [I] gf@arch:~/C/zig_cc_isystem_bug=>zig cc -E test.c -isystem include1
    # 1 "test.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 465 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "test.c" 2
    # 1 "/home/gabe/Code/zig_cc_isystem_bug/include2/foo.h" 1 3
    
    int im_from_include2 = 2;
    # 2 "test.c" 2
    
    [I] gf@arch:~/C/zig_cc_isystem_bug=>clang -E test.c -isystem include1
    # 1 "test.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 411 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "test.c" 2
    # 1 "include1/foo.h" 1 3
    
    int im_from_include1 = 1;
    # 2 "test.c" 2

    Ok, so if I export C_INCLUDE_PATH=$PWD/include2/ and use the flag -isystem include1 then zig will override the path set via cli flag with the C_INCLUDE_PATH env var. But with clang and gcc, the same setup prefers the path from the cli flag.

    So currently the zig include path search order is:

    1. $C_INCLUDE_PATH
    2. -isystem

    But the gcc and clang include path search order is:

    1. -isystem
    2. $C_INCLUDE_PATH

    So the desired behavior of zig cc is that the search order matches gcc and clang right?

  12. The-Briel-Deal commented on Jul 3, 2025

    @The-Briel-Deal

    Out of curiousity I just took a look at at the ISO C99 spec to see if there is a defined way to order search paths and it says it is implementation defined for angle and quote includes in section 6.10.2 Source file inclusion:

    https://www.dii.uchile.cl/~daespino/files/Iso_C_1999_definition.pdf

    So I'm assuming we want to just follow the behavior of clang and gcc as close as we can.

  13. henderkes commented on Jul 3, 2025

    @henderkes
    ContributorAuthor

    Correct, it should be adjusted to use -isystem before internal paths. I tried to do the change myself, but couldn't test because compiling zig failed on the step (3?) where zig compiles zig.

  14. The-Briel-Deal commented on Jul 3, 2025

    @The-Briel-Deal

    Correct, it should be adjusted to use -isystem before internal paths. I tried to do the change myself, but couldn't test because compiling zig failed on the step (3?) where zig compiles zig.

    Oh! I had a similar issue, try downloading the latest nightly release and just do a zig build with that. I had issues with the cmake based compilation too.

    Follow https://github.com/ziglang/zig/wiki/Building-Zig-From-Source#option-b-use-a-pre-built-zig-binary where you use zig bootstrap to get the right llvm version too.

  15. 10 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorcontributor friendlyThis issue is limited in scope and/or knowledge of Zig internals.zig ccZig as a drop-in C compiler feature

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions