Skip to content

Commit 8fbe309

Browse files
committed
std.Build: it is no longer necessary to manually create a compile step 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") }, });
1 parent b677e19 commit 8fbe309

File tree

6 files changed

+140
-25
lines changed

6 files changed

+140
-25
lines changed

lib/std/Build/Module.zig

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,37 @@ pub const CSourceLang = enum {
146146
}
147147
};
148148

149+
pub const PrecompiledHeader = union(enum) {
150+
/// automatically create the PCH compile step for the source header file,
151+
/// inheriting the options from the parent compile step.
152+
source_header: struct { path: LazyPath, lang: ?CSourceLang = null },
153+
154+
/// final PCH compile step,
155+
/// can be provided by the user or created from the `source_file` field during step finalization.
156+
pch_step: *Step.Compile,
157+
158+
pub fn getPath(pch: PrecompiledHeader, b: *std.Build) []const u8 {
159+
switch (pch) {
160+
.source_header => unreachable,
161+
.pch_step => |pch_step| return pch_step.getEmittedBin().getPath(b),
162+
}
163+
}
164+
};
149165
pub const CSourceFiles = struct {
150166
root: LazyPath,
151167
/// `files` is relative to `root`, which is
152168
/// the build root by default
153169
files: []const []const u8,
154170
lang: ?CSourceLang = null,
155171
flags: []const []const u8,
156-
precompiled_header: ?LazyPath = null,
172+
precompiled_header: ?PrecompiledHeader = null,
157173
};
158174

159175
pub const CSourceFile = struct {
160176
file: LazyPath,
161177
lang: ?CSourceLang = null,
162178
flags: []const []const u8 = &.{},
163-
precompiled_header: ?LazyPath = null,
179+
precompiled_header: ?PrecompiledHeader = null,
164180

165181
pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
166182
return .{
@@ -396,7 +412,7 @@ fn addStepDependencies(m: *Module, module: *Module, dependee: *Step) void {
396412
}
397413
}
398414

399-
fn addStepDependenciesOnly(m: *Module, dependee: *Step) void {
415+
pub fn addStepDependenciesOnly(m: *Module, dependee: *Step) void {
400416
for (m.depending_steps.keys()) |compile| {
401417
compile.step.dependOn(dependee);
402418
}
@@ -560,7 +576,7 @@ pub const AddCSourceFilesOptions = struct {
560576
files: []const []const u8,
561577
lang: ?CSourceLang = null,
562578
flags: []const []const u8 = &.{},
563-
precompiled_header: ?LazyPath = null,
579+
precompiled_header: ?PrecompiledHeader = null,
564580
};
565581

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

591607
if (options.precompiled_header) |pch| {
592-
addLazyPathDependenciesOnly(m, pch);
608+
switch (pch) {
609+
.source_header => |src| addLazyPathDependenciesOnly(m, src.path),
610+
.pch_step => |step| {
611+
_ = step.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
612+
addStepDependenciesOnly(m, &step.step);
613+
},
614+
}
593615
}
594616
}
595617

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

604626
if (source.precompiled_header) |pch| {
605-
addLazyPathDependenciesOnly(m, pch);
627+
switch (pch) {
628+
.source_header => |src| addLazyPathDependenciesOnly(m, src.path),
629+
.pch_step => |step| {
630+
_ = step.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
631+
addStepDependenciesOnly(m, &step.step);
632+
},
633+
}
606634
}
607635
}
608636

lib/std/Build/Step/Compile.zig

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

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

18231824
if (compile.kind == .pch) {
18241825
// precompiled headers must have a single input header file.
@@ -1837,6 +1838,71 @@ fn finalize(step: *Step) !void {
18371838
if (key.module.link_libcpp == true) compile.is_linking_libcpp = true;
18381839
}
18391840
}
1841+
1842+
// materialize additional compile steps for precompiled headers
1843+
for (compile.root_module.link_objects.items) |*link_object| {
1844+
var precompiled_header_ptr: ?*Module.PrecompiledHeader = null;
1845+
var flags: []const []const u8 = undefined;
1846+
switch (link_object.*) {
1847+
.c_source_file => |c_source_file| {
1848+
if (c_source_file.precompiled_header) |*pch| {
1849+
precompiled_header_ptr = pch;
1850+
flags = c_source_file.flags;
1851+
}
1852+
},
1853+
.c_source_files => |c_source_files| {
1854+
if (c_source_files.precompiled_header) |*pch| {
1855+
precompiled_header_ptr = pch;
1856+
flags = c_source_files.flags;
1857+
}
1858+
},
1859+
else => {},
1860+
}
1861+
1862+
if (precompiled_header_ptr) |pch_ptr| {
1863+
switch (pch_ptr.*) {
1864+
.pch_step => {},
1865+
.source_header => |src| {
1866+
const name = switch (src.path) {
1867+
.src_path => |sp| fs.path.basename(sp.sub_path),
1868+
.cwd_relative => |p| fs.path.basename(p),
1869+
.generated => "generated",
1870+
.dependency => "dependency",
1871+
};
1872+
1873+
const step_name = b.fmt("zig build-pch {s}{s} {s}", .{
1874+
name,
1875+
@tagName(compile.root_module.optimize orelse .Debug),
1876+
compile.root_module.resolved_target.?.query.zigTriple(b.allocator) catch @panic("OOM"),
1877+
});
1878+
1879+
// while a new compile step is generated for each use,
1880+
// we leverage the cache system to reuse the generated pch file when possible.
1881+
const compile_pch = b.allocator.create(Compile) catch @panic("OOM");
1882+
1883+
// For robustness, suppose all options have an impact on the header compilation.
1884+
// (instead of auditing each llvm version for flags observable from header compilation)
1885+
// So, copy everything and minimally adjust as needed:
1886+
compile_pch.* = compile.*;
1887+
1888+
compile_pch.kind = .pch;
1889+
compile_pch.step.name = step_name;
1890+
compile_pch.name = name;
1891+
compile_pch.out_filename = std.fmt.allocPrint(b.allocator, "{s}.pch", .{name}) catch @panic("OOM");
1892+
compile_pch.installed_headers = ArrayList(HeaderInstallation).init(b.allocator);
1893+
compile_pch.force_undefined_symbols = StringHashMap(void).init(b.allocator);
1894+
1895+
compile_pch.root_module.link_objects = .{};
1896+
compile_pch.addCSourceFile(.{ .file = src.path, .lang = src.lang, .flags = flags });
1897+
1898+
// finalize the step by modifying it to use the generated pch compile step
1899+
pch_ptr.* = .{ .pch_step = compile_pch };
1900+
_ = compile_pch.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
1901+
compile.root_module.addStepDependenciesOnly(&compile_pch.step);
1902+
},
1903+
}
1904+
}
1905+
}
18401906
}
18411907

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

