This guide walks you through setting up new projects with zidar, from the simplest "Hello World" console application to multi-project solutions with libraries, dependencies, tests, and Qt integration.
Before starting, ensure you have:
- GENie build system generator in your
PATH gitinstalled (for downloading 3rd party dependencies)- A C/C++ compiler:
- Windows: Visual Studio 2017 or later, or MinGW
- Linux: GCC or Clang
- macOS: Xcode with command-line tools
makeorninjafor building (unless using Visual Studio / Xcode IDE)- Windows only: GnuWin32 for
sedandmake - Qt projects only: Qt 6 installed with
QTDIRenvironment variable set
The simplest possible zidar project — a command-line tool that prints a message.
my_hello_world/
├── scripts/
│ ├── genie.lua -- entry point for GENie
│ └── my_hello_world.lua -- project definition
└── src/
└── main.c -- source code
Create the project root and subdirectories:
mkdir -p my_hello_world/scripts
mkdir -p my_hello_world/srcCreate my_hello_world/src/main.c:
#include <stdio.h>
int main()
{
printf("Hello, zidar!\n");
return 0;
}Zidar automatically discovers all .c, .cpp, .h, and .hpp files inside src/. You never need to list individual files — just put them in the right directory.
Create my_hello_world/scripts/my_hello_world.lua:
function projectAdd_my_hello_world()
addProject_cmd("my_hello_world")
endThis registers a single callback function that tells zidar how to add the project. addProject_cmd() creates a console application — it looks for source files in the project's root directory and produces an executable.
Key concept: Zidar identifies projects by name. The function name must follow the pattern projectAdd_<name>() where <name> matches the project name with dashes and dots replaced by underscores.
Create my_hello_world/scripts/genie.lua:
newoption { trigger = "zidar-path", description = "Path to zidar" }
dofile(_OPTIONS["zidar-path"] .. "/zidar.lua")
dofile("my_hello_world.lua")
solution "my_hello_world"
setPlatforms()
projectAdd("my_hello_world")This file is what GENie executes. It does four things:
- Declares the
--zidar-pathoption so you can tell GENie where zidar is installed - Loads zidar which initializes the entire framework (globals, toolchain, project types, 3rd party registry)
- Loads your project script which registers the
projectAdd_my_hello_world()callback - Creates a solution (the top-level container for all projects), configures platforms, and adds the project
From the my_hello_world/scripts/ directory, run GENie with the desired build system:
# Visual Studio 2022 (Windows)
genie --zidar-path=/path/to/zidar vs2022
# GNU Makefiles with GCC (Linux)
genie --zidar-path=/path/to/zidar --gcc=linux-gcc gmake
# GNU Makefiles with Clang (Linux)
genie --zidar-path=/path/to/zidar --gcc=linux-clang gmake
# Xcode (macOS)
genie --zidar-path=/path/to/zidar --xcode=osx xcode9
# Ninja build files
genie --zidar-path=/path/to/zidar ninjaGENie generates native project files in a .zidar/ directory. For Visual Studio, this produces a .sln file you can open directly. For Makefiles, you then run make to build.
# Makefiles
make -C .zidar/projects/gmake-linux-clang config=debug64
make -C .zidar/projects/gmake-linux-clang config=release64
# Ninja
ninja -C .zidar/projects/ninja-linux-clang config=debug64
# Visual Studio: open .zidar/projects/vs2022/my_hello_world.slnOutput binaries are placed in .zidar/ with a configuration suffix (e.g. my_hello_world_debug, my_hello_world_release).
To avoid typing long GENie commands, create a my_hello_world/makefile:
RG_ZIDAR_DIR?=/path/to/zidar
GENIE?=$(RG_ZIDAR_DIR)/tools/bin/$(OS)/genie --zidar-path=$(RG_ZIDAR_DIR)
projgen: ## Generate project files for all configurations.
$(GENIE) vs2022
$(GENIE) --gcc=linux-clang gmake
$(GENIE) --gcc=linux-gcc gmake
$(GENIE) --gcc=osx-arm64 gmake
$(GENIE) --xcode=osx xcode9
clean:
$(GENIE) cleanThen simply run make projgen to generate all build variants at once.
Libraries are the building blocks of zidar projects. A library has public headers (in include/) that dependents can use and source files (in src/) that implement the functionality.
my_math_lib/
├── scripts/
│ ├── genie.lua
│ └── my_math_lib.lua
├── include/
│ └── my_math_lib.h -- public header (auto-added to dependents' include paths)
├── src/
│ └── my_math_lib.c -- implementation
└── tests/
└── my_math_lib_test.c -- unit tests (optional)
Create my_math_lib/include/my_math_lib.h:
#ifndef MY_MATH_LIB_H
#define MY_MATH_LIB_H
int myMathAdd(int a, int b);
int myMathMultiply(int a, int b);
#endifCreate my_math_lib/src/my_math_lib.c:
#include "my_math_lib.h"
int myMathAdd(int a, int b)
{
return a + b;
}
int myMathMultiply(int a, int b)
{
return a * b;
}Convention: The include/ directory contains public headers. When another project depends on your library, zidar automatically adds your include/ directory to its include paths. The src/ directory contains implementation files and private headers.
Create my_math_lib/scripts/my_math_lib.lua:
function projectAdd_my_math_lib()
addProject_lib("my_math_lib")
endaddProject_lib() creates a static library by default. It:
- Searches
src/andinclude/for source files - Auto-detects whether the project is C or C++ based on file extensions
- Configures precompiled headers if found
- Handles platform-specific files (e.g. Objective-C++
.mmfiles on Apple platforms)
Create my_math_lib/scripts/genie.lua:
newoption { trigger = "zidar-path", description = "Path to zidar" }
dofile(_OPTIONS["zidar-path"] .. "/zidar.lua")
dofile("my_math_lib.lua")
solution "my_math_lib"
setPlatforms()
addLibProjects("my_math_lib")Note the use of addLibProjects() instead of projectAdd(). This function is designed for libraries and automatically handles sub-projects:
- If
--with-unittestsis passed: discovers and adds test projects fromtests/, named<lib>_test - If
--with-samplesis passed: discovers and adds sample projects fromsamples/, named<lib>_<sampledir> - If
--with-toolsis passed: discovers and adds tool projects fromtools/, named<lib>_<tooldir>
Create my_math_lib/tests/my_math_lib_test.c:
#include "my_math_lib.h"
#include <rg_core/rg_test.h>
TEST(MyMathLib, Add)
{
CHECK_EQUAL(5, myMathAdd(2, 3));
CHECK_EQUAL(0, myMathAdd(-1, 1));
}
TEST(MyMathLib, Multiply)
{
CHECK_EQUAL(6, myMathMultiply(2, 3));
CHECK_EQUAL(0, myMathMultiply(0, 5));
}Zidar automatically selects the test framework based on language:
- C++ projects link to
unittest-cpp - C projects link to the
unitytest framework
The test project is named my_math_lib_test and placed in the tests IDE group.
Generate build files with tests enabled:
genie --zidar-path=/path/to/zidar --with-unittests vs2022Most real projects depend on libraries. Zidar resolves dependencies recursively — you just declare what you need.
my_calculator/
├── scripts/
│ ├── genie.lua
│ └── my_calculator.lua
└── src/
└── calculator.c
Create my_calculator/src/calculator.c:
#include <stdio.h>
#include "my_math_lib.h"
int main()
{
int result = myMathAdd(10, 20);
printf("10 + 20 = %d\n", result);
result = myMathMultiply(5, 6);
printf("5 * 6 = %d\n", result);
return 0;
}Notice that you #include "my_math_lib.h" directly — zidar will add the library's include/ directory to your include paths automatically.
Create my_calculator/scripts/my_calculator.lua:
function projectDependencies_my_calculator()
return { "my_math_lib" }
end
function projectAdd_my_calculator()
addProject_cmd("my_calculator")
endThe projectDependencies_<name>() callback returns a table listing the project names this project depends on. Zidar will:
- Locate
my_math_libby searching the directory tree (or the 3rd party registry) - Load its script and resolve its own dependencies recursively
- Add include paths from
my_math_lib/include/to the calculator's configuration - Link the
my_math_libstatic library into the calculator executable - Ensure
my_math_libis built beforemy_calculator
Create my_calculator/scripts/genie.lua:
newoption { trigger = "zidar-path", description = "Path to zidar" }
dofile(_OPTIONS["zidar-path"] .. "/zidar.lua")
dofile("my_calculator.lua")
solution "my_calculator"
setPlatforms()
projectAdd("my_calculator")You only need to call projectAdd() for the calculator — zidar automatically adds my_math_lib (and any of its transitive dependencies) to the solution.
When zidar processes projectAdd("my_calculator"):
- Calls
projectDependencies_my_calculator()→ gets{ "my_math_lib" } - Searches for
my_math_lib's directory (checks cache, then walks up parent directories searching up to 3 subdirectory levels deep at each step, then 3rd party registry) - Loads
my_math_lib.luaif not already loaded - Calls
projectDependencies_my_math_lib()to get its dependencies (recursing further if needed) - Flattens all transitive dependencies into a single deduplicated list
- For GNU Make, sorts by dependency depth (deepest-first) for correct link order
- Caches the result in
g_resolvedDependenciesso subsequent lookups are instant - Creates the
my_math_libproject in the solution, then createsmy_calculatorwith proper include paths and link settings
For larger codebases, you typically have multiple libraries, tools, and applications in a single solution. This is how real game engines and frameworks are structured.
my_engine/
├── scripts/
│ └── genie.lua -- top-level entry point
├── src/
│ ├── libraries/
│ │ ├── libraries.lua -- loads all library scripts
│ │ ├── core/
│ │ │ ├── include/core.h
│ │ │ ├── src/core.c
│ │ │ └── scripts/core.lua
│ │ └── renderer/
│ │ ├── include/renderer.h
│ │ ├── src/renderer.cpp
│ │ └── scripts/renderer.lua
│ ├── tools/
│ │ ├── tools.lua -- loads all tool scripts
│ │ └── asset_converter/
│ │ ├── src/asset_converter.c
│ │ └── scripts/asset_converter.lua
│ └── games/
│ ├── games.lua -- loads all game scripts
│ └── my_game/
│ ├── src/my_game.c
│ └── scripts/my_game.lua
Create src/libraries/core/scripts/core.lua:
function projectAdd_core()
addProject_lib("core")
endCreate src/libraries/renderer/scripts/renderer.lua:
function projectDependencies_renderer()
return { "core" }
end
function projectAdd_renderer()
addProject_lib("renderer")
endCreate src/libraries/libraries.lua:
dofile "../src/libraries/core/scripts/core.lua"
dofile "../src/libraries/renderer/scripts/renderer.lua"Create src/tools/tools.lua:
dofile "../src/tools/asset_converter/scripts/asset_converter.lua"Create src/games/games.lua:
dofile "../src/games/my_game/scripts/my_game.lua"Create src/tools/asset_converter/scripts/asset_converter.lua:
function projectDependencies_asset_converter()
return { "core" }
end
function projectAdd_asset_converter()
addProject_cmd("asset_converter")
endCreate src/games/my_game/scripts/my_game.lua:
function projectDependencies_my_game()
return { "renderer", "core" }
end
function projectAdd_my_game()
addProject_game("my_game")
endNote: addProject_game() produces a windowed application in retail builds and a console application in debug/release (so you get a console window for debug output during development).
Create scripts/genie.lua:
newoption { trigger = "zidar-path", description = "Path to zidar" }
dofile(_OPTIONS["zidar-path"] .. "/zidar.lua")
dofile "../src/libraries/libraries.lua"
dofile "../src/tools/tools.lua"
dofile "../src/games/games.lua"
solution "my_engine"
setPlatforms()
projectAdd("core")
projectAdd("renderer")
projectAdd("asset_converter")
projectAdd("my_game")All projects appear in the same solution, organized into IDE groups: libs for libraries, tools_cmd for command-line tools, and games for game projects.
Zidar has first-class Qt 6 support with automatic MOC, UIC, RCC, and translation file processing.
- Qt 6 installed
QTDIRenvironment variable pointing to your Qt installation (e.g.C:\Qt\6.5.0\msvc2019_64)- Lua interpreter in your
PATH(required for Qt prebuild steps)
my_qt_app/
├── scripts/
│ ├── genie.lua
│ └── my_qt_app.lua
└── src/
├── main.cpp -- application entry point
├── main_window.h -- QMainWindow subclass (processed by MOC)
├── main_window.cpp -- implementation
├── main_window.ui -- Qt Designer form (optional, processed by UIC)
├── resources.qrc -- Qt resource file (optional, processed by RCC)
└── translations/
└── app_en.ts -- translation file (optional, processed by lrelease)
Create my_qt_app/src/main_window.h:
#pragma once
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
private slots:
void onButtonClicked();
};Create my_qt_app/src/main_window.cpp:
#include "main_window.h"
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
QWidget* central = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(central);
QPushButton* button = new QPushButton("Click me!", central);
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
setCentralWidget(central);
setWindowTitle("My Qt App");
resize(400, 300);
}
void MainWindow::onButtonClicked()
{
QMessageBox::information(this, "Hello", "Hello from zidar + Qt!");
}Create my_qt_app/src/main.cpp:
#include <QApplication>
#include "main_window.h"
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}Create my_qt_app/scripts/my_qt_app.lua:
function projectAdd_my_qt_app()
addProject_qt("my_qt_app")
endaddProject_qt() automatically:
- Sets the language to C++
- Produces a windowed application (no console window)
- Discovers
.ui,.qrc, and.tsfiles insrc/ - Generates MOC prebuild commands for headers containing
Q_OBJECT - Links Qt modules: Core, Gui, Widgets, and Network by default
To link additional Qt modules, pass them as the last argument:
function projectAdd_my_qt_app()
addProject_qt("my_qt_app", nil, nil, nil, { "Sql", "Xml", "WebEngineWidgets" })
endCreate my_qt_app/scripts/genie.lua:
newoption { trigger = "zidar-path", description = "Path to zidar" }
dofile(_OPTIONS["zidar-path"] .. "/zidar.lua")
dofile("my_qt_app.lua")
solution "my_qt_app"
setPlatforms()
projectAdd("my_qt_app")Generate and build:
genie --zidar-path=/path/to/zidar vs2022Qt apps can depend on your own libraries just like any other project:
function projectDependencies_my_qt_app()
return { "my_math_lib" }
end
function projectAdd_my_qt_app()
addProject_qt("my_qt_app")
endZidar ships with 63 pre-configured build scripts for popular C/C++ libraries. When you declare a dependency on one of these, zidar automatically downloads the source code via git and builds it as part of your project.
Simply list the library name in your dependencies:
function projectDependencies_my_project()
return { "zlib", "imgui", "spdlog" }
end
function projectAdd_my_project()
addProject_cmd("my_project")
endWhen you generate build files, zidar will:
- Recognize
zlib,imgui, andspdlogfrom its 3rd party registry - Clone the source code into the
.3rd/directory (if not already present) - Create library projects for each dependency in the
3rdIDE group - Add include paths and link settings to your project
No manual downloading, no submodules, no CMake — just declare the dependency.
Some commonly used libraries from the 63 included scripts:
| Category | Libraries |
|---|---|
| Graphics | bgfx, bimg, bx, imgui, nanosvg, nanovg |
| 3D/Mesh | assimp, cgltf, tinygltf, ufbx, meshoptimizer |
| Compression | zlib, zstd, basis_universal |
| Audio | ogg, vorbis |
| Networking | curl, usockets, uwebsockets, librdkafka |
| Physics | box2d, jolt |
| Cryptography | mbedtls, openssl, wolfssl |
| Scripting | lua, minilua |
| Utilities | spdlog, enkiTS, tbb, tomlplusplus, xxHash |
| Testing | unittest-cpp, unity |
Each 3rd party library defines a projectSource_<name>() callback returning a git URL:
function projectSource_zlib()
return "https://github.com/madler/zlib.git"
endWhen zidar cannot find a library locally and this callback exists, it runs git clone into the .3rd/ directory automatically. On subsequent runs, the cached clone is reused.
Every project is defined by registering global Lua functions. Below is a summary of all available callbacks and when to use them.
| Callback | Purpose |
|---|---|
projectAdd_<name>() |
Creates the project by calling the appropriate addProject_*() function |
| Callback | Purpose |
|---|---|
projectDependencies_<name>() |
Returns a table of dependency project names |
projectSource_<name>() |
Returns a git URL for auto-downloading (3rd party libraries) |
projectExtraConfig_<name>() |
Adds per-configuration flags, defines, or include paths |
projectExtraConfigExecutable_<name>() |
Same as above but only for executable targets linking this library |
projectDependencyConfig_<name>() |
Configuration applied to all projects that depend on this one |
projectDescription_<name>() |
Returns a human-readable description string |
| Variable | Purpose |
|---|---|
projectHeaderOnlyLib_<name> = true |
Marks a header-only library (no linking, only include paths) |
- Project names may contain letters, numbers, dashes (
-), and dots (.) - In callback function names, dashes and dots are replaced with underscores
- Example: project
rg-core.lib→ functions namedprojectAdd_rg_core_lib(), etc.
| Function | Kind | IDE Group | Use For |
|---|---|---|---|
addProject_cmd(name) |
ConsoleApp | tools_cmd |
Command-line tools and utilities |
addProject_lib(name) |
StaticLib | libs |
Reusable static/shared libraries |
addProject_game(name) |
ConsoleApp / WindowedApp | games |
Game executables (windowed in retail) |
addProject_qt(name) |
WindowedApp | tools |
Qt 6 GUI applications |
addProject_3rdParty_lib(name, files) |
StaticLib | 3rd |
External library wrappers |
addProject_lib_test(name) |
ConsoleApp | tests |
Unit test executables |
addProject_lib_sample(name, sampleName) |
ConsoleApp | samples |
Sample/demo executables named <lib>_<sample> |
addProject_lib_tool(name, libName) |
ConsoleApp | libs-tools |
Library-related tools named <lib>_<tool> |
| Option | Description |
|---|---|
--zidar-path=PATH |
Path to the zidar directory (required) |
--with-unittests |
Generate unit test projects for libraries |
--with-tools |
Generate tool projects for libraries |
--with-samples |
Generate sample projects for libraries |
--with-no-pch |
Disable precompiled headers for all projects |
| Option | Description |
|---|---|
--gcc=VARIANT |
GCC/Clang compiler variant (see below) |
--vs=TOOLSET |
Visual Studio toolset variant |
--xcode=TARGET |
Xcode target platform |
--with-android=LEVEL |
Android API level (default: 24) |
--with-ios=VERSION |
iOS minimum deployment target (default: 13.0) |
--with-macos=VERSION |
macOS minimum deployment target (default: 13.0) |
--with-tvos=VERSION |
tvOS minimum deployment target (default: 13.0) |
--with-visionos=VERSION |
visionOS minimum deployment target (default: 1.0) |
--with-windows=VERSION |
Windows SDK version |
--with-dynamic-runtime |
Use dynamic runtime linking |
--with-32bit-compiler |
Use 32-bit compiler |
--with-avx |
Enable AVX instruction set |
--with-glfw |
Link GLFW libraries |
--with-remove-crt |
Remove CRT from linking |
| Variant | Platform |
|---|---|
linux-gcc |
Linux with GCC |
linux-clang |
Linux with Clang |
linux-gcc-afl |
Linux with GCC + AFL fuzzer |
linux-clang-afl |
Linux with Clang + AFL fuzzer |
linux-arm-gcc |
Linux ARM with GCC |
linux-ppc64le-gcc |
Linux PPC64LE with GCC |
linux-ppc64le-clang |
Linux PPC64LE with Clang |
linux-riscv64-gcc |
Linux RISC-V 64 with GCC |
android-arm |
Android ARM |
android-arm64 |
Android ARM64 |
android-x86 |
Android x86 |
android-x86_64 |
Android x86_64 |
ios-arm |
iOS ARM |
ios-arm64 |
iOS ARM64 |
ios-simulator |
iOS Simulator |
tvos-arm64 |
tvOS ARM64 |
tvos-simulator |
tvOS Simulator |
xros-arm64 |
visionOS ARM64 |
xros-simulator |
visionOS Simulator |
osx-x64 |
macOS x64 |
osx-arm64 |
macOS ARM64 (Apple Silicon) |
mingw-gcc |
MinGW with GCC |
mingw-clang |
MinGW with Clang |
wasm |
WebAssembly (Emscripten) |
wasm2js |
WebAssembly to JS (Emscripten) |
orbis |
PlayStation 4 |
prospero |
PlayStation 5 |
rpi |
Raspberry Pi |
riscv |
RISC-V |
freebsd |
FreeBSD |
netbsd |
NetBSD |
| Toolset | Description |
|---|---|
vs2017-clang |
Clang with MS CodeGen |
vs2017-xp |
VS 2017 targeting Windows XP |
winstore100 |
Universal Windows App 10.0 |
durango |
Xbox One |
orbis |
PlayStation 4 |
prospero |
PlayStation 5 |
| Target | Platform |
|---|---|
osx |
macOS |
ios |
iOS |
tvos |
tvOS |
xros |
visionOS |
Zidar defines three build configurations:
| Configuration | Defines | Flags | Use For |
|---|---|---|---|
debug |
RG_DEBUG_BUILD, _DEBUG, DEBUG |
Symbols | Development and debugging |
release |
RG_RELEASE_BUILD, NDEBUG |
OptimizeSpeed, NoFramePointer, NoBufferSecurityCheck, Symbols | Performance testing with debug symbols |
retail |
RG_RETAIL_BUILD, NDEBUG, RETAIL |
OptimizeSpeed, NoFramePointer, NoBufferSecurityCheck | Final shipping builds |
Output binaries are suffixed with the configuration name (e.g. mylib_debug.lib, mylib_release.lib).
You can use these defines in your code to conditionally compile:
#if defined(RG_DEBUG_BUILD)
printf("Debug mode: extra logging enabled\n");
#endifZidar automatically detects and configures precompiled headers. For a project named my_project, place these files in the src/ directory:
my_project_pch.h— PCH header (required)my_project_pch.cppormy_project_pch.c— PCH source (required)
Zidar checks for these files by name convention and enables PCH automatically. No configuration needed.
PCH is disabled on macOS/Xcode and can be globally disabled with --with-no-pch.
genie --zidar-path=/path/to/zidar cleanThis removes:
- The
.zidar/build directory (generated project files and intermediates) - The
.3rd/dependency directory (cloned 3rd party sources)
The .3rd/ directory is preserved if the RG_ZIDAR_DEPENDENCY_DIR environment variable is set to a custom location.
| Variable | Description |
|---|---|
QTDIR |
Qt 6 root directory (required for Qt projects) |
RG_ZIDAR_DEPENDENCY_DIR |
Override the default .3rd/ dependency directory location |
HOME / HOMEPATH |
User home directory (used as a search boundary for project discovery) |
PATH |
Must contain GENie, git, and build tools |
WindowsSDKVersion |
Windows SDK version (auto-detected from Visual Studio environment) |
Zidar ships with five progressively complex sample projects in the samples/ directory:
| Sample | What It Demonstrates |
|---|---|
01_hello_world |
Minimal console application — the simplest possible zidar project |
02_hello_library |
Static library with public headers and unit tests |
03_tool_using_a_library |
Console tool with a library dependency — demonstrates dependency resolution |
04_Qt_app_using_a_library |
Qt 6 GUI application that depends on a library |
05_fancy_game_engine |
Multi-project solution with libraries, tools, Qt apps, and games |
Each sample is self-contained and can be built standalone:
cd samples/01_hello_world/
make projgen # Generate build files for all platformsThese samples are the best reference for understanding zidar conventions in practice.