Skip to content

Commit

Permalink
std.Build: add precompiled C header support
Browse files Browse the repository at this point in the history
- add build-obj syntax to emit pch:
    `zig build-obj -femit-pch -lc++ -x c++-header test.h`
    `zig run -lc++ -cflags -include-pch test.pch -- main.cpp`

- add a `.pch` kind of Build.Step.Compile and Builder.addPrecompiledCHeader()

- augment addCSourceFiles() to include an optional precompiled_header
  • Loading branch information
xxxbxxx committed Jan 21, 2024
1 parent bf7ebfa commit 12f6a7d
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 22 deletions.
29 changes: 29 additions & 0 deletions lib/std/Build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,35 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
});
}

pub const PchOptions = struct {
name: []const u8,
target: ResolvedTarget,
optimize: std.builtin.OptimizeMode,
max_rss: usize = 0,
error_tracing: ?bool = null,
cpp_header: bool = false,
};

pub fn addPrecompiledCHeader(b: *Build, options: PchOptions, source: Module.CSourceFile) *Step.Compile {
const pch = Step.Compile.create(b, .{
.name = options.name,
.root_module = .{
.target = options.target,
.optimize = options.optimize,
.link_libc = !options.cpp_header,
.link_libcpp = options.cpp_header,
.error_tracing = options.error_tracing,
},
.kind = .pch,
.max_rss = options.max_rss,
.use_llvm = true,
});
pch.is_linking_libc = !options.cpp_header;
pch.is_linking_libcpp = options.cpp_header;
pch.addCSourceFile(source);
return pch;
}

pub const SharedLibraryOptions = struct {
name: []const u8,
/// To choose the same computer as the one building the package, pass the
Expand Down
17 changes: 17 additions & 0 deletions lib/std/Build/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,19 @@ pub const CSourceFiles = struct {
/// else relative to the build root.
files: []const []const u8,
flags: []const []const u8,
precompiled_header: ?*Step.Compile = null,
};

pub const CSourceFile = struct {
file: LazyPath,
flags: []const []const u8 = &.{},
precompiled_header: ?*Step.Compile = null,

pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile {
return .{
.file = self.file.dupe(b),
.flags = b.dupeStrings(self.flags),
.precompiled_header = self.precompiled_header,
};
}
};
Expand Down Expand Up @@ -458,6 +461,7 @@ pub const AddCSourceFilesOptions = struct {
dependency: ?*std.Build.Dependency = null,
files: []const []const u8,
flags: []const []const u8 = &.{},
precompiled_header: ?*Step.Compile = null,
};

/// Handy when you have many C/C++ source files and want them all to have the same flags.
Expand All @@ -469,8 +473,15 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
.dependency = options.dependency,
.files = b.dupeStrings(options.files),
.flags = b.dupeStrings(options.flags),
.precompiled_header = options.precompiled_header,
};
m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM");

if (options.precompiled_header) |pch| {
assert(pch.kind == .pch);
_ = pch.getEmittedBin(); // Indicate there is a dependency on the outputted pch binary.
m.addStepDependenciesOnly(&pch.step);
}
}

pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
Expand All @@ -480,6 +491,12 @@ pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
c_source_file.* = source.dupe(b);
m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM");
addLazyPathDependenciesOnly(m, source.file);

if (source.precompiled_header) |pch| {
assert(pch.kind == .pch);
_ = pch.getEmittedBin(); // Indicate there is a dependency on the outputted pch binary.
m.addStepDependenciesOnly(&pch.step);
}
}

/// Resource files must have the extension `.rc`.
Expand Down
74 changes: 57 additions & 17 deletions lib/std/Build/Step/Compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ pub const Kind = enum {
exe,
lib,
obj,
pch,
@"test",
};

Expand All @@ -263,27 +264,32 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.exe => "zig build-exe",
.lib => "zig build-lib",
.obj => "zig build-obj",
.pch => "zig build-pch",
.@"test" => "zig test",
},
name_adjusted,
@tagName(options.root_module.optimize orelse .Debug),
resolved_target.query.zigTriple(owner.allocator) catch @panic("OOM"),
});