test/standalone/pch/build.zig

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ pub fn build(b: *std.Build) void {
77
const target = b.standardTargetOptions(.{});
88
const optimize = b.standardOptimizeOption(.{});
99

10-
// c-header
10+
// test case 1: precompiled hader in C, from a generated file, with a compile step generated automaticcaly, twice with a cache hit
11+
// and it also test the explicit source lang not inferred from file extenson.
1112
{
1213
const exe = b.addExecutable(.{
1314
.name = "pchtest",
@@ -16,28 +17,30 @@ pub fn build(b: *std.Build) void {
1617
.link_libc = true,
1718
});
1819

19-
const pch = b.addPrecompiledCHeader(.{
20-
.name = "pch_c",
21-
.target = target,
22-
.optimize = optimize,
23-
.link_libc = true,
24-
}, .{
25-
.file = b.path("include_a.h"),
20+
const generated_header = b.addWriteFiles().add("generated.h",
21+
\\ /* generated file */
22+
\\ #include "include_a.h"
23+
);
24+
25+
exe.addCSourceFile(.{
26+
.file = b.path("test.c2"),
2627
.flags = &[_][]const u8{},
27-
.lang = .h,
28+
.lang = .c,
29+
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
2830
});
29-
3031
exe.addCSourceFiles(.{
3132
.files = &.{"test.c"},
3233
.flags = &[_][]const u8{},
3334
.lang = .c,
34-
.precompiled_header = pch.getEmittedBin(),
35+
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
3536
});
3637

38+
exe.addIncludePath(b.path("."));
39+
3740
test_step.dependOn(&b.addRunArtifact(exe).step);
3841
}
3942

40-
// c++-header
43+
// test case 2: precompiled hader in C++, from a .h file that must be precompiled as c++, with an explicit compile step.
4144
{
4245
const exe = b.addExecutable(.{
4346
.name = "pchtest++",
@@ -61,7 +64,7 @@ pub fn build(b: *std.Build) void {
6164
exe.addCSourceFile(.{
6265
.file = b.path("test.cpp"),
6366
.flags = &[_][]const u8{},
64-
.precompiled_header = pch.getEmittedBin(),
67+
.precompiled_header = .{ .pch_step = pch },
6568
});
6669

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

test/standalone/pch/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/pch/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/pch/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)