Skip to content

Commit

Permalink
std.Build: it is no longer necessary to manually create a compile ste…
Browse files Browse the repository at this point in the history
…p to use precompiled headers.

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 = b.path("all.h") },
        });
  • Loading branch information
xxxbxxx committed Jul 31, 2024
1 parent b677e19 commit 8fbe309
Show file tree
Hide file tree
Showing 6 changed files with 140 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 created from the `source_file` 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
66 changes: 66 additions & 0 deletions lib/std/Build/Step/Compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,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 @@ -1837,6 +1838,71 @@ fn finalize(step: *Step) !void {
if (key.module.link_libcpp == true) compile.is_linking_libcpp = true;
}
}

// materialize 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 => |c_source_file| {
if (c_source_file.precompiled_header) |*pch| {
precompiled_header_ptr = pch;
flags = c_source_file.flags;
}
},
.c_source_files => |c_source_files| {
if (c_source_files.precompiled_header) |*pch| {
precompiled_header_ptr = pch;
flags = c_source_files.flags;
}
},
else => {},
}

if (precompiled_header_ptr) |pch_ptr| {
switch (pch_ptr.*) {
.pch_step => {},
.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 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/pch/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// c-header
// test case 1: precompiled hader 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 @@ -16,28 +17,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);
}

// c++-header
// test case 2: precompiled hader in C++, from a .h file that must be precompiled as c++, with an explicit compile step.
{
const exe = b.addExecutable(.{
.name = "pchtest++",
Expand All @@ -61,7 +64,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/pch/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/pch/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/pch/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 8fbe309

Please sign in to comment.