Skip to content

Commit 0bd9380

Browse files
committed
std.Build: precompiled_header option can now directly be a headerfile.
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("rootincludes.h") } }, });
1 parent 1dbc006 commit 0bd9380

File tree

7 files changed

+142
-24
lines changed

7 files changed

+142
-24
lines changed

lib/compiler/build_runner.zig

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,11 +1546,17 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
15461546
.system_lib => {},
15471547
.c_source_file => |source| {
15481548
source.file.addStepDependencies(step);
1549-
if (source.precompiled_header) |pch| pch.addStepDependencies(step);
1549+
if (source.precompiled_header) |pch| switch (pch) {
1550+
.source_header => |src| src.path.addStepDependencies(step),
1551+
.pch_step => |s| step.dependOn(&s.step),
1552+
};
15501553
},
15511554
.c_source_files => |source_files| {
15521555
source_files.root.addStepDependencies(step);
1553-
if (source_files.precompiled_header) |pch| pch.addStepDependencies(step);
1556+
if (source_files.precompiled_header) |pch| switch (pch) {
1557+
.source_header => |src| src.path.addStepDependencies(step),
1558+
.pch_step => |s| step.dependOn(&s.step),
1559+
};
15541560
},
15551561
.win32_resource_file => |rc_source| {
15561562
rc_source.file.addStepDependencies(step);

lib/std/Build/Module.zig

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,22 @@ pub const CSourceLang = enum {
149149
}
150150
};
151151

152+
pub const PrecompiledHeader = union(enum) {
153+
/// automatically create the PCH compile step for the source header file,
154+
/// inheriting the options from the parent compile step.
155+
source_header: struct { path: LazyPath, lang: ?CSourceLang = null },
156+
157+
/// final PCH compile step,
158+
/// can be provided by the user or else will be created from the `source_header` field during step finalization.
159+
pch_step: *Step.Compile,
160+
161+
pub fn getPath(pch: PrecompiledHeader, b: *std.Build) []const u8 {
162+
switch (pch) {
163+
.source_header => unreachable,
164+
.pch_step => |pch_step| return pch_step.getEmittedBin().getPath(b),
165+
}
166+
}
167+
};
152168
pub const CSourceFiles = struct {
153169
root: LazyPath,
154170
/// `files` is relative to `root`, which is
@@ -157,14 +173,14 @@ pub const CSourceFiles = struct {
157173
/// if null, deduce the language from the file extension
158174
lang: ?CSourceLang = null,
159175
flags: []const []const u8,
160-
precompiled_header: ?LazyPath = null,
176+
precompiled_header: ?PrecompiledHeader = null,
161177
};
162178

163179
pub const CSourceFile = struct {
164180
file: LazyPath,
165181
lang: ?CSourceLang = null,
166182
flags: []const []const u8 = &.{},
167-
precompiled_header: ?LazyPath = null,
183+
precompiled_header: ?PrecompiledHeader = null,
168184

169185
pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
170186
return .{
@@ -457,7 +473,7 @@ pub const AddCSourceFilesOptions = struct {
457473
files: []const []const u8,
458474
lang: ?CSourceLang = null,
459475
flags: []const []const u8 = &.{},
460-
precompiled_header: ?LazyPath = null,
476+
precompiled_header: ?PrecompiledHeader = null,
461477
};
462478

463479
/// Handy when you have many C/C++ source files and want them all to have the same flags.
@@ -483,6 +499,13 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
483499
.precompiled_header = options.precompiled_header,
484500
};
485501
m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM");
502+
503+
if (options.precompiled_header) |pch| {
504+
switch (pch) {
505+
.source_header => {},
506+
.pch_step => |step| _ = step.getEmittedBin(), // Indicate there is a dependency on the outputted binary.
507+
}
508+
}
486509
}
487510

488511
pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
@@ -491,6 +514,13 @@ pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
491514
const c_source_file = allocator.create(CSourceFile) catch @panic("OOM");
492515
c_source_file.* = source.dupe(b);
493516
m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM");
517+
518+
if (source.precompiled_header) |pch| {
519+
switch (pch) {
520+
.source_header => {},
521+
.pch_step => |step| _ = step.getEmittedBin(), // Indicate there is a dependency on the outputted binary.
522+
}
523+
}
494524
}
495525

496526
/// Resource files must have the extension `.rc`.

lib/std/Build/Step/Compile.zig

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
17731773

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

