This commit is contained in:
xxxbxxx 2024-11-21 11:21:36 +11:00 committed by GitHub
commit a5d85cdf90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 589 additions and 45 deletions

View File

@ -339,6 +339,10 @@ pub fn main() !void {
try builder.runBuild(root);
}
for (builder.top_level_steps.values()) |s| {
try finalizeSteps(&s.step);
}
if (graph.needed_lazy_dependencies.entries.len != 0) {
var buffer: std.ArrayListUnmanaged(u8) = .empty;
for (graph.needed_lazy_dependencies.keys()) |k| {
@ -636,6 +640,7 @@ fn runStepNames(
test_count += s.test_results.test_count;
switch (s.state) {
.unfinalized => unreachable,
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
.running => unreachable,
@ -781,6 +786,7 @@ fn printStepStatus(
run: *const Run,
) !void {
switch (s.state) {
.unfinalized => unreachable,
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
.precheck_done => unreachable,
@ -978,6 +984,34 @@ fn printTreeStep(
}
}
/// Traverse the dependency graph after the user build() call,
/// this allows for checks and postprocessing after the steps are fully configured by the user.
fn finalizeSteps(
s: *Step,
) !void {
switch (s.state) {
.unfinalized => {
try s.finalize();
s.state = .precheck_unstarted;
for (s.dependencies.items) |dep| {
try finalizeSteps(dep);
}
},
.precheck_unstarted => {},
.precheck_started => unreachable,
.precheck_done => unreachable,
.dependency_failure => unreachable,
.running => unreachable,
.success => unreachable,
.failure => unreachable,
.skipped => unreachable,
.skipped_oom => unreachable,
}
}
/// Traverse the dependency graph depth-first and make it undirected by having
/// steps know their dependants (they only know dependencies at start).
/// Along the way, check that there is no dependency loop, and record the steps
@ -996,6 +1030,7 @@ fn constructGraphAndCheckForDependencyLoop(
rand: std.Random,
) !void {
switch (s.state) {
.unfinalized => unreachable,
.precheck_started => {
std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
return error.DependencyLoopDetected;
@ -1058,6 +1093,7 @@ fn workerMakeOneStep(
// dependency is not finished yet.
return;
},
.unfinalized => unreachable,
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
}

View File

@ -796,6 +796,31 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
});
}
pub const PchOptions = struct {
name: []const u8,
target: ResolvedTarget,
optimize: std.builtin.OptimizeMode,
max_rss: usize = 0,
link_libc: ?bool = null,
link_libcpp: ?bool = null,
};
pub fn addPrecompiledCHeader(b: *Build, options: PchOptions, source: Module.CSourceFile) *Step.Compile {
const pch = Step.Compile.create(b, .{
.name = options.name,
.root_module = .{
.target = options.target,
.optimize = options.optimize,
.link_libc = options.link_libc,
.link_libcpp = options.link_libcpp,
},
.kind = .pch,
.max_rss = options.max_rss,
});
pch.addCSourceFile(source);
return pch;
}
pub const SharedLibraryOptions = struct {
name: []const u8,
/// To choose the same computer as the one building the package, pass the

View File

@ -46,11 +46,36 @@ pub const RPath = union(enum) {
special: []const u8,
};
// subset of Compilation.FileExt
pub const AsmSourceLang = enum {
assembly,
assembly_with_cpp,
pub fn getLangName(lang: @This()) []const u8 {
return switch (lang) {
.assembly => "assembler",
.assembly_with_cpp => "assembler-with-cpp",
};
}
};
pub const AsmSourceFile = struct {
file: LazyPath,
lang: ?AsmSourceLang = null,
pub fn dupe(file: AsmSourceFile, b: *std.Build) AsmSourceFile {
return .{
.file = file.file.dupe(b),
.lang = file.lang,
};
}
};
pub const LinkObject = union(enum) {
static_path: LazyPath,
other_step: *Step.Compile,
system_lib: SystemLib,
assembly_file: LazyPath,
assembly_file: *AsmSourceFile,
c_source_file: *CSourceFile,
c_source_files: *CSourceFiles,
win32_resource_file: *RcSourceFile,
@ -78,22 +103,87 @@ pub const SystemLib = struct {
pub const SearchStrategy = enum { paths_first, mode_first, no_fallback };
};
/// Supported languages for "zig clang -x <lang>".
// subset of Compilation.FileExt
pub const CSourceLang = enum {
/// "c"
c,
/// "c-header"
h,
/// "c++"
cpp,
/// "c++-header"
hpp,
/// "objective-c"
m,
/// "objective-c-header"
hm,
/// "objective-c++"
mm,
/// "objective-c++-header"
hmm,
/// "assembler"
assembly,
/// "assembler-with-cpp"
assembly_with_cpp,
/// "cuda"
cu,
pub fn getLangName(lang: @This()) []const u8 {
return switch (lang) {
.assembly => "assembler",
.assembly_with_cpp => "assembler-with-cpp",
.c => "c",
.cpp => "c++",
.h => "c-header",
.hpp => "c++-header",
.hm => "objective-c-header",
.hmm => "objective-c++-header",
.cu => "cuda",
.m => "objective-c",
.mm => "objective-c++",
};
}
};
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
/// the build root by default
files: []const []const u8,
lang: ?CSourceLang = null,
flags: []const []const u8,
precompiled_header: ?PrecompiledHeader = null,
};
pub const CSourceFile = struct {
file: LazyPath,
lang: ?CSourceLang = null,
flags: []const []const u8 = &.{},
precompiled_header: ?PrecompiledHeader = null,
pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
return .{
.file = file.file.dupe(b),
.lang = file.lang,
.flags = b.dupeStrings(file.flags),
.precompiled_header = file.precompiled_header,
};
}
};
@ -324,10 +414,9 @@ fn addShallowDependencies(m: *Module, dependee: *Module) void {
addLazyPathDependenciesOnly(m, compile.getEmittedIncludeTree());
},
.static_path,
.assembly_file,
=> |lp| addLazyPathDependencies(m, dependee, lp),
.static_path => |lp| addLazyPathDependencies(m, dependee, lp),
.assembly_file => |x| addLazyPathDependencies(m, dependee, x.file),
.c_source_file => |x| addLazyPathDependencies(m, dependee, x.file),
.win32_resource_file => |x| addLazyPathDependencies(m, dependee, x.file),
@ -361,7 +450,7 @@ fn addStepDependencies(m: *Module, module: *Module, dependee: *Step) void {
}
}
fn addStepDependenciesOnly(m: *Module, dependee: *Step) void {
pub fn addStepDependenciesOnly(m: *Module, dependee: *Step) void {
for (m.depending_steps.keys()) |compile| {
compile.step.dependOn(dependee);
}
@ -523,7 +612,9 @@ pub const AddCSourceFilesOptions = struct {
/// package that owns the `Compile` step.
root: ?LazyPath = null,
files: []const []const u8,
lang: ?CSourceLang = null,
flags: []const []const u8 = &.{},
precompiled_header: ?PrecompiledHeader = null,
};
/// Handy when you have many C/C++ source files and want them all to have the same flags.
@ -544,10 +635,22 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
c_source_files.* = .{
.root = options.root orelse b.path(""),
.files = b.dupeStrings(options.files),
.lang = options.lang,
.flags = b.dupeStrings(options.flags),
.precompiled_header = options.precompiled_header,
};
m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM");
addLazyPathDependenciesOnly(m, c_source_files.root);
if (options.precompiled_header) |pch| {
switch (pch) {
.source_header => |src| addLazyPathDependenciesOnly(m, src.path),
.pch_step => |step| {
_ = step.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
addStepDependenciesOnly(m, &step.step);
},
}
}
}
pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
@ -557,6 +660,16 @@ pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
c_source_file.* = source.dupe(b);
m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM");
addLazyPathDependenciesOnly(m, source.file);
if (source.precompiled_header) |pch| {
switch (pch) {
.source_header => |src| addLazyPathDependenciesOnly(m, src.path),
.pch_step => |step| {
_ = step.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
addStepDependenciesOnly(m, &step.step);
},
}
}
}
/// Resource files must have the extension `.rc`.
@ -579,10 +692,12 @@ pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void {
}
}
pub fn addAssemblyFile(m: *Module, source: LazyPath) void {
pub fn addAssemblyFile(m: *Module, source: AsmSourceFile) void {
const b = m.owner;
m.link_objects.append(b.allocator, .{ .assembly_file = source.dupe(b) }) catch @panic("OOM");
addLazyPathDependenciesOnly(m, source);
const source_file = b.allocator.create(AsmSourceFile) catch @panic("OOM");
source_file.* = source.dupe(b);
m.link_objects.append(b.allocator, .{ .assembly_file = source_file }) catch @panic("OOM");
addLazyPathDependenciesOnly(m, source.file);
}
pub fn addObjectFile(m: *Module, object: LazyPath) void {

View File

@ -1,6 +1,7 @@
id: Id,
name: []const u8,
owner: *Build,
finalizeFn: FinalizeFn,
makeFn: MakeFn,
dependencies: std.ArrayList(*Step),
@ -68,6 +69,8 @@ pub const TestResults = struct {
}
};
pub const FinalizeFn = *const fn (step: *Step) anyerror!void;
pub const MakeOptions = struct {
progress_node: std.Progress.Node,
thread_pool: *std.Thread.Pool,
@ -77,6 +80,7 @@ pub const MakeOptions = struct {
pub const MakeFn = *const fn (step: *Step, options: MakeOptions) anyerror!void;
pub const State = enum {
unfinalized,
precheck_unstarted,
precheck_started,
/// This is also used to indicate "dirty" steps that have been modified
@ -183,6 +187,7 @@ pub const StepOptions = struct {
id: Id,
name: []const u8,
owner: *Build,
finalizeFn: FinalizeFn = finalizeNoOp,
makeFn: MakeFn = makeNoOp,
first_ret_addr: ?usize = null,
max_rss: usize = 0,
@ -195,11 +200,12 @@ pub fn init(options: StepOptions) Step {
.id = options.id,
.name = arena.dupe(u8, options.name) catch @panic("OOM"),
.owner = options.owner,
.finalizeFn = options.finalizeFn,
.makeFn = options.makeFn,
.dependencies = std.ArrayList(*Step).init(arena),
.dependants = .{},
.inputs = Inputs.init,
.state = .precheck_unstarted,
.state = .unfinalized,
.max_rss = options.max_rss,
.debug_stack_trace = blk: {
const addresses = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM");
@ -222,6 +228,11 @@ pub fn init(options: StepOptions) Step {
};
}
pub fn finalize(s: *Step) !void {
assert(s.state == .unfinalized);
try s.finalizeFn(s);
}
/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
/// have already reported the error. Otherwise, we add a simple error report
/// here.
@ -266,6 +277,10 @@ pub fn getStackTrace(s: *Step) ?std.builtin.StackTrace {
};
}
fn finalizeNoOp(step: *Step) anyerror!void {
_ = step;
}
fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void {
_ = options;

View File

@ -216,9 +216,9 @@ generated_h: ?*GeneratedFile,
/// Defaults to `std.math.maxInt(u16)`
error_limit: ?u32 = null,
/// Computed during make().
/// Computed during finalize().
is_linking_libc: bool = false,
/// Computed during make().
/// Computed during finalize().
is_linking_libcpp: bool = false,
no_builtin: bool = false,
@ -284,6 +284,7 @@ pub const Kind = enum {
exe,
lib,
obj,
pch,
@"test",
};
@ -367,6 +368,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,
@ -374,17 +376,21 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
resolved_target.query.zigTriple(owner.allocator) catch @panic("OOM"),
});
const out_filename = std.zig.binNameAlloc(owner.allocator, .{
.root_name = name,
.target = target,
.output_mode = switch (options.kind) {
.lib => .Lib,
.obj => .Obj,
.exe, .@"test" => .Exe,
},
.link_mode = options.linkage,
.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,
.output_mode = switch (options.kind) {
.lib => .Lib,
.obj => .Obj,
.exe, .@"test" => .Exe,
.pch => unreachable,
},
.link_mode = options.linkage,
.version = options.version,
}) catch @panic("OOM");
const compile = owner.allocator.create(Compile) catch @panic("OOM");
compile.* = .{
@ -398,6 +404,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.id = base_id,
.name = step_name,
.owner = owner,
.finalizeFn = finalize,
.makeFn = make,
.max_rss = options.max_rss,
}),
@ -815,10 +822,12 @@ pub fn linkFrameworkWeak(c: *Compile, name: []const u8) void {
/// Handy when you have many C/C++ source files and want them all to have the same flags.
pub fn addCSourceFiles(compile: *Compile, options: Module.AddCSourceFilesOptions) void {
assert(compile.kind != .pch); // pch can only be generated from a single C header file
compile.root_module.addCSourceFiles(options);
}
pub fn addCSourceFile(compile: *Compile, source: Module.CSourceFile) void {
assert(compile.kind != .pch or compile.root_module.link_objects.items.len == 0); // pch can only be generated from a single C header file
compile.root_module.addCSourceFile(source);
}
@ -826,6 +835,7 @@ pub fn addCSourceFile(compile: *Compile, source: Module.CSourceFile) void {
/// Can be called regardless of target. The .rc file will be ignored
/// if the target object format does not support embedded resources.
pub fn addWin32ResourceFile(compile: *Compile, source: Module.RcSourceFile) void {
assert(compile.kind != .pch); // pch can only be generated from a single C header file
compile.root_module.addWin32ResourceFile(source);
}
@ -905,15 +915,18 @@ pub fn getEmittedLlvmBc(compile: *Compile) LazyPath {
return compile.getEmittedFileGeneric(&compile.generated_llvm_bc);
}
pub fn addAssemblyFile(compile: *Compile, source: LazyPath) void {
pub fn addAssemblyFile(compile: *Compile, source: Module.AsmSourceFile) void {
assert(compile.kind != .pch); // pch can only be generated from a single C header file
compile.root_module.addAssemblyFile(source);
}
pub fn addObjectFile(compile: *Compile, source: LazyPath) void {
assert(compile.kind != .pch); // pch can only be generated from a single C header file
compile.root_module.addObjectFile(source);
}
pub fn addObject(compile: *Compile, object: *Compile) void {
assert(compile.kind != .pch); // pch can only be generated from a single C header file
compile.root_module.addObject(object);
}
@ -1038,6 +1051,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
.lib => "build-lib",
.exe => "build-exe",
.obj => "build-obj",
.pch => "build-pch",
.@"test" => "test",
};
try zig_args.append(cmd);
@ -1089,22 +1103,13 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
var prev_has_cflags = false;
var prev_has_rcflags = false;
var prev_has_xflag = false;
var prev_search_strategy: Module.SystemLib.SearchStrategy = .paths_first;
var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
// Track the number of positional arguments so that a nice error can be
// emitted if there is nothing to link.
var total_linker_objects: usize = @intFromBool(compile.root_module.root_source_file != null);
{
// Fully recursive iteration including dynamic libraries to detect
// libc and libc++ linkage.
var dep_it = compile.root_module.iterateDependencies(compile, true);
while (dep_it.next()) |key| {
if (key.module.link_libc == true) compile.is_linking_libc = true;
if (key.module.link_libcpp == true) compile.is_linking_libcpp = true;
}
}
var cli_named_modules = try CliNamedModules.init(arena, &compile.root_module);
// For this loop, don't chase dynamic libraries because their link
@ -1127,6 +1132,12 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
// Inherit dependencies on system libraries and static libraries.
for (dep.module.link_objects.items) |link_object| {
if (prev_has_xflag and link_object != .c_source_file and link_object != .c_source_files and link_object != .assembly_file) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}
switch (link_object) {
.static_path => |static_path| {
if (my_responsibility) {
@ -1210,6 +1221,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
switch (other.kind) {
.exe => return step.fail("cannot link with an executable build artifact", .{}),
.@"test" => return step.fail("cannot link with a test", .{}),
.pch => @panic("Cannot link with a precompiled header file"),
.obj => {
const included_in_lib_or_obj = !my_responsibility and
(dep.compile.?.kind == .lib or dep.compile.?.kind == .obj);
@ -1257,14 +1269,25 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
try zig_args.append("--");
prev_has_cflags = false;
}
try zig_args.append(asm_file.getPath2(dep.module.owner, step));
if (asm_file.lang) |lang| {
try zig_args.append("-x");
try zig_args.append(lang.getLangName());
prev_has_xflag = true;
} else if (prev_has_xflag) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}
try zig_args.append(asm_file.file.getPath2(dep.module.owner, step));
total_linker_objects += 1;
},
.c_source_file => |c_source_file| l: {
if (!my_responsibility) break :l;
if (c_source_file.flags.len == 0) {
if (c_source_file.flags.len == 0 and c_source_file.precompiled_header == null) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
@ -1275,9 +1298,25 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
for (c_source_file.flags) |arg| {
try zig_args.append(arg);
}
if (c_source_file.precompiled_header) |pch| {
try zig_args.append("-include-pch");
try zig_args.append(pch.getPath(b));
try zig_args.append("-fpch-validate-input-files-content");
}
try zig_args.append("--");
prev_has_cflags = true;
}
if (c_source_file.lang) |lang| {
try zig_args.append("-x");
try zig_args.append(lang.getLangName());
prev_has_xflag = true;
} else if (prev_has_xflag) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}
try zig_args.append(c_source_file.file.getPath2(dep.module.owner, step));
total_linker_objects += 1;
},
@ -1285,7 +1324,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
.c_source_files => |c_source_files| l: {
if (!my_responsibility) break :l;
if (c_source_files.flags.len == 0) {
if (c_source_files.flags.len == 0 and c_source_files.precompiled_header == null) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
@ -1296,10 +1335,27 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
for (c_source_files.flags) |flag| {
try zig_args.append(flag);
}
if (c_source_files.precompiled_header) |pch| {
try zig_args.append("-include-pch");
try zig_args.append(pch.getPath(b));
try zig_args.append("-fpch-validate-input-files-content");
}
try zig_args.append("--");
prev_has_cflags = true;
}
if (c_source_files.lang) |lang| {
try zig_args.append("-x");
try zig_args.append(lang.getLangName());
prev_has_xflag = true;
} else if (prev_has_xflag) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}
const root_path = c_source_files.root.getPath2(dep.module.owner, step);
for (c_source_files.files) |file| {
try zig_args.append(b.pathJoin(&.{ root_path, file }));
@ -1780,6 +1836,90 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
return try zig_args.toOwnedSlice();
}
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.
var dep_it = compile.root_module.iterateDependencies(compile, true);
while (dep_it.next()) |key| {
if (key.module.link_libc == true) compile.is_linking_libc = true;
if (key.module.link_libcpp == true) compile.is_linking_libcpp = true;
}
}
if (compile.kind == .pch) {
// precompiled headers must have a single input header file.
var it = compile.root_module.iterateDependencies(compile, false);
const link_objects = it.next().?.module.link_objects;
assert(link_objects.items.len == 1 and link_objects.items[0] == .c_source_file);
assert(it.next() == null);
}
// 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.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.
compile.root_module.addStepDependenciesOnly(&compile_pch.step);
},
}
}
}
fn make(step: *Step, options: Step.MakeOptions) !void {
const b = step.owner;
const compile: *Compile = @fieldParentPtr("step", step);

View File

@ -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" => .bin,
.lib => if (artifact.isDll()) .bin else .lib,
},

View File

@ -4691,11 +4691,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
else
"/dev/null";
try argv.ensureUnusedCapacity(6);
try argv.ensureUnusedCapacity(7);
switch (comp.clang_preprocessor_mode) {
.no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }),
.yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-fpch-validate-input-files-content", "-o", out_obj_path }),
.stdout => argv.appendAssumeCapacity("-E"),
}
@ -4734,11 +4734,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
try argv.appendSlice(c_object.src.extra_flags);
try argv.appendSlice(c_object.src.cache_exempt_flags);
try argv.ensureUnusedCapacity(6);
try argv.ensureUnusedCapacity(7);
switch (comp.clang_preprocessor_mode) {
.no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }),
.yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-o", out_obj_path }),
.pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-fpch-validate-input-files-content", "-o", out_obj_path }),
.stdout => argv.appendAssumeCapacity("-E"),
}
if (out_diag_path) |diag_file_path| {

View File

@ -82,6 +82,7 @@ const normal_usage =
\\ build-exe Create executable from source or object files
\\ build-lib Create library from source or object files
\\ build-obj Create object from source or object files
\\ build-pch Create a precompiled header from a c or c++ header
\\ test Perform unit testing
\\ run Create executable and run immediately
\\
@ -263,6 +264,8 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
} else if (mem.eql(u8, cmd, "build-obj")) {
dev.check(.build_obj_command);
return buildOutputType(gpa, arena, args, .{ .build = .Obj });
} else if (mem.eql(u8, cmd, "build-pch")) {
return buildOutputType(gpa, arena, args, .pch);
} else if (mem.eql(u8, cmd, "test")) {
dev.check(.test_command);
return buildOutputType(gpa, arena, args, .zig_test);
@ -380,6 +383,7 @@ const usage_build_generic =
\\Usage: zig build-exe [options] [files]
\\ zig build-lib [options] [files]
\\ zig build-obj [options] [files]
\\ zig build-pch [options] [files]
\\ zig test [options] [files]
\\ zig run [options] [files] [-- [args]]
\\ zig translate-c [options] [file]
@ -733,6 +737,7 @@ const ArgMode = union(enum) {
build: std.builtin.OutputMode,
cc,
cpp,
pch,
translate_c,
zig_test,
run,
@ -999,11 +1004,15 @@ fn buildOutputType(
var n_jobs: ?u32 = null;
switch (arg_mode) {
.build, .translate_c, .zig_test, .run => {
.build, .translate_c, .zig_test, .run, .pch => {
switch (arg_mode) {
.build => |m| {
create_module.opts.output_mode = m;
},
.pch => {
create_module.opts.output_mode = .Obj;
clang_preprocessor_mode = .pch;
},
.translate_c => {
emit_bin = .no;
create_module.opts.output_mode = .Obj;

View File

@ -119,7 +119,7 @@ fn addCompileStep(
});
}
if (overlay.asm_source_bytes) |bytes| {
compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
compile_step.addAssemblyFile(.{ .file = b.addWriteFiles().add("a.s", bytes) });
}
return compile_step;
}
@ -147,7 +147,7 @@ pub fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
const b = comp.step.owner;
const actual_bytes = std.fmt.allocPrint(b.allocator, "{s}\n", .{bytes}) catch @panic("OOM");
const file = WriteFile.create(b).add("a.s", actual_bytes);
comp.addAssemblyFile(file);
comp.addAssemblyFile(.{ .file = file });
}
pub fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {

View File

@ -101,7 +101,7 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void {
.target = b.graph.host,
.optimize = .Debug,
});
exe.addAssemblyFile(first_file);
exe.addAssemblyFile(.{ .file = first_file });
const run = b.addRunArtifact(exe);
run.setName(annotated_case_name);

View File

@ -126,6 +126,9 @@
.c_compiler = .{
.path = "c_compiler",
},
.c_header = .{
.path = "c_header",
},
.pie = .{
.path = "pie",
},

View File

@ -0,0 +1,91 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// testcase 1: single-file c-header library
{
const exe = b.addExecutable(.{
.name = "single-file-library",
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibC();
exe.addIncludePath(b.path("."));
exe.addCSourceFile(.{
.file = b.path("single_file_library.h"),
.lang = .c,
.flags = &.{"-DTSTLIB_IMPLEMENTATION"},
});
test_step.dependOn(&b.addRunArtifact(exe).step);
}
// 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",
.target = target,
.optimize = optimize,
.link_libc = true,
});
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 = .c,
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
});
exe.addCSourceFiles(.{
.files = &.{"test.c"},
.flags = &[_][]const u8{},
.lang = .c,
.precompiled_header = .{ .source_header = .{ .path = generated_header, .lang = .h } },
});
exe.addIncludePath(b.path("."));
test_step.dependOn(&b.addRunArtifact(exe).step);
}
// 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++",
.target = target,
.optimize = optimize,
});
exe.linkLibCpp();
const pch = b.addPrecompiledCHeader(.{
.name = "pch_c++",
.target = target,
.optimize = optimize,
.link_libcpp = true,
}, .{
.file = b.path("include_a.h"),
.flags = &[_][]const u8{},
.lang = .hpp,
});
exe.addCSourceFile(.{
.file = b.path("test.cpp"),
.flags = &[_][]const u8{},
.precompiled_header = .{ .pch_step = pch },
});
test_step.dependOn(&b.addRunArtifact(exe).step);
}
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "include_b.h"
#include <string.h>
#include <stdlib.h>
#if defined(__cplusplus)
#include <iostream>
#else
#include <stdio.h>
#include <stdbool.h>
#endif
#define A_INCLUDED 1

View File

@ -0,0 +1,7 @@
#pragma once
#include <math.h>
typedef double real;
#define B_INCLUDED 1

View File

@ -0,0 +1,11 @@
const std = @import("std");
const C = @cImport({
@cInclude("single_file_library.h");
});
pub fn main() !void {
const msg = "hello";
const val = C.tstlib_len(msg);
if (val != msg.len)
std.process.exit(1);
}

View File

@ -0,0 +1,14 @@
// library header:
extern unsigned tstlib_len(const char* msg);
// library implementation:
#ifdef TSTLIB_IMPLEMENTATION
#include <string.h>
unsigned tstlib_len(const char* msg)
{
return strlen(msg);
}
#endif

View File

@ -0,0 +1,19 @@
// 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
extern int func(real a, bool cond);
int main(int argc, char *argv[])
{
real a = 0.123;
return func(a, (argc > 1));
}

View File

@ -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;
}

View File

@ -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;
}