Skip to content

Commit

Permalink
std.Build: precompiled_header option can now simply be a headerfile.
Browse files Browse the repository at this point in the history
a compile step to build the pch file will be automatically created.

To benefit from precompiled headers, it is now possible to simply change
exe.addCSourceFiles(.{
            .files = &.{"file.c"},
            .flags = ...,
        });

into

exe.addCSourceFiles(.{
            .files = &.{"file.c"},
            .flags = ...,
            .precompiled_header = .{ .source_header = .{ path = b.path("allincludes.h") } },
        });
  • Loading branch information
xxxbxxx committed Aug 24, 2024
1 parent 94a7715 commit 09e8594
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 25 deletions.
40 changes: 34 additions & 6 deletions lib/std/Build/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -146,21 +146,37 @@ pub const CSourceLang = enum {
}
};

pub const PrecompiledHeader = union(enum) {
/// automatically create the PCH compile step for the source header file,
/// inheriting the options from the parent compile step.
source_header: struct { path: LazyPath, lang: ?CSourceLang = null },

/// final PCH compile step,
/// can be provided by the user or else will be created from the `source_header` field during step finalization.
pch_step: *Step.Compile,

pub fn getPath(pch: PrecompiledHeader, b: *std.Build) []const u8 {
switch (pch) {
.source_header => unreachable,
.pch_step => |pch_step| return pch_step.getEmittedBin().getPath(b),
}
}
};
pub const CSourceFiles = struct {
root: LazyPath,
/// `files` is relative to `root`, which is
/// the build root by default
files: []const []const u8,
lang: ?CSourceLang = null,
flags: []const []const u8,
precompiled_header: ?LazyPath = null,
precompiled_header: ?PrecompiledHeader = null,
};