17771778
// Fully recursive iteration including dynamic libraries to detect
17781779
// libc and libc++ linkage.
@@ -1790,6 +1791,66 @@ fn finalize(step: *Step) !void {
17901791
const link_objects = deps[0].root_module.link_objects.items;
17911792
assert(link_objects.len == 1 and link_objects[0] == .c_source_file);
17921793
}
1794+
1795+
// add additional compile steps for precompiled headers
1796+
for (compile.root_module.link_objects.items) |*link_object| {
1797+
const pch_ptr: *Module.PrecompiledHeader, const flags: []const []const u8 = blk: {
1798+
switch (link_object.*) {
1799+
.c_source_file => |src| {
1800+
if (src.precompiled_header) |*pch| break :blk .{ pch, src.flags };
1801+
},
1802+
.c_source_files => |src| {
1803+
if (src.precompiled_header) |*pch| break :blk .{ pch, src.flags };
1804+
},
1805+
else => {},
1806+
}
1807+
1808+
continue;
1809+
};
1810+
1811+
switch (pch_ptr.*) {
1812+
.pch_step => {
1813+
// step customized by the user, nothing to do.
1814+
},
1815+
.source_header => |src| {
1816+
const name = switch (src.path) {
1817+
.src_path => |sp| fs.path.basename(sp.sub_path),
1818+
.cwd_relative => |p| fs.path.basename(p),
1819+
.generated => "generated",
1820+
.dependency => "dependency",
1821+
};
1822+
1823+
const step_name = b.fmt("zig build-pch {s}{s} {s}", .{
1824+
name,
1825+
@tagName(compile.root_module.optimize orelse .Debug),
1826+
compile.root_module.resolved_target.?.query.zigTriple(b.allocator) catch @panic("OOM"),
1827+
});
1828+
1829+
// We generate a new compile step for each use,
1830+
// leveraging the cache system to reuse the generated pch file when possible.
1831+
const compile_pch = b.allocator.create(Compile) catch @panic("OOM");
1832+
1833+
// For robustness, suppose all options have an impact on the header compilation.
1834+
// (instead of auditing each llvm version for flags observable from header compilation)
1835+
// So, copy everything and minimally adjust as needed:
1836+
compile_pch.* = compile.*;
1837+
1838+
compile_pch.kind = .pch;
1839+
compile_pch.step.name = step_name;
1840+
compile_pch.name = name;
1841+
compile_pch.out_filename = std.fmt.allocPrint(b.allocator, "{s}.pch", .{name}) catch @panic("OOM");
1842+
compile_pch.installed_headers = .init(b.allocator);
1843+
compile_pch.force_undefined_symbols = .init(b.allocator);
1844+
1845+
compile_pch.root_module.link_objects = .{};
1846+
compile_pch.root_module.addCSourceFile(.{ .file = src.path, .lang = src.lang, .flags = flags });
1847+
1848+
// finalize the parent compile step by modifying it to use the generated pch compile step
1849+
pch_ptr.* = .{ .pch_step = compile_pch };
1850+
_ = compile_pch.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
1851+
},
1852+
}
1853+
}
17931854
}
17941855

17951856
fn make(step: *Step, options: Step.MakeOptions) !void {

test/standalone/c_header/build.zig

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ pub fn build(b: *std.Build) void {
2727
test_step.dependOn(&b.addRunArtifact(exe).step);
2828
}
2929

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

39-
const pch = b.addPrecompiledCHeader(.{
40-
.name = "pch_c",
41-
.target = target,
42-
.optimize = optimize,
43-
.link_libc = true,
44-
}, .{
45-
.file = b.path("include_a.h"),
40+
const generated_header = b.addWriteFiles().add("generated.h",
41+
\\ /* generated file */
42+
\\ #include "include_a.h"
43+
);
44+
45+
exe.addCSourceFile(.{
46+
.file = b.path("test.c2"),
4647
.flags = &[_][]const u8{},
47-
.lang = .h,
48+
.lang = .c,
49+
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
4850
});
49-
5051
exe.addCSourceFiles(.{
5152
.files = &.{"test.c"},
5253
.flags = &[_][]const u8{},
5354
.lang = .c,
54-
.precompiled_header = pch.getEmittedBin(),
55+
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
5556
});
5657

58+
exe.addIncludePath(b.path("."));
59+
5760
test_step.dependOn(&b.addRunArtifact(exe).step);
5861
}
5962

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

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

test/standalone/c_header/include_a.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <iostream>
1010
#else
1111
#include <stdio.h>
12+
#include <stdbool.h>
1213
#endif
1314

1415
#define A_INCLUDED 1

test/standalone/c_header/test.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@
1010
#error "pch not included"
1111
#endif
1212

13+
extern int func(real a, bool cond);
14+
1315
int main(int argc, char *argv[])
1416
{
1517
real a = 0.123;
16-
17-
if (argc > 1) {
18-
fprintf(stdout, "abs(%g)=%g\n", a, fabs(a));
19-
}
20-
21-
return EXIT_SUCCESS;
18+
return func(a, (argc > 1));
2219
}

test/standalone/c_header/test.c2

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
// includes commented out to make sure the symbols come from the precompiled header.
3+
//#include "include_a.h"
4+
//#include "include_b.h"
5+
6+
#ifndef A_INCLUDED
7+
#error "pch not included"
8+
#endif
9+
#ifndef B_INCLUDED
10+
#error "pch not included"
11+
#endif
12+
13+
int func(real a, bool cond)
14+
{
15+
if (cond) {
16+
fprintf(stdout, "abs(%g)=%g\n", a, fabs(a));
17+
}
18+
19+
return EXIT_SUCCESS;
20+
}

0 commit comments

Comments
 (0)