Project-local overrides for stdlib files

Is there a mechanism that I’m not aware of that gives the possibility to “per-project override” files of the standard library?
I’m thinking about something like a zig-std-overlay directory in the build root, that for example could contain ./debug.zig or ./fs/path.zig, making these files be used instead of {std_dir}/debug.zig or {std_dir}/fs/path.zig.

It would allow me to avoid having multiple std lib copies when I make project-specific std lib hacks that are impossible to upstream, while also making these changes very easy to add to version control.

1 Like

I’m not aware of such a thing, but maybe zig_lib_dir and a step to write files fits your needs?

I was thinking about this kind of solution as well, using const exe = b.addExecutable(.{ ... , .zig_lib_dir = b.path("zig-std-overlay")}). How would one add a step that copies the std lib directory into this directory? Would it require an addSystemCommand? I’d prefer not to introduce a system dependency.

Theres std.Build.Step.WriteFile, how far can you get with just that?

Unless I’m missing something, .zig_lib_dir expects a “complete” lib directory. If I could copy the original one, then I should be able to replace the files of interest in it with the help of the build system.

The original directory is available in std.Build.Graph. std.Build.Step.WriteFile didn’t work for me because directories are copied after files, but adding a separate WriteFile step fixed this. Here’s an example of how to replace std.debug:

build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const tf = b.addWriteFiles();
    const copied_lib = tf.addCopyDirectory(.{ .cwd_relative = b.graph.zig_lib_directory.path.? }, "lib", .{.include_extensions = &.{"zig"}});

    const mf = b.addMutateFiles(copied_lib);
    _ = mf.addCopyFile(b.path("debug.zig"), "std/debug.zig");

    const mutated_lib = mf.getDirectory();

    const mod = b.createModule(.{
        .root_source_file = b.path("build.zig"),
        .target = b.standardTargetOptions(.{}),
        .optimize = b.standardOptimizeOption(.{})
    });

    const exe = b.addExecutable(.{
        .name = "run",
        .zig_lib_dir = mutated_lib,
        .root_module = mod,
    });

    b.installArtifact(exe);
}


pub fn main() !void {
    std.debug.print("Hello, World!", .{});
}
1 Like

Still, this is probably a bad idea. It’s probably better to just vendor the entire stdlib.

I ended up with almost the same solution:

fn makePatchedZigLibDir(b: *std.Build) std.Build.LazyPath {
    const original_std_zig_path = b.graph.zig_lib_directory.join(
        b.allocator,
        &.{ "std", "std.zig" },
    ) catch @panic("OOM");

    const original_std_zig_content = std.Io.Dir.cwd().readFileAlloc(
        b.graph.io,
        original_std_zig_path,
        b.allocator,
        .limited(1024 * 1024),
    ) catch |err| std.debug.panic("failed to read '{s}': {t}", .{ original_std_zig_path, err });

    const patched_std_zig_content = b.fmt(
        \\{s}
        \\
        \\pub const patched_std_zig_marker = "patched std lib was used";
        \\
    , .{original_std_zig_content});

    const copy_zig_lib = b.addWriteFiles();
    const copied_zig_lib = copy_zig_lib.addCopyDirectory(
        .{ .cwd_relative = b.graph.zig_lib_directory.path.? },
        "",
        .{ .include_extensions = &.{"zig"} },
    );

    const patched_zig_lib = b.addMutateFiles(copied_zig_lib);
    _ = patched_zig_lib.add("std/std.zig", patched_std_zig_content);

    return patched_zig_lib.getDirectory();
}

I agree this is not ideal. Thanks for your help anyway!