const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
"hello",
.name = "hello.zig"),
.root_source_file = b.path(
.target = b.host,});
b.installArtifact(exe);}
9 Build System
In this chapter, we are going to talk about the build system, and how an entire project is built in Zig. One key advantage of Zig is that it includes a build system embedded in the language itself. This is great, because then you do not have to depend on an external system, separated from the compiler, to build your code.
You can find a good description of Zig’s build system on the article entitled “Build System”1 from the official Zig’s website. We also have the excellent series of posts written by Felix2. Hence, this chapter represents an extra resource for you to consult and rely on.
Building code is one of the things that Zig is best at. One thing that is particularly difficult in C/C++ and even in Rust, is to cross-compile source code to multiple targets (e.g. multiple computer architectures and operational systems), and the zig
compiler is known for being one of the best existing pieces of software for this particular task.
9.1 How source code is built?
We already have talked about the challenges of building source code in low-level languages at Section 1.2.1. As we described at that section, programmers invented Build Systems to surpass these challenges on the process of building source code in low-level languages.
Low-level languages uses a compiler to compile (or to build) your source code into binary instructions. In C and C++, we normally use compilers like gcc
, g++
or clang
to compile our C and C++ source code into these instructions. Every language have its own compiler, and this is no different in Zig.
In Zig, we have the zig
compiler to compile our Zig source code into binary instructions that can be executed by our computer. In Zig, the compilation (or the build) process involves the following components:
- The Zig modules that contains your source code;
- Library files (either a dynamic library or a static library);
- Compiler flags that tailors the build process to your needs.
These are the things that you need to connect together in order to build your source code in Zig. In C and C++, you would have an extra component, which are the header files of the libraries that you are using. But header files do not exist in Zig, so, you only need to care about them if you are linking your Zig source code with a C library. If that is not your case, you can forget about it.
Your build process is usually organized in a build script. In Zig, we normally write this build script into a Zig module in the root directory of our project, named as build.zig
. You write this build script, then, when you run it, your project get’s built into binary files that you can use and distribute to your users.
This build script is normally organized around target objects. A target is simply something to be built, or, in other words, it’s something that you want the zig
compiler to build for you. This concept of “targets” is present in most Build Systems, especially in CMake3.
There are four types of target objects that you can build in Zig, which are:
- An executable (e.g. a
.exe
file on Windows). - A shared library (e.g. a
.so
file in Linux or a.dll
file on Windows). - A static library (e.g. a
.a
file in Linux or a.lib
file on Windows). - An executable file that executes only unit tests (or, a “unit tests executable”).
We are going to talk more about these target objects at Section 9.3.
9.2 The build()
function
A build script in Zig always contains a public (and top-level) build()
function declared. It is like the main()
function on the main Zig module of your project, that we discussed at Section 1.2.3. But instead of creating the entrypoint to your code, this build()
function is the entrypoint to the build process.
This build()
function should accept a pointer to a Build
object as input, and it should use this “build object” to perform the necessary steps to build your project. The return type of this function is always void
, and this Build
struct comes directly from the Zig Standard Library (std.Build
). So, you can access this struct by just importing the Zig Standard Library into your build.zig
module.
Just as a very simple example, here you can see the source code necessary to build an executable file from the hello.zig
Zig module.
You can define and use other functions and objects in this build script. You can also import other Zig modules as you would normally do in any other module of your project. The only real requirement for this build script, is to have a public and top-level build()
function defined, that accepts a pointer to a Build
struct as input.
9.3 Target objects
As we described over the previous sections, a build script is composed around target objects. Each target object is normally a binary file (or an output) that you want to get from the build process. You can list multiple target objects in your build script, so that the build process generates multiple binary files for you at once.
For example, maybe you are a developer working in a cross-platform application, and, because this application is cross-platform, you probably need to release binary files of your software for each OS supported by your application to your end users. Thus, you can define a different target object in your build script for each OS (Windows, Linux, etc.) where you want to publish your software. This will make the zig
compiler to build your project to multiple target OS’s at once. The Zig Build System official documentation have a great code example that demonstrates this strategy4.
A target object is created by the following methods of the Build
struct that we introduced at Section 9.2:
addExecutable()
creates an executable file;addSharedLibrary()
creates a shared library file;addStaticLibrary()
creates a static library file;addTest()
creates an executable file that executes unit tests.
These functions are methods from the Build
struct that you receive as input of the build()
function. All of them, create as output a Compile
object, which represents a target object to be compiled by the zig
compiler. All of these functions accept a similar struct literal as input. This struct literal defines three essential specs about this target object that you are building: name
, target
and root_source_file
.
We have already seen these three options being used on the previous example, where we used the addExecutable()
method to create an executable target object. This example is reproduced below. Notice the use of the path()
method from the Build
struct, to define a path in the root_source_file
option.
{
exe = b.addExecutable(."hello",
.name = "hello.zig"),
.root_source_file = b.path(
.target = b.host,});
The name
option specify the name that you want to give to the binary file defined by this target object. So, in this example, we are building an executable file named hello
. Is common to set this name
option to the name of your project.
Furthermore, the target
option specify the target computer architecture (or the target operational system) of this binary file. For example, if you want this target object to run on a Windows machine that uses a x86_64
architecture, you can set this target
option to x86_64-windows-gnu
for example. This will make the zig
compiler to compile the project to run on a x86_64
Windows machine. You can see the full list of architectures and OS’s that the zig
compiler supports by running the zig targets
command in the terminal.
Now, if you are building the project to run on the current machine that you are using to run this build script, you can set this target
option to the host
method of the Build
object, like we did in the example above. This host
method identifies the current machine where you are currently running the zig
compiler.
At last, the root_source_file
option specifies the root Zig module of your project. That is the Zig module that contains the entrypoint to your application (i.e. the main()
function), or, the main API of your library. This also means that, all the Zig modules that compose your project are automatically discovered from the import statements you have inside this “root source file”. The zig
compiler can detect when a Zig module depends on the other through the import statements, and, as a result, it can discover the entire map of Zig modules used in your project.
This is handy, and it is different from what happens in other build systems. In CMake for example, you have to explicitly list the paths to all source files that you want to include in your build process. This is probably a symptom of the “lack of conditional compilation” in the C and C++ compilers. Since they lack this feature, you have to explicitly choose which source files should be sent to the C/C++ compiler, because not every C/C++ code is portable or supported in every operational system, and, therefore, would cause a compilation error in the C/C++ compiler.
Now, one important detail about the build process is that, you have to explicitly install the target objects that you create in your build script, by using the installArtifact()
method of the Build
struct.
Everytime you invoke the build process of your project, by calling the build
command of the zig
compiler, a new directory named zig-out
is created in the root directory of your project. This new directory contains the outputs of the build process, that is, the binary files built from your source code.
What the installArtifact()
method do is to install (or copy) the built target objects that you defined to this zig-out
directory. This means that, if you do not install the target objects you define in your build script, these target objects are essentially discarded at the end of the build process.
For example, you might be building a project that uses a third party library that is built together with the project. So, when you build your project, you would need first, to build the third party library, and then, you link it with the source code of your project. So, in this case, we have two binary files that are generated in the build process (the executable file of your project, and the third party library). But only one is of interest, which is the executable file of our project. We can discard the binary file of the third party library, by simply not installing it into this zig-out
directory.
This installArtifact()
method is pretty straightforward. Just remember to apply it to every target object that you want to save into the zig-out
directory, like in the example below:
{
exe = b.addExecutable(."hello",
.name = "hello.zig"),
.root_source_file = b.path(
.target = b.host,});
b.installArtifact(exe);
9.4 Setting the build mode
We have talked about the three essential options that are set when you create a new target object. But there is also a fourth option that you can use to set the build mode of this target object, which is the optimize
option. This option is called this way, because the build modes in Zig are treated more of an “optimization vs safety” problem. So optimization plays an important role here. Don’t worry, I’m going back to this question very soon.
In Zig, we have four build modes (which are listed below). Each one of these build modes offer different advantages and characteristics. As we described at Section 5.2.1, the zig
compiler uses the Debug
build mode by default, when you don’t explicitly choose a build mode.
Debug
, mode that produces and includes debugging information in the output of the build process (i.e. the binary file defined by the target object);ReleaseSmall
, mode that tries to produce a binary file that is small in size;ReleaseFast
, mode that tries to optimize your code, in order to produce a binary file that is as fast as possible;ReleaseSafe
, mode that tries to make your code as safe as possible, by including safeguards when possible.
So, when you build your project, you can set the build mode of your target object to ReleaseFast
for example, which will tell the zig
compiler to apply important optimizations in your code. This creates a binary file that simply runs faster on most contexts, because it contains a more optimized version of your code. However, as a result, we often lose some safety features in our code. Because some safety checks are removed from the final binary file, which makes your code run faster, but in a less safe manner.
This choice depends on your current priorities. If you are building a cryptography or banking system, you might prefer to prioritize safety in your code, so, you would choose the ReleaseSafe
build mode, which is a little slower to run, but much more secure, because it includes all possible runtime safety checks in the binary file built in the build process. In the other hand, if you are writing a game for example, you might prefer to prioritize performance over safety, by using the ReleaseFast
build mode, so that your users can experience faster frame rates in your game.
In the example below, we are creating the same target object that we have used on previous examples. But this time, we are specifying the build mode of this target object to the ReleaseSafe
mode.
const exe = b.addExecutable(.{
"hello",
.name = "hello.zig"),
.root_source_file = b.path(
.target = b.host,
.optimize = .ReleaseSafe});
b.installArtifact(exe);
9.5 Setting the version of your build
Everytime you build a target object in your build script, you can assign a version number to this specific build, following a semantic versioning framework. You can find more about semantic versioning by visiting the Semantic Versioning website5. Anyway, in Zig, you can specify the version of your build, by providing a SemanticVersion
struct to the version
option, like in the example below:
const exe = b.addExecutable(.{
"hello",
.name = "hello.zig"),
.root_source_file = b.path(
.target = b.host,{
.version = .2, .minor = 9, .patch = 7
.major = }
});
b.installArtifact(exe);
9.6 Detecting the OS in your build script
Is very common in Build Systems to use different options, or, to include different modules, or, to link against different libraries depending on the Operational System (OS) that you are targeting in the build process.
In Zig, you can detect the target OS of the build process, by looking at the os.tag
inside the builtin
module from the Zig library. In the example below, we are using an if statement to run some arbitrary code when the target of the build process is a Windows system.
const builtin = @import("builtin");
if (builtin.target.os.tag == .windows) {
// Code that runs only when the target of
// the compilation process is Windows.
}
9.7 Adding a run step to your build process
One thing that is neat in Rust is that you can compile and run your source code with one single command (cargo run
) from the Rust compiler. We saw at Section 1.2.5 how can we perform a similar job in Zig, by building and running our Zig source code through the run
command from the zig
compiler.
But how can we, at the same time, build and run the binary file specified by a target object in our build script? The answer is by including a “run artifact” in our build script. A run artifact is created through the addRunArtifact()
method from the Build
struct. We simply provide as input to this function the target object that describes the binary file that we want to execute. As a result, this function creates a run artifact that is capable of executing this binary file.
In the example below, we are defining an executable binary file named hello
, and we use this addRunArtifact()
method to create a run artifact that will execute this hello
executable file.
const exe = b.addExecutable(.{
"hello",
.name = "src/hello.zig"),
.root_source_file = b.path(
.target = b.host});
b.installArtifact(exe);const run_arti = b.addRunArtifact(exe);
Now that we have created this run artifact, we need to include it in the build process. We do that by declaring a new step in our build script to call this artifact, through the step()
method of the Build
struct.
We can give any name we want to this step, but, for our context here, I’m going to name this step as “run”. Also, I give it a brief description to this step (“Run the project”).
const run_step = b.step(
"run", "Run the project"
);
Now that we have declared this “run step” we need to tell Zig that this “run step” depends on the run artifact. In other words, a run artifact always depends on a “step” to effectively be executed. By creating this dependency we finally stablish the necessary commands to build and run the executable file from the build script.
We can establish a dependency between the run step and the run artifact by using the dependsOn()
method from the run step. So, we first create the run step, and then, we link it with the run artifact, by using this dependsOn()
method from the run step.
run_step.dependOn(&run_arti.step);
The entire source code of this specific build script that we wrote, piece by piece, in this section, is available in the build_and_run.zig
module. You can see this module by visiting the official repository of this book 6.
When you declare a new step in your build script, this step becomes available through the build
command in the zig
compiler. You can actually see this step by running zig build --help
in the terminal, like in the example below, where we can see that this new “run” step that we declared in the build script appeared in the output.
zig build --help
Steps:
...
run Run the project
...
Now, everything that we need to is to call this “run” step that we created in our build script. We call it by using the name that we gave to this step after the build
command from the zig
compiler. This will cause the compiler to build our executable file and execute it at the same time.
zig build run
9.8 Build unit tests in your project
We have talked at length about writing unit tests in Zig at Chapter 8, and we also have talked about how to execute these unit tests through the test
command of the zig
compiler. However, as we did with the run
command on the previous section, we also might want to include some commands in our build script to also build and execute the unit tests in our project.
So, once again, we are going to discuss how a specific built-in command from the zig
compiler, (in this case, the test
command) can be used in a build script in Zig. Here is where a “test target object” comes into play. As was described at Section 9.3, we can create a test target object by using the addTest()
method of the Build
struct. The first thing that we need to do is to declare a test target object in our build script.
const test_exe = b.addTest(.{
"unit_tests",
.name = "src/main.zig"),
.root_source_file = b.path(
.target = b.host,});
b.installArtifact(test_exe);
A test target object essentially selects all test
blocks in all Zig modules across your project, and builds only the source code present inside these test
blocks in your project. As a result, this target object creates an executable file that contains only the source code present in all of these test
blocks (i.e. the unit tests) in your project.
Perfect! Now that we have declared this test target object, an executable file named unit_tests
is built by the zig
compiler when we trigger the build script with the build
command. After the build process is finished, we can simply execute this unit_tests
executable in the terminal.
However, if you remember the previous section, we already learned how can we create a run step in our build script, to execute an executable file built by the build script.
So, we could simply add a run step in our build script to run these unit tests from a single command in the zig
compiler, to make our lifes easier. In the example below, we demonstrate the commands to register a new build step called “tests” in our build script to run these unit tests.
const run_arti = b.addRunArtifact(test_exe);
const run_step = b.step("tests", "Run unit tests");
run_step.dependOn(&run_arti.step);
Now that we registered this new build step, we can trigger it by calling the command below in the terminal. You can also checkout the complete source code for this specific build script at the build_tests.zig
module at the official repository of this book 7.
zig build tests
9.9 Tailoring your build process with user-provided options
Sometimes, you want to make a build script that is customizable by the user of your project. You can do that by creating user-provided options in your build script. We create an user-provided option by using the option()
method from the Build
struct.
With this method, we create a “build option” which can be passed to the build.zig
script at the command line. The user have the power of setting this option at the build
command from the zig
compiler. In other words, each build option that we create becomes a new command line argument that is accessible through the build
command of the compiler.
These “user-provided options” are set by using the prefix -D
in the command line. For example, if we declare an option named use_zlib
, that receives a boolean value which indicates if we should link our source code to zlib
or not, we can set the value of this option in the command line with -Duse_zlib
. The code example below demonstrates this idea:
const std = @import("std");
pub fn build(b: *std.Build) void {
const use_zlib = b.option(
bool,
"use_zlib",
"Should link to zlib?"
orelse false;
) const exe = b.addExecutable(.{
"hello",
.name = "example.zig"),
.root_source_file = b.path(
.target = b.host,});
if (use_zlib) {
"zlib");
exe.linkSystemLibrary(}
b.installArtifact(exe);}
zig build -Duse_zlib=false
9.10 Linking to external libraries
One essential part of every build process is the linking stage. This stage is responsible for combining the multiple object files that represent your code, into a single executable file. It also links this executable file to external libraries, if you use any in your code.
In Zig, we have two notions of a “library”, which are: 1) a system’s library; 2) a local library. A system’s library is just a library that already is installed in your system. While a local library is a library that belongs to the current project. Is a library that is present in your project directory, and that you are building together with your project source code.
The basic difference between the two, is that a system’s library is already built and installed in your system, supposedly, and all you need to do is to link your source code to this library to start using it. We do that by using the linkSystemLibrary()
method from a Compile
object. This method accepts the name of the library in a string as input. Remember from Section 9.3 that a Compile
object is a target object that you declare in your build script.
When you link a particular target object with a system’s library, the zig
compiler will use pkg-config
to find where are the binary files and also the header files of this library in your system. When it finds these files, the linker present in the zig
compiler will link your object files with the files of this library to produce a single binary file for you.
In the example below, we are creating an executable file named image_filter
, and, we are linking this executable file to the C Standard Library with the method linkLibC()
, but we also are linking this executable file to the C library libpng
that is currently installed in my system.
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
"image_filter",
.name = "src/main.zig"),
.root_source_file = b.path(
.target = target,
.optimize = optimize,});
exe.linkLibC();"png");
exe.linkSystemLibrary(
b.installArtifact(exe);}
If you are linking with a C library in your project, is generally a good idea to also link your code with the C Standard Library. Because is very likely that this C library uses some functionality of the C Standard Library at some point. The same goes to C++ libraries. So, if you are linking with C++ libraries, is a good idea to link your project with the C++ Standard Library by using the linkLibCpp()
method.
On the order side, when you want to link with a local library, you should use the linkLibrary()
method of a Compile
object. This method expects to receive another Compile
object as input. That is, another target object defined in your build script, using either the addStaticLibrary()
or addSharedLibrary()
methods which defines a library to be built.
As we discussed earlier, a local library is a library that is local to your project, and that is being built together with your project. So, you need to create a target object in your build script to build this local library. Then, you link the target objects of interest in your project, with this target object that identifies this local library.
Take a look at this example extracted from the build script of the libxev
library8. You can see in this snippet that we are declaring a shared library file, from the c_api.zig
module. Then, later in the build script, we declare an executable file named "dynamic-binding-test"
, which links to this shared library that we defined earlier in the script.
const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
const dynamic_lib = b.addSharedLibrary(.{
.name = dynamic_lib_name,"src/c_api.zig"),
.root_source_file = b.path(
.target = target,
.optimize = optimize,});
b.installArtifact(dynamic_lib);// ... more lines in the script
const dynamic_binding_test = b.addExecutable(.{
"dynamic-binding-test",
.name =
.target = target,
.optimize = optimize,});
dynamic_binding_test.linkLibrary(dynamic_lib);
9.11 Building C code
The zig
compiler comes with a C compiler embedded in it. In other words, you can use the zig
compiler to build C projects. This C compiler is available through the cc
command of the zig
compiler.
As an example, let’s use the famous FreeType library9. FreeType is one of the most widely used pieces of software in the world. It is a C library designed to produce high-quality fonts. But it is also heavily used in the industry to natively render text and fonts in the screen of your computer.
In this section, we are going to write a build script, piece by piece, that is capable of building the FreeType project from source. You can find the source code of this build script on the freetype-zig
repository10 available at GitHub.
After you download the source code of FreeType from the official website11, you can start writing the build.zig
module. We begin by defining the target object that defines the binary file that we want to compile.
As an example, I will build the project as a static library file using the addStaticLibrary()
method to create the target object. Also, since FreeType is a C library, I will also link the library against libc
through the linkLibC()
method, to guarantee that any use of the C Standard Library is covered in the compilation process.
const target = b.standardTargetOptions(.{});
const opti = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
"freetype",
.name =
.optimize = opti,
.target = target,});
lib.linkLibC();
9.11.1 Creating C compiler flags
Compiler flags are also known as “compiler options” by many programmers, or also, as “command options” in the GCC official documentation. It’s fair to also call them as the “command-line arguments” of the C compiler. In general, we use compiler flags to turn on (or turn off) some features from the compiler, or to tweak the compilation process to fit the needs of our project.
In build scripts written in Zig, we normally list the C compiler flags to be used in the compilation process in a simple array, like in the example below.
const c_flags = [_][]const u8{
"-Wall",
"-Wextra",
"-Werror",
};
In theory, there is nothing stopping you from using this array to add “include paths” (with the -I
flag) or “library paths” (with the -L
flag) to the compilation process. But there are formal ways in Zig to add these types of paths in the compilation process. Both are discussed at Section 9.11.5 and Section 9.11.4.
Anyway, in Zig, we add C flags to the build process together with the C files that we want to compile, by using the addCSourceFile()
and addCSourceFiles()
methods. In the example above, we have just declared the C flags that we want to use. But we haven’t added them to the build process yet. To do that, we also need to list the C files to be compiled.
9.11.2 Listing your C files
The C files that contains “cross-platform” source code are listed in the c_source_files
object below. These are the C files that are included by default in every platform supported by the FreeType library. Now, since the amount of C files in the FreeType library is big, I have omitted the rest of the files in the code example below, for brevity purposes.
const c_source_files = [_][]const u8{
"src/autofit/autofit.c",
"src/base/ftbase.c",
// ... and many other C files.
};
Now, in addition to “cross-platform” source code, we also have some C files in the FreeType project that are platform-specific, meaning that, they contain source code that can only be compiled in specific platforms, and, as a result, they are only included in the build process on these specific target platforms. The objects that list these C files are exposed in the code example below.
const windows_c_source_files = [_][]const u8{
"builds/windows/ftdebug.c",
"builds/windows/ftsystem.c"
};
const linux_c_source_files = [_][]const u8{
"src/base/ftsystem.c",
"src/base/ftdebug.c"
};
Now that we declared both the files that we want to include and the C compiler flags to be used, we can add them to the target object that describes the FreeType library, by using the addCSourceFile()
and addCSourceFiles()
methods.
Both of these functions are methods from a Compile
object (i.e. a target object). The addCSourceFile()
method is capable of adding a single C file to the target object, while the addCSourceFiles()
method is used to add multiple C files in a single command. You might prefer to use addCSourceFile()
when you need to use different compiler flags on specific C files in your project. But, if you can use the same compiler flags across all C files, then, you will probably find addCSourceFiles()
a better choice.
Notice that we are using the addCSourceFiles()
method in the example below, to add both the C files and the C compiler flags. Also notice that we are using the os.tag
that we learned about at Section 9.6, to add the platform-specific C files.
const builtin = @import("builtin");
lib.addCSourceFiles(
&c_source_files, &c_flags
);
switch (builtin.target.os.tag) {
{
.windows =>
lib.addCSourceFiles(
&windows_c_source_files,
&c_flags
);},
{
.linux =>
lib.addCSourceFiles(
&linux_c_source_files,
&c_flags
);},
else => {},
}
9.11.3 Defining C Macros
C Macros are an essential part of the C programming language, and they are commonly defined through the -D
flag from a C compiler. In Zig, you can define a C Macro to be used in your build process by using the defineCMacro()
method from the target object that defines the binary file that you are building.
In the example below, we are using the lib
object that we have defined on the previous sections to define some C Macros used by the FreeType project in the compilation process. These C Macros specify if FreeType should (or should not) include functionalities from different external libraries.
"FT_DISABLE_ZLIB", "TRUE");
lib.defineCMacro("FT_DISABLE_PNG", "TRUE");
lib.defineCMacro("FT_DISABLE_HARFBUZZ", "TRUE");
lib.defineCMacro("FT_DISABLE_BZIP2", "TRUE");
lib.defineCMacro("FT_DISABLE_BROTLI", "TRUE");
lib.defineCMacro("FT2_BUILD_LIBRARY", "TRUE"); lib.defineCMacro(
9.11.4 Adding library paths
Library paths are paths in your computer where the C compiler will look (or search) for library files to link against your source code. In other words, when you use a library in your C source code, and you ask the C compiler to link your source code against this library, the C compiler will search for the binary files of this library across the paths listed in this “library paths” set.
These paths are platform specific, and, by default, the C compiler starts by looking at a pre-defined set of places in your computer. But you can add more paths (or more places) to this list. For example, you may have a library installed in a non-conventional place of your computer, and you can make the C compiler “see” this “non-conventional place” by adding this path to this list of pre-defined paths.
In Zig, you can add more paths to this set by using the addLibraryPath()
method from your target object. First, you defined a LazyPath
object, containing the path you want to add, then, you provide this object as input to the addLibraryPath()
method, like in the example below:
const lib_path: std.Build.LazyPath = .{
"/usr/local/lib/"
.cwd_relative = };
lib.addLibraryPath(lib_path);
9.11.5 Adding include paths
The preprocessor search path is a popular concept from the C community, but it is also known by many C programmers as “include paths”, because the paths in this “search path” relate to the #include
statements found in the C files.
Include paths are similar to library paths. They are a set of pre-defined places in your computer where the C compiler will look for files during the compilation process. But instead of looking for library files, the include paths are places where the compiler looks for header files included in your C source code. This is why many C programmers prefer to call these paths as the “preprocessor search path”. Because header files are processed during the preprocessor stage of the compilation process.
So, every header file that you include in your C source code, through a #include
statements needs to be found somewhere, and the C compiler will search for this header file across the paths listed in this “include paths” set. Include paths are added to the compilation process through the -I
flag.
In Zig, you can add new paths to this pre-defined set of paths, by using the addIncludePath()
method from your target object. This method also accepts a LazyPath
object as input.
const inc_path: std.Build.LazyPath = .{
"./include"
.path = };
lib.addIncludePath(inc_path);
https://ziglang.org/learn/build-system/#user-provided-options↩︎
https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html↩︎
https://github.com/pedropark99/zig-book/blob/main/ZigExamples/build_system/build_and_run.zig↩︎
https://github.com/pedropark99/zig-book/blob/main/ZigExamples/build_system/build_tests.zig↩︎