const out_filename = std.zig.binNameAlloc(owner.allocator, .{
.root_name = name,
.target = target,
.output_mode = switch (options.kind) {
.lib => .Lib,
.obj => .Obj,
.exe, .@"test" => .Exe,
},
.link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
.dynamic => .Dynamic,
.static => .Static,
}) else null,
.version = options.version,
}) catch @panic("OOM");
const out_filename = if (options.kind == .pch)
std.fmt.allocPrint(owner.allocator, "{s}.pch", .{name}) catch @panic("OOM")
else
std.zig.binNameAlloc(owner.allocator, .{
.root_name = name,
.target = target,
.output_mode = switch (options.kind) {
.lib => .Lib,
.obj => .Obj,
.exe, .@"test" => .Exe,
.pch => unreachable,
},
.link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
.dynamic => .Dynamic,
.static => .Static,
}) else null,
.version = options.version,
}) catch @panic("OOM");

const self = owner.allocator.create(Compile) catch @panic("OOM");
self.* = .{
Expand Down Expand Up @@ -691,17 +697,20 @@ pub fn linkFrameworkWeak(c: *Compile, name: []const u8) void {

/// Handy when you have many C/C++ source files and want them all to have the same flags.
pub fn addCSourceFiles(self: *Compile, options: Module.AddCSourceFilesOptions) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file
self.root_module.addCSourceFiles(options);
}

pub fn addCSourceFile(self: *Compile, source: Module.CSourceFile) void {
assert(self.kind != .pch or self.root_module.link_objects.items.len == 0); // pch can only be generated from a single C header file
self.root_module.addCSourceFile(source);
}

/// Resource files must have the extension `.rc`.
/// Can be called regardless of target. The .rc file will be ignored
/// if the target object format does not support embedded resources.
pub fn addWin32ResourceFile(self: *Compile, source: Module.RcSourceFile) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file
self.root_module.addWin32ResourceFile(source);
}

Expand Down Expand Up @@ -784,14 +793,17 @@ pub fn getEmittedLlvmBc(self: *Compile) LazyPath {
}

pub fn addAssemblyFile(self: *Compile, source: LazyPath) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file
self.root_module.addAssemblyFile(source);
}

pub fn addObjectFile(self: *Compile, source: LazyPath) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file
self.root_module.addObjectFile(source);
}

pub fn addObject(self: *Compile, object: *Compile) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file
self.root_module.addObject(object);
}

Expand Down Expand Up @@ -915,7 +927,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const cmd = switch (self.kind) {
.lib => "build-lib",
.exe => "build-exe",
.obj => "build-obj",
.obj, .pch => "build-obj",
.@"test" => "test",
};
try zig_args.append(cmd);
Expand Down Expand Up @@ -975,6 +987,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
}
}

if (self.kind == .pch) {
// precompiled headers must have a single input header file.
var it = self.root_module.iterateDependencies(self, false);
const link_objects = it.next().?.module.link_objects;
assert(link_objects.items.len == 1 and link_objects.items[0] == .c_source_file);
assert(it.next() == null);
}

var cli_named_modules = try CliNamedModules.init(arena, &self.root_module);

