Step.WriteFile or Step.UpdateSourceFiles?

In Cubyz we have a modding system where there are directories (e.g “mods/rotations”), which have files which define certain features. To import those in the main game we have created an automated system in the build phase which generates us the “list” files. these then contain as imports the needed files.

So in the build.zig this happens:
For each “system” go into the mods/[systemName]directory, walk through it and for each file create a pub const ... = @import("..."); entry in a newly created mods/[systemName].zigfile.
In zig 0.16.0 we achived this through a custom std.Build.Step, but these are with 0.17.0 removed.

Now is the question how do we migrate our system?

I looked at the UpdateSourceFiles Step, which seems to behave exactly as our current custom step behaves? But this comment in the std.Build.addUpdateSourceFiles function brought the question up if we maybe want to go into a different direction:

This build step was designed not to be used during the normal build process, but rather as a utility run by a developer with intention to update source files, which will then be committed to version control.

Maybe we should use Step.WriteFile ? Seems to be better? But now there is this problem:

error: import of file outside module path
pub const @"cubyz:branch" = @import("../../../mods/cubyz/rotations/branch.zig");

Do we maybe need to copy over the entire mods directory?

Or maybe there is complete other solution to our use-case?

Step.WriteFile is for writing temporary files to .zig-cache that only need to exist for one single zig build invocation. Step.UpdateSourceFiles is for writing “permanent” files to the source tree (e.g. a src directory) that you commit to version control.

Because Step.UpdateSourceFiles modifies files in the source tree, it is undesirable to run it as part of the main build because it might prevent the build system from utilizing its cache system efficiently (or break it altogether). Instead, you are intended to use Step.UpdateSourceFiles for tasks that you run manually every X days/weeks/months, for example a zig build generate-db-client step that connects to a database and (re)generates code based on the database’s schema.

I’ve only quickly skimmed through your repo but judging by the fact that you have /mods/* in your gitignore, it looks like you want to use Step.WriteFile. However, as you’ve noted, this means that you would also need to copy the mods directory to the same Step.WriteFile since the generated file will not be allowed to import files outside of the .zig-cache/o/<hash> directory in which it resides.


As a side note, consider moving your directory traversal and code generation logic out from something that is done imperatively in build.zig itself and into its own dedicated executable that is compiled and invoked with b.runArtifact, to be more friendly to the build system cache and ensure that code is only regenerated/re-copied when needed. See Running the Project’s Tools from the build system docs for more details. In your case, you’d likely your program to take two arguments, the input directory to traverse and output directory to generate/copy code to, and provide them using run.addDirectoryArg(b.path("mods")) and run.addOutputDirectoryArg("mods").