// Bootstrap: // Windows: cl /nologo build.c // macOS: cc build.c -o build // After that, just run: ./build (or build.exe on Windows) #define NOB_IMPLEMENTATION #include "vendor/nob/nob.h" //////////////////////////////// // lunasvg/plutovg static library build helpers static const char *plutovg_sources[] = { "vendor/lunasvg/plutovg/source/plutovg-blend.c", "vendor/lunasvg/plutovg/source/plutovg-canvas.c", "vendor/lunasvg/plutovg/source/plutovg-font.c", "vendor/lunasvg/plutovg/source/plutovg-ft-math.c", "vendor/lunasvg/plutovg/source/plutovg-ft-raster.c", "vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c", "vendor/lunasvg/plutovg/source/plutovg-matrix.c", "vendor/lunasvg/plutovg/source/plutovg-paint.c", "vendor/lunasvg/plutovg/source/plutovg-path.c", "vendor/lunasvg/plutovg/source/plutovg-rasterize.c", "vendor/lunasvg/plutovg/source/plutovg-surface.c", }; static const char *lunasvg_sources[] = { "vendor/lunasvg/source/graphics.cpp", "vendor/lunasvg/source/lunasvg.cpp", "vendor/lunasvg/source/svgelement.cpp", "vendor/lunasvg/source/svggeometryelement.cpp", "vendor/lunasvg/source/svglayoutstate.cpp", "vendor/lunasvg/source/svgpaintelement.cpp", "vendor/lunasvg/source/svgparser.cpp", "vendor/lunasvg/source/svgproperty.cpp", "vendor/lunasvg/source/svgrenderstate.cpp", "vendor/lunasvg/source/svgtextelement.cpp", }; #ifdef __APPLE__ //////////////////////////////// // macOS build (clang++ with Objective-C++) static const char *frameworks[] = { "-framework", "Metal", "-framework", "Cocoa", "-framework", "CoreAudio", "-framework", "AudioToolbox", "-framework", "CoreMIDI", "-framework", "QuartzCore", "-framework", "CoreText", "-framework", "CoreFoundation", "-framework", "CoreGraphics", }; static bool build_lunasvg_lib(const char *build_dir, bool debug) { const char *obj_dir = nob_temp_sprintf("%s/lunasvg_obj", build_dir); const char *lib_path = nob_temp_sprintf("%s/liblunasvg.a", build_dir); // Collect all source paths to check if rebuild is needed { const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)]; size_t n = 0; for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) all_sources[n++] = plutovg_sources[i]; for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) all_sources[n++] = lunasvg_sources[i]; if (!nob_needs_rebuild(lib_path, all_sources, n)) { nob_log(NOB_INFO, "lunasvg is up to date"); return true; } } if (!nob_mkdir_if_not_exists(obj_dir)) return false; // Compile plutovg C sources for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) { const char *src = plutovg_sources[i]; // Extract filename for .o const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char obj_name[256]; snprintf(obj_name, sizeof(obj_name), "%s", base); char *dot = strrchr(obj_name, '.'); if (dot) { dot[1] = 'o'; dot[2] = '\0'; } const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name); Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "clang"); nob_cmd_append(&cmd, "-std=c11", "-c"); nob_cmd_append(&cmd, "-DPLUTVOG_BUILD", "-DPLUTVOG_BUILD_STATIC"); nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include"); nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/source"); nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter"); if (debug) { nob_cmd_append(&cmd, "-g", "-O0"); } else { nob_cmd_append(&cmd, "-O2"); } nob_cmd_append(&cmd, "-o", obj_path); nob_cmd_append(&cmd, src); Nob_Cmd_Opt copt = {0}; if (!nob_cmd_run_opt(&cmd, copt)) return false; } // Compile lunasvg C++ sources for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) { const char *src = lunasvg_sources[i]; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char obj_name[256]; snprintf(obj_name, sizeof(obj_name), "%s", base); char *dot = strrchr(obj_name, '.'); if (dot) { dot[1] = 'o'; dot[2] = '\0'; } const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name); Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "clang++"); nob_cmd_append(&cmd, "-std=c++17", "-c"); nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti"); nob_cmd_append(&cmd, "-DLUNASVG_BUILD", "-DLUNASVG_BUILD_STATIC"); nob_cmd_append(&cmd, "-Ivendor/lunasvg/include"); nob_cmd_append(&cmd, "-Ivendor/lunasvg/source"); nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include"); nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter"); if (debug) { nob_cmd_append(&cmd, "-g", "-O0"); } else { nob_cmd_append(&cmd, "-O2"); } nob_cmd_append(&cmd, "-o", obj_path); nob_cmd_append(&cmd, src); Nob_Cmd_Opt copt = {0}; if (!nob_cmd_run_opt(&cmd, copt)) return false; } // Archive into static library { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "ar", "rcs", lib_path); for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) { const char *src = plutovg_sources[i]; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char obj_name[256]; snprintf(obj_name, sizeof(obj_name), "%s", base); char *dot = strrchr(obj_name, '.'); if (dot) { dot[1] = 'o'; dot[2] = '\0'; } nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name)); } for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) { const char *src = lunasvg_sources[i]; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char obj_name[256]; snprintf(obj_name, sizeof(obj_name), "%s", base); char *dot = strrchr(obj_name, '.'); if (dot) { dot[1] = 'o'; dot[2] = '\0'; } nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name)); } Nob_Cmd_Opt copt = {0}; if (!nob_cmd_run_opt(&cmd, copt)) return false; } // Clean up obj dir — only the .a is needed { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", obj_dir); Nob_Cmd_Opt copt = {0}; nob_cmd_run_opt(&cmd, copt); } nob_log(NOB_INFO, "Built %s", lib_path); return true; } int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF(argc, argv); bool debug = false; bool clean = false; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) debug = true; else if (strcmp(argv[i], "clean") == 0) clean = true; } const char *build_dir = debug ? "build_debug" : "build_release"; if (clean) { nob_log(NOB_INFO, "Cleaning %s/", build_dir); Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", build_dir); { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } return 0; } // App bundle paths const char *app_dir = nob_temp_sprintf("%s/autosample.app", build_dir); const char *contents = nob_temp_sprintf("%s/Contents", app_dir); const char *macos_dir = nob_temp_sprintf("%s/Contents/MacOS", app_dir); const char *res_dir = nob_temp_sprintf("%s/Contents/Resources", app_dir); const char *binary_path = nob_temp_sprintf("%s/Contents/MacOS/autosample", app_dir); if (!nob_mkdir_if_not_exists(build_dir)) return 1; if (!nob_mkdir_if_not_exists(app_dir)) return 1; if (!nob_mkdir_if_not_exists(contents)) return 1; if (!nob_mkdir_if_not_exists(macos_dir)) return 1; if (!nob_mkdir_if_not_exists(res_dir)) return 1; // Build lunasvg static library if (!build_lunasvg_lib(build_dir, debug)) return 1; // Unity build: single clang++ invocation compiles main.cpp (which #includes everything) { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "clang++"); nob_cmd_append(&cmd, "-std=c++20", "-x", "objective-c++"); nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti"); nob_cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers"); nob_cmd_append(&cmd, "-Wno-deprecated-declarations"); nob_cmd_append(&cmd, "-Isrc", "-Ivendor/clay"); nob_cmd_append(&cmd, "-Ivendor/lunasvg/include"); nob_cmd_append(&cmd, "-DLUNASVG_BUILD_STATIC"); if (debug) { nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG"); } else { nob_cmd_append(&cmd, "-O2", "-DNDEBUG"); } nob_cmd_append(&cmd, "-o", binary_path); nob_cmd_append(&cmd, "src/main.cpp"); // Reset language mode so .a is treated as a library, not source nob_cmd_append(&cmd, "-x", "none"); nob_cmd_append(&cmd, nob_temp_sprintf("%s/liblunasvg.a", build_dir)); { size_t i; for (i = 0; i < NOB_ARRAY_LEN(frameworks); i++) nob_cmd_append(&cmd, frameworks[i]); } nob_cmd_append(&cmd, "-lstdc++"); { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } } // Write Info.plist { const char *plist_path = nob_temp_sprintf("%s/Contents/Info.plist", app_dir); nob_write_entire_file(plist_path, "\n" "\n" "\n" "\n" " CFBundleExecutable\n" " autosample\n" " CFBundleIdentifier\n" " com.autosample.app\n" " CFBundleName\n" " autosample\n" " CFBundleVersion\n" " 0.1\n" " CFBundleShortVersionString\n" " 0.1\n" " CFBundlePackageType\n" " APPL\n" " NSHighResolutionCapable\n" " \n" " NSSupportsAutomaticGraphicsSwitching\n" " \n" "\n" "\n", strlen( "\n" "\n" "\n" "\n" " CFBundleExecutable\n" " autosample\n" " CFBundleIdentifier\n" " com.autosample.app\n" " CFBundleName\n" " autosample\n" " CFBundleVersion\n" " 0.1\n" " CFBundleShortVersionString\n" " 0.1\n" " CFBundlePackageType\n" " APPL\n" " NSHighResolutionCapable\n" " \n" " NSSupportsAutomaticGraphicsSwitching\n" " \n" "\n" "\n" )); } nob_log(NOB_INFO, "Build complete: %s", app_dir); return 0; } #else //////////////////////////////// // Windows build (MSVC cl.exe) static const char *link_libs[] = { "d3d12.lib", "dxgi.lib", "d3dcompiler.lib", "user32.lib", "gdi32.lib", "shell32.lib", "ole32.lib", "advapi32.lib", "dwmapi.lib", "winmm.lib", }; static bool build_lunasvg_lib(const char *build_dir, bool debug) { const char *obj_dir = nob_temp_sprintf("%s\\lunasvg_obj", build_dir); const char *lib_path = nob_temp_sprintf("%s\\lunasvg.lib", build_dir); // Check if rebuild is needed { const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)]; size_t n = 0; for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) all_sources[n++] = plutovg_sources[i]; for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) all_sources[n++] = lunasvg_sources[i]; if (!nob_needs_rebuild(lib_path, all_sources, n)) { nob_log(NOB_INFO, "lunasvg is up to date"); return true; } } if (!nob_mkdir_if_not_exists(obj_dir)) return false; // Compile plutovg C sources for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) { const char *src = plutovg_sources[i]; Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c"); nob_cmd_append(&cmd, "/std:c11"); nob_cmd_append(&cmd, "/DPLUTVOG_BUILD", "/DPLUTVOG_BUILD_STATIC"); nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include"); nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/source"); nob_cmd_append(&cmd, "/W3"); if (debug) { nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od"); } else { nob_cmd_append(&cmd, "/MT", "/O2"); } nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir)); nob_cmd_append(&cmd, src); Nob_Cmd_Opt copt = {0}; if (!nob_cmd_run_opt(&cmd, copt)) return false; } // Compile lunasvg C++ sources for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) { const char *src = lunasvg_sources[i]; Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c"); nob_cmd_append(&cmd, "/std:c++17", "/EHsc"); nob_cmd_append(&cmd, "/DLUNASVG_BUILD", "/DLUNASVG_BUILD_STATIC"); nob_cmd_append(&cmd, "/Ivendor/lunasvg/include"); nob_cmd_append(&cmd, "/Ivendor/lunasvg/source"); nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include"); nob_cmd_append(&cmd, "/W3"); if (debug) { nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od"); } else { nob_cmd_append(&cmd, "/MT", "/O2"); } nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir)); nob_cmd_append(&cmd, src); Nob_Cmd_Opt copt = {0}; if (!nob_cmd_run_opt(&cmd, copt)) return false; } // Archive into static library { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "lib.exe", "/nologo", nob_temp_sprintf("/OUT:%s", lib_path)); nob_cmd_append(&cmd, nob_temp_sprintf("%s/*.obj", obj_dir)); Nob_Cmd_Opt copt = {0}; if (!nob_cmd_run_opt(&cmd, copt)) return false; } // Clean up obj dir — only the .lib is needed { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", nob_temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir)); Nob_Cmd_Opt copt = {0}; nob_cmd_run_opt(&cmd, copt); } nob_log(NOB_INFO, "Built %s", lib_path); return true; } int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF(argc, argv); bool debug = false; bool clean = false; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) debug = true; else if (strcmp(argv[i], "clean") == 0) clean = true; } const char *build_dir = debug ? "build_debug" : "build_release"; if (clean) { nob_log(NOB_INFO, "Cleaning %s/", build_dir); Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", nob_temp_sprintf("if exist %s rmdir /s /q %s", build_dir, build_dir)); { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } return 0; } if (!nob_mkdir_if_not_exists(build_dir)) return 1; // Build lunasvg static library if (!build_lunasvg_lib(build_dir, debug)) return 1; // Unity build: single cl.exe invocation compiles main.cpp (which #includes everything) { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cl.exe"); nob_cmd_append(&cmd, "/nologo", "/std:c++20", "/EHsc", "/W3"); nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay"); nob_cmd_append(&cmd, "/Ivendor/lunasvg/include"); nob_cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC"); if (debug) { nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od", "/D_DEBUG"); } else { nob_cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG"); } nob_cmd_append(&cmd, nob_temp_sprintf("/Fe:%s/autosample.exe", build_dir)); nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", build_dir)); nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/autosample.pdb", build_dir)); nob_cmd_append(&cmd, "src/main.cpp"); nob_cmd_append(&cmd, "/link"); nob_cmd_append(&cmd, "/MACHINE:X64"); nob_cmd_append(&cmd, "/SUBSYSTEM:CONSOLE"); nob_cmd_append(&cmd, nob_temp_sprintf("/PDB:%s/autosample.pdb", build_dir)); nob_cmd_append(&cmd, "/DEBUG"); nob_cmd_append(&cmd, nob_temp_sprintf("%s/lunasvg.lib", build_dir)); { size_t i; for (i = 0; i < NOB_ARRAY_LEN(link_libs); i++) nob_cmd_append(&cmd, link_libs[i]); } { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } } // Clean up obj files nob_delete_file(nob_temp_sprintf("%s/main.obj", build_dir)); nob_log(NOB_INFO, "Build complete: %s/autosample.exe", build_dir); return 0; } #endif