diff --git a/src/Parser.zig b/src/Parser.zig index 818abda..40d99fe 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -82,8 +82,10 @@ pub fn init(gpa: std.mem.Allocator, arena: std.mem.Allocator, io: std.Io, cflags driver.nostdlibinc = true; driver.nobuiltininc = true; - // Configure include search order from the bundled sysroot. - try include_paths.addZigCcImplicitIncludes(driver.comp, resource_dir_dupe); + // Stage bundled-sysroot search paths in `driver.includes`; user `-I` flags + // from `parseArgs` will join the same list and a single `initSearchPath` + // call below commits them all. + try include_paths.addZigCcImplicitIncludes(driver, resource_dir_dupe); // Add compatibility macros for LLVM/Clang 18+ headers. // __building_module(x) is a Clang builtin that returns 0 unless building a specific @@ -93,9 +95,11 @@ pub fn init(gpa: std.mem.Allocator, arena: std.mem.Allocator, io: std.Io, cflags \\#define __building_module(x) 0 \\ ); - // We call buildUserMacros before generateBuiltinMacros as calling parseArgs will - // update the driver state + // buildUserMacros runs parseArgs, which appends user includes to + // `driver.includes` and updates driver state, so it must come before + // initSearchPath and generateBuiltinMacros. const user_macros = try buildUserMacros(toolchain, cflags, gpa); + try driver.comp.initSearchPath(driver.includes.items, driver.verbose_search_path); const builtin_source = try driver.comp.generateBuiltinMacros(driver.system_defines); return .{ @@ -146,8 +150,8 @@ pub fn deinit(p: *Parser) void { var diag: *aro.Diagnostics = p.diagnostics; p.toolchain.deinit(); - comp.deinit(); driver.deinit(); + comp.deinit(); diag.deinit(); p.gpa.destroy(comp); diff --git a/src/include_paths.zig b/src/include_paths.zig index 572fe75..5cb47af 100644 --- a/src/include_paths.zig +++ b/src/include_paths.zig @@ -6,7 +6,10 @@ const aro = @import("aro"); const std = @import("std"); -/// Configure the compilation's include search paths using the bundled sysroot. +/// Append the bundled-sysroot include paths to `driver.includes`. +/// The caller commits the merged list (system + user) to the compilation +/// via a single `initSearchPath` call, mirroring aro's normal driver flow. +/// /// The bundled sysroot lives under `resource_dir/lib/...`. We add directories /// in the same order that `zig cc -E -v` reports, except we intentionally omit /// /usr/local/include and /usr/include to keep absolution self-contained: @@ -15,18 +18,16 @@ const std = @import("std"); /// 3. /lib/libc/include/generic- (generic libc headers) /// 4. /lib/libc/include/--any (arch+os wildcards) /// 5. /lib/libc/include/any--any (os-only wildcards) -pub fn addZigCcImplicitIncludes(comp: *aro.Compilation, resource_dir: []const u8) !void { +pub fn addZigCcImplicitIncludes(driver: *aro.Driver, resource_dir: []const u8) !void { + const comp = driver.comp; const target = comp.target; const arch = target.cpu.arch; const os = target.os.tag; const abi = target.abi; - var includes: std.ArrayList(aro.Compilation.Include) = .empty; - defer includes.deinit(comp.gpa); - // 1. Zig's compiler-provided headers (stddef.h, stdarg.h, etc.) const zig_lib_include = try std.fs.path.join(comp.arena, &.{ resource_dir, "lib", "include" }); - try includes.append(comp.gpa, .{ .kind = .system, .path = zig_lib_include }); + try driver.includes.append(comp.gpa, .{ .kind = .system, .path = zig_lib_include }); const libc_include_base = try std.fs.path.join(comp.arena, &.{ resource_dir, "lib", "libc", "include" }); @@ -34,31 +35,30 @@ pub fn addZigCcImplicitIncludes(comp: *aro.Compilation, resource_dir: []const u8 const target_subdir = try getTargetLibcSubdir(comp.arena, arch, os, abi); if (target_subdir) |subdir| { const target_include = try std.fs.path.join(comp.arena, &.{ libc_include_base, subdir }); - try includes.append(comp.gpa, .{ .kind = .system, .path = target_include }); + try driver.includes.append(comp.gpa, .{ .kind = .system, .path = target_include }); } // 3. Generic libc headers based on ABI family (glibc, musl, etc.) const generic_subdir = getGenericLibcSubdir(abi); if (generic_subdir) |subdir| { const generic_include = try std.fs.path.join(comp.arena, &.{ libc_include_base, subdir }); - try includes.append(comp.gpa, .{ .kind = .system, .path = generic_include }); + try driver.includes.append(comp.gpa, .{ .kind = .system, .path = generic_include }); } // 4. Architecture + OS wildcard headers (e.g., x86-linux-any) const arch_os_any = try getArchOsAnySubdir(comp.arena, arch, os); if (arch_os_any) |subdir| { const arch_os_include = try std.fs.path.join(comp.arena, &.{ libc_include_base, subdir }); - try includes.append(comp.gpa, .{ .kind = .system, .path = arch_os_include }); + try driver.includes.append(comp.gpa, .{ .kind = .system, .path = arch_os_include }); } // 5. OS-only wildcard headers (e.g., any-linux-any) const any_os_any = try getAnyOsAnySubdir(comp.arena, os); if (any_os_any) |subdir| { const any_os_include = try std.fs.path.join(comp.arena, &.{ libc_include_base, subdir }); - try includes.append(comp.gpa, .{ .kind = .system, .path = any_os_include }); + try driver.includes.append(comp.gpa, .{ .kind = .system, .path = any_os_include }); } - try comp.initSearchPath(includes.items, false); // Note: We intentionally do NOT add /usr/local/include or /usr/include. // absolution is self-contained and uses only the bundled headers from the // build-time copied sysroot. This ensures: diff --git a/tests/user_include_path_resolution/inc/inner.h b/tests/user_include_path_resolution/inc/inner.h new file mode 100644 index 0000000..db4e406 --- /dev/null +++ b/tests/user_include_path_resolution/inc/inner.h @@ -0,0 +1,6 @@ +#pragma once + +typedef struct { + int id; + int value; +} inner_t; diff --git a/tests/user_include_path_resolution/target.c b/tests/user_include_path_resolution/target.c new file mode 100644 index 0000000..dce5076 --- /dev/null +++ b/tests/user_include_path_resolution/target.c @@ -0,0 +1,15 @@ +/* Regression test: user-supplied -I flags must reach the preprocessor's + * include search path. + * + * The header `inner.h` lives in a sibling subdirectory `inc/`, so the + * preprocessor cannot find it via the includer's directory alone — it + * MUST honour the `-I .../inc` flag passed via target.c.flags. + * + * This guards against a regression where Driver.parseArgs collected + * `-I/-isystem/-iquote/-idirafter` into `driver.includes` but the result + * was never committed to `comp.search_path`, leaving every user-supplied + * include path invisible to the preprocessor. + */ +#include "inner.h" + +inner_t global_state; diff --git a/tests/user_include_path_resolution/target.c.flags b/tests/user_include_path_resolution/target.c.flags new file mode 100644 index 0000000..146d70c --- /dev/null +++ b/tests/user_include_path_resolution/target.c.flags @@ -0,0 +1,4 @@ +# Force the preprocessor to rely on a user-supplied include path: the +# header `inner.h` only exists under tests/user_include_path_resolution/inc/. +-I +tests/user_include_path_resolution/inc diff --git a/tests/user_include_path_resolution/target.c.zon b/tests/user_include_path_resolution/target.c.zon new file mode 100644 index 0000000..d6f7f33 --- /dev/null +++ b/tests/user_include_path_resolution/target.c.zon @@ -0,0 +1,22 @@ +.{.{ + .name = "global_state", + .source_file = "tests/user_include_path_resolution/target.c", + .size_bytes = 8, + .is_static = false, + .dims = .{}, + .fields = .{ .{ + .name = ".id", + .offset_bits = 0, + .bit_width = 32, + .dims = .{}, + .is_padding = false, + .domain = .top, + }, .{ + .name = ".value", + .offset_bits = 32, + .bit_width = 32, + .dims = .{}, + .is_padding = false, + .domain = .top, + } }, +}}