Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 .{
Expand Down Expand Up @@ -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);
Expand Down
22 changes: 11 additions & 11 deletions src/include_paths.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -15,50 +18,47 @@ const std = @import("std");
/// 3. <prefix>/lib/libc/include/generic-<abi-family> (generic libc headers)
/// 4. <prefix>/lib/libc/include/<arch>-<os>-any (arch+os wildcards)
/// 5. <prefix>/lib/libc/include/any-<os>-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" });

// 2. Target-specific libc headers (e.g., x86_64-linux-gnu or x86-linux-gnu)
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:
Expand Down
6 changes: 6 additions & 0 deletions tests/user_include_path_resolution/inc/inner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

typedef struct {
int id;
int value;
} inner_t;
15 changes: 15 additions & 0 deletions tests/user_include_path_resolution/target.c
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions tests/user_include_path_resolution/target.c.flags
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions tests/user_include_path_resolution/target.c.zon
Original file line number Diff line number Diff line change
@@ -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,
} },
}}
Loading