diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 3bfd8c9a7db9..5be3e3b9ab98 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -13,6 +13,7 @@ const Allocator = mem.Allocator; const Target = std.Target; const process = std.process; const EnvMap = std.process.EnvMap; +const Thread = std.Thread; const File = fs.File; const Sha256 = std.crypto.hash.sha2.Sha256; const Build = @This(); @@ -94,6 +95,8 @@ pkg_hash: []const u8, /// A mapping from dependency names to package hashes. available_deps: AvailableDeps, +compile_commands: ?*CompileCommands = null, + release_mode: ReleaseMode, build_id: ?std.zig.BuildId = null, @@ -106,6 +109,132 @@ pub const ReleaseMode = enum { small, }; +pub const CompileCommands = struct { + step: Step, + entries: ArrayList(CompileCommandsEntry), + mutex: Thread.Mutex, + output_file: LazyPath, + generated_file: GeneratedFile, + + pub const base_id: Step.Id = .custom; + + pub fn create(owner: *Build, output_file: LazyPath) *CompileCommands { + const self = owner.allocator.create(CompileCommands) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = base_id, + .name = owner.fmt("compile_commands.json -> {s}", .{output_file.getDisplayName()}), + .owner = owner, + .makeFn = make, + }), + .entries = ArrayList(CompileCommandsEntry).init(owner.allocator), + .mutex = .{}, + .output_file = output_file.dupe(owner), + .generated_file = .{ .step = undefined }, + }; + self.generated_file.step = &self.step; + + // Set this as the global compile commands database + owner.compile_commands = self; + + // Add dependencies from the output file's LazyPath + output_file.addStepDependencies(&self.step); + + return self; + } + + pub fn append(self: *CompileCommands, entry: CompileCommandsEntry) !void { + self.mutex.lock(); + defer self.mutex.unlock(); + try self.entries.append(entry); + } + + pub fn getEntries(self: *CompileCommands) []CompileCommandsEntry { + self.mutex.lock(); + defer self.mutex.unlock(); + return self.entries.items; + } + + /// Returns a LazyPath representing the generated compile_commands.json file + pub fn getOutput(self: *CompileCommands) LazyPath { + return .{ .generated = .{ .file = &self.generated_file } }; + } + + fn make(build_step: *Step, options: Step.MakeOptions) anyerror!void { + _ = options; + const self: *CompileCommands = @fieldParentPtr("step", build_step); + const b = build_step.owner; + const gpa = b.allocator; + + // Create a temporary buffer for the JSON content + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + + var writer = buffer.writer(); + + try writer.writeAll("["); + + const entries = self.getEntries(); + + var temp = std.ArrayList(u8).init(gpa); + defer temp.deinit(); + + for (entries, 0..) |entry, i| { + if (i != 0) try writer.writeAll(","); + try writer.writeAll("\n {\n"); + + try writer.writeAll(" \"directory\": \""); + try std.json.encodeJsonStringChars(entry.working_directory, .{}, writer); + try writer.writeAll("\",\n"); + + try writer.writeAll(" \"file\": \""); + const full_path = b.pathJoin(&.{ entry.working_directory, entry.relative_path }); + try std.json.encodeJsonStringChars(full_path, .{}, writer); + try writer.writeAll("\",\n"); + + try writer.writeAll(" \"command\": \""); + + temp.clearRetainingCapacity(); + var temp_writer = temp.writer(); + + // Write the compiler command + try temp_writer.writeAll("zig cc"); + for (entry.flags) |flag| { + try temp_writer.writeAll(" "); + try temp_writer.writeAll(flag); + } + try temp_writer.writeAll(" "); + try temp_writer.writeAll(entry.relative_path); + + try std.json.encodeJsonStringChars(temp.items, .{}, writer); + try writer.writeAll("\"\n }"); + } + + try writer.writeAll("\n]\n"); + + // Now write the content to the output file based on its type + const output_path = self.output_file.getPath2(b, build_step); + + // Ensure directory exists + if (std.fs.path.dirname(output_path)) |dirname| { + try b.build_root.handle.makePath(dirname); + } + + // Write the file + try b.build_root.handle.writeFile(.{ .sub_path = output_path, .data = buffer.items }); + + // Set the generated file path for LazyPath access + self.generated_file.path = try b.build_root.join(gpa, &.{output_path}); + } +}; + +pub const CompileCommandsEntry = struct { + module: *Module, + working_directory: []const u8, + relative_path: []const u8, + flags: []const []const u8, +}; + /// Shared state among all Build instances. /// Settings that are here rather than in Build are not configurable per-package. pub const Graph = struct { @@ -400,6 +529,7 @@ fn createChildOnly( .named_lazy_paths = .init(allocator), .pkg_hash = pkg_hash, .available_deps = pkg_deps, + .compile_commands = parent.compile_commands, .release_mode = parent.release_mode, }; try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls); @@ -2439,6 +2569,10 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void { } } +pub fn addCompileCommands(b: *Build, output_file: LazyPath) *CompileCommands { + return CompileCommands.create(b, output_file); +} + /// A file that is generated by a build step. /// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic. pub const GeneratedFile = struct { diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 924dc18f91be..8a6a4a0e9b07 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1285,6 +1285,16 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { try zig_args.append(lang.internalIdentifier()); } + if (b.compile_commands) |compdb| { + const path = c_source_file.file.getPath3(mod.owner, step); + try compdb.append(.{ + .module = compile.root_module, + .working_directory = b.pathResolve(&.{path.root_dir.path orelse "."}), + .relative_path = path.sub_path, + .flags = c_source_file.flags, + }); + } + try zig_args.append(c_source_file.file.getPath2(mod.owner, step)); if (c_source_file.language != null) { @@ -1312,6 +1322,18 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { } const root_path = c_source_files.root.getPath2(mod.owner, step); + + if (b.compile_commands) |compdb| { + for (c_source_files.files) |file| { + try compdb.append(.{ + .module = compile.root_module, + .working_directory = root_path, + .relative_path = file, + .flags = c_source_files.flags, + }); + } + } + for (c_source_files.files) |file| { try zig_args.append(b.pathJoin(&.{ root_path, file })); }