#define Rtc 1 #include "buildit.h" #include /* Capabilities: * * Run commands. Return result of command. * * */ /* ========================== * * Globals * ========================== */ /* ========================== * * Args * ========================== */ #define ARGS_XLIST(X) \ X(CLANG, "clang", "Compile with clang") \ X(MSVC, "msvc", "Compile with msvc") \ X(RTC, "rtc", "Should the build compile with runtime checks enabled (asserts, asan, etc.) - Requires crtlib") \ X(ASAN, "asan", "Should the build compile with the address sanitizer enabled (asserts, asan, etc.) - Requires crtlib") \ X(CRTLIB, "crtlib", "Should the build link with the CRTLIB") \ X(DEBINFO, "debinfo", "Should the build compile with debug info") \ X(DEVELOPER, "developer", "Should the build compile with developer mode enabled") \ X(PROFILING, "profiling", "Should the build compile with profiling enabled - Requires crtlib, clang") \ X(UNOPTIMIZED, "unoptimized", "Should the build compile with optimization disabled") /* Make args global variables */ #define X(id, str, desc) bool id = 0; ARGS_XLIST(X) #undef X /* Setup arg table */ #define X(id, str, desc) _ARGID_ ## id, enum ArgId { ARGS_XLIST(X) ARGS_COUNT }; #undef X typedef struct ArgEntry ArgEntry; struct ArgEntry { String name; bool *var; }; #define X(id, str, desc) [_ARGID_ ## id] = { .name = Lit(str), .var = &id }, ArgEntry arg_entries[ARGS_COUNT] = { ARGS_XLIST(X) }; #undef X /* ========================== * * Util * ========================== */ void Error(String msg) { SH_PrintF(Lit("ERROR: %F\n"), FmtStr(msg)); } /* ========================== * * Compile command * ========================== */ typedef struct CompileCommandListNode CompileCommandListNode; struct CompileCommandListNode { String comp_command; String link_file_path; Bool silent_if_success; CompileCommandListNode *next; CompileCommandListNode *prev; }; typedef struct CompileCommandList CompileCommandList; struct CompileCommandList { CompileCommandListNode *first; CompileCommandListNode *last; Size count; }; void CompileCommandListAppend(Arena *arena, CompileCommandList *l, String comp_command, String link_file_path, Bool silent_if_success) { CompileCommandListNode *n = ArenaPush(arena, CompileCommandListNode); n->comp_command = comp_command; n->link_file_path = link_file_path; n->silent_if_success = silent_if_success; DllPushBack(l->first, l->last, n); ++l->count; } /* ========================== * * Rc include * ========================== */ typedef struct RcIncludeListNode RcIncludeListNode; struct RcIncludeListNode { D_Tag tag; String rc_type; RcIncludeListNode *next; RcIncludeListNode *prev; }; typedef struct RcIncludeList RcIncludeList; struct RcIncludeList { RcIncludeListNode *first; RcIncludeListNode *last; Size count; }; void RcIncludeListAppend(Arena *arena, RcIncludeList *l, D_Tag tag, String rc_type) { RcIncludeListNode *n = ArenaPush(arena, RcIncludeListNode); n->tag = tag; n->rc_type = rc_type; DllPushBack(l->first, l->last, n); ++l->count; } /* ========================== * * Build * ========================== */ void OnBuild(StringList args_list) { Arena arena = ArenaAlloc(Gigabyte(64)); /* ========================== * * Unpack command line args * ========================== */ for (StringListNode *n = args_list.first; n; n = n->next) { String arg_name = n->string; for (int i = 0; i < ArrayCount(arg_entries); ++i) { ArgEntry *entry = &arg_entries[i]; if (StringEqual(arg_name, entry->name)) { *entry->var = 1; } } } if (MSVC && CLANG) { Error(Lit("Msvc & clang arguments cannot both be set")); OS_Exit(1); } else if (!(MSVC || CLANG)) { CLANG = 1; } if (MSVC) CLANG = 0; if (CLANG) MSVC = 0; /* ========================== * * Determine output dir * ========================== */ String out_obj_dir_path = { 0 }; String out_inc_dir_path = { 0 }; String out_bin_dir_path = { 0 }; { StringList out_dir_path_parts = { 0 }; if (CLANG) { StringListAppend(&arena, &out_dir_path_parts, Lit("clang")); } else if (MSVC) { StringListAppend(&arena, &out_dir_path_parts, Lit("msvc")); }a if (DEVELOPER) { StringListAppend(&arena, &out_dir_path_parts, Lit("developer")); } else if (MSVC) { StringListAppend(&arena, &out_dir_path_parts, Lit("user")); } if (RTC) { StringListAppend(&arena, &out_dir_path_parts, Lit("rtc")); } if (ASAN) { StringListAppend(&arena, &out_dir_path_parts, Lit("asan")); } if (CRTLIB) { StringListAppend(&arena, &out_dir_path_parts, Lit("crtlib")); } if (DEBINFO) { StringListAppend(&arena, &out_dir_path_parts, Lit("debinfo")); } if (PROFILING) { StringListAppend(&arena, &out_dir_path_parts, Lit("profiling")); } if (UNOPTIMIZED) { StringListAppend(&arena, &out_dir_path_parts, Lit("unoptimized")); } String out_dir_name = StringFromStringList(&arena, Lit("-"), out_dir_path_parts); out_obj_dir_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("build/%F/obj/"), FmtStr(out_dir_name))); out_inc_dir_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("build/%F/inc/"), FmtStr(out_dir_name))); out_bin_dir_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("build/%F/bin/"), FmtStr(out_dir_name))); } if (!OS_DirExists(out_obj_dir_path)) { OS_CreateDirAtAbsPath(out_obj_dir_path); } if (!OS_DirExists(out_inc_dir_path)) { OS_CreateDirAtAbsPath(out_inc_dir_path); } if (!OS_DirExists(out_bin_dir_path)) { OS_CreateDirAtAbsPath(out_bin_dir_path); } /* ========================== * * Constants * ========================== */ CompileCommandList compile_command_list = { 0 }; String obj_file_extension = PlatformWindows ? Lit("obj") : Lit("o"); D_Tag executable = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/PowerPlay.exe"), FmtStr(out_bin_dir_path))); D_Tag pch_input = D_FileTagFromPath(&arena, Lit("src/common.h")); D_Tag pch_c_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common_c.pch"), FmtStr(out_obj_dir_path))); D_Tag pch_cpp_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common_cpp.pch"), FmtStr(out_obj_dir_path))); /* ========================== * * Determine compiler args * ========================== */ String final_c_compile_args_fmt = { 0 }; String final_cpp_compile_args_fmt = { 0 }; String final_pch_c_compile_args_fmt = { 0 }; String final_pch_cpp_compile_args_fmt = { 0 }; String final_link_args_fmt = { 0 }; { StringList warnings = { 0 }; StringList compile_and_link_args = { 0 }; StringList compile_args = { 0 }; StringList c_compile_args = { 0 }; StringList cpp_compile_args = { 0 }; StringList pch_c_compile_args = { 0 }; StringList pch_cpp_compile_args = { 0 }; StringList link_args = { 0 }; if (CLANG) { #if 1 StringListAppend(&arena, &c_compile_args, Lit("clang -xc -std=c99 -c %F -o %F")); StringListAppend(&arena, &pch_c_compile_args, Lit("clang -xc-header -std=c99 -c %F -o %F")); StringListAppend(&arena, &cpp_compile_args, Lit("clang -xc++ -std=c++20 -c %F -o %F")); StringListAppend(&arena, &pch_cpp_compile_args, Lit("clang -xc++-header -std=c++20 -c %F -o %F")); StringListAppend(&arena, &link_args, Lit("clang %F -o %F")); #else StringListAppend(&arena, &compile_args, Lit("clang -c")); StringListAppend(&arena, &link_args, Lit("lld-link")); StringListAppend(&arena, &c_compile_args, Lit("-std=c99")); StringListAppend(&arena, &pch_c_compile_args, Lit("-x c-header -std=c99")); StringListAppend(&arena, &cpp_compile_args, Lit("-std=c++20")); StringListAppend(&arena, &pch_cpp_compile_args, Lit("-x c++-header -std=c++20")); StringListAppend(&arena, &compile_args, Lit("-o %F %F")); StringListAppend(&arena, &link_args, Lit("%F")); #endif //"-fuse-ld=lld-link" StringListAppend(&arena, &compile_and_link_args, Lit("-fuse-ld=lld-link " "-nostdlib " "-fno-strict-aliasing " "-fno-finite-loops " "-fwrapv " "-msse4.1 " "-msse4.2 ")); StringListAppend(&arena, &warnings, Lit("-Weverything -Werror " "-Wframe-larger-than=65536 " "-Wno-unused-macros -Wno-gnu-zero-variadic-macro-arguments -Wno-documentation " "-Wno-old-style-cast -Wno-conversion -Wno-sign-conversion " "-Wno-declaration-after-statement -Wno-extra-semi -Wno-extra-semi-stmt " "-Wno-bad-function-cast -Wno-class-varargs -Wno-unreachable-code-break " "-Wno-cast-align -Wno-float-equal -Wno-zero-as-null-pointer-constant " "-Wno-cast-qual -Wno-missing-noreturn -Wno-missing-field-initializers " "-Wno-missing-braces -Wno-initializer-overrides " "-Wno-c99-extensions -Wno-c++98-compat-pedantic -Wno-c++98-compat " "-Wno-switch-enum -Wno-switch-default " "-Wno-reserved-identifier -Wno-reserved-macro-identifier " "-Wno-unsafe-buffer-usage -Wno-writable-strings " "" "-Wno-double-promotion")); /* -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-parameter */ /* Pre-compiled header */ StringListAppend(&arena, &c_compile_args, StringF(&arena, Lit("-include-pch %F"), FmtStr(pch_c_output.full_path))); StringListAppend(&arena, &cpp_compile_args, StringF(&arena, Lit("-include-pch %F"), FmtStr(pch_cpp_output.full_path))); } /* RTC */ if (RTC) { if (!CRTLIB) { Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with RTC (runtime checks)")); OS_Exit(1); } StringListAppend(&arena, &compile_and_link_args, Lit("-DRTC=1")); if (MSVC) { if (!ASAN) { /* Enable /RTC option (not compatible with ASAN) */ StringListAppend(&arena, &compile_and_link_args, Lit("/RTCcsu")); } } else { /* Enable UBSan */ StringListAppend(&arena, &compile_and_link_args, Lit("-fsanitize=undefined -fsanitize-trap=all")); //StringListAppend(&compile_and_link_args, "-fsanitize=undefined"); } } /* CRTLIB */ if (CRTLIB) { StringListAppend(&arena, &compile_and_link_args, Lit("-DCRTLIB=1")); String crt_libs = { 0 }; if (MSVC) { crt_libs = RTC ? Lit("msvcrtd.lib ucrtd.lib msvcprtd.lib vcruntimed.lib") : Lit("msvcrt.lib ucrt.lib msvcprt.lib vcruntime.lib"); } else { crt_libs = RTC ? Lit("-lmsvcrtd -lucrtd -lmsvcprtd -lvcruntimed") : Lit("-lmsvcrt -lucrt -lmsvcprt -lvcruntime"); } StringListAppend(&arena, &link_args, crt_libs); } else { if (MSVC) { /* TODO */ Error(Lit("TODO\n")); OS_Exit(1); } else { StringListAppend(&arena, &compile_and_link_args, Lit("-mno-stack-arg-probe -fno-builtin")); } } /* Optimization */ if (UNOPTIMIZED) { StringListAppend(&arena, &compile_and_link_args, Lit("-DUNOPTIMIZED=1")); StringListAppend(&arena, &compile_and_link_args, MSVC ? Lit("/Od") : Lit("-O0")); } else { StringListAppend(&arena, &compile_and_link_args, MSVC ? Lit("/O2 /LTCG") : Lit("-O3 -flto")); } /* Debug info */ if (DEBINFO) { StringListAppend(&arena, &compile_and_link_args, Lit("-DDEBINFO=1")); StringListAppend(&arena, &compile_and_link_args, MSVC ? Lit("/Zi") : Lit("-g")); } /* Address sanitizer */ if (ASAN) { if (!CRTLIB) { Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with asan enabled")); OS_Exit(1); } StringListAppend(&arena, &compile_and_link_args, Lit("-DASAN=1")); if (MSVC) { /* TODO: Copy asan libs */ StringListAppend(&arena, &compile_and_link_args, Lit("/fsanitize=address")); } else { StringListAppend(&arena, &compile_and_link_args, Lit("-fsanitize=address -shared-libasan")); } } /* Developer mode */ if (DEVELOPER) { StringListAppend(&arena, &compile_and_link_args, Lit("-DDEVELOPER=1")); } /* Profiling */ if (PROFILING) { if (!CRTLIB) { Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with profiling enabled")); OS_Exit(1); } if (MSVC) { Error(Lit("MSVC not supported with profiling enabled (Profiling relies on Clang attributes)")); OS_Exit(1); } StringListAppend(&arena, &compile_and_link_args, Lit("-DPROFILING=1")); /* Tracy flags */ StringListAppend(&arena, &compile_and_link_args, Lit("-DTRACY_ENABLE=1")); StringListAppend(&arena, &compile_and_link_args, Lit("-DTRACY_CALLSTACK=5")); StringListAppend(&arena, &compile_and_link_args, Lit("-DTRACY_NO_SAMPLING -DTRACY_NO_SYSTEM_TRACING -DTRACY_NO_CALLSTACK")); /* Disable warnings when compiling tracy client */ warnings = (StringList) { 0 }; } String incbin_dir = StringReplace(&arena, out_inc_dir_path, Lit("\\"), Lit("/")); StringListAppend(&arena, &compile_and_link_args, StringF(&arena, Lit("-DINCBIN_DIR=\"%F\""), FmtStr(incbin_dir))); final_c_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), c_compile_args, compile_args, compile_and_link_args, warnings); final_cpp_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), cpp_compile_args, compile_args, compile_and_link_args, warnings); final_pch_c_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), pch_c_compile_args, compile_args, compile_and_link_args); final_pch_cpp_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), pch_cpp_compile_args, compile_args, compile_and_link_args); final_link_args_fmt = StringFromStringLists(&arena, Lit(" "), link_args, compile_and_link_args, warnings); } /* ========================== * * Generate embeddable tar files * ========================== */ bool embed_in_rc = !!MSVC; D_Tag res_dir = D_DirTagFromPath(&arena, Lit("res")); D_Tag shaders_dir = D_DirTagFromPath(&arena, Lit("src/shaders")); D_Tag inc_file = D_FileTagFromPath(&arena, Lit("src/inc.c")); RcIncludeList rc_includes = { 0 }; /* Generate shaders tar */ D_Tag shaders_tar = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/shaders.tar"), FmtStr(out_inc_dir_path))); D_AddDependency(shaders_tar, shaders_dir); if (embed_in_rc) { RcIncludeListAppend(&arena, &rc_includes, shaders_tar, Lit("RCDATA")); } else { D_AddDependency(inc_file, shaders_tar); } if (D_IsDirty(shaders_tar)) { String tar_cmd = StringF(&arena, Lit("cd %F && tar cvf %F ."), FmtStr(shaders_dir.full_path), FmtStr(shaders_tar.full_path)); CompileCommandListAppend(&arena, &compile_command_list, tar_cmd, (String) { 0 }, true); } /* Generate res tar */ if (!DEVELOPER) { D_Tag res_tar = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/res.tar"), FmtStr(out_inc_dir_path))); D_AddDependency(res_tar, res_dir); if (embed_in_rc) { RcIncludeListAppend(&arena, &rc_includes, res_tar, Lit("RCDATA")); } else { D_AddDependency(inc_file, res_tar); } if (D_IsDirty(res_tar)) { String tar_cmd = StringF(&arena, Lit("cd %F && tar cvf %F ."), FmtStr(res_dir.full_path), FmtStr(res_tar.full_path)); CompileCommandListAppend(&arena, &compile_command_list, tar_cmd, (String) { 0 }, true); } } /* ========================== * * RC file (windows) * ========================== */ if (PlatformWindows) { D_Tag rc_file = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/rc.rc"), FmtStr(out_obj_dir_path)));; /* Add icon file to rc list */ { D_Tag icon_file = D_FileTagFromPath(&arena, Lit("icon.ico")); RcIncludeListAppend(&arena, &rc_includes, icon_file, Lit("ICON")); } /* Add rc dependencies */ for (RcIncludeListNode *rin = rc_includes.first; rin; rin = rin->next) { D_AddDependency(rc_file, rin->tag); } if (D_IsDirty(rc_file)) { /* Generate rc file */ D_ClearWrite(rc_file, Lit("")); for (RcIncludeListNode *rin = rc_includes.first; rin; rin = rin->next) { String name = D_GetName(rin->tag); String line = StringF(&arena, Lit("%F %F DISCARDABLE %F\n"), FmtStr(name), FmtStr(rin->rc_type), FmtStr(name)); D_AppendWrite(rc_file, line); } /* Append rc -> res compile command */ D_Tag rc_res_file = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/rc.res"), FmtStr(out_obj_dir_path)));; String rc_compile_cmd = { 0 }; if (MSVC) { rc_compile_cmd = StringF(&arena, Lit("rc %F"), FmtStr(rc_file.full_path)); } else { rc_compile_cmd = StringF(&arena, Lit("llvm-rc %F"), FmtStr(rc_file.full_path)); } CompileCommandListAppend(&arena, &compile_command_list, rc_compile_cmd, rc_res_file.full_path, true); } } /* ========================== * * Add pch compile commands * ========================== */ { /* C */ { String comp_cmd = StringF(&arena, final_pch_c_compile_args_fmt, FmtStr(pch_input.full_path), FmtStr(pch_c_output.full_path)); CompileCommandListAppend(&arena, &compile_command_list, comp_cmd, (String) { 0 }, true); } /* Cpp */ { String comp_cmd = StringF(&arena, final_pch_cpp_compile_args_fmt, FmtStr(pch_input.full_path), FmtStr(pch_cpp_output.full_path)); CompileCommandListAppend(&arena, &compile_command_list, comp_cmd, (String) { 0 }, true); } } /* ========================== * * Add src file compile commands * ========================== */ D_Tag src_dir = D_DirTagFromPath(&arena, Lit("src")); D_TagList src_files = D_GetDirContents(&arena, src_dir); for (D_TagListNode *n = src_files.first; n; n = n->next) { D_Tag file = n->tag; bool include = !file.is_dir; if (!include) continue; String name = D_GetName(file); String extension = D_GetExtension(file); bool is_c = StringEqual(extension, Lit("c")); bool is_cpp = !is_c && StringEqual(extension, Lit("cpp")); include = (is_c || is_cpp) && D_IsDirty(file); if (!include) continue; /* Determine platform specific source files */ { if (StringBeginsWith(name, Lit("sys_")) || StringBeginsWith(name, Lit("renderer_")) || StringBeginsWith(name, Lit("playback_")) || StringBeginsWith(name, Lit("mp3_")) || StringBeginsWith(name, Lit("ttf_"))) { include = false; if (PlatformWindows) { include = StringEqual(name, Lit("sys_win32.c")) || StringEqual(name, Lit("renderer_d3d11.c")) || StringEqual(name, Lit("playback_wasapi.c")) || StringEqual(name, Lit("mp3_mmf.c")) || StringEqual(name, Lit("ttf_dwrite.cpp")); } } } if (!include) continue; String obj_file_path = { 0 }; { String name_no_extension = name; if ((extension.len + 1) <= name.len) { name_no_extension.len -= extension.len + 1; } obj_file_path = StringF(&arena, Lit("%F/%F.%F"), FmtStr(out_obj_dir_path), FmtStr(name_no_extension), FmtStr(obj_file_extension)); obj_file_path = OS_GetAbsPath(&arena, obj_file_path); } String comp_cmd_fmt = is_c ? final_c_compile_args_fmt : final_cpp_compile_args_fmt; String comp_cmd = StringF(&arena, comp_cmd_fmt, FmtStr(file.full_path), FmtStr(obj_file_path)); CompileCommandListAppend(&arena, &compile_command_list, comp_cmd, obj_file_path, true); } /* ========================== * * Compile / link * ========================== */ if (compile_command_list.first) { StringList link_files = { 0 }; /* Compile */ Size comp_i = 0; Size comp_count = compile_command_list.count; for (CompileCommandListNode *n = compile_command_list.first; n; n = n->next) { ++comp_i; String comp_cmd = n->comp_command; if (comp_cmd.len > 0 ) { //SH_PrintF(Lit("[Comp %F/%F] %F\n"), FmtI64(comp_i), FmtI64(comp_count), FmtStr(comp_cmd)); SH_PrintF(Lit("[Comp %F/%F]\n"), FmtI64(comp_i), FmtI64(comp_count)); SH_CommandResult result = SH_RunCommandCaptureOutput(&arena, comp_cmd, true); if (!n->silent_if_success || result.error != 0) { SH_PrintF(Lit("%F\n"), result.output); } if (result.error != 0) { Error(Lit("Compilation failed")); OS_Exit(1); } } String link_file_path = n->link_file_path; if (link_file_path.len > 0) { StringListAppend(&arena, &link_files, link_file_path); } } /* Link */ { String link_files_str = StringFromStringList(&arena, Lit(" "), link_files); String link_cmd = StringF(&arena, final_link_args_fmt, FmtStr(link_files_str), FmtStr(executable.full_path)); //SH_PrintF(Lit("[Link] %F\n"), FmtStr(link_cmd)); SH_Print(Lit("Linking...\n")); SH_CommandResult result = SH_RunCommandCaptureOutput(&arena, link_cmd, false); if (result.error != 0) { Error(Lit("Linking failed")); OS_Exit(1); } D_SetDirty(executable); } } else { /* Nothing to build */ SH_Print(Lit("Nothing to build")); } #if 0 #if Rtc getchar(); #endif #endif }