pub const CSourceFile = struct {
file: LazyPath,
lang: ?CSourceLang = null,
flags: []const []const u8 = &.{},
precompiled_header: ?LazyPath = null,
precompiled_header: ?PrecompiledHeader = null,

pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
return .{
Expand Down Expand Up @@ -396,7 +412,7 @@ fn addStepDependencies(m: *Module, module: *Module, dependee: *Step) void {
}
}

fn addStepDependenciesOnly(m: *Module, dependee: *Step) void {
pub fn addStepDependenciesOnly(m: *Module, dependee: *Step) void {
for (m.depending_steps.keys()) |compile| {
compile.step.dependOn(dependee);
}
Expand Down Expand Up @@ -560,7 +576,7 @@ pub const AddCSourceFilesOptions = struct {
files: []const []const u8,
lang: ?CSourceLang = null,
flags: []const []const u8 = &.{},
precompiled_header: ?LazyPath = null,
precompiled_header: ?PrecompiledHeader = null,
};

/// Handy when you have many C/C++ source files and want them all to have the same flags.
Expand Down Expand Up @@ -589,7 +605,13 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
addLazyPathDependenciesOnly(m, c_source_files.root);

if (options.precompiled_header) |pch| {
addLazyPathDependenciesOnly(m, pch);
switch (pch) {
.source_header => |src| addLazyPathDependenciesOnly(m, src.path),
.pch_step => |step| {
_ = step.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
addStepDependenciesOnly(m, &step.step);
},
}
}
}

Expand All @@ -602,7 +624,13 @@ pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
addLazyPathDependenciesOnly(m, source.file);

if (source.precompiled_header) |pch| {
addLazyPathDependenciesOnly(m, pch);
switch (pch) {
.source_header => |src| addLazyPathDependenciesOnly(m, src.path),
.pch_step => |step| {
_ = step.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
addStepDependenciesOnly(m, &step.step);
},
}
}
}

Expand Down
68 changes: 68 additions & 0 deletions lib/std/Build/Step/Compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {

fn finalize(step: *Step) !void {
const compile: *Compile = @fieldParentPtr("step", step);
const b = step.owner;

if (compile.kind == .pch) {
// precompiled headers must have a single input header file.
Expand All @@ -1836,6 +1837,73 @@ fn finalize(step: *Step) !void {
if (key.module.link_libcpp == true) compile.is_linking_libcpp = true;
}
}

// add additional compile steps for precompiled headers
for (compile.root_module.link_objects.items) |*link_object| {
var precompiled_header_ptr: ?*Module.PrecompiledHeader = null;
var flags: []const []const u8 = undefined;
switch (link_object.*) {
.c_source_file => |src| {
if (src.precompiled_header) |*pch| {
precompiled_header_ptr = pch;
flags = src.flags;
}
},
.c_source_files => |src| {
if (src.precompiled_header) |*pch| {
precompiled_header_ptr = pch;
flags = src.flags;
}
},
else => {},
}

if (precompiled_header_ptr) |pch_ptr| {
switch (pch_ptr.*) {
.pch_step => {
// step customized by the user, nothing to do.
},
.source_header => |src| {
const name = switch (src.path) {
.src_path => |sp| fs.path.basename(sp.sub_path),
.cwd_relative => |p| fs.path.basename(p),
.generated => "generated",
.dependency => "dependency",
};

const step_name = b.fmt("zig build-pch {s}{s} {s}", .{
name,
@tagName(compile.root_module.optimize orelse .Debug),
compile.root_module.resolved_target.?.query.zigTriple(b.allocator) catch @panic("OOM"),
});

// while a new compile step is generated for each use,
// we leverage the cache system to reuse the generated pch file when possible.
const compile_pch = b.allocator.create(Compile) catch @panic("OOM");

// For robustness, suppose all options have an impact on the header compilation.
// (instead of auditing each llvm version for flags observable from header compilation)
// So, copy everything and minimally adjust as needed:
compile_pch.* = compile.*;

compile_pch.kind = .pch;
compile_pch.step.name = step_name;
compile_pch.name = name;
compile_pch.out_filename = std.fmt.allocPrint(b.allocator, "{s}.pch", .{name}) catch @panic("OOM");
compile_pch.installed_headers = ArrayList(HeaderInstallation).init(b.allocator);
compile_pch.force_undefined_symbols = StringHashMap(void).init(b.allocator);

compile_pch.root_module.link_objects = .{};
compile_pch.addCSourceFile(.{ .file = src.path, .lang = src.lang, .flags = flags });

// finalize the parent compile step by modifying it to use the generated pch compile step
pch_ptr.* = .{ .pch_step = compile_pch };
_ = compile_pch.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
compile.root_module.addStepDependenciesOnly(&compile_pch.step);
},
}
}
}
}

fn make(step: *Step, options: Step.MakeOptions) !void {
Expand Down
29 changes: 16 additions & 13 deletions test/standalone/c_header/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&b.addRunArtifact(exe).step);
}

// testcase 2: precompiled c-header
// testcase 2: precompiled header in C, from a generated file, with a compile step generated automaticcaly, twice with a cache hit
// and it also test the explicit source lang not inferred from file extenson.
{
const exe = b.addExecutable(.{
.name = "pchtest",
Expand All @@ -36,28 +37,30 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});

const pch = b.addPrecompiledCHeader(.{
.name = "pch_c",
.target = target,
.optimize = optimize,
.link_libc = true,
}, .{
.file = b.path("include_a.h"),
const generated_header = b.addWriteFiles().add("generated.h",
\\ /* generated file */
\\ #include "include_a.h"
);

exe.addCSourceFile(.{
.file = b.path("test.c2"),
.flags = &[_][]const u8{},
.lang = .h,
.lang = .c,
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
});

exe.addCSourceFiles(.{
.files = &.{"test.c"},
.flags = &[_][]const u8{},
.lang = .c,
.precompiled_header = pch.getEmittedBin(),
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
});

exe.addIncludePath(b.path("."));

test_step.dependOn(&b.addRunArtifact(exe).step);
}

// testcase 3: precompiled c++-header
// testcase 3: precompiled header in C++, from a .h file that must be precompiled as c++, with an explicit pch compile step.
{
const exe = b.addExecutable(.{
.name = "pchtest++",
Expand All @@ -80,7 +83,7 @@ pub fn build(b: *std.Build) void {
exe.addCSourceFile(.{
.file = b.path("test.cpp"),
.flags = &[_][]const u8{},
.precompiled_header = pch.getEmittedBin(),
.precompiled_header = .{ .pch_step = pch },
});

test_step.dependOn(&b.addRunArtifact(exe).step);
Expand Down
1 change: 1 addition & 0 deletions test/standalone/c_header/include_a.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <iostream>
#else
#include <stdio.h>
#include <stdbool.h>
#endif

#define A_INCLUDED 1
Expand Down
9 changes: 3 additions & 6 deletions test/standalone/c_header/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@
#error "pch not included"
#endif

extern int func(real a, bool cond);

int main(int argc, char *argv[])
{
real a = 0.123;

if (argc > 1) {
fprintf(stdout, "abs(%g)=%g\n", a, fabs(a));
}

return EXIT_SUCCESS;
return func(a, (argc > 1));
}
20 changes: 20 additions & 0 deletions test/standalone/c_header/test.c2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

// includes commented out to make sure the symbols come from the precompiled header.
//#include "include_a.h"
//#include "include_b.h"

#ifndef A_INCLUDED
#error "pch not included"
#endif
#ifndef B_INCLUDED
#error "pch not included"
#endif

int func(real a, bool cond)
{
if (cond) {
fprintf(stdout, "abs(%g)=%g\n", a, fabs(a));
}

return EXIT_SUCCESS;
}

0 comments on commit 09e8594

Please sign in to comment.