As far as I know there’s not really any tutorial. Loris has an old (2021) explainer series where he goes over porting Redis, and it also had an archived livestream of him and Andrew porting it (but it appears to have been deleted), but it kind of skims over the difficult part of actually writing the build.zig.
There’s no one true quick fix or trick to porting projects to the Zig build system, it’s mostly a matter of carefully reading the upstream build script, observing its outputs and figuring out how to replicate it in Zig.
Blend2D appears to use CMake. My approach for SDL, which I would also apply to other CMake projects, has been something like this:
1. Try to read/skim through CMakeLists.txt carefully. If it’s a very simple project, just reading it and translating it 1:1 to your build.zig might be enough. If it’s very complicated or split over multiple different files, to the point where you can’t make sense of it simply by reading the code, continue:
2. Paste this function into the bottom of CMakeLists.txt and pass the names of the targets (the names passed to add_library
, add_executable
, etc.) to it. It will log a bunch of additional details about the targets to your terminal, such as which source files are included, which symbols are defined or which system libraries are linked.
# Based on <https://stackoverflow.com/a/56738858>
execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)
STRING(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
STRING(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
list(REMOVE_DUPLICATES CMAKE_PROPERTY_LIST)
list(FILTER CMAKE_PROPERTY_LIST EXCLUDE REGEX "^LOCATION$|^LOCATION_|_LOCATION$")
function(print_target_properties tgt)
if(NOT TARGET ${tgt})
message("There is no target named '${tgt}'")
return()
endif()
foreach(prop_name ${CMAKE_PROPERTY_LIST})
string(REPLACE "<CONFIG>" "${CMAKE_BUILD_TYPE}" prop ${prop_name})
get_target_property(prop_val ${tgt} ${prop_name})
if(prop_val)
message ("${tgt} ${prop} = ${prop_val}")
endif()
endforeach(prop_name)
endfunction(print_target_properties)
print_target_properties(YOUR_TARGET_HERE)
print_target_properties(SOME_OTHER_TARGET_HERE)
3. Configure using cmake
, but specify zig cc
as the C (and C++/Objective-C compiler, if relevant), and pipe the output to a file:
cmake -S . -B build -G Ninja '-DCMAKE_C_COMPILER=zig;cc' -DCMAKE_BUILD_TYPE=Release --log-level=DEBUG 2>&1 | tee log-01.txt
4. Set the environment variables ZIG_VERBOSE_CC
and ZIG_VERBOSE_LINK
and run cmake --build
, again piping the output to a file:
ZIG_VERBOSE_CC=1 ZIG_VERBOSE_LINK=1 cmake --build build --config Release 2>&1 | tee log-02.txt
5. Skim through the logs to see what information about the targets got logged and which exact clang
and ld.lld
commands were invoked (you might need to cleverly edit the logs using regex in a text editor to make the output actually readable). If the project uses config headers or generates code at build time, you might also want to browse the build/
directory (or whichever name you chose) for generated files to see which values got written.
6. Now you have all the information you need, so here comes the hard part: take everything you know and try to replicate it in build.zig. Things like source files, C flags, include paths, linked system libraries are usually pretty straightforward. For config headers or generated files, I would suggest just hard coding the values for now, copying whatever was written to the files in build/
.
By following this process, you should hopefully be able to switch out the original CMakeLists.txt for your build.zig and be able to build it on your system and OS. If you want to be able to build it on a different system, you have to switch over there and repeat the process. If you want to cross-compile, you might need to vendor headers and other dependencies, or port system dependencies to build.zig. Depending on how ambitious and how much of a perfectionist you are it can be quite hard work and become very time consuming.
I would also suggest looking at other build.zigs for similar ports for ideas and inspiration. I tried to keep my SDL3 build.zig as simple and uncomplicated as possible, so that you can read it from top to bottom and understand everything it does. I hope it might be able to serve as a useful reference point.