// For this loop, don't chase dynamic libraries because their link
Expand Down Expand Up @@ -1076,6 +1096,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
switch (other.kind) {
.exe => return step.fail("cannot link with an executable build artifact", .{}),
.@"test" => return step.fail("cannot link with a test", .{}),
.pch => @panic("Cannot link with a precompiled header file"),
.obj => {
const included_in_lib_or_obj = !my_responsibility and (compile.kind == .lib or compile.kind == .obj);
if (!already_linked and !included_in_lib_or_obj) {
Expand Down Expand Up @@ -1129,7 +1150,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
.c_source_file => |c_source_file| l: {
if (!my_responsibility) break :l;

if (c_source_file.flags.len == 0) {
if (c_source_file.flags.len == 0 and c_source_file.precompiled_header == null) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
Expand All @@ -1140,17 +1161,28 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
for (c_source_file.flags) |arg| {
try zig_args.append(arg);
}
if (c_source_file.precompiled_header) |pch| {
try zig_args.append("-include-pch");
try zig_args.append(pch.getEmittedBin().getPath(b));
try zig_args.append("-fpch-validate-input-files-content");
}
try zig_args.append("--");
prev_has_cflags = true;
}

if (self.kind == .pch) {
try zig_args.append("-x");
try zig_args.append(if (self.is_linking_libcpp) "c++-header" else "c-header");
}

try zig_args.append(c_source_file.file.getPath2(module.owner, step));
total_linker_objects += 1;
},

.c_source_files => |c_source_files| l: {
if (!my_responsibility) break :l;

if (c_source_files.flags.len == 0) {
if (c_source_files.flags.len == 0 and c_source_files.precompiled_header == null) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
Expand All @@ -1161,6 +1193,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
for (c_source_files.flags) |flag| {
try zig_args.append(flag);
}

if (c_source_files.precompiled_header) |pch| {
try zig_args.append("-include-pch");
try zig_args.append(pch.getEmittedBin().getPath(b));
try zig_args.append("-fpch-validate-input-files-content");
}

try zig_args.append("--");
prev_has_cflags = true;
}
Expand Down Expand Up @@ -1307,6 +1346,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
if (self.generated_llvm_bc != null) try zig_args.append("-femit-llvm-bc");
if (self.generated_llvm_ir != null) try zig_args.append("-femit-llvm-ir");
if (self.generated_h != null) try zig_args.append("-femit-h");
if (self.kind == .pch) try zig_args.append("-femit-pch");

try addFlag(&zig_args, "formatted-panics", self.formatted_panics);

Expand Down
2 changes: 1 addition & 1 deletion lib/std/Build/Step/InstallArtifact.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
const dest_dir: ?InstallDir = switch (options.dest_dir) {
.disabled => null,
.default => switch (artifact.kind) {
.obj => @panic("object files have no standard installation procedure"),
.obj, .pch => @panic("object files have no standard installation procedure"),
.exe, .@"test" => InstallDir{ .bin = {} },
.lib => InstallDir{ .lib = {} },
},
Expand Down
8 changes: 4 additions & 4 deletions src/Compilation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4414,11 +4414,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
else
"/dev/null";

try argv.ensureUnusedCapacity(6);
try argv.ensureUnusedCapacity(7);
switch (comp.clang_preprocessor_mode) {
.no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }),
.yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-fpch-validate-input-files-content", "-o", out_obj_path }),
.stdout => argv.appendAssumeCapacity("-E"),
}

Expand Down Expand Up @@ -4453,11 +4453,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
try argv.appendSlice(c_object.src.extra_flags);
try argv.appendSlice(c_object.src.cache_exempt_flags);

try argv.ensureUnusedCapacity(6);
try argv.ensureUnusedCapacity(7);
switch (comp.clang_preprocessor_mode) {
.no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }),
.yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-fpch-validate-input-files-content", "-o", out_obj_path }),
.stdout => argv.appendAssumeCapacity("-E"),
}
if (comp.clang_passthrough_mode) {
Expand Down
6 changes: 6 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,12 @@ fn buildOutputType(
emit_bin = .{ .yes = arg["-femit-bin=".len..] };
} else if (mem.eql(u8, arg, "-fno-emit-bin")) {
emit_bin = .no;
} else if (mem.eql(u8, arg, "-femit-pch")) {
clang_preprocessor_mode = .pch;
emit_bin = .yes_default_path;
} else if (mem.startsWith(u8, arg, "-femit-pch=")) {
clang_preprocessor_mode = .pch;
emit_bin = .{ .yes = arg["-femit-pch=".len..] };
} else if (mem.eql(u8, arg, "-femit-h")) {
emit_h = .yes_default_path;
} else if (mem.startsWith(u8, arg, "-femit-h=")) {
Expand Down
4 changes: 4 additions & 0 deletions test/standalone.zig
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ pub const build_cases = [_]BuildCase{
.build_root = "test/standalone/depend_on_main_mod",
.import = @import("standalone/depend_on_main_mod/build.zig"),
},
.{
.build_root = "test/standalone/pch",
.import = @import("standalone/pch/build.zig"),
},
};

const std = @import("std");
Loading

0 comments on commit 12f6a7d

Please sign in to comment.