Better way to patch file with zig?

Hoi.

In my first Zig project I needed to patch the header file of miniaudio with some « anyopaque » pointer since otherwise zig would not correctly understand I can pass a struct pointer to the right place (it’s a bug already reported).

For that I use this code to run the patch command and create inside of the build system a patched version. But I have now the issue Windows don’t have a native patch command.

fn prepareMiniaudio(b: *std.Build) std.Build.LazyPath {
    if (builtin.os.tag == .windows) {
        const tool_run = b.addSystemCommand(&.{"C:/Program Files/Git/usr/bin/patch"});
        tool_run.addFileArg(b.path("src/miniaudio/miniaudio.h"));

        tool_run.addArg("-o");
        const ret = tool_run.addOutputFileArg("miniaudio.h");
        tool_run.addFileArg(b.path("src/miniaudio/zig_18247.patch"));
        b.getInstallStep().dependOn(&tool_run.step);
        return ret;
    }

    const tool_run = b.addSystemCommand(&.{"patch"});
    tool_run.addFileArg(b.path("src/miniaudio/miniaudio.h"));

    tool_run.addArg("-o");
    const ret = tool_run.addOutputFileArg("miniaudio.h");
    tool_run.addFileArg(b.path("src/miniaudio/zig_18247.patch"));
    b.getInstallStep().dependOn(&tool_run.step);
    return ret;
}

Do anyone have an idea on how to improve windows ? I have some ideas of using the zig package manager to download a version of gnu patch windows for directly using it inside of the build pipeline but my issues are this build is very old and so ask Windows administrators permission (yeah I know it’s a patch command) but also downloading arbitrary programs on the internet to execute feel odd.

For now I did use a temporary absolute path but maybe I should just add as requirements to have a patch.exe in the path.

For ziglua I apply a small patch with the build system to the Lua 5.1 sources using a Zig program I wrote. It’s not the best code, and it is only enough to apply a small patch file in my specific case. But the code could be improved and made to work for all patch files.

patch.zig: ziglua/build/lua.zig at d9a158505912396f304b89c0e688effe5aeb4918 · natecraddock/ziglua · GitHub

Used here: ziglua/build/lua.zig at d9a158505912396f304b89c0e688effe5aeb4918 · natecraddock/ziglua · GitHub

The idea is you can compile an executable as part of the build process. Then run that executable and the output can be used as an input file

// Patch ldo.c for Lua 5.1
if (lang == .lua51) {
    const patched = patchFile(b, target, lib, upstream.path("src/ldo.c"), b.path("build/lua-5.1.patch"));
    lib.addCSourceFile(.{ .file = patched, .flags = &flags });
}

If you don’t want to download pre-built executables, add as a dependency a repository that contains a source version of an implementation of patch, build it yourself in as part the build pipeline and then execute it with b.runArtifact

3 Likes

Oh yes I didn’t think about this route, I was thinking the best thing was maybe to recreate an equivalent of GNU patch in zig but I can just fetch a zip of the source of GNU patch and build it in the Windows build path ^^

After I still think your tiny version of patch in zig would work for me. But I will first try the kristoff solution ^^

GNU software sometimes is pretty annoying to build, if you discover that it has a bunch of dependencies then pivoting to something else might not be a bad idea.

But if you do end up writing a build.zig for patch, consider gifting the repo to All Your Codebase · GitHub :^)

I wonder if git-apply can be used instead of a dedicated patch tool:

The user most likely got the code to build via git, so the tool must exist and is most likely also in the path.

I never tried manually applying patches via git though.

1 Like

There’s a Zig port of DiffMatchPatch called diffz. I’m linking to my fork, because I completed the match and patch parts, and the pull request for that work is not yet merged.

I use it in ohsnap to provide a visible diff of snapshots which don’t pass, so it’s out in the wild, if you will.

It does pass the test suite with complete line coverage, which is not a guarantee of correctness: as Djikstra famously observed, tests can prove the presence of bugs, but not their absence. But it may be effective for your use case.

4 Likes

I solved the same problem, but I applied patches to the C code I received as a dependency. Something like this:

pub fn apply_patch(b: *std.Build, dep: *std.Build.Dependency, filename: []const u8) void {
    return b.addSystemCommand(&[_][]const u8{
        "sh", "-c",
        b.fmt("patch --forward -p1 --directory {s} -i {s} || true", .{
            dep.path("").getPath(b),
            b.path(filename).getPath(b),
        }),
    });
}

The unfortunate thing is that the dependency code will be in ~/.cache/zig, which means that the patch changes the global cache and makes the build non-hermetic (possibly even for other projects if the cache is shared).

The hack for discarding the error code is also necessary because when restarting the build, the patch has already been applied. Zig, as far as I know, does not check the integrity of the cache.

I’ve tried several different solutions (revert the patch after the build or applying it to a copy of the file), but they all seem like dirty tricks.

I’d appreciate advice on how to make this more reliable.

I recall Andrew or another team member mentioning integrating patching to the build system, so it doesn’t affect other projects with the same dependency.

But I can’t find a proposal so, ‘just trust me bro’ :stuck_out_tongue:

1 Like