Skip to content

Commit

Permalink
std.Build: add precompiled C header support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
xxxbxxx committed Nov 25, 2023
1 parent 71540d3 commit f37ee3f
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 18 deletions.
23 changes: 23 additions & 0 deletions lib/std/Build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,29 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
});
}

pub const PchOptions = struct {
name: []const u8,
target: CrossTarget,
optimize: std.builtin.OptimizeMode,
max_rss: usize = 0,
cpp_header: bool = false,
};

pub fn addPrecompiledCHeader(b: *Build, options: PchOptions, source: Step.Compile.CSourceFile) *Step.Compile {
const pch = Step.Compile.create(b, .{
.name = options.name,
.target = options.target,
.optimize = options.optimize,
.kind = .pch,
.max_rss = options.max_rss,
.use_llvm = true,
});
pch.is_linking_libc = !options.cpp_header;
pch.is_linking_libcpp = options.cpp_header;
pch.addCSourceFile(source);
return pch;
}

pub const SharedLibraryOptions = struct {
name: []const u8,
root_source_file: ?LazyPath = null,
Expand Down
78 changes: 61 additions & 17 deletions lib/std/Build/Step/Compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ root_src: ?LazyPath,
out_lib_filename: []const u8,
modules: std.StringArrayHashMap(*Module),

precompiled_header: ?*Compile,
link_objects: ArrayList(LinkObject),
include_dirs: ArrayList(IncludeDir),
c_macros: ArrayList([]const u8),
Expand Down Expand Up @@ -439,6 +440,7 @@ pub const Kind = enum {
exe,
lib,
obj,
pch,
@"test",
};

Expand All @@ -462,6 +464,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.exe => "zig build-exe",
.lib => "zig build-lib",
.obj => "zig build-obj",
.pch => "zig build-pch",
.@"test" => "zig test",
},
name_adjusted,
Expand All @@ -471,20 +474,24 @@ pub fn create(owner: *std.Build, options: Options) *Compile {

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

const out_filename = std.zig.binNameAlloc(owner.allocator, .{
.root_name = name,
.target = target_info.target,
.output_mode = switch (options.kind) {
.lib => .Lib,
.obj => .Obj,
.exe, .@"test" => .Exe,
},
.link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
.dynamic => .Dynamic,
.static => .Static,
}) else null,
.version = options.version,
}) catch @panic("OOM");
const out_filename = if (options.kind == .pch)
std.fmt.allocPrint(owner.allocator, "{s}.pch", .{name}) catch @panic("OOM")
else
std.zig.binNameAlloc(owner.allocator, .{
.root_name = name,
.target = target_info.target,
.output_mode = switch (options.kind) {
.lib => .Lib,
.obj => .Obj,
.exe, .@"test" => .Exe,
.pch => unreachable,
},
.link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
.dynamic => .Dynamic,
.static => .Static,
}) else null,
.version = options.version,
}) catch @panic("OOM");

const self = owner.allocator.create(Compile) catch @panic("OOM");
self.* = .{
Expand Down Expand Up @@ -518,6 +525,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.lib_paths = ArrayList(LazyPath).init(owner.allocator),
.rpaths = ArrayList(LazyPath).init(owner.allocator),
.installed_headers = ArrayList(*Step).init(owner.allocator),
.precompiled_header = null,
.c_std = std.Build.CStd.C99,
.zig_lib_dir = null,
.main_mod_path = null,
Expand Down Expand Up @@ -980,6 +988,8 @@ pub const AddCSourceFilesOptions = struct {

/// Handy when you have many C/C++ source files and want them all to have the same flags.
pub fn addCSourceFiles(self: *Compile, options: AddCSourceFilesOptions) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file

const b = self.step.owner;
const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM");

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

pub fn addCSourceFile(self: *Compile, source: CSourceFile) void {
assert(self.kind != .pch or self.link_objects.items.len == 0); // pch can only be generated from a single C header file

const b = self.step.owner;
const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM");
c_source_file.* = source.dupe(b);
Expand Down Expand Up @@ -1111,23 +1123,39 @@ pub fn getEmittedLlvmBc(self: *Compile) LazyPath {
}

pub fn addAssemblyFile(self: *Compile, source: LazyPath) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file

const b = self.step.owner;
const source_duped = source.dupe(b);
self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM");
source_duped.addStepDependencies(&self.step);
}

pub fn addObjectFile(self: *Compile, source: LazyPath) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file

const b = self.step.owner;
self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM");
source.addStepDependencies(&self.step);
}

pub fn addObject(self: *Compile, obj: *Compile) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file

assert(obj.kind == .obj);
self.linkLibraryOrObject(obj);
}

pub fn addPrecompiledCHeader(self: *Compile, pch: *Compile) void {
assert(self.kind != .pch); // pch can only be generated from a single C header file
assert(pch.kind == .pch);

if (self.precompiled_header != null) @panic("Precompiled header already defined.");
self.precompiled_header = pch;

pch.getEmittedBin().addStepDependencies(&self.step);
}

pub fn addAfterIncludePath(self: *Compile, path: LazyPath) void {
const b = self.step.owner;
self.include_dirs.append(IncludeDir{ .path_after = path.dupe(b) }) catch @panic("OOM");
Expand Down Expand Up @@ -1419,7 +1447,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const cmd = switch (self.kind) {
.lib => "build-lib",
.exe => "build-exe",
.obj => "build-obj",
.obj, .pch => "build-obj",
.@"test" => "test",
};
try zig_args.append(cmd);
Expand All @@ -1435,6 +1463,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)}));
}

if (self.kind == .pch) {
try zig_args.append("-x");
try zig_args.append(if (self.is_linking_libcpp) "c++-header" else "c-header");
}

switch (self.entry) {
.default => {},
.disabled => try zig_args.append("-fno-entry"),
Expand Down Expand Up @@ -1486,6 +1519,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
.other_step => |other| switch (other.kind) {
.exe => @panic("Cannot link with an executable build artifact"),
.@"test" => @panic("Cannot link with a test"),
.pch => @panic("Cannot link with a precompiled header file"),
.obj => {
try zig_args.append(other.getEmittedBin().getPath(b));
},
Expand Down Expand Up @@ -1582,7 +1616,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
},

.c_source_file => |c_source_file| {
if (c_source_file.flags.len == 0) {
if (c_source_file.flags.len == 0 and self.precompiled_header == null) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
Expand All @@ -1593,14 +1627,19 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
for (c_source_file.flags) |arg| {
try zig_args.append(arg);
}
if (self.precompiled_header) |pch| {
try zig_args.append("-include-pch");
try zig_args.append(pch.getEmittedBin().getPath(b));
try zig_args.append("-fpch-validate-input-files-content");
}
try zig_args.append("--");
prev_has_cflags = true;
}
try zig_args.append(c_source_file.file.getPath(b));
},

.c_source_files => |c_source_files| {
if (c_source_files.flags.len == 0) {
if (c_source_files.flags.len == 0 and self.precompiled_header == null) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
Expand All @@ -1611,6 +1650,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
for (c_source_files.flags) |flag| {
try zig_args.append(flag);
}
if (self.precompiled_header) |pch| {
try zig_args.append("-include-pch");
try zig_args.append(pch.getEmittedBin().getPath(b));
try zig_args.append("-fpch-validate-input-files-content");
}
try zig_args.append("--");
prev_has_cflags = true;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/std/Build/Step/InstallArtifact.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
const dest_dir: ?InstallDir = switch (options.dest_dir) {
.disabled => null,
.default => switch (artifact.kind) {
.obj => @panic("object files have no standard installation procedure"),
.obj, .pch => @panic("object files have no standard installation procedure"),
.exe, .@"test" => InstallDir{ .bin = {} },
.lib => InstallDir{ .lib = {} },
},
Expand Down
4 changes: 4 additions & 0 deletions test/standalone.zig
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ pub const build_cases = [_]BuildCase{
.build_root = "test/standalone/ios",
.import = @import("standalone/ios/build.zig"),
},
.{
.build_root = "test/standalone/pch",
.import = @import("standalone/pch/build.zig"),
},
};

const std = @import("std");
69 changes: 69 additions & 0 deletions test/standalone/pch/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const std = @import("std");
const Builder = std.build.Builder;

pub fn build(b: *Builder) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;

const target = b.standardTargetOptions(.{});
const mode = b.standardOptimizeOption(.{});

// c-header
{
const exe = b.addExecutable(.{
.name = "pchtest",
.target = target,
.optimize = mode,
.link_libc = true,
});

const pch = b.addPrecompiledCHeader(.{
.name = "pch_c",
.target = target,
.optimize = mode,
.cpp_header = false,
}, .{
.file = .{ .path = "include_a.h" },
.flags = &[_][]const u8{},
});

exe.addPrecompiledCHeader(pch);

exe.addCSourceFile(.{
.file = .{ .path = "test.c" },
.flags = &[_][]const u8{},
});

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

// c++-header
{
const exe = b.addExecutable(.{
.name = "pchtest++",
.target = target,
.optimize = mode,
.link_libc = true,
});
exe.linkLibCpp();

const pch = b.addPrecompiledCHeader(.{
.name = "pch_c++",
.target = target,
.optimize = mode,
.cpp_header = true,
}, .{
.file = .{ .path = "include_a.h" },
.flags = &[_][]const u8{},
});

exe.addPrecompiledCHeader(pch);

exe.addCSourceFile(.{
.file = .{ .path = "test.cpp" },
.flags = &[_][]const u8{},
});

test_step.dependOn(&b.addRunArtifact(exe).step);
}
}
15 changes: 15 additions & 0 deletions test/standalone/pch/include_a.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include "include_b.h"

#include <string.h>
#include <stdlib.h>

#if defined(__cplusplus)
#include <iostream>
#else
#include <stdio.h>
#endif

#define A_INCLUDED 1

7 changes: 7 additions & 0 deletions test/standalone/pch/include_b.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include <math.h>

typedef double real;

#define B_INCLUDED 1
22 changes: 22 additions & 0 deletions test/standalone/pch/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

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

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

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

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

return EXIT_SUCCESS;
}
23 changes: 23 additions & 0 deletions test/standalone/pch/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

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

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

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

if (argc > 1) {
std::cout << "abs(" << a << ")=" << fabs(a) << "\n";
}

return EXIT_SUCCESS;
}

0 comments on commit f37ee3f

Please sign in to comment.