diff --git a/Chapters/07-build-system.qmd b/Chapters/07-build-system.qmd index 85899e3..61c2a5d 100644 --- a/Chapters/07-build-system.qmd +++ b/Chapters/07-build-system.qmd @@ -19,18 +19,18 @@ knitr::opts_chunk$set( # Build System {#sec-build-system} -In this chapter, we are going to talk about the build system in Zig, and how an entire project +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 a external system, separated +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"](https://ziglang.org/learn/build-system/#user-provided-options)[^zig-art1] -available in the official Zig's website. +from the official Zig's website. We also have the excellent [series of posts written by Felix](https://zig.news/xq/zig-build-explained-part-1-59lf)[^felix]. -But this chapter represents an extra resource for you to consult and rely on. +Hence, this chapter represents an extra resource for you to consult and rely on. [^felix]: [^zig-art1]: @@ -46,9 +46,9 @@ for this particular task. ## How source code is built? -We already talked about the challenges of building source code in low-level languages +We already have talked about the challenges of building source code in low-level languages at @sec-project-files. As we described at that section, programmers invented Build Systems -to surpass these challenges on the building processes of low-level languages. +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 @@ -65,7 +65,7 @@ the following components: - 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 is the header files of +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. @@ -84,10 +84,10 @@ especially in CMake[^cmake]. There are four types of target objects that you can build in Zig, which are: -- An executable, which is simply a binary executable file (e.g. a `.exe` file on Windows). -- A shared library, which is simply a binary library file (e.g. a `.so` file in Linux or a `.dll` file on Windows). -- A static library, which is simply a binary library file (e.g. a `.a` file in Linux or a `.lib` file on Windows). -- An unit tests executable, which is an executable file that executes only unit tests. +- 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 @sec-targets. @@ -133,10 +133,10 @@ Each target object is normally a binary file (or an output) that you want to get multiple target objects in your build script, so that the build process generates multiple binary files for you at once. -For example, you may be a developer working in a cross-platform application, -and, because this application is cross-platform, you probably need to realease +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. -You can define a target object in your build script +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 strategy](https://ziglang.org/learn/build-system/#handy-examples)[^zig-ex]. @@ -160,7 +160,7 @@ by the `zig` compiler. All of these functions accept a similar struct literal as This struct literal defines three essential specs about this target object that you are building: `name`, `target` and `root_source_file`. -We already saw these three options being used on the previous example, +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. @@ -176,7 +176,7 @@ exe = b.addExecutable(.{ The `name` option specificy 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 traditional to set this `name` option to the name of your project. +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 @@ -196,15 +196,15 @@ 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 that you have inside this "root source file". +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 C and C++ compilers. Since they lack this feature, you have -to explicitly choose which source files are sent to the C/C++ compiler, since not +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. @@ -215,10 +215,10 @@ install the target objects that you create in your build script**, by using the 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 output of the build process, +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 install (or copy) the built target objects +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 @@ -232,8 +232,8 @@ 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. -So, is easy to use this `installArtifact()` method. Just remember to apply it to every -target object that you want to save it into the `zig-out` directory, like in the example below: +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: ```{zig} @@ -250,14 +250,14 @@ b.installArtifact(exe); ## Setting the build mode -We talked about the three essential options that are set when you create a new target object. +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 optmization plays an important role here. +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 the four build modes listed below. Each one of these build modes offer +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 @sec-compile-debug-mode, the `zig` compiler uses the `Debug` build mode by default, when you don't explicitly choose a build mode. @@ -269,7 +269,7 @@ uses the `Debug` build mode by default, when you don't explicitly choose a build 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 normally loose some security funcionalities in our 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. @@ -279,9 +279,8 @@ slower to run, but much more secure, because it includes all possible runtime sa 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 used on previous examples. But this time, we are specifying -the build mode of this target object to use the `ReleaseSafe` mode. +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. ```{zig} #| eval: false @@ -300,8 +299,7 @@ b.installArtifact(exe); 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 website](https://semver.org/)[^semver]. -Anyway, in Zig, -you can specify the version of your build, by providing a `SemanticVersion` struct to +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: @@ -356,7 +354,7 @@ 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, and the function creates as output, a run artifact capable of executing +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`, @@ -374,10 +372,11 @@ b.installArtifact(exe); const run_arti = b.addRunArtifact(exe); ``` -Now that we created the run artifact, we need to include it in +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"). @@ -390,14 +389,14 @@ const run_step = b.step( ``` -Now that we declared this "run step" we need to tell Zig that +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 establish a dependency between the run step and the run artifact +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. @@ -447,19 +446,17 @@ zig build run ## Build unit tests in your project -We talk at length about writing unit tests in Zig, and we also talk about how to execute these unit tests through -the `test` command of the `zig` compiler at @sec-unittests. However, +We have talked at length about writing unit tests in Zig at @sec-unittests, +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. -This means that, we can include a step in our build script to build and run -all unit tests in our project at once. - Here is where a "test target object" comes into play. As was described at @sec-targets, we can create a test target object by using the `addTest()` method of -the `Build` struct. So the first thing that we need to do is to +the `Build` struct. The first thing that we need to do is to declare a test target object in our build script. @@ -474,13 +471,13 @@ b.installArtifact(test_exe); ``` -A test target object essentially filter all `test` blocks in all Zig modules +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 declared this test target object, an executable file +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 @@ -522,14 +519,14 @@ zig build tests 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. In Zig, we create these options using the +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 accessible in the `build` command +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. @@ -559,11 +556,6 @@ pub fn build(b: *std.Build) void { } ``` -You can set this `use_zlib` option at the command line when you are invoking the -`build` command from the `zig` compiler. In the example below, we set this option -to false, which means that the build script will not link our binary executable to -the `zlib` library. - ```bash zig build -Duse_zlib=false ``` @@ -575,7 +567,7 @@ zig build -Duse_zlib=false 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 an external libraries, if you use any in your code. +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 @@ -592,11 +584,12 @@ in a string as input. Remember from @sec-targets 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 in your system -are the binary files and also the header files of this library that you requested for. +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 executable file. +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 @@ -624,7 +617,7 @@ 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 using the `linkLibCpp()` method. +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. @@ -633,7 +626,7 @@ That is, another target object defined in your build script, using either the `addStaticLibrary()` or `addSharedLibrary()` methods which defines a library to be built. -Because as we discussed earlier, a local library is a library +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, @@ -642,8 +635,8 @@ with this target object that identifies this local library. Take a look at this example extracted from the build script of the [`libxev` library](https://github.com/mitchellh/libxev/tree/main)[^libxev2]. 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 +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. @@ -742,7 +735,7 @@ or "library paths" (with the `-L` flag) to the compilation process. But there ar add these types of paths in the compilation process. Both are discussed at @sec-include-paths and @sec-library-paths. -Anyway, in Zig, we add C flags to the build process together with the C files that we want to compile, using the +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. @@ -764,7 +757,7 @@ const c_source_files = [_][]const u8{ ``` 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 obly be compiled in specific platforms, +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. @@ -829,8 +822,8 @@ 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 defined -on the previous sections to define some C Macros used by FreeType +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. diff --git a/_freeze/Chapters/07-build-system/execute-results/html.json b/_freeze/Chapters/07-build-system/execute-results/html.json index 6383f1d..e7a9b86 100644 --- a/_freeze/Chapters/07-build-system/execute-results/html.json +++ b/_freeze/Chapters/07-build-system/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "0ecc6c8fa1dcf480a1f26a65b11ec204", + "hash": "5adc142b0d10426f3f423680a0988cd2", "result": { "engine": "knitr", - "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n\n\n\n\n# Build System {#sec-build-system}\n\n\nIn this chapter, we are going to talk about the build system in Zig, and how an entire project\nis built in Zig.\nOne key advantage of Zig is that it includes a build system embedded in the language itself.\nThis is great, because then you do not have to depend on a external system, separated\nfrom the compiler, to build your code.\n\n\nYou can find a good description of Zig's build system\non the [article entitled \"Build System\"](https://ziglang.org/learn/build-system/#user-provided-options)[^zig-art1]\navailable in the official Zig's website.\nWe also have the excellent [series of posts written by Felix](https://zig.news/xq/zig-build-explained-part-1-59lf)[^felix].\nBut this chapter represents an extra resource for you to consult and rely on.\n\n[^felix]: \n[^zig-art1]: \n\nBuilding code is one of the things that Zig is best at. One thing that is particularly\ndifficult in C/C++ and even in Rust, is to cross-compile source code to multiple targets\n(e.g. multiple computer architectures and operational systems),\nand the `zig` compiler is known for being one of the best existing pieces of software\nfor this particular task.\n\n\n\n\n## How source code is built?\n\nWe already talked about the challenges of building source code in low-level languages\nat @sec-project-files. As we described at that section, programmers invented Build Systems\nto surpass these challenges on the building processes of low-level languages.\n\nLow-level languages uses a compiler to compile (or to build) your source code into binary instructions.\nIn C and C++, we normally use compilers like `gcc`, `g++` or `clang` to compile\nour C and C++ source code into these instructions.\nEvery language have it's own compiler, and this is no different in Zig.\n\nIn Zig, we have the `zig` compiler to compile our Zig source code into\nbinary instructions that can be executed by our computer.\nIn Zig, the compilation (or the build) process involves\nthe following components:\n\n- The Zig modules that contains your source code;\n- Library files (either a dynamic library or a static library);\n- Compiler flags that tailors the build process to your needs.\n\nThese are the things that you need to connect together in order to build your\nsource code in Zig. In C and C++, you would have an extra component, which is the header files of\nthe libraries that you are using. But header files do not exist in Zig, so, you only need\nto care about them if you are linking your Zig source code with a C library.\nIf that is not your case, you can forget about it.\n\nYour build process is usually organized in a build script. In Zig, we normally\nwrite this build script into a Zig module in the root directory of our project,\nnamed as `build.zig`. You write this build script, then, when you run it, your project\nget's built into binary files that you can use and distribute to your users.\n\nThis build script is normally organized around *target objects*. A target is simply\nsomething to be built, or, in other words, it's something that you want the `zig` compiler\nto build for you. This concept of \"targets\" is present in most Build Systems,\nespecially in CMake[^cmake].\n\n[^cmake]: \n\nThere are four types of target objects that you can build in Zig, which are:\n\n- An executable, which is simply a binary executable file (e.g. a `.exe` file on Windows).\n- A shared library, which is simply a binary library file (e.g. a `.so` file in Linux or a `.dll` file on Windows).\n- A static library, which is simply a binary library file (e.g. a `.a` file in Linux or a `.lib` file on Windows).\n- An unit tests executable, which is an executable file that executes only unit tests.\n\nWe are going to talk more about these target objects at @sec-targets.\n\n\n\n## The `build()` function {#sec-build-fun}\n\nA build script in Zig always contains a public (and top-level) `build()` function declared.\nIt is like the `main()` function on the main Zig module of your project, that we discussed at @sec-main-file.\nBut instead of creating the entrypoint to your code, this `build()` function is the entrypoint to the build process.\n\nThis `build()` function should accept a pointer to a `Build` object as input, and it should use this \"build object\" to perform\nthe necessary steps to build your project. The return type of this function is always `void`,\nand this `Build` struct comes directly from the Zig Standard Library (`std.Build`). So, you can \naccess this struct by just importing the Zig Standard Library into your `build.zig` module.\n\nJust as a very simple example, here you can see the source code necessary to build\nan executable file from the `hello.zig` Zig module.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn build(b: *std.Build) void {\n const exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n });\n b.installArtifact(exe);\n}\n```\n:::\n\n\n\n\nYou can define and use other functions and objects in this build script. You can also import\nother Zig modules as you would normally do in any other module of your project.\nThe only real requirement for this build script, is to have a public and top-level\n`build()` function defined, that accepts a pointer to a `Build` struct as input.\n\n\n## Target objects {#sec-targets}\n\nAs we described over the previous sections, a build script is composed around target objects.\nEach target object is normally a binary file (or an output) that you want to get from the build process. You can list\nmultiple target objects in your build script, so that the build process generates multiple\nbinary files for you at once.\n\nFor example, you may be a developer working in a cross-platform application,\nand, because this application is cross-platform, you probably need to realease\nbinary files of your software for each OS supported by your application to your end users.\nYou can define a target object in your build script\nfor each OS (Windows, Linux, etc.) where you want to publish your software.\nThis will make the `zig` compiler to build your project to multiple target OS's at once.\nThe Zig Build System official documentation have a [great code example that demonstrates this strategy](https://ziglang.org/learn/build-system/#handy-examples)[^zig-ex].\n\n[^zig-ex]: \n\n\nA target object is created by the following methods of the `Build` struct that we introduced\nat @sec-build-fun:\n\n- `addExecutable()` creates an executable file;\n- `addSharedLibrary()` creates a shared library file;\n- `addStaticLibrary()` creates a static library file;\n- `addTest()` creates an executable file that executes unit tests.\n\n\nThese functions are methods from the `Build` struct that you receive\nas input of the `build()` function. All of them, create as output\na `Compile` object, which represents a target object to be compiled\nby the `zig` compiler. All of these functions accept a similar struct literal as input.\nThis struct literal defines three essential specs about this target object that you are building:\n`name`, `target` and `root_source_file`.\n\nWe already saw these three options being used on the previous example,\nwhere we used the `addExecutable()` method to create an executable target object.\nThis example is reproduced below. Notice the use of the `path()` method\nfrom the `Build` struct, to define a path in the `root_source_file` option.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nexe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n});\n```\n:::\n\n\n\n\nThe `name` option specificy the name that you want to give to the binary file defined\nby this target object. So, in this example, we are building an executable file named `hello`.\nIs traditional to set this `name` option to the name of your project.\n\n\nFurthermore, the `target` option specify the target computer architecture (or the target operational system) of this\nbinary file. For example, if you want this target object to run on a Windows machine\nthat uses a `x86_64` architecture, you can set this `target` option to `x86_64-windows-gnu` for example.\nThis will make the `zig` compiler to compile the project to run on a `x86_64` Windows machine.\nYou can see the full list of architectures and OS's that the `zig` compiler supports by running\nthe `zig targets` command in the terminal.\n\nNow, if you are building the project to run on the current machine\nthat you are using to run this build script, you can set this `target`\noption to the `host` method of the `Build` object, like we did in the example above.\nThis `host` method identifies the current machine where you are\ncurrently running the `zig` compiler.\n\n\nAt last, the `root_source_file` option specifies the root Zig module of your project.\nThat is the Zig module that contains the entrypoint to your application (i.e. the `main()` function), or, the main API of your library.\nThis also means that, all the Zig modules that compose your project are automatically discovered\nfrom the import statements that you have inside this \"root source file\".\nThe `zig` compiler can detect when a Zig module depends on the other through the import statements,\nand, as a result, it can discover the entire map of Zig modules used in your project.\n\nThis is handy, and it is different from what happens in other build systems.\nIn CMake for example, you have to explicitly list the paths to all source files that you want to\ninclude in your build process. This is probably a symptom of the \"lack of conditional\ncompilation\" in C and C++ compilers. Since they lack this feature, you have\nto explicitly choose which source files are sent to the C/C++ compiler, since not\nevery C/C++ code is portable or supported in every operational system, and, therefore,\nwould cause a compilation error in the C/C++ compiler.\n\n\nNow, one important detail about the build process is that, you have to **explicitly\ninstall the target objects that you create in your build script**, by using the\n`installArtifact()` method of the `Build` struct.\n\nEverytime you invoke the build process of your project, by calling the `build` command\nof the `zig` compiler, a new directory named `zig-out` is created in the root\ndirectory of your project. This new directory contains the output of the build process,\nthat is, the binary files built from your source code.\n\nWhat the `installArtifact()` method do is install (or copy) the built target objects\nthat you defined to this `zig-out` directory.\nThis means that, if you do not\ninstall the target objects you define in your build script, these target objects are\nessentially discarded at the end of the build process.\n\nFor example, you might be building a project that uses a third party library that is built\ntogether with the project. So, when you build your project, you would need first, to\nbuild the third party library, and then, you link it with the source code of your project.\nSo, 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).\nBut only one is of interest, which is the executable file of our project.\nWe can discard the binary file of the third party library, by simply not installing it\ninto this `zig-out` directory.\n\nSo, is easy to use this `installArtifact()` method. Just remember to apply it to every\ntarget object that you want to save it into the `zig-out` directory, like in the example below:\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nexe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n});\n\nb.installArtifact(exe);\n```\n:::\n\n\n\n\n\n## Setting the build mode\n\nWe talked about the three essential options that are set when you create a new target object.\nBut there is also a fourth option that you can use to set the build mode of this target object,\nwhich is the `optimize` option.\nThis option is called this way, because the build modes in Zig are treated more of\nan \"optimization vs safety\" problem. So optmization plays an important role here.\nDon't worry, I'm going back to this question very soon.\n\nIn Zig, we have the four build modes listed below. Each one of these build modes offer\ndifferent advantages and characteristics. As we described at @sec-compile-debug-mode, the `zig` compiler\nuses the `Debug` build mode by default, when you don't explicitly choose a build mode.\n\n- `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);\n- `ReleaseSmall`, mode that tries to produce a binary file that is small in size;\n- `ReleaseFast`, mode that tries to optimize your code, in order to produce a binary file that is as fast as possible;\n- `ReleaseSafe`, mode that tries to make your code as safe as possible, by including safeguards when possible.\n\nSo, when you build your project, you can set the build mode of your target object to `ReleaseFast` for example, which will tell\nthe `zig` compiler to apply important optimizations in your code. This creates a binary file\nthat simply runs faster on most contexts, because it contains a more optimized version of your code.\nHowever, as a result, we normally loose some security funcionalities in our code.\nBecause some safety checks are removed from the final binary file,\nwhich makes your code run faster, but in a less safe manner.\n\nThis choice depends on your current priorities. If you are building a cryptography or banking system, you might\nprefer to prioritize safety in your code, so, you would choose the `ReleaseSafe` build mode, which is a little\nslower to run, but much more secure, because it includes all possible runtime safety checks in the binary file\nbuilt in the build process. In the other hand, if you are writing a game for example, you might prefer to prioritize performance\nover safety, by using the `ReleaseFast` build mode, so that your users can experience faster frame rates in your game.\n\nIn the example below, we are creating the same\ntarget object that we used on previous examples. But this time, we are specifying\nthe build mode of this target object to use the `ReleaseSafe` mode.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n .optimize = .ReleaseSafe\n});\nb.installArtifact(exe);\n```\n:::\n\n\n\n\n\n## Setting the version of your build\n\nEverytime you build a target object in your build script, you can assign a version\nnumber to this specific build, following a semantic versioning framework.\nYou can find more about semantic versioning by visiting the [Semantic Versioning website](https://semver.org/)[^semver].\nAnyway, in Zig,\nyou can specify the version of your build, by providing a `SemanticVersion` struct to\nthe `version` option, like in the example below:\n\n\n[^semver]: \n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n .version = .{\n .major = 2, .minor = 9, .patch = 7\n }\n});\nb.installArtifact(exe);\n```\n:::\n\n\n\n\n\n## Detecting the OS in your build script {#sec-detect-os}\n\nIs very common in Build Systems to use different options, or, to include different modules, or,\nto link against different libraries depending on the Operational System (OS)\nthat you are targeting in the build process.\n\nIn Zig, you can detect the target OS of the build process, by looking\nat the `os.tag` inside the `builtin` module from the Zig library.\nIn the example below, we are using an if statement to run some\narbitrary code when the target of the build process is a\nWindows system.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst builtin = @import(\"builtin\");\nif (builtin.target.os.tag == .windows) {\n // Code that runs only when the target of\n // the compilation process is Windows.\n}\n```\n:::\n\n\n\n\n\n## Adding a run step to your build process\n\nOne thing that is neat in Rust is that you can compile and run your\nsource code with one single command (`cargo run`) from the Rust compiler.\nWe saw at @sec-compile-run-code how can we perform a similar job in Zig, by\nbuilding and running our Zig source code through the `run` command from the `zig` compiler.\n\nBut how can we, at the same time, build and run the binary file specified by a target object in our\nbuild script?\nThe answer is by including a \"run artifact\" in our build script.\nA run artifact is created through the `addRunArtifact()` method from the `Build` struct.\nWe simply provide as input to this function the target object that describes the binary file that we\nwant to execute, and the function creates as output, a run artifact capable of executing\nthis binary file.\n\nIn the example below, we are defining an executable binary file named `hello`,\nand we use this `addRunArtifact()` method to create a run artifact that will execute\nthis `hello` executable file.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"src/hello.zig\"),\n .target = b.host\n});\nb.installArtifact(exe);\nconst run_arti = b.addRunArtifact(exe);\n```\n:::\n\n\n\n\nNow that we created the run artifact, we need to include it in\nthe build process. We do that by declaring a new step in our build\nscript to call this artifact, through the `step()` method of the `Build`\nstruct.\nWe can give any name we want to this step, but, for our\ncontext here, I'm going to name this step as \"run\".\nAlso, I give it a brief description to this step (\"Run the project\").\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst run_step = b.step(\n \"run\", \"Run the project\"\n);\n```\n:::\n\n\n\n\n\nNow that we declared this \"run step\" we need to tell Zig that\nthis \"run step\" depends on the run artifact.\nIn other words, a run artifact always depends on a \"step\" to effectively be executed.\nBy creating this dependency\nwe finally stablish the necessary commands to build and run the executable file\nfrom the build script.\n\nWe establish a dependency between the run step and the run artifact\nby using the `dependsOn()` method from the run step. So, we first\ncreate the run step, and then, we link it with the run artifact, by\nusing this `dependsOn()` method from the run step.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nrun_step.dependOn(&run_arti.step);\n```\n:::\n\n\n\n\nThe entire source code of this specific build script that\nwe wrote, piece by piece, in this section, is\navailable in the `build_and_run.zig` module. You can\nsee this module by\n[visiting the official repository of this book](https://github.com/pedropark99/zig-book/blob/main/ZigExamples/build_system/build_and_run.zig)\n[^module-src].\n\n\n[^module-src]: \n\nWhen you declare a new step in your build script, this step\nbecomes available through the `build` command in the `zig` compiler.\nYou can actually see this step by running `zig build --help` in the terminal, like\nin the example below, where we can see that this new \"run\"\nstep that we declared in the build script appeared in the output.\n\n```bash\nzig build --help\n```\n\n```\nSteps:\n ...\n run Run the project\n ...\n```\n\nNow, everything that we need to is to\ncall this \"run\" step that we created in our build script. We\ncall it by using the name that we gave to this step\nafter the `build` command from the `zig` compiler.\nThis will cause the compiler to build our executable file\nand execute it at the same time.\n\n```bash\nzig build run\n```\n\n## Build unit tests in your project\n\nWe talk at length about writing unit tests in Zig, and we also talk about how to execute these unit tests through\nthe `test` command of the `zig` compiler at @sec-unittests. However,\nas we did with the `run` command on the previous section, we also might want to\ninclude some commands in our build script to also build and execute the unit tests in our project.\n\nSo, once again, we are going to discuss how a specific built-in command from the `zig` compiler,\n(in this case, the `test` command) can be used in a build script in Zig.\nThis means that, we can include a step in our build script to build and run\nall unit tests in our project at once.\n\nHere is where a \"test target object\" comes into play.\nAs was described at @sec-targets, we can create a test target object by using the `addTest()` method of\nthe `Build` struct. So the first thing that we need to do is to\ndeclare a test target object in our build script.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst test_exe = b.addTest(.{\n .name = \"unit_tests\",\n .root_source_file = b.path(\"src/main.zig\"),\n .target = b.host,\n});\nb.installArtifact(test_exe);\n```\n:::\n\n\n\n\n\nA test target object essentially filter all `test` blocks in all Zig modules\nacross your project, and builds only the source code present inside\nthese `test` blocks in your project. As a result, this target object\ncreates an executable file that contains only the source code present\nin all of these `test` blocks (i.e. the unit tests) in your project.\n\nPerfect! Now that we declared this test target object, an executable file\nnamed `unit_tests` is built by the `zig` compiler when we trigger the build\nscript with the `build` command. After the build\nprocess is finished, we can simply execute this `unit_tests` executable\nin the terminal.\n\nHowever, if you remember the previous section, we already learned\nhow can we create a run step in our build script, to execute an executable file\nbuilt by the build script.\n\nSo, we could simply add a run step in our build script to run these unit tests\nfrom a single command in the `zig` compiler, to make our lifes easier.\nIn the example below, we demonstrate the commands to\nregister a new build step called \"tests\" in our build script\nto run these unit tests.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst run_arti = b.addRunArtifact(test_exe);\nconst run_step = b.step(\"tests\", \"Run unit tests\");\nrun_step.dependOn(&run_arti.step);\n```\n:::\n\n\n\n\nNow that we registered this new build step, we can trigger it by calling the command below\nin the terminal. You can also checkout the complete source\ncode for this specific build script at the `build_tests.zig` module at the\n[official repository of this book](https://github.com/pedropark99/zig-book/blob/main/ZigExamples/build_system/build_tests.zig)\n[^module-src2].\n\n\n[^module-src2]: \n\n\n```bash\nzig build tests\n```\n\n\n## Tailoring your build process with user-provided options\n\nSometimes, you want to make a build script that is customizable by the user\nof your project. You can do that by creating user-provided options in\nyour build script. In Zig, we create these options using the\n`option()` method from the `Build` struct.\n\nWith this method, we create a \"build option\" which can be passed\nto the `build.zig` script at the command line. The user have the power of setting\nthis option at the `build` command from the\n`zig` compiler. In other words, each build option that we create\nbecomes a new command line argument accessible in the `build` command\nof the compiler.\n\nThese \"user-provided options\" are set by using the prefix `-D` in the command line.\nFor example, if we declare an option named `use_zlib`, that receives a boolean value which\nindicates if we should link our source code to `zlib` or not, we can set the value\nof this option in the command line with `-Duse_zlib`. The code example below\ndemonstrates this idea:\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn build(b: *std.Build) void {\n const use_zlib = b.option(\n bool,\n \"use_zlib\",\n \"Should link to zlib?\"\n ) orelse false;\n const exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"example.zig\"),\n .target = b.host,\n });\n if (use_zlib) {\n exe.linkSystemLibrary(\"zlib\");\n }\n b.installArtifact(exe);\n}\n```\n:::\n\n\n\n\nYou can set this `use_zlib` option at the command line when you are invoking the\n`build` command from the `zig` compiler. In the example below, we set this option\nto false, which means that the build script will not link our binary executable to\nthe `zlib` library.\n\n```bash\nzig build -Duse_zlib=false\n```\n\n\n## Linking to external libraries\n\n\nOne essential part of every build process is the linking stage.\nThis stage is responsible for combining the multiple object files\nthat represent your code, into a single executable file. It also links\nthis executable file to an external libraries, if you use any in your code.\n\nIn Zig, we have two notions of a \"library\", which are: 1) a system's library;\n2) a local library. A system's library is just a library that already is installed\nin your system. While a local library is a library that belongs to the current\nproject. Is a library that is present in your project directory, and\nthat you are building together with your project source code.\n\nThe basic difference between the two, is that a system's library is already\nbuilt and installed in your system, supposedly, and all you need to do\nis to link your source code to this library to start using it.\nWe do that by using the `linkSystemLibrary()` method from a\n`Compile` object. This method accepts the name of the library\nin a string as input. Remember from @sec-targets that a `Compile` object\nis a target object that you declare in your build script.\n\nWhen you link a particular target object with a system's library,\nthe `zig` compiler will use `pkg-config` to find where in your system\nare the binary files and also the header files of this library that you requested for.\nWhen it finds these files, the linker present in the `zig` compiler\nwill link your object files with the files of this library to\nproduce a single executable file.\n\nIn the example below, we are creating an executable file named `image_filter`,\nand, we are linking this executable file to the C Standard Library with the\nmethod `linkLibC()`, but we also are linking this executable file to the\nC library `libpng` that is currently installed in my system.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn build(b: *std.Build) void {\n const exe = b.addExecutable(.{\n .name = \"image_filter\",\n .root_source_file = b.path(\"src/main.zig\"),\n .target = target,\n .optimize = optimize,\n });\n exe.linkLibC();\n exe.linkSystemLibrary(\"png\");\n b.installArtifact(exe);\n}\n```\n:::\n\n\n\n\nIf you are linking with a C library in your project, is generally a good idea\nto also link your code with the C Standard Library. Because is very likely\nthat this C library uses some functionality of the C Standard Library at some point.\nThe same goes to C++ libraries. So, if you are linking with\nC++ libraries, is a good idea to link your project with the C++\nStandard Library using the `linkLibCpp()` method.\n\nOn the order side, when you want to link with a local library,\nyou should use the `linkLibrary()` method of a `Compile` object.\nThis method expects to receive another `Compile` object as input.\nThat is, another target object defined in your build script,\nusing either the `addStaticLibrary()` or `addSharedLibrary()` methods\nwhich defines a library to be built.\n\nBecause as we discussed earlier, a local library is a library\nthat is local to your project, and that is being built together\nwith your project. So, you need to create a target object in your build script\nto build this local library. Then, you link the target objects of interest in your project,\nwith this target object that identifies this local library.\n\nTake a look at this example extracted from the build script of the [`libxev` library](https://github.com/mitchellh/libxev/tree/main)[^libxev2].\nYou can see in this snippet that\nwe are declaring a shared library file, from the `c_api.zig`\nmodule. Then later in the build script, we declare an\nexecutable file named \"dynamic-binding-test\", which\nlinks to this shared library that we defined earlier\nin the script.\n\n[^libxev2]: \n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst optimize = b.standardOptimizeOption(.{});\nconst target = b.standardTargetOptions(.{});\n\nconst dynamic_lib = b.addSharedLibrary(.{\n .name = dynamic_lib_name,\n .root_source_file = b.path(\"src/c_api.zig\"),\n .target = target,\n .optimize = optimize,\n});\nb.installArtifact(dynamic_lib);\n// ... more lines in the script\nconst dynamic_binding_test = b.addExecutable(.{\n .name = \"dynamic-binding-test\",\n .target = target,\n .optimize = optimize,\n});\ndynamic_binding_test.linkLibrary(dynamic_lib);\n```\n:::\n\n\n\n\n\n\n## Building C code {#sec-building-c-code}\n\nThe `zig` compiler comes with a C compiler embedded in it. In other words,\nyou can use the `zig` compiler to build C projects. This C compiler is available\nthrough the `cc` command of the `zig` compiler.\n\nAs an example, let's use the famous [FreeType library](https://freetype.org/)[^freetype].\nFreeType is one of the most widely used pieces of software in the world.\nIt is a C library designed to produce high-quality fonts. But it is also\nheavily used in the industry to natively render text and fonts\nin the screen of your computer.\n\nIn this section, we are going to write a build script, piece by piece, that is capable\nof building the FreeType project from source.\nYou can find the source code of this build script on the\n[`freetype-zig` repository](https://github.com/pedropark99/freetype-zig/tree/main)[^freetype-zig]\navailable at GitHub.\n\n[^freetype]: \n[^freetype-zig]: \n\nAfter you download the source code of FreeType from the official website[^freetype],\nyou can start writing the `build.zig` module. We begin by defining the target object\nthat defines the binary file that we want to compile.\n\nAs an example, I will build the project as a static library file using the `addStaticLibrary()` method\nto create the target object.\nAlso, since FreeType is a C library, I will also link the library\nagainst `libc` through the `linkLibC()` method, to garantee that any use\nof the C Standard Library is covered in the compilation process.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst target = b.standardTargetOptions(.{});\nconst opti = b.standardOptimizeOption(.{});\nconst lib = b.addStaticLibrary(.{\n .name = \"freetype\",\n .optimize = opti,\n .target = target,\n});\nlib.linkLibC();\n```\n:::\n\n\n\n\n### Creating C compiler flags\n\nCompiler flags are also known as \"compiler options\" by many programmers,\nor also, as \"command options\" in the GCC official documentation.\nIt's fair to also call them as the \"command-line arguments\" of the C compiler.\nIn general, we use compiler flags to turn on (or turn off) some features from the compiler,\nor to tweak the compilation process to fit the needs of our project.\n\nIn build scripts written in Zig, we normally list the C compiler flags to be used in the compilation process\nin a simple array, like in the example below.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst c_flags = [_][]const u8{\n \"-Wall\",\n \"-Wextra\",\n \"-Werror\",\n};\n```\n:::\n\n\n\n\nIn theory, there is nothing stopping you from using this array to add \"include paths\" (with the `-I` flag)\nor \"library paths\" (with the `-L` flag) to the compilation process. But there are formal ways in Zig to\nadd these types of paths in the compilation process. Both are discussed at @sec-include-paths\nand @sec-library-paths.\n\nAnyway, in Zig, we add C flags to the build process together with the C files that we want to compile, using the\n`addCSourceFile()` and `addCSourceFiles()` methods. In the example above, we have just declared\nthe C flags that we want to use. But we haven't added them to the build process yet.\nTo do that, we also need to list the C files to be compiled.\n\n### Listing your C files\n\nThe C files that contains \"cross-platform\" source code are listed in the `c_source_files`\nobject below. These are the C files that are included by default in every platform\nsupported by the FreeType library. Now, since the amount of C files in the FreeType library is big,\nI have omitted the rest of the files in the code example below, for brevity purposes.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst c_source_files = [_][]const u8{\n \"src/autofit/autofit.c\",\n \"src/base/ftbase.c\",\n // ... and many other C files.\n};\n```\n:::\n\n\n\n\nNow, in addition to \"cross-platform\" source code, we also have some C files in the FreeType project\nthat are platform-specific, meaning that, they contain source code that can obly be compiled in specific platforms,\nand, as a result, they are only included in the build process on these specific target platforms.\nThe objects that list these C files are exposed in the code example below.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst windows_c_source_files = [_][]const u8{\n \"builds/windows/ftdebug.c\",\n \"builds/windows/ftsystem.c\"\n};\nconst linux_c_source_files = [_][]const u8{\n \"src/base/ftsystem.c\",\n \"src/base/ftdebug.c\"\n};\n```\n:::\n\n\n\n\nNow that we declared both the files that we want to include and the C compiler flags to be used,\nwe can add them to the target object that describes the FreeType library, by using the\n`addCSourceFile()` and `addCSourceFiles()` methods.\n\nBoth of these functions are methods from a `Compile` object (i.e. a target object).\nThe `addCSourceFile()` method is capable of adding a single C file to the target object,\nwhile the `addCSourceFiles()` method is used to add multiple C files in a single command.\nYou might prefer to use `addCSourceFile()` when you need to use different compiler flags\non specific C files in your project. But, if you can use the same compiler flags\nacross all C files, then, you will probably find `addCSourceFiles()` a better choice.\n\nNotice that we are using the `addCSourceFiles()` method in the example below,\nto add both the C files and the C compiler flags. Also notice that we\nare using the `os.tag` that we learned about at @sec-detect-os, to add the platform-specific\nC files.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst builtin = @import(\"builtin\");\nlib.addCSourceFiles(\n &c_source_files, &c_flags\n);\n\nswitch (builtin.target.os.tag) {\n .windows => {\n lib.addCSourceFiles(\n &windows_c_source_files,\n &c_flags\n );\n },\n .linux => {\n lib.addCSourceFiles(\n &linux_c_source_files,\n &c_flags\n );\n },\n else => {},\n}\n```\n:::\n\n\n\n\n\n### Defining C Macros\n\nC Macros are an essential part of the C programming language,\nand they are commonly defined through the `-D` flag from a C compiler.\nIn Zig, you can define a C Macro to be used in your build process\nby using the `defineCMacro()` method from the target object that\ndefines the binary file that you are building.\n\nIn the example below, we are using the `lib` object that we defined\non the previous sections to define some C Macros used by FreeType\nin the compilation process. These C Macros specify if FreeType\nshould (or should not) include functionalities from different\nexternal libraries.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nlib.defineCMacro(\"FT_DISABLE_ZLIB\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_PNG\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_HARFBUZZ\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_BZIP2\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_BROTLI\", \"TRUE\");\nlib.defineCMacro(\"FT2_BUILD_LIBRARY\", \"TRUE\");\n```\n:::\n\n\n\n\n\n### Adding library paths {#sec-library-paths}\n\nLibrary paths are paths in your computer where the C compiler will look (or search) for\nlibrary files to link against your source code. In other words, when you use a library in your\nC source code, and you ask the C compiler to link your source code against this library,\nthe C compiler will search for the binary files of this library across the paths listed\nin this \"library paths\" set.\n\nThese paths are platform specific, and, by default, the C compiler starts by looking at a\npre-defined set of places in your computer. But you can add more paths (or more places)\nto this list. For example, you may have a library installed in a non-conventional place of your\ncomputer, and you can make the C compiler \"see\" this \"non-conventional place\" by adding this path\nto this list of pre-defined paths.\n\nIn Zig, you can add more paths to this set by using the `addLibraryPath()` method from your target object.\nFirst, you defined a `LazyPath` object, containing the path you want to add, then,\nyou provide this object as input to the `addLibraryPath()` method, like in the example below:\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst lib_path: std.Build.LazyPath = .{\n .cwd_relative = \"/usr/local/lib/\"\n};\nlib.addLibraryPath(lib_path);\n```\n:::\n\n\n\n\n\n\n\n### Adding include paths {#sec-include-paths}\n\nThe preprocessor search path is a popular concept from the\nC community, but it is also known by many C programmers as \"include paths\", because\nthe paths in this \"search path\" relate to the `#include` statements found in the C files.\n\nInclude paths are similar to library paths. They are a set of pre-defined places in your computer where\nthe C compiler will look for files during the compilation process. But instead of looking for\nlibrary files, the include paths are places where the compiler looks for header files included\nin your C source code.\nThis is why many C programmers prefer to call these paths as the \"preprocessor search path\".\nBecause header files are processed during the preprocessor stage of the compilation\nprocess.\n\nSo, every header file that you include in your C source code, through a `#include` statements needs to\nbe found somewhere, and the C compiler will search for this header file across the paths listed in this \"include paths\" set.\nInclude paths are added to the compilation process through the `-I` flag.\n\nIn Zig, you can add new paths to this pre-defined set of paths, by using the `addIncludePath()` method\nfrom your target object. This method also accepts a `LazyPath` object as input.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst inc_path: std.Build.LazyPath = .{\n .path = \"./include\"\n};\nlib.addIncludePath(inc_path);\n```\n:::\n", + "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n\n\n\n\n# Build System {#sec-build-system}\n\n\nIn this chapter, we are going to talk about the build system, and how an entire project\nis built in Zig.\nOne key advantage of Zig is that it includes a build system embedded in the language itself.\nThis is great, because then you do not have to depend on an external system, separated\nfrom the compiler, to build your code.\n\n\nYou can find a good description of Zig's build system\non the [article entitled \"Build System\"](https://ziglang.org/learn/build-system/#user-provided-options)[^zig-art1]\nfrom the official Zig's website.\nWe also have the excellent [series of posts written by Felix](https://zig.news/xq/zig-build-explained-part-1-59lf)[^felix].\nHence, this chapter represents an extra resource for you to consult and rely on.\n\n[^felix]: \n[^zig-art1]: \n\nBuilding code is one of the things that Zig is best at. One thing that is particularly\ndifficult in C/C++ and even in Rust, is to cross-compile source code to multiple targets\n(e.g. multiple computer architectures and operational systems),\nand the `zig` compiler is known for being one of the best existing pieces of software\nfor this particular task.\n\n\n\n\n## How source code is built?\n\nWe already have talked about the challenges of building source code in low-level languages\nat @sec-project-files. As we described at that section, programmers invented Build Systems\nto surpass these challenges on the process of building source code in low-level languages.\n\nLow-level languages uses a compiler to compile (or to build) your source code into binary instructions.\nIn C and C++, we normally use compilers like `gcc`, `g++` or `clang` to compile\nour C and C++ source code into these instructions.\nEvery language have it's own compiler, and this is no different in Zig.\n\nIn Zig, we have the `zig` compiler to compile our Zig source code into\nbinary instructions that can be executed by our computer.\nIn Zig, the compilation (or the build) process involves\nthe following components:\n\n- The Zig modules that contains your source code;\n- Library files (either a dynamic library or a static library);\n- Compiler flags that tailors the build process to your needs.\n\nThese are the things that you need to connect together in order to build your\nsource code in Zig. In C and C++, you would have an extra component, which are the header files of\nthe libraries that you are using. But header files do not exist in Zig, so, you only need\nto care about them if you are linking your Zig source code with a C library.\nIf that is not your case, you can forget about it.\n\nYour build process is usually organized in a build script. In Zig, we normally\nwrite this build script into a Zig module in the root directory of our project,\nnamed as `build.zig`. You write this build script, then, when you run it, your project\nget's built into binary files that you can use and distribute to your users.\n\nThis build script is normally organized around *target objects*. A target is simply\nsomething to be built, or, in other words, it's something that you want the `zig` compiler\nto build for you. This concept of \"targets\" is present in most Build Systems,\nespecially in CMake[^cmake].\n\n[^cmake]: \n\nThere are four types of target objects that you can build in Zig, which are:\n\n- An executable (e.g. a `.exe` file on Windows).\n- A shared library (e.g. a `.so` file in Linux or a `.dll` file on Windows).\n- A static library (e.g. a `.a` file in Linux or a `.lib` file on Windows).\n- An executable file that executes only unit tests (or, a \"unit tests executable\").\n\nWe are going to talk more about these target objects at @sec-targets.\n\n\n\n## The `build()` function {#sec-build-fun}\n\nA build script in Zig always contains a public (and top-level) `build()` function declared.\nIt is like the `main()` function on the main Zig module of your project, that we discussed at @sec-main-file.\nBut instead of creating the entrypoint to your code, this `build()` function is the entrypoint to the build process.\n\nThis `build()` function should accept a pointer to a `Build` object as input, and it should use this \"build object\" to perform\nthe necessary steps to build your project. The return type of this function is always `void`,\nand this `Build` struct comes directly from the Zig Standard Library (`std.Build`). So, you can \naccess this struct by just importing the Zig Standard Library into your `build.zig` module.\n\nJust as a very simple example, here you can see the source code necessary to build\nan executable file from the `hello.zig` Zig module.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn build(b: *std.Build) void {\n const exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n });\n b.installArtifact(exe);\n}\n```\n:::\n\n\n\n\nYou can define and use other functions and objects in this build script. You can also import\nother Zig modules as you would normally do in any other module of your project.\nThe only real requirement for this build script, is to have a public and top-level\n`build()` function defined, that accepts a pointer to a `Build` struct as input.\n\n\n## Target objects {#sec-targets}\n\nAs we described over the previous sections, a build script is composed around target objects.\nEach target object is normally a binary file (or an output) that you want to get from the build process. You can list\nmultiple target objects in your build script, so that the build process generates multiple\nbinary files for you at once.\n\nFor example, maybe you are a developer working in a cross-platform application,\nand, because this application is cross-platform, you probably need to release\nbinary files of your software for each OS supported by your application to your end users.\nThus, you can define a different target object in your build script\nfor each OS (Windows, Linux, etc.) where you want to publish your software.\nThis will make the `zig` compiler to build your project to multiple target OS's at once.\nThe Zig Build System official documentation have a [great code example that demonstrates this strategy](https://ziglang.org/learn/build-system/#handy-examples)[^zig-ex].\n\n[^zig-ex]: \n\n\nA target object is created by the following methods of the `Build` struct that we introduced\nat @sec-build-fun:\n\n- `addExecutable()` creates an executable file;\n- `addSharedLibrary()` creates a shared library file;\n- `addStaticLibrary()` creates a static library file;\n- `addTest()` creates an executable file that executes unit tests.\n\n\nThese functions are methods from the `Build` struct that you receive\nas input of the `build()` function. All of them, create as output\na `Compile` object, which represents a target object to be compiled\nby the `zig` compiler. All of these functions accept a similar struct literal as input.\nThis struct literal defines three essential specs about this target object that you are building:\n`name`, `target` and `root_source_file`.\n\nWe have already seen these three options being used on the previous example,\nwhere we used the `addExecutable()` method to create an executable target object.\nThis example is reproduced below. Notice the use of the `path()` method\nfrom the `Build` struct, to define a path in the `root_source_file` option.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nexe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n});\n```\n:::\n\n\n\n\nThe `name` option specificy the name that you want to give to the binary file defined\nby this target object. So, in this example, we are building an executable file named `hello`.\nIs common to set this `name` option to the name of your project.\n\n\nFurthermore, the `target` option specify the target computer architecture (or the target operational system) of this\nbinary file. For example, if you want this target object to run on a Windows machine\nthat uses a `x86_64` architecture, you can set this `target` option to `x86_64-windows-gnu` for example.\nThis will make the `zig` compiler to compile the project to run on a `x86_64` Windows machine.\nYou can see the full list of architectures and OS's that the `zig` compiler supports by running\nthe `zig targets` command in the terminal.\n\nNow, if you are building the project to run on the current machine\nthat you are using to run this build script, you can set this `target`\noption to the `host` method of the `Build` object, like we did in the example above.\nThis `host` method identifies the current machine where you are\ncurrently running the `zig` compiler.\n\n\nAt last, the `root_source_file` option specifies the root Zig module of your project.\nThat is the Zig module that contains the entrypoint to your application (i.e. the `main()` function), or, the main API of your library.\nThis also means that, all the Zig modules that compose your project are automatically discovered\nfrom the import statements you have inside this \"root source file\".\nThe `zig` compiler can detect when a Zig module depends on the other through the import statements,\nand, as a result, it can discover the entire map of Zig modules used in your project.\n\nThis is handy, and it is different from what happens in other build systems.\nIn CMake for example, you have to explicitly list the paths to all source files that you want to\ninclude in your build process. This is probably a symptom of the \"lack of conditional\ncompilation\" in the C and C++ compilers. Since they lack this feature, you have\nto explicitly choose which source files should be sent to the C/C++ compiler, because not\nevery C/C++ code is portable or supported in every operational system, and, therefore,\nwould cause a compilation error in the C/C++ compiler.\n\n\nNow, one important detail about the build process is that, you have to **explicitly\ninstall the target objects that you create in your build script**, by using the\n`installArtifact()` method of the `Build` struct.\n\nEverytime you invoke the build process of your project, by calling the `build` command\nof the `zig` compiler, a new directory named `zig-out` is created in the root\ndirectory of your project. This new directory contains the outputs of the build process,\nthat is, the binary files built from your source code.\n\nWhat the `installArtifact()` method do is to install (or copy) the built target objects\nthat you defined to this `zig-out` directory.\nThis means that, if you do not\ninstall the target objects you define in your build script, these target objects are\nessentially discarded at the end of the build process.\n\nFor example, you might be building a project that uses a third party library that is built\ntogether with the project. So, when you build your project, you would need first, to\nbuild the third party library, and then, you link it with the source code of your project.\nSo, 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).\nBut only one is of interest, which is the executable file of our project.\nWe can discard the binary file of the third party library, by simply not installing it\ninto this `zig-out` directory.\n\nThis `installArtifact()` method is pretty straightforward. Just remember to apply it to every\ntarget object that you want to save into the `zig-out` directory, like in the example below:\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nexe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n});\n\nb.installArtifact(exe);\n```\n:::\n\n\n\n\n\n## Setting the build mode\n\nWe have talked about the three essential options that are set when you create a new target object.\nBut there is also a fourth option that you can use to set the build mode of this target object,\nwhich is the `optimize` option.\nThis option is called this way, because the build modes in Zig are treated more of\nan \"optimization vs safety\" problem. So optimization plays an important role here.\nDon't worry, I'm going back to this question very soon.\n\nIn Zig, we have four build modes (which are listed below). Each one of these build modes offer\ndifferent advantages and characteristics. As we described at @sec-compile-debug-mode, the `zig` compiler\nuses the `Debug` build mode by default, when you don't explicitly choose a build mode.\n\n- `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);\n- `ReleaseSmall`, mode that tries to produce a binary file that is small in size;\n- `ReleaseFast`, mode that tries to optimize your code, in order to produce a binary file that is as fast as possible;\n- `ReleaseSafe`, mode that tries to make your code as safe as possible, by including safeguards when possible.\n\nSo, when you build your project, you can set the build mode of your target object to `ReleaseFast` for example, which will tell\nthe `zig` compiler to apply important optimizations in your code. This creates a binary file\nthat simply runs faster on most contexts, because it contains a more optimized version of your code.\nHowever, as a result, we often lose some safety features in our code.\nBecause some safety checks are removed from the final binary file,\nwhich makes your code run faster, but in a less safe manner.\n\nThis choice depends on your current priorities. If you are building a cryptography or banking system, you might\nprefer to prioritize safety in your code, so, you would choose the `ReleaseSafe` build mode, which is a little\nslower to run, but much more secure, because it includes all possible runtime safety checks in the binary file\nbuilt in the build process. In the other hand, if you are writing a game for example, you might prefer to prioritize performance\nover safety, by using the `ReleaseFast` build mode, so that your users can experience faster frame rates in your game.\n\nIn the example below, we are creating the same target object that we have used on previous examples.\nBut this time, we are specifying the build mode of this target object to the `ReleaseSafe` mode.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n .optimize = .ReleaseSafe\n});\nb.installArtifact(exe);\n```\n:::\n\n\n\n\n\n## Setting the version of your build\n\nEverytime you build a target object in your build script, you can assign a version\nnumber to this specific build, following a semantic versioning framework.\nYou can find more about semantic versioning by visiting the [Semantic Versioning website](https://semver.org/)[^semver].\nAnyway, in Zig, you can specify the version of your build, by providing a `SemanticVersion` struct to\nthe `version` option, like in the example below:\n\n\n[^semver]: \n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"hello.zig\"),\n .target = b.host,\n .version = .{\n .major = 2, .minor = 9, .patch = 7\n }\n});\nb.installArtifact(exe);\n```\n:::\n\n\n\n\n\n## Detecting the OS in your build script {#sec-detect-os}\n\nIs very common in Build Systems to use different options, or, to include different modules, or,\nto link against different libraries depending on the Operational System (OS)\nthat you are targeting in the build process.\n\nIn Zig, you can detect the target OS of the build process, by looking\nat the `os.tag` inside the `builtin` module from the Zig library.\nIn the example below, we are using an if statement to run some\narbitrary code when the target of the build process is a\nWindows system.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst builtin = @import(\"builtin\");\nif (builtin.target.os.tag == .windows) {\n // Code that runs only when the target of\n // the compilation process is Windows.\n}\n```\n:::\n\n\n\n\n\n## Adding a run step to your build process\n\nOne thing that is neat in Rust is that you can compile and run your\nsource code with one single command (`cargo run`) from the Rust compiler.\nWe saw at @sec-compile-run-code how can we perform a similar job in Zig, by\nbuilding and running our Zig source code through the `run` command from the `zig` compiler.\n\nBut how can we, at the same time, build and run the binary file specified by a target object in our\nbuild script?\nThe answer is by including a \"run artifact\" in our build script.\nA run artifact is created through the `addRunArtifact()` method from the `Build` struct.\nWe simply provide as input to this function the target object that describes the binary file that we\nwant to execute. As a result, this function creates a run artifact that is capable of executing\nthis binary file.\n\nIn the example below, we are defining an executable binary file named `hello`,\nand we use this `addRunArtifact()` method to create a run artifact that will execute\nthis `hello` executable file.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"src/hello.zig\"),\n .target = b.host\n});\nb.installArtifact(exe);\nconst run_arti = b.addRunArtifact(exe);\n```\n:::\n\n\n\n\nNow that we have created this run artifact, we need to include it in\nthe build process. We do that by declaring a new step in our build\nscript to call this artifact, through the `step()` method of the `Build`\nstruct.\n\nWe can give any name we want to this step, but, for our\ncontext here, I'm going to name this step as \"run\".\nAlso, I give it a brief description to this step (\"Run the project\").\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst run_step = b.step(\n \"run\", \"Run the project\"\n);\n```\n:::\n\n\n\n\n\nNow that we have declared this \"run step\" we need to tell Zig that\nthis \"run step\" depends on the run artifact.\nIn other words, a run artifact always depends on a \"step\" to effectively be executed.\nBy creating this dependency\nwe finally stablish the necessary commands to build and run the executable file\nfrom the build script.\n\nWe can establish a dependency between the run step and the run artifact\nby using the `dependsOn()` method from the run step. So, we first\ncreate the run step, and then, we link it with the run artifact, by\nusing this `dependsOn()` method from the run step.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nrun_step.dependOn(&run_arti.step);\n```\n:::\n\n\n\n\nThe entire source code of this specific build script that\nwe wrote, piece by piece, in this section, is\navailable in the `build_and_run.zig` module. You can\nsee this module by\n[visiting the official repository of this book](https://github.com/pedropark99/zig-book/blob/main/ZigExamples/build_system/build_and_run.zig)\n[^module-src].\n\n\n[^module-src]: \n\nWhen you declare a new step in your build script, this step\nbecomes available through the `build` command in the `zig` compiler.\nYou can actually see this step by running `zig build --help` in the terminal, like\nin the example below, where we can see that this new \"run\"\nstep that we declared in the build script appeared in the output.\n\n```bash\nzig build --help\n```\n\n```\nSteps:\n ...\n run Run the project\n ...\n```\n\nNow, everything that we need to is to\ncall this \"run\" step that we created in our build script. We\ncall it by using the name that we gave to this step\nafter the `build` command from the `zig` compiler.\nThis will cause the compiler to build our executable file\nand execute it at the same time.\n\n```bash\nzig build run\n```\n\n## Build unit tests in your project\n\nWe have talked at length about writing unit tests in Zig at @sec-unittests,\nand we also have talked about how to execute these unit tests through\nthe `test` command of the `zig` compiler. However,\nas we did with the `run` command on the previous section, we also might want to\ninclude some commands in our build script to also build and execute the unit tests in our project.\n\nSo, once again, we are going to discuss how a specific built-in command from the `zig` compiler,\n(in this case, the `test` command) can be used in a build script in Zig.\nHere is where a \"test target object\" comes into play.\nAs was described at @sec-targets, we can create a test target object by using the `addTest()` method of\nthe `Build` struct. The first thing that we need to do is to\ndeclare a test target object in our build script.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst test_exe = b.addTest(.{\n .name = \"unit_tests\",\n .root_source_file = b.path(\"src/main.zig\"),\n .target = b.host,\n});\nb.installArtifact(test_exe);\n```\n:::\n\n\n\n\n\nA test target object essentially selects all `test` blocks in all Zig modules\nacross your project, and builds only the source code present inside\nthese `test` blocks in your project. As a result, this target object\ncreates an executable file that contains only the source code present\nin all of these `test` blocks (i.e. the unit tests) in your project.\n\nPerfect! Now that we have declared this test target object, an executable file\nnamed `unit_tests` is built by the `zig` compiler when we trigger the build\nscript with the `build` command. After the build\nprocess is finished, we can simply execute this `unit_tests` executable\nin the terminal.\n\nHowever, if you remember the previous section, we already learned\nhow can we create a run step in our build script, to execute an executable file\nbuilt by the build script.\n\nSo, we could simply add a run step in our build script to run these unit tests\nfrom a single command in the `zig` compiler, to make our lifes easier.\nIn the example below, we demonstrate the commands to\nregister a new build step called \"tests\" in our build script\nto run these unit tests.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst run_arti = b.addRunArtifact(test_exe);\nconst run_step = b.step(\"tests\", \"Run unit tests\");\nrun_step.dependOn(&run_arti.step);\n```\n:::\n\n\n\n\nNow that we registered this new build step, we can trigger it by calling the command below\nin the terminal. You can also checkout the complete source\ncode for this specific build script at the `build_tests.zig` module at the\n[official repository of this book](https://github.com/pedropark99/zig-book/blob/main/ZigExamples/build_system/build_tests.zig)\n[^module-src2].\n\n\n[^module-src2]: \n\n\n```bash\nzig build tests\n```\n\n\n## Tailoring your build process with user-provided options\n\nSometimes, you want to make a build script that is customizable by the user\nof your project. You can do that by creating user-provided options in\nyour build script. We create an user-provided option by using the\n`option()` method from the `Build` struct.\n\nWith this method, we create a \"build option\" which can be passed\nto the `build.zig` script at the command line. The user have the power of setting\nthis option at the `build` command from the\n`zig` compiler. In other words, each build option that we create\nbecomes a new command line argument that is accessible through the `build` command\nof the compiler.\n\nThese \"user-provided options\" are set by using the prefix `-D` in the command line.\nFor example, if we declare an option named `use_zlib`, that receives a boolean value which\nindicates if we should link our source code to `zlib` or not, we can set the value\nof this option in the command line with `-Duse_zlib`. The code example below\ndemonstrates this idea:\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn build(b: *std.Build) void {\n const use_zlib = b.option(\n bool,\n \"use_zlib\",\n \"Should link to zlib?\"\n ) orelse false;\n const exe = b.addExecutable(.{\n .name = \"hello\",\n .root_source_file = b.path(\"example.zig\"),\n .target = b.host,\n });\n if (use_zlib) {\n exe.linkSystemLibrary(\"zlib\");\n }\n b.installArtifact(exe);\n}\n```\n:::\n\n\n\n\n```bash\nzig build -Duse_zlib=false\n```\n\n\n## Linking to external libraries\n\n\nOne essential part of every build process is the linking stage.\nThis stage is responsible for combining the multiple object files\nthat represent your code, into a single executable file. It also links\nthis executable file to external libraries, if you use any in your code.\n\nIn Zig, we have two notions of a \"library\", which are: 1) a system's library;\n2) a local library. A system's library is just a library that already is installed\nin your system. While a local library is a library that belongs to the current\nproject. Is a library that is present in your project directory, and\nthat you are building together with your project source code.\n\nThe basic difference between the two, is that a system's library is already\nbuilt and installed in your system, supposedly, and all you need to do\nis to link your source code to this library to start using it.\nWe do that by using the `linkSystemLibrary()` method from a\n`Compile` object. This method accepts the name of the library\nin a string as input. Remember from @sec-targets that a `Compile` object\nis a target object that you declare in your build script.\n\nWhen you link a particular target object with a system's library,\nthe `zig` compiler will use `pkg-config` to find where\nare the binary files and also the header files of this library\nin your system.\nWhen it finds these files, the linker present in the `zig` compiler\nwill link your object files with the files of this library to\nproduce a single binary file for you.\n\nIn the example below, we are creating an executable file named `image_filter`,\nand, we are linking this executable file to the C Standard Library with the\nmethod `linkLibC()`, but we also are linking this executable file to the\nC library `libpng` that is currently installed in my system.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn build(b: *std.Build) void {\n const exe = b.addExecutable(.{\n .name = \"image_filter\",\n .root_source_file = b.path(\"src/main.zig\"),\n .target = target,\n .optimize = optimize,\n });\n exe.linkLibC();\n exe.linkSystemLibrary(\"png\");\n b.installArtifact(exe);\n}\n```\n:::\n\n\n\n\nIf you are linking with a C library in your project, is generally a good idea\nto also link your code with the C Standard Library. Because is very likely\nthat this C library uses some functionality of the C Standard Library at some point.\nThe same goes to C++ libraries. So, if you are linking with\nC++ libraries, is a good idea to link your project with the C++\nStandard Library by using the `linkLibCpp()` method.\n\nOn the order side, when you want to link with a local library,\nyou should use the `linkLibrary()` method of a `Compile` object.\nThis method expects to receive another `Compile` object as input.\nThat is, another target object defined in your build script,\nusing either the `addStaticLibrary()` or `addSharedLibrary()` methods\nwhich defines a library to be built.\n\nAs we discussed earlier, a local library is a library\nthat is local to your project, and that is being built together\nwith your project. So, you need to create a target object in your build script\nto build this local library. Then, you link the target objects of interest in your project,\nwith this target object that identifies this local library.\n\nTake a look at this example extracted from the build script of the [`libxev` library](https://github.com/mitchellh/libxev/tree/main)[^libxev2].\nYou can see in this snippet that\nwe are declaring a shared library file, from the `c_api.zig`\nmodule. Then, later in the build script, we declare an\nexecutable file named `\"dynamic-binding-test\"`, which\nlinks to this shared library that we defined earlier\nin the script.\n\n[^libxev2]: \n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst optimize = b.standardOptimizeOption(.{});\nconst target = b.standardTargetOptions(.{});\n\nconst dynamic_lib = b.addSharedLibrary(.{\n .name = dynamic_lib_name,\n .root_source_file = b.path(\"src/c_api.zig\"),\n .target = target,\n .optimize = optimize,\n});\nb.installArtifact(dynamic_lib);\n// ... more lines in the script\nconst dynamic_binding_test = b.addExecutable(.{\n .name = \"dynamic-binding-test\",\n .target = target,\n .optimize = optimize,\n});\ndynamic_binding_test.linkLibrary(dynamic_lib);\n```\n:::\n\n\n\n\n\n\n## Building C code {#sec-building-c-code}\n\nThe `zig` compiler comes with a C compiler embedded in it. In other words,\nyou can use the `zig` compiler to build C projects. This C compiler is available\nthrough the `cc` command of the `zig` compiler.\n\nAs an example, let's use the famous [FreeType library](https://freetype.org/)[^freetype].\nFreeType is one of the most widely used pieces of software in the world.\nIt is a C library designed to produce high-quality fonts. But it is also\nheavily used in the industry to natively render text and fonts\nin the screen of your computer.\n\nIn this section, we are going to write a build script, piece by piece, that is capable\nof building the FreeType project from source.\nYou can find the source code of this build script on the\n[`freetype-zig` repository](https://github.com/pedropark99/freetype-zig/tree/main)[^freetype-zig]\navailable at GitHub.\n\n[^freetype]: \n[^freetype-zig]: \n\nAfter you download the source code of FreeType from the official website[^freetype],\nyou can start writing the `build.zig` module. We begin by defining the target object\nthat defines the binary file that we want to compile.\n\nAs an example, I will build the project as a static library file using the `addStaticLibrary()` method\nto create the target object.\nAlso, since FreeType is a C library, I will also link the library\nagainst `libc` through the `linkLibC()` method, to garantee that any use\nof the C Standard Library is covered in the compilation process.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst target = b.standardTargetOptions(.{});\nconst opti = b.standardOptimizeOption(.{});\nconst lib = b.addStaticLibrary(.{\n .name = \"freetype\",\n .optimize = opti,\n .target = target,\n});\nlib.linkLibC();\n```\n:::\n\n\n\n\n### Creating C compiler flags\n\nCompiler flags are also known as \"compiler options\" by many programmers,\nor also, as \"command options\" in the GCC official documentation.\nIt's fair to also call them as the \"command-line arguments\" of the C compiler.\nIn general, we use compiler flags to turn on (or turn off) some features from the compiler,\nor to tweak the compilation process to fit the needs of our project.\n\nIn build scripts written in Zig, we normally list the C compiler flags to be used in the compilation process\nin a simple array, like in the example below.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst c_flags = [_][]const u8{\n \"-Wall\",\n \"-Wextra\",\n \"-Werror\",\n};\n```\n:::\n\n\n\n\nIn theory, there is nothing stopping you from using this array to add \"include paths\" (with the `-I` flag)\nor \"library paths\" (with the `-L` flag) to the compilation process. But there are formal ways in Zig to\nadd these types of paths in the compilation process. Both are discussed at @sec-include-paths\nand @sec-library-paths.\n\nAnyway, in Zig, we add C flags to the build process together with the C files that we want to compile, by using the\n`addCSourceFile()` and `addCSourceFiles()` methods. In the example above, we have just declared\nthe C flags that we want to use. But we haven't added them to the build process yet.\nTo do that, we also need to list the C files to be compiled.\n\n### Listing your C files\n\nThe C files that contains \"cross-platform\" source code are listed in the `c_source_files`\nobject below. These are the C files that are included by default in every platform\nsupported by the FreeType library. Now, since the amount of C files in the FreeType library is big,\nI have omitted the rest of the files in the code example below, for brevity purposes.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst c_source_files = [_][]const u8{\n \"src/autofit/autofit.c\",\n \"src/base/ftbase.c\",\n // ... and many other C files.\n};\n```\n:::\n\n\n\n\nNow, in addition to \"cross-platform\" source code, we also have some C files in the FreeType project\nthat are platform-specific, meaning that, they contain source code that can only be compiled in specific platforms,\nand, as a result, they are only included in the build process on these specific target platforms.\nThe objects that list these C files are exposed in the code example below.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst windows_c_source_files = [_][]const u8{\n \"builds/windows/ftdebug.c\",\n \"builds/windows/ftsystem.c\"\n};\nconst linux_c_source_files = [_][]const u8{\n \"src/base/ftsystem.c\",\n \"src/base/ftdebug.c\"\n};\n```\n:::\n\n\n\n\nNow that we declared both the files that we want to include and the C compiler flags to be used,\nwe can add them to the target object that describes the FreeType library, by using the\n`addCSourceFile()` and `addCSourceFiles()` methods.\n\nBoth of these functions are methods from a `Compile` object (i.e. a target object).\nThe `addCSourceFile()` method is capable of adding a single C file to the target object,\nwhile the `addCSourceFiles()` method is used to add multiple C files in a single command.\nYou might prefer to use `addCSourceFile()` when you need to use different compiler flags\non specific C files in your project. But, if you can use the same compiler flags\nacross all C files, then, you will probably find `addCSourceFiles()` a better choice.\n\nNotice that we are using the `addCSourceFiles()` method in the example below,\nto add both the C files and the C compiler flags. Also notice that we\nare using the `os.tag` that we learned about at @sec-detect-os, to add the platform-specific\nC files.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst builtin = @import(\"builtin\");\nlib.addCSourceFiles(\n &c_source_files, &c_flags\n);\n\nswitch (builtin.target.os.tag) {\n .windows => {\n lib.addCSourceFiles(\n &windows_c_source_files,\n &c_flags\n );\n },\n .linux => {\n lib.addCSourceFiles(\n &linux_c_source_files,\n &c_flags\n );\n },\n else => {},\n}\n```\n:::\n\n\n\n\n\n### Defining C Macros\n\nC Macros are an essential part of the C programming language,\nand they are commonly defined through the `-D` flag from a C compiler.\nIn Zig, you can define a C Macro to be used in your build process\nby using the `defineCMacro()` method from the target object that\ndefines the binary file that you are building.\n\nIn the example below, we are using the `lib` object that we have defined\non the previous sections to define some C Macros used by the FreeType project\nin the compilation process. These C Macros specify if FreeType\nshould (or should not) include functionalities from different\nexternal libraries.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nlib.defineCMacro(\"FT_DISABLE_ZLIB\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_PNG\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_HARFBUZZ\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_BZIP2\", \"TRUE\");\nlib.defineCMacro(\"FT_DISABLE_BROTLI\", \"TRUE\");\nlib.defineCMacro(\"FT2_BUILD_LIBRARY\", \"TRUE\");\n```\n:::\n\n\n\n\n\n### Adding library paths {#sec-library-paths}\n\nLibrary paths are paths in your computer where the C compiler will look (or search) for\nlibrary files to link against your source code. In other words, when you use a library in your\nC source code, and you ask the C compiler to link your source code against this library,\nthe C compiler will search for the binary files of this library across the paths listed\nin this \"library paths\" set.\n\nThese paths are platform specific, and, by default, the C compiler starts by looking at a\npre-defined set of places in your computer. But you can add more paths (or more places)\nto this list. For example, you may have a library installed in a non-conventional place of your\ncomputer, and you can make the C compiler \"see\" this \"non-conventional place\" by adding this path\nto this list of pre-defined paths.\n\nIn Zig, you can add more paths to this set by using the `addLibraryPath()` method from your target object.\nFirst, you defined a `LazyPath` object, containing the path you want to add, then,\nyou provide this object as input to the `addLibraryPath()` method, like in the example below:\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst lib_path: std.Build.LazyPath = .{\n .cwd_relative = \"/usr/local/lib/\"\n};\nlib.addLibraryPath(lib_path);\n```\n:::\n\n\n\n\n\n\n\n### Adding include paths {#sec-include-paths}\n\nThe preprocessor search path is a popular concept from the\nC community, but it is also known by many C programmers as \"include paths\", because\nthe paths in this \"search path\" relate to the `#include` statements found in the C files.\n\nInclude paths are similar to library paths. They are a set of pre-defined places in your computer where\nthe C compiler will look for files during the compilation process. But instead of looking for\nlibrary files, the include paths are places where the compiler looks for header files included\nin your C source code.\nThis is why many C programmers prefer to call these paths as the \"preprocessor search path\".\nBecause header files are processed during the preprocessor stage of the compilation\nprocess.\n\nSo, every header file that you include in your C source code, through a `#include` statements needs to\nbe found somewhere, and the C compiler will search for this header file across the paths listed in this \"include paths\" set.\nInclude paths are added to the compilation process through the `-I` flag.\n\nIn Zig, you can add new paths to this pre-defined set of paths, by using the `addIncludePath()` method\nfrom your target object. This method also accepts a `LazyPath` object as input.\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst inc_path: std.Build.LazyPath = .{\n .path = \"./include\"\n};\nlib.addIncludePath(inc_path);\n```\n:::\n", "supporting": [ "07-build-system_files" ], diff --git a/docs/Chapters/07-build-system.html b/docs/Chapters/07-build-system.html index 506c740..f6265e7 100644 --- a/docs/Chapters/07-build-system.html +++ b/docs/Chapters/07-build-system.html @@ -321,12 +321,12 @@

-

In this chapter, we are going to talk about the build system in Zig, 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 a 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 available in the official Zig’s website. We also have the excellent series of posts written by Felix2. But this chapter represents an extra resource for you to consult and rely on.

+

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 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 building processes of low-level languages.

+

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 it’s 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:

    @@ -334,15 +334,15 @@

    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 is 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.

+

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, which is simply a binary executable file (e.g. a .exe file on Windows).
  • -
  • A shared library, which is simply a binary library file (e.g. a .so file in Linux or a .dll file on Windows).
  • -
  • A static library, which is simply a binary library file (e.g. a .a file in Linux or a .lib file on Windows).
  • -
  • An unit tests executable, which is an executable file that executes only unit tests.
  • +
  • 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.

@@ -367,7 +367,7 @@

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, you may be a developer working in a cross-platform application, and, because this application is cross-platform, you probably need to realease binary files of your software for each OS supported by your application to your end users. You can define a 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.

+

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;
  • @@ -376,7 +376,7 @@

    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 already saw 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.

+

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(.{
     .name = "hello",
@@ -384,16 +384,16 @@ 

.target = b.host, });

-

The name option specificy 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 traditional to set this name option to the name of your project.

+

The name option specificy 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 that 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 C and C++ compilers. Since they lack this feature, you have to explicitly choose which source files are sent to the C/C++ compiler, since 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.

+

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 output of the build process, that is, the binary files built from your source code.

-

What the installArtifact() method do is 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.

+

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.

-

So, is easy to use this installArtifact() method. Just remember to apply it to every target object that you want to save it into the zig-out directory, like in the example below:

+

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(.{
     .name = "hello",
@@ -406,17 +406,17 @@ 

9.4 Setting the build mode

-

We 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 optmization plays an important role here. Don’t worry, I’m going back to this question very soon.

-

In Zig, we have the four build modes 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.

+

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 normally loose some security funcionalities 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.

+

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 used on previous examples. But this time, we are specifying the build mode of this target object to use the ReleaseSafe mode.

+

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(.{
     .name = "hello",
@@ -457,7 +457,7 @@ 

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, and the function creates as output, a run artifact capable of executing this binary file.

+

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(.{
@@ -468,14 +468,15 @@ 

b.installArtifact(exe); const run_arti = b.addRunArtifact(exe);

-

Now that we created the 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”).

+

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 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 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.

+

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);
@@ -491,9 +492,8 @@

9.8 Build unit tests in your project

-

We talk at length about writing unit tests in Zig, and we also talk about how to execute these unit tests through the test command of the zig compiler at Chapter 8. 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. This means that, we can include a step in our build script to build and run all unit tests in our project at once.

-

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. So the first thing that we need to do is to declare a test target object in our build script.

+

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(.{
     .name = "unit_tests",
@@ -502,8 +502,8 @@ 

}); b.installArtifact(test_exe);

-

A test target object essentially filter 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 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.

+

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.

@@ -516,8 +516,8 @@

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. In Zig, we create these options 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 accessible in the build command of the compiler.

+

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");
@@ -538,15 +538,14 @@ 

b.installArtifact(exe); }

-

You can set this use_zlib option at the command line when you are invoking the build command from the zig compiler. In the example below, we set this option to false, which means that the build script will not link our binary executable to the zlib library.

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 an external libraries, if you use any in your code.

+

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 in your system are the binary files and also the header files of this library that you requested for. 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 executable file.

+

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");
@@ -562,10 +561,10 @@ 

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 using the linkLibCpp() method.

+

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.

-

Because 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.

+

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(.{});
@@ -615,7 +614,7 @@ 

};

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, 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.

+

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

@@ -627,7 +626,7 @@

// ... 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 obly 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.

+

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",
@@ -667,7 +666,7 @@ 

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 defined on the previous sections to define some C Macros used by FreeType in the compilation process. These C Macros specify if FreeType should (or should not) include functionalities from different external libraries.

+

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.

lib.defineCMacro("FT_DISABLE_ZLIB", "TRUE");
 lib.defineCMacro("FT_DISABLE_PNG", "TRUE");
diff --git a/docs/index.html b/docs/index.html
index f790774..458b40d 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -7,7 +7,7 @@
 
 
 
-
+
 
 Introduction to Zig: a project-based book