Skip to content

Commit 6b7a99b

Browse files
committed
std.Build: add precompiled C header support
v2: add `-fpch-validate-input-files-content` "Validate PCH input files based on content if mtime differs" so that llvm check matches zig caching behaviour. v3: explicitly require llvm extensions for the build step
1 parent f888a3a commit 6b7a99b

File tree

9 files changed

+228
-18
lines changed

9 files changed

+228
-18
lines changed

lib/std/Build.zig

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,29 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
700700
});
701701
}
702702

703+
pub const PchOptions = struct {
704+
name: []const u8,
705+
target: CrossTarget,
706+
optimize: std.builtin.OptimizeMode,
707+
max_rss: usize = 0,
708+
cpp_header: bool = false,
709+
};
710+
711+
pub fn addPrecompiledCHeader(b: *Build, options: PchOptions, source: Step.Compile.CSourceFile) *Step.Compile {
712+
const pch = Step.Compile.create(b, .{
713+
.name = options.name,
714+
.target = options.target,
715+
.optimize = options.optimize,
716+
.kind = .pch,
717+
.max_rss = options.max_rss,
718+
.use_llvm = true,
719+
});
720+
pch.is_linking_libc = !options.cpp_header;
721+
pch.is_linking_libcpp = options.cpp_header;
722+
pch.addCSourceFile(source);
723+
return pch;
724+
}
725+
703726
pub const SharedLibraryOptions = struct {
704727
name: []const u8,
705728
root_source_file: ?LazyPath = null,

lib/std/Build/Step/Compile.zig

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ root_src: ?LazyPath,
8383
out_lib_filename: []const u8,
8484
modules: std.StringArrayHashMap(*Module),
8585

86+
precompiled_header: ?*Compile,
8687
link_objects: ArrayList(LinkObject),
8788
include_dirs: ArrayList(IncludeDir),
8889
c_macros: ArrayList([]const u8),
@@ -439,6 +440,7 @@ pub const Kind = enum {
439440
exe,
440441
lib,
441442
obj,
443+
pch,
442444
@"test",
443445
};
444446

@@ -462,6 +464,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
462464
.exe => "zig build-exe",
463465
.lib => "zig build-lib",
464466
.obj => "zig build-obj",
467+
.pch => "zig build-pch",
465468
.@"test" => "zig test",
466469
},
467470
name_adjusted,
@@ -471,20 +474,24 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
471474

472475
const target_info = NativeTargetInfo.detect(options.target) catch @panic("unhandled error");
473476

474-
const out_filename = std.zig.binNameAlloc(owner.allocator, .{
475-
.root_name = name,
476-
.target = target_info.target,
477-
.output_mode = switch (options.kind) {
478-
.lib => .Lib,
479-
.obj => .Obj,
480-
.exe, .@"test" => .Exe,
481-
},
482-
.link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
483-
.dynamic => .Dynamic,
484-
.static => .Static,
485-
}) else null,
486-
.version = options.version,
487-
}) catch @panic("OOM");
477+
const out_filename = if (options.kind == .pch)
478+
std.fmt.allocPrint(owner.allocator, "{s}.pch", .{name}) catch @panic("OOM")
479+
else
480+
std.zig.binNameAlloc(owner.allocator, .{
481+
.root_name = name,
482+
.target = target_info.target,
483+
.output_mode = switch (options.kind) {
484+
.lib => .Lib,
485+
.obj => .Obj,
486+
.exe, .@"test" => .Exe,
487+
.pch => unreachable,
488+
},
489+
.link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
490+
.dynamic => .Dynamic,
491+
.static => .Static,
492+
}) else null,
493+
.version = options.version,
494+
}) catch @panic("OOM");
488495

