Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add precompiled c/c++ header support to zig build #17956

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
57 changes: 52 additions & 5 deletions lib/compiler/build_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ pub fn main() !void {
createModuleDependencies(builder) catch @panic("OOM");
}

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| {
Expand Down Expand Up @@ -637,6 +641,7 @@ fn runStepNames(
test_count += s.test_results.test_count;

switch (s.state) {
.unfinalized => unreachable,
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
.running => unreachable,
Expand Down Expand Up @@ -782,6 +787,7 @@ fn printStepStatus(
run: *const Run,
) !void {
switch (s.state) {
.unfinalized => unreachable,
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
.precheck_done => unreachable,
Expand Down Expand Up @@ -979,6 +985,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
Expand All @@ -997,6 +1031,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;
Expand Down Expand Up @@ -1059,6 +1094,7 @@ fn workerMakeOneStep(
// dependency is not finished yet.
return;
},
.unfinalized => unreachable,
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
}
Expand Down Expand Up @@ -1504,13 +1540,24 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
.special => {},
};
for (mod.link_objects.items) |link_object| switch (link_object) {
.static_path,
.assembly_file,
=> |lp| lp.addStepDependencies(step),
.static_path => |lp| lp.addStepDependencies(step),
.assembly_file => |source| source.file.addStepDependencies(step),
.other_step => |other| step.dependOn(&other.step),
.system_lib => {},
.c_source_file => |source| source.file.addStepDependencies(step),
.c_source_files => |source_files| source_files.root.addStepDependencies(step),
.c_source_file => |source| {
source.file.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| 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);
for (rc_source.include_paths) |lp| lp.addStepDependencies(step);
Expand Down
25 changes: 25 additions & 0 deletions lib/std/Build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,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 = b.createModule(.{
.target = options.target,
.optimize = options.optimize,
.link_libc = options.link_libc,
.link_libcpp = options.link_libcpp,
}),
.kind = .pch,
.max_rss = options.max_rss,
});
pch.root_module.addCSourceFile(source);
return pch;
}

pub const SharedLibraryOptions = struct {
name: []const u8,
version: ?std.SemanticVersion = null,
Expand Down
120 changes: 117 additions & 3 deletions lib/std/Build/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,39 @@ pub const RPath = union(enum) {
special: []const u8,
};

// subset of Compilation.FileExt
pub const AsmSourceLang = enum {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a way to specify defines and/or flags? When using assembly with the C preprocessor, defines are definitely important; I'm not sure if general purpose flags would be of any use though, and defines are only used in assembly with the C preprocessor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so, but I never used addAssemblyFile() myself so I don't know how it is used by people. And I don't know if there is a reason to have it separate from addCSourceFile() - which coincidentally also allows for asm files with defines and all.
maybe it should just be removed?

Altough I don't want to include unrelated changes to the pull request, looks like I still felt compelled to add AsmSourceFile to make the API of addAssemblyFile() more similar to addCSourceFile() for some reason, maybe I shouldn't have or I should go all the way. (but that looks like wasted effort if it is to be deleted)

assembly,
assembly_with_cpp,

pub fn getName(lang: @This()) []const u8 {
return switch (lang) {
.assembly => "assembler",
.assembly_with_cpp => "assembler-with-cpp",
};
}
};

pub const AsmSourceFile = struct {
file: LazyPath,
/// if null, deduce the language from the file extension
lang: ?AsmSourceLang = null,
flags: []const []const u8 = &.{},

pub fn dupe(file: AsmSourceFile, b: *std.Build) AsmSourceFile {
return .{
.file = file.file.dupe(b),
.flags = b.dupeStrings(file.flags),
.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,
Expand Down Expand Up @@ -78,22 +106,88 @@ 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,
Comment on lines +128 to +131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is already an addAssemblyFile, is .assembly and .assembly_with_cpp necessary in CSourceLang? The only difference is the ability to provide flags and/or defines (which may be important). We probably don't want two ways to do the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, as I said I'm more drawn to removing it. and maybe finding a better name for addCSourceFile to reflect that it is not "C" source, but "not zig" source?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant comment about the name: #20655 (comment)

Zig primarily has built-in first-class support for the C and C-like languages, and I don't believe there is intention to have that kind of support for other languages, so addCSourceFile may be fine.

/// "cuda"
cu,

pub fn getName(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,
/// if null, deduce the language from the file extension
lang: ?CSourceLang = null,
xxxbxxx marked this conversation as resolved.
Show resolved Hide resolved
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,
};
}
};
Expand Down Expand Up @@ -377,7 +471,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.
Expand All @@ -398,9 +494,18 @@ 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");

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 {
Expand All @@ -409,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`.
Expand All @@ -427,9 +539,11 @@ pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void {
m.link_objects.append(allocator, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM");
}

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");
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");
}

pub fn addObjectFile(m: *Module, object: LazyPath) void {
Expand Down
17 changes: 16 additions & 1 deletion lib/std/Build/Step.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id: Id,
name: []const u8,
owner: *Build,
finalizeFn: FinalizeFn,
makeFn: MakeFn,

dependencies: std.ArrayList(*Step),
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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");
Expand All @@ -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.
Expand Down Expand Up @@ -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;

Expand Down
Loading
Loading