diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 267d28570d86..59bb75e3f34f 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -1546,11 +1546,17 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { .system_lib => {}, .c_source_file => |source| { source.file.addStepDependencies(step); - if (source.precompiled_header) |pch| pch.addStepDependencies(step); + if (source.precompiled_header) |pch| switch (pch) { + .source_header => |src| src.path.addStepDependencies(step), + .pch_step => |s| step.dependOn(&s.step), + }; }, .c_source_files => |source_files| { source_files.root.addStepDependencies(step); - if (source_files.precompiled_header) |pch| pch.addStepDependencies(step); + if (source_files.precompiled_header) |pch| switch (pch) { + .source_header => |src| src.path.addStepDependencies(step), + .pch_step => |s| step.dependOn(&s.step), + }; }, .win32_resource_file => |rc_source| { rc_source.file.addStepDependencies(step); diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 50a68c1c23fe..a306e10ec517 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -149,6 +149,22 @@ 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 else will be created from the `source_header` 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 @@ -157,14 +173,14 @@ pub const CSourceFiles = struct { /// if null, deduce the language from the file extension 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 .{ @@ -457,7 +473,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. @@ -483,6 +499,13 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void { .precompiled_header = options.precompiled_header, }; m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM"); + + if (options.precompiled_header) |pch| { + switch (pch) { + .source_header => {}, + .pch_step => |step| _ = step.getEmittedBin(), // Indicate there is a dependency on the outputted binary. + } + } } pub fn addCSourceFile(m: *Module, source: CSourceFile) void { @@ -491,6 +514,13 @@ pub fn addCSourceFile(m: *Module, source: CSourceFile) void { const c_source_file = allocator.create(CSourceFile) catch @panic("OOM"); c_source_file.* = source.dupe(b); m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM"); + + if (source.precompiled_header) |pch| { + switch (pch) { + .source_header => {}, + .pch_step => |step| _ = step.getEmittedBin(), // Indicate there is a dependency on the outputted binary. + } + } } /// Resource files must have the extension `.rc`. diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index ab3ab14df850..8e2d10002a63 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1773,6 +1773,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { fn finalize(step: *Step) !void { const compile: *Compile = @fieldParentPtr("step", step); + const b = step.owner; // Fully recursive iteration including dynamic libraries to detect // libc and libc++ linkage. @@ -1790,6 +1791,66 @@ fn finalize(step: *Step) !void { const link_objects = deps[0].root_module.link_objects.items; assert(link_objects.len == 1 and link_objects[0] == .c_source_file); } + + // add additional compile steps for precompiled headers + for (compile.root_module.link_objects.items) |*link_object| { + const pch_ptr: *Module.PrecompiledHeader, const flags: []const []const u8 = blk: { + switch (link_object.*) { + .c_source_file => |src| { + if (src.precompiled_header) |*pch| break :blk .{ pch, src.flags }; + }, + .c_source_files => |src| { + if (src.precompiled_header) |*pch| break :blk .{ pch, src.flags }; + }, + else => {}, + } + + continue; + }; + + switch (pch_ptr.*) { + .pch_step => { + // step customized by the user, nothing to do. + }, + .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"), + }); + + // We generate a new compile step for each use, + // leveraging 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 = .init(b.allocator); + compile_pch.force_undefined_symbols = .init(b.allocator); + + compile_pch.root_module.link_objects = .{}; + compile_pch.root_module.addCSourceFile(.{ .file = src.path, .lang = src.lang, .flags = flags }); + + // finalize the parent compile 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. + }, + } + } } fn make(step: *Step, options: Step.MakeOptions) !void { diff --git a/test/standalone/c_header/build.zig b/test/standalone/c_header/build.zig index 78ce3caf2323..b7c73e6480ce 100644 --- a/test/standalone/c_header/build.zig +++ b/test/standalone/c_header/build.zig @@ -27,7 +27,8 @@ pub fn build(b: *std.Build) void { test_step.dependOn(&b.addRunArtifact(exe).step); } - // testcase 2: precompiled c-header + // testcase 2: precompiled header 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", @@ -36,28 +37,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); } - // testcase 3: precompiled c++-header + // testcase 3: precompiled header in C++, from a .h file that must be precompiled as c++, with an explicit pch compile step. { const exe = b.addExecutable(.{ .name = "pchtest++", @@ -80,7 +83,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); diff --git a/test/standalone/c_header/include_a.h b/test/standalone/c_header/include_a.h index 42921826c8a7..9b092aec5a20 100644 --- a/test/standalone/c_header/include_a.h +++ b/test/standalone/c_header/include_a.h @@ -9,6 +9,7 @@ #include #else #include +#include #endif #define A_INCLUDED 1 diff --git a/test/standalone/c_header/test.c b/test/standalone/c_header/test.c index ba7c9017752e..5add8667bfdc 100644 --- a/test/standalone/c_header/test.c +++ b/test/standalone/c_header/test.c @@ -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)); } diff --git a/test/standalone/c_header/test.c2 b/test/standalone/c_header/test.c2 new file mode 100644 index 000000000000..033ec00b27b1 --- /dev/null +++ b/test/standalone/c_header/test.c2 @@ -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; +}