489496
const self = owner.allocator.create(Compile) catch @panic("OOM");
490497
self.* = .{
@@ -518,6 +525,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
518525
.lib_paths = ArrayList(LazyPath).init(owner.allocator),
519526
.rpaths = ArrayList(LazyPath).init(owner.allocator),
520527
.installed_headers = ArrayList(*Step).init(owner.allocator),
528+
.precompiled_header = null,
521529
.c_std = std.Build.CStd.C99,
522530
.zig_lib_dir = null,
523531
.main_mod_path = null,
@@ -980,6 +988,8 @@ pub const AddCSourceFilesOptions = struct {
980988

981989
/// Handy when you have many C/C++ source files and want them all to have the same flags.
982990
pub fn addCSourceFiles(self: *Compile, options: AddCSourceFilesOptions) void {
991+
assert(self.kind != .pch); // pch can only be generated from a single C header file
992+
983993
const b = self.step.owner;
984994
const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM");
985995

@@ -995,6 +1005,8 @@ pub fn addCSourceFiles(self: *Compile, options: AddCSourceFilesOptions) void {
9951005
}
9961006

9971007
pub fn addCSourceFile(self: *Compile, source: CSourceFile) void {
1008+
assert(self.kind != .pch or self.link_objects.items.len == 0); // pch can only be generated from a single C header file
1009+
9981010
const b = self.step.owner;
9991011
const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM");
10001012
c_source_file.* = source.dupe(b);
@@ -1111,23 +1123,40 @@ pub fn getEmittedLlvmBc(self: *Compile) LazyPath {
11111123
}
11121124

11131125
pub fn addAssemblyFile(self: *Compile, source: LazyPath) void {
1126+
assert(self.kind != .pch); // pch can only be generated from a single C header file
1127+
11141128
const b = self.step.owner;
11151129
const source_duped = source.dupe(b);
11161130
self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM");
11171131
source_duped.addStepDependencies(&self.step);
11181132
}
11191133

11201134
pub fn addObjectFile(self: *Compile, source: LazyPath) void {
1135+
assert(self.kind != .pch); // pch can only be generated from a single C header file
1136+
11211137
const b = self.step.owner;
11221138
self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM");
11231139
source.addStepDependencies(&self.step);
11241140
}
11251141

11261142
pub fn addObject(self: *Compile, obj: *Compile) void {
1143+
assert(self.kind != .pch); // pch can only be generated from a single C header file
1144+
11271145
assert(obj.kind == .obj);
11281146
self.linkLibraryOrObject(obj);
11291147
}
11301148

1149+
pub fn addPrecompiledCHeader(self: *Compile, pch: *Compile) void {
1150+
assert(self.kind != .pch); // pch can only be generated from a single C header file
1151+
assert(pch.kind == .pch);
1152+
1153+
if (self.precompiled_header != null) @panic("Precompiled header already defined.");
1154+
if (self.use_llvm != true) @panic("LLVM extensions are required for precompiled header support.");
1155+
self.precompiled_header = pch;
1156+
1157+
pch.getEmittedBin().addStepDependencies(&self.step);
1158+
}
1159+
11311160
pub fn addAfterIncludePath(self: *Compile, path: LazyPath) void {
11321161
const b = self.step.owner;
11331162
self.include_dirs.append(IncludeDir{ .path_after = path.dupe(b) }) catch @panic("OOM");
@@ -1419,7 +1448,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
14191448
const cmd = switch (self.kind) {
14201449
.lib => "build-lib",
14211450
.exe => "build-exe",
1422-
.obj => "build-obj",
1451+
.obj, .pch => "build-obj",
14231452
.@"test" => "test",
14241453
};
14251454
try zig_args.append(cmd);
@@ -1435,6 +1464,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
14351464
try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)}));
14361465
}
14371466

1467+
if (self.kind == .pch) {
1468+
try zig_args.append("-x");
1469+
try zig_args.append(if (self.is_linking_libcpp) "c++-header" else "c-header");
1470+
}
1471+
14381472
switch (self.entry) {
14391473
.default => {},
14401474
.disabled => try zig_args.append("-fno-entry"),
@@ -1486,6 +1520,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
14861520
.other_step => |other| switch (other.kind) {
14871521
.exe => @panic("Cannot link with an executable build artifact"),
14881522
.@"test" => @panic("Cannot link with a test"),
1523+
.pch => @panic("Cannot link with a precompiled header file"),
14891524
.obj => {
14901525
try zig_args.append(other.getEmittedBin().getPath(b));
14911526
},
@@ -1582,7 +1617,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
15821617
},
15831618

15841619
.c_source_file => |c_source_file| {
1585-
if (c_source_file.flags.len == 0) {
1620+
if (c_source_file.flags.len == 0 and self.precompiled_header == null) {
15861621
if (prev_has_cflags) {
15871622
try zig_args.append("-cflags");
15881623
try zig_args.append("--");
@@ -1593,14 +1628,19 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
15931628
for (c_source_file.flags) |arg| {
15941629
try zig_args.append(arg);
15951630
}
1631+
if (self.precompiled_header) |pch| {
1632+
try zig_args.append("-include-pch");
1633+
try zig_args.append(pch.getEmittedBin().getPath(b));
1634+
try zig_args.append("-fpch-validate-input-files-content");
1635+
}
15961636
try zig_args.append("--");
15971637
prev_has_cflags = true;
15981638
}
15991639
try zig_args.append(c_source_file.file.getPath(b));
16001640
},
16011641

16021642
.c_source_files => |c_source_files| {
1603-
if (c_source_files.flags.len == 0) {
1643+
if (c_source_files.flags.len == 0 and self.precompiled_header == null) {
16041644
if (prev_has_cflags) {
16051645
try zig_args.append("-cflags");
16061646
try zig_args.append("--");
@@ -1611,6 +1651,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
16111651
for (c_source_files.flags) |flag| {
16121652
try zig_args.append(flag);
16131653
}
1654+
if (self.precompiled_header) |pch| {
1655+
try zig_args.append("-include-pch");
1656+
try zig_args.append(pch.getEmittedBin().getPath(b));
1657+
try zig_args.append("-fpch-validate-input-files-content");
1658+
}
16141659
try zig_args.append("--");
16151660
prev_has_cflags = true;
16161661
}

lib/std/Build/Step/InstallArtifact.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
5656
const dest_dir: ?InstallDir = switch (options.dest_dir) {
5757
.disabled => null,
5858
.default => switch (artifact.kind) {
59-
.obj => @panic("object files have no standard installation procedure"),
59+
.obj, .pch => @panic("object files have no standard installation procedure"),
6060
.exe, .@"test" => InstallDir{ .bin = {} },
6161
.lib => InstallDir{ .lib = {} },
6262
},

test/standalone.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ pub const build_cases = [_]BuildCase{
262262
.build_root = "test/standalone/ios",
263263
.import = @import("standalone/ios/build.zig"),
264264
},
265+
.{
266+
.build_root = "test/standalone/pch",
267+
.import = @import("standalone/pch/build.zig"),
268+
},
265269
};
266270

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

test/standalone/pch/build.zig

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const std = @import("std");
2+
const Builder = std.build.Builder;
3+
4+
pub fn build(b: *Builder) void {
5+
const test_step = b.step("test", "Test it");
6+
b.default_step = test_step;
7+
8+
const target = b.standardTargetOptions(.{});
9+
const mode = b.standardOptimizeOption(.{});
10+
11+
// c-header
12+
{
13+
const exe = b.addExecutable(.{
14+
.name = "pchtest",
15+
.target = target,
16+
.optimize = mode,
17+
.link_libc = true,
18+
.use_llvm = true,
19+
});
20+
21+
const pch = b.addPrecompiledCHeader(.{
22+
.name = "pch_c",
23+
.target = target,
24+
.optimize = mode,
25+
.cpp_header = false,
26+
}, .{
27+
.file = .{ .path = "include_a.h" },
28+
.flags = &[_][]const u8{},
29+
});
30+
31+
exe.addPrecompiledCHeader(pch);
32+
33+
exe.addCSourceFile(.{
34+
.file = .{ .path = "test.c" },
35+
.flags = &[_][]const u8{},
36+
});
37+
38+
test_step.dependOn(&b.addRunArtifact(exe).step);
39+
}
40+
41+
// c++-header
42+
{
43+
const exe = b.addExecutable(.{
44+
.name = "pchtest++",
45+
.target = target,
46+
.optimize = mode,
47+
.link_libc = true,
48+
.use_llvm = true,
49+
});
50+
exe.linkLibCpp();
51+
52+
const pch = b.addPrecompiledCHeader(.{
53+
.name = "pch_c++",
54+
.target = target,
55+
.optimize = mode,
56+
.cpp_header = true,
57+
}, .{
58+
.file = .{ .path = "include_a.h" },
59+
.flags = &[_][]const u8{},
60+
});
61+
62+
exe.addPrecompiledCHeader(pch);
63+
64+
exe.addCSourceFile(.{
65+
.file = .{ .path = "test.cpp" },
66+
.flags = &[_][]const u8{},
67+
});
68+
69+
test_step.dependOn(&b.addRunArtifact(exe).step);
70+
}
71+
}

test/standalone/pch/include_a.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include "include_b.h"
4+
5+
#include <string.h>
6+
#include <stdlib.h>
7+
8+
#if defined(__cplusplus)
9+
#include <iostream>
10+
#else
11+
#include <stdio.h>
12+
#endif
13+
14+
#define A_INCLUDED 1
15+

test/standalone/pch/include_b.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
#include <math.h>
4+
5+
typedef double real;
6+
7+
#define B_INCLUDED 1

test/standalone/pch/test.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
// includes commented out to make sure the symbols come from the precompiled header.
3+
//#include "includeA.h"
4+
//#include "includeB.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 main(int argc, char *argv[])
14+
{
15+
real a = 0.123;
16+
17+
if (argc > 1) {
18+
fprintf(stdout, "abs(%g)=%g\n", a, abs(a));
19+
}
20+
21+
return EXIT_SUCCESS;
22+
}

test/standalone/pch/test.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
// includes commented out to make sure the symbols come from the precompiled header.
3+
//#include "includeA.h"
4+
//#include "includeB.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 main(int argc, char *argv[])
14+
{
15+
real a = -0.123;
16+
17+
if (argc > 1) {
18+
std::cout << "abs(" << a << ")=" << fabs(a) << "\n";
19+
}
20+
21+
return EXIT_SUCCESS;
22+
}
23+

0 commit comments

Comments
 (0)