How to reference source files in a build.zig

I’m writing a build.zig file – and finding it very frustrating. In my build.zig file, I’d like to know the path to the cwd – e.g. the moral equivalent of linux pwd command. I’ve been unable to do so. Any ideas?

You can get the root dir of the project via b.build_root, or the actual cwd via std.fs.cwd(), then just use realPath or realPathAlloc.

Edit: Can you share what you’re trying to achieve by “referencing source files”? There may be a better way than this.

1 Like

I have a folder X and in it I have a build.zig file that I’m running. And in that build.zig, I need to create / delete some files in my folder X and its sub-folders.

I’m having trouble finding any zig documentation other than the one file full of examples but no reference material.

Okay, it seems like you just need b.build_root.handle. You can check out the standard library docs at Zig Documentation, or by running zig std

It’s not clear exactly what you are trying to do. It would be helpful if you could provide a concrete example, perhaps by posting the build.zig you have written so far.

I’m going to make a wild guess and assume that you’re trying to write imperative code that creates and deletes files in you build.zig. Generally, you are not expected to write imperative code in your build function. The purpose of the build function is to construct a graph of build steps which are then invoked by a build runner. So if you have code that is accessing the std.fs or std.process namespaces, there are probably better and more idiomatic ways of constructing such tasks.

For creating files, there is b.addWriteFile(). For installing files to zig-out or the install prefix specified by the user via --prefix, there is b.addInstallFile(). For deleting files, there is b.addRemoveDirTree() (which has some ugly warts but might still work). And if you want to invoke a system command like cp or rm, you are expected to use b.addSystemCommand().

The Zig Build System docs provide some good examples on how to use these APIs.

Again, if you can provide more concrete details, we can probably help you figure out how to express your needs in a way that plays nicely with the build system.

3 Likes

OK. Sure. The problem is that from what I can see, there is just a file full of examples, but other than that no reference documentation other than the source code itself. I’m new to Zig.

I have a multi-phase build process for what I’m doing – and today it uses make. I’m trying to replace make with build.zig, but at my current rate it’s futile. I’ve spent hours so far and have about three lines of code working.

The truth may be that I should just keep my currently working makefile system.

Here are snippets from my currently working makefile.

Phase 1:

host:
	@rm -f .gen/targ.zig
	@touch .gen/targ.zig
	@rm -rf .out/*
	@zig build-exe -I. -femit-bin=.out/host-main.exe .main-host.zig
	@.out/host-main.exe 
	@zig fmt .gen/targ.zig 2>&1 >/dev/null

Phase 2:

targ:
	@zig build-exe -I. $(TARG_OPTS) --script .out/linkcmd.ld .out/startup.c .main-targ.zig
	@rm -f .out/main.out.o
	@$(OBJCOPY) -O ihex .out/main.out .out/main.out.hex
	@$(OBJDUMP) -h -d .out/main.out >.out/main.out.dis
	@$(OBJDUMP) -t .out/main.out | tail -n +5 | sed -e 's/[FO] /  /' | sed -e 's/df /   /' >.out/main.out.sym
	@sort -k1 .out/main.out.sym > .out/main.out.syma
	@sort -k5 .out/main.out.sym > .out/main.out.symn
	@sha256sum .out/main.out.hex | cut -c -8 >.out/main.out.sha32
	@$(OBJDUMP) -h .out/main.out

BTW, b.build_root did have what I was looking for. Thank you for that.

Welcome to Ziggit!

Yes, this is accurate. The build system is new, and is not yet stable, and documentation is incomplete.

That might be the case for now, but maybe we can help you out with doing it the build.zig way. Underdocumented though it is, the system is fairly capable, but it functions differently from Make, and helping us understand the outcome you need will help us help you figure out how to get it with the build system.

Great, thanks for the details.

First off, it should be noted that the Zig build system supports CWD-relative paths via std.Build.LazyPath{ .cwd_relative = "foo" }, but that they are generally discouraged in favor of build root-relative paths, which are obtained via b.path("foo"). This is mainly for reliability/reproducibility and cache reasons. In cases such as yours where you seem to be migrating from or interoping with a different build system that uses CWD-relative paths, they might make sense, but you’re encouraged to see if you can rethink your project structure to only use build root-relative paths.

Note that the examples below is a fairly straightforward translation of your current setup. There are some changes you could make to them to make them even more idiomatic (such as redesigning host-main.exe in such a way that it writes its generated file to a user-provided path rather than a hard-coded one), but I’m omitting those details for now.

Let’s start with the host target.

@rm -f .gen/targ.zig
@touch .gen/targ.zig
@rm -rf .out/*

These usage patterns are generally not needed by Zig build system projects. The build system already writes intermediate files to .zig-cache and caches them, and it is only when you invoke an install step like b.installArtifact that these intermediate files are copied to zig-out or some other install prefix.

@zig build-exe -I. -femit-bin=.out/host-main.exe .main-host.zig
@.out/host-main.exe

These would be replicated by something like this:

// 'zig build-exe ... .main-host.zig'
const host_main_exe = b.addExecutable(.{
    .name = "host-main",
    .root_source_file = .{ .cwd_relative = ".main-host.zig" },
    .target = b.graph.host,
    .optimize = .Debug,
});

// '-I.'
host_main_exe.addIncludePath(.{ .cwd_relative = "." });

// run the artifact (which will be located somewhere in '.zig-cache')
// this implicitly adds '-femit-bin=.zig-cache/...' to the 'zig build-exe' command
const run_host_main_exe = b.addRunArtifact(host_main_exe);

// define a top-level step named 'host', which can be invoked with 'zig build host'
const host = b.step("host", "build and run main-host.exe");

// make the 'host' actually run the artifact
host.dependOn(&run_host_main_exe.step);

With this, you should be able to invoke zig build host to generate the .gen/targ.zig file.

Onwards to targ.

@zig build-exe -I. $(TARG_OPTS) --script .out/linkcmd.ld .out/startup.c .main-targ.zig
@$(OBJCOPY) -O ihex .out/main.out .out/main.out.hex

These should roughly correspond to:

// these add the standard '-Dtarget' and '-Doptimize' options to 'zig build'
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// 'zig build-exe ... $(TARG_OPTS) ... .main-targ.zig'
const main_exe = b.addExecutable(.{
    .name = "main",
    .root_source_file = .{ .cwd_relative = ".main-targ.zig" },
    .target = target,
    .optimize = optimize,
});

// '-I.'
main_exe.addIncludePath(.{ .cwd_relative = "." });

// '--script .out/linkcmd.ld'
main_exe.setLinkerScript(.{ .cwd_relative = ".out/linkcmd.ld" });

// '.out/startup.c'
main_exe.addCSourceFile(.{ .file = .{ .cwd_relative = ".out/startup.c" } });

// define the 'targ' top-level step, which will install the artifact
const targ = b.step("targ", "build main.exe");

// install 'main.exe' to 'zig-out' (or whatever the user specified via the '--prefix' arg)
const install_main_exe = b.addInstallArtifact(main_exe, .{});
targ.dependOn(&install_main_exe.step);

// objcopy
const main_hex = main_exe.addObjCopy(.{ .format = .hex });

// install the objcopy output to 'zig-out'
const install_main_hex = b.addInstallFile(main_hex.getOutput(), "main.out.hex");
targ.dependOn(&install_main_exe.step);

@$(OBJCOPY) -O ihex .out/main.out .out/main.out.hex
@$(OBJDUMP) -h -d .out/main.out >.out/main.out.dis
@$(OBJDUMP) -t .out/main.out | tail -n +5 | sed -e 's/[FO] / /' | sed -e 's/df / /' >.out/main.out.sym
@sort -k1 .out/main.out.sym > .out/main.out.syma
@sort -k5 .out/main.out.sym > .out/main.out.symn
@sha256sum .out/main.out.hex | cut -c -8 >.out/main.out.sha32
@$(OBJDUMP) -h .out/main.out

These are a bit awkward because there’s nothing built into the Zig build system that resembles objdump. For the time being, I would suggest moving these commands to a shell script, which could subsequently be invoked with b.addSystemCommand:

const run_objdump_sh = b.addSystemCommand(.{"bash"});
run_objdump_sh.addFileArg(b.path("objdump.sh"));
run_objdump_sh.addArtifactArg(main_exe);

targ.dependOn(&run_objdump_sh.step);

Porting a non-trivial project like yours to the Zig build system in a way that takes advantage of all the features and cross-compilation options Zig offers will require significant amount of work and time investment. To take full advantage of the Zig build system, you will probably need to restructure your entire build process. I wouldn’t necessarily blame you for sticking with your current makefiles for as long as they work. But if you still want to try, these examples should hopefully be enough to point you in the direction of the APIs you might need.

3 Likes

Since nobody has linked any learning resources yet:

Zig build system docs from the official website: Zig Build System ⚡ Zig Programming Language
GitHub Organization full of C/C++ projects ported to Zig: All Your Codebase · GitHub

4 Likes

You could also check out the Build System Tricks doc.

3 Likes

Thank you all very much. This information is hugely helpful. I’m proceeding along this path slowly. I’ll keep you posted as I progress. But you can feel free to close this issue for now – as I’ll repost more specific questions as I learn more.

Again, thank you for your prompt, and detailed, and considerate help on this topic.