#include "buildit.h" typedef struct StepList StepList; /* ========================== * * Globals * ========================== */ Bool force_rebuild = false; Arena perm = { 0 }; D_Store store = { 0 }; D_Hist hist = { 0 }; StepList *sl = { 0 }; /* Args */ String arg_outdir = Lit("build"); Bool arg_msvc = false; Bool arg_rtc = false; Bool arg_asan = false; Bool arg_crtlib = false; Bool arg_debinfo = false; Bool arg_developer = false; Bool arg_profiling = false; Bool arg_unoptimized = false; /* ========================== * * Util * ========================== */ void Error(String msg) { SH_PrintF(Lit("ERROR: %F\n"), FmtStr(msg)); } Bool IsDirty(D_Tag tag) { Bool res = force_rebuild ? true : D_IsDirty(&store, &hist, tag); if (!res) { if (tag.kind == D_TagKind_File || tag.kind == D_TagKind_Dir) { res = !D_Exists(tag); } } return res; } SH_CommandResult RunCommand(Arena *arena, String command) { SH_CommandResult res = SH_RunCommandCaptureOutput(arena, command, true); return res; } void KillRunningProcesses(void) { SH_RunCommand(Lit("taskkill /im PowerPlay.exe /f /fi \"STATUS eq RUNNING\""), true); } /* ========================== * * Step * ========================== */ typedef struct StepList StepList; typedef enum StepStatus { StepStatus_None, StepStatus_Success, StepStatus_Failure, StepStatus_Skipped } StepStatus; typedef struct Step Step; struct Step { String name; void *arg; OS_Mutex res_mutex; OS_ConditionVariable res_cv; StepStatus res_status; String res_output; Step *next; }; typedef struct StepList StepList; struct StepList { OS_Mutex mutex; Arena arena; T_Queue tq; Step *first; Step *last; I64 count; }; void AddStep(String name, T_TaskFunc *func, void *arg) { OS_Lock lock = OS_MutexLockE(&sl->mutex); Step *s = ArenaPush(&sl->arena, Step); s->res_mutex = OS_MutexAlloc(); s->res_cv = OS_ConditionVariableAlloc(); s->name = name; s->arg = arg; SllQueuePush(sl->first, sl->last, s); ++sl->count; T_AddTask(&sl->tq, func, s); OS_MutexUnlock(&lock); } void AddSyncPoint(void) { OS_Lock lock = OS_MutexLockE(&sl->mutex); T_AddSyncPoint(&sl->tq); OS_MutexUnlock(&lock); } void ExecuteSteps(void) { T_Execute(&sl->tq); } /* ========================== * * Clean result output * ========================== */ /* TODO: Move to buildit */ Size StringFind(String s, String pattern) { Size pos = -1; for (Size i = 0; i < s.len; ++i) { if ((s.len - i) >= pattern.len) { String cmp = { 0 }; cmp.text = s.text + i; cmp.len = pattern.len; if (StringEqual(cmp, pattern)) { pos = i; break; } } else { break; } } return pos; } String CleanResultOutput(Arena *arena, String output) { TempArena scratch = ScratchBegin(arena); StringList res_lines = { 0 }; StringList patterns = { 0 }; StringListAppend(scratch.arena, &patterns, Lit("\r\n")); StringListAppend(scratch.arena, &patterns, Lit("\r\n")); StringListAppend(scratch.arena, &patterns, Lit("\n")); StringList lines = StringSplit(scratch.arena, output, patterns); for (StringListNode *n = lines.first; n; n = n->next) { String line = n->string; String line_cleaned = line; /* Ignore MSVC header include messages */ if (StringContains(line, Lit("Note: including file"))) { line_cleaned.len = 0; } /* Truncate note src locations. * This is a hack to get around visual studio's "GoToNextLocation" * picking up noise from clang & msvc notes since src location * info is present, rather than going to the next error. */ if (StringContains(line_cleaned, Lit(": note: "))) { String trunc = Lit("power_play\\src\\"); Size pos = StringFind(line_cleaned, trunc); if (pos < 0) { trunc = Lit("power_play\\src/"); pos = StringFind(line_cleaned, trunc); } String line_trunced = line_cleaned; if (pos >= 0) { line_trunced.text += pos + trunc.len; line_trunced.len -= pos + trunc.len; line_cleaned = StringCopy(scratch.arena, Lit("[note]: ")); line_cleaned.len += StringCopy(scratch.arena, line_trunced).len; } } if (line_cleaned.len > 0) { StringListAppend(scratch.arena, &res_lines, line_cleaned); } } String res = StringFromStringList(arena, Lit("\n"), res_lines); ScratchEnd(scratch); return res; } /* ========================== * * Build steps * ========================== */ typedef struct BuildStepSimpleCommandArg BuildStepSimpleCommandArg; struct BuildStepSimpleCommandArg { String cmd; AtomicI32 *skip_flag; AtomicI32 *failure_flag; }; typedef struct BuildStepMsvcCompileCommandArg BuildStepMsvcCompileCommandArg; struct BuildStepMsvcCompileCommandArg { String cmd; AtomicI32 *skip_flag; AtomicI32 *failure_flag; D_Tag depfile_dependent; D_Tag output_depfile; D_TagList depfile_force_includes; }; void BuildStepSimpleCommand(void *arg_raw) { TempArena scratch = ScratchBeginNoConflict(); Step *s = arg_raw; BuildStepSimpleCommandArg *arg = s->arg; AtomicI32 *skip_flag = arg->skip_flag; AtomicI32 *failure_flag = arg->failure_flag; if (!skip_flag || !AtomicI32Eval(skip_flag)) { SH_CommandResult result = RunCommand(scratch.arena, arg->cmd); String result_output_cleaned = CleanResultOutput(scratch.arena, result.output); { OS_Lock res_lock = OS_MutexLockE(&s->res_mutex); if (result_output_cleaned.len > 0) { OS_Lock sl_lock = OS_MutexLockE(&sl->mutex); { s->res_output.text = ArenaPushArrayNoZero(&sl->arena, Byte, result_output_cleaned.len); } OS_MutexUnlock(&sl_lock); s->res_output.len = result_output_cleaned.len; MemoryCopy(s->res_output.text, result_output_cleaned.text, result_output_cleaned.len); } s->res_status = result.error ? StepStatus_Failure : StepStatus_Success; if (result.error && failure_flag) { AtomicI32EvalExchange(failure_flag, 1); } OS_ConditionVariableBroadcast(&s->res_cv); OS_MutexUnlock(&res_lock); } } else { OS_Lock res_lock = OS_MutexLockE(&s->res_mutex); s->res_status = StepStatus_Skipped; if (failure_flag) { AtomicI32EvalExchange(failure_flag, 1); } OS_ConditionVariableBroadcast(&s->res_cv); OS_MutexUnlock(&res_lock); } ScratchEnd(scratch); } void BuildStepMsvcCompileCommand(void *arg_raw) { TempArena scratch = ScratchBeginNoConflict(); Step *s = arg_raw; BuildStepMsvcCompileCommandArg *arg = s->arg; AtomicI32 *skip_flag = arg->skip_flag; AtomicI32 *failure_flag = arg->failure_flag; if (!skip_flag || !AtomicI32Eval(skip_flag)) { SH_CommandResult result = RunCommand(scratch.arena, arg->cmd); if (!result.error && !D_IsNil(arg->depfile_dependent) && !D_IsNil(arg->output_depfile)) { String depfile_data = D_DepfileTextFromMsvcOutput(scratch.arena, arg->depfile_dependent, arg->depfile_force_includes, result.output); D_ClearWrite(arg->output_depfile, depfile_data); } String result_output_cleaned = CleanResultOutput(scratch.arena, result.output); { OS_Lock res_lock = OS_MutexLockE(&s->res_mutex); if (result_output_cleaned.len > 0) { OS_Lock sl_lock = OS_MutexLockE(&sl->mutex); { s->res_output.text = ArenaPushArrayNoZero(&sl->arena, Byte, result_output_cleaned.len); } OS_MutexUnlock(&sl_lock); s->res_output.len = result_output_cleaned.len; MemoryCopy(s->res_output.text, result_output_cleaned.text, result_output_cleaned.len); } s->res_status = result.error ? StepStatus_Failure : StepStatus_Success; if (result.error && failure_flag) { AtomicI32EvalExchange(failure_flag, 1); } OS_ConditionVariableBroadcast(&s->res_cv); OS_MutexUnlock(&res_lock); } } else { OS_Lock res_lock = OS_MutexLockE(&s->res_mutex); s->res_status = StepStatus_Skipped; if (failure_flag) { AtomicI32EvalExchange(failure_flag, 1); } OS_ConditionVariableBroadcast(&s->res_cv); OS_MutexUnlock(&res_lock); } ScratchEnd(scratch); } /* ========================== * * Build * ========================== */ void OnBuild(StringList cli_args) { I64 worker_count = OS_GetProcessorCount(); T_StartupWorkers(worker_count); perm = ArenaAlloc(Gigabyte(64)); store = D_StoreAlloc(); sl = ArenaPush(&perm, StepList); sl->arena = ArenaAlloc(Gigabyte(64)); sl->mutex = OS_MutexAlloc(); sl->tq = T_QueueAlloc(); /* ========================== * * Read args * ========================== */ String tracy_env_var_name = Lit("TRACY_SRC_PATH"); String tracy_src_dir_path = OS_GetEnvVar(&perm, tracy_env_var_name); String tracy_client_header_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F%F"), FmtStr(tracy_src_dir_path), FmtStr(Lit("/public/tracy/TracyC.h")))); String tracy_client_src_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F%F"), FmtStr(tracy_src_dir_path), FmtStr(Lit("/public/TracyClient.cpp")))); { typedef enum ArgState { ArgState_None, ArgState_OutputDir } ArgState; ArgState arg_state = ArgState_None; for (StringListNode *n = cli_args.first; n; n = n->next) { String arg = n->string; if (n != cli_args.first) { switch (arg_state) { case ArgState_OutputDir: { arg_outdir = arg; arg_state = ArgState_None; } break; default: { if (StringEqual(arg, Lit("-O"))) arg_state = ArgState_OutputDir; if (StringEqual(arg, Lit("-clang"))) arg_msvc = false; if (StringEqual(arg, Lit("-msvc"))) arg_msvc = true; if (StringEqual(arg, Lit("-rtc"))) arg_rtc = true; if (StringEqual(arg, Lit("-asan"))) arg_asan = true; if (StringEqual(arg, Lit("-crtlib"))) arg_crtlib = true; if (StringEqual(arg, Lit("-debinfo"))) arg_debinfo = true; if (StringEqual(arg, Lit("-developer"))) arg_developer = true; if (StringEqual(arg, Lit("-profiling"))) arg_profiling = true; if (StringEqual(arg, Lit("-unoptimized"))) arg_unoptimized = true; } break; } } } } String hist_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/.dephist"), FmtStr(arg_outdir))); String build_hash_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/.pp_build_hash"), FmtStr(arg_outdir))); String out_dep_dir_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/dep/"), FmtStr(arg_outdir))); String out_obj_dir_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/obj/"), FmtStr(arg_outdir))); String out_inc_dir_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/inc/"), FmtStr(arg_outdir))); String out_bin_dir_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/bin/"), FmtStr(arg_outdir))); 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); } { SH_Print(Lit("------------------------------\n")); String compiler = { 0 }; String compiler_loc = { 0 }; { SH_CommandResult where_res = { 0 }; if (arg_msvc) { compiler = Lit("Msvc"); where_res = RunCommand(&perm, Lit("where cl.exe")); } else { compiler = Lit("Clang"); where_res = RunCommand(&perm, Lit("where clang.exe")); } compiler_loc = where_res.error ? Lit("Not found") : StringReplace(&perm, where_res.output, Lit("\n"), Lit("")); } SH_PrintF(Lit("Compiler path: %F\n"), FmtStr(compiler_loc)); SH_PrintF(Lit("Build path: \"%F\"\n"), FmtStr(out_bin_dir_path)); SH_PrintF(Lit("[%F]\n"), FmtStr(compiler)); if (arg_asan) SH_Print(Lit("[Asan Enabled]\n")); if (arg_profiling) SH_Print(Lit("[Profiling]\n")); if (arg_developer) SH_Print(Lit("[Developer build]\n")); SH_Print(Lit("------------------------------\n\n")); } /* ========================== * * Constants * ========================== */ String dep_file_extension = Lit("d"); String obj_file_extension = Lit("obj"); Bool should_embed_res_dir = !arg_developer; Bool should_embed_in_rc = !!arg_msvc; D_Tag executable_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/PowerPlay.exe"), FmtStr(out_bin_dir_path)), D_TagKind_File); D_Tag res_dir = D_TagFromPath(&perm, Lit("res"), D_TagKind_Dir); D_Tag icon_file = D_TagFromPath(&perm, Lit("icon.ico"), D_TagKind_File); D_Tag inc_src_file = D_TagFromPath(&perm, Lit("src/inc.c"), D_TagKind_File); D_Tag rc_res_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/rc.res"), FmtStr(out_obj_dir_path)), D_TagKind_File); /* ========================== * * Determine compiler args * ========================== */ StringList c_compile_args = { 0 }; StringList cpp_compile_args = { 0 }; StringList pch_c_compile_args = { 0 }; StringList pch_cpp_compile_args = { 0 }; StringList compile_and_link_args = { 0 }; StringList compile_args = { 0 }; StringList compile_warnings = { 0 }; StringList link_warnings = { 0 }; StringList link_args = { 0 }; StringList rc_compile_args = { 0 }; { if (arg_msvc) { /* Msvc */ StringListAppend(&perm, &c_compile_args, Lit("cl.exe /nologo /c \"%F\" /Fo\"%F\" /FS /showIncludes")); StringListAppend(&perm, &cpp_compile_args, Lit("cl.exe /nologo /std:c++20 /c \"%F\" /Fo\"%F\" /FS /showIncludes")); StringListAppend(&perm, &pch_c_compile_args, Lit("cl.exe /nologo /c /Yc\"%F\" \"%F\" /FS /showIncludes")); StringListAppend(&perm, &pch_cpp_compile_args, Lit("cl.exe /nologo /std:c++20 /c /Yc\"%F\" \"%F\" /FS /showIncludes")); StringListAppend(&perm, &rc_compile_args, Lit("rc /fo\"%F\" \"%F\"")); StringListAppend(&perm, &link_args, Lit("link.exe /nologo %F /OUT:\"%F\" /DEBUG:FULL /OPT:REF /OPT:ICF")); String warnings = Lit("/WX /Wall " "/options:strict " "/wd4820 /wd4201 /wd5220 /wd4514 /wd4244 /wd5045 /wd4242 /wd4061 /wd4189 /wd4723 /wd5246"); StringListAppend(&perm, &compile_warnings, warnings); StringListAppend(&perm, &link_warnings, Lit("/WX")); StringListAppend(&perm, &compile_args, StringF(&perm, Lit("/Fd\"%F\\\""), FmtStr(out_bin_dir_path))); } else { /* Clang */ StringListAppend(&perm, &c_compile_args, Lit("clang -xc -std=c99 -c \"%F\" -o \"%F\" -MD")); StringListAppend(&perm, &cpp_compile_args, Lit("clang -xc++ -std=c++20 -c \"%F\" -o \"%F\" -MD")); StringListAppend(&perm, &pch_c_compile_args, Lit("clang -xc-header -std=c99 -c \"%F\" -o \"%F\" -MD")); StringListAppend(&perm, &pch_cpp_compile_args, Lit("clang -xc++-header -std=c++20 -c \"%F\" -o \"%F\" -MD")); StringListAppend(&perm, &rc_compile_args, Lit("llvm-rc /fo\"%F\" \"%F\"")); StringListAppend(&perm, &link_args, Lit("clang %F -o \"%F\"")); StringListAppend(&perm, &compile_and_link_args, Lit("-fuse-ld=lld-link " "-fno-strict-aliasing " "-fno-finite-loops " "-fwrapv " "-msse4.1 " "-msse4.2 ")); String 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-double-promotion " "-Wno-declaration-after-statement " "-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-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-c11-extensions -Wno-gnu-anonymous-struct -Wno-nested-anon-types "); /* -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-parameter */ StringListAppend(&perm, &compile_warnings, warnings); StringListAppend(&perm, &link_warnings, warnings); } /* RTC */ if (arg_rtc) { if (!arg_crtlib) { Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with RTC (runtime checks)")); OS_Exit(1); } StringListAppend(&perm, &compile_args, Lit("-DRTC=1")); if (arg_msvc) { if (!arg_asan) { /* Enable /RTC option (not compatible with ASAN) */ StringListAppend(&perm, &compile_args, Lit("/RTCcsu")); } } else { /* Enable UBSan */ StringListAppend(&perm, &compile_and_link_args, Lit("-fsanitize=undefined -fsanitize-trap=all")); //StringListAppend(&perm, &compile_and_link_args, Lit("-fsanitize=undefined")); } } /* CRTLIB */ if (arg_crtlib) { StringListAppend(&perm, &compile_args, Lit("-DCRTLIB=1")); } else { if (arg_msvc) { /* TODO */ Error(Lit("TODO\n")); OS_Exit(1); } else { StringListAppend(&perm, &compile_and_link_args, Lit("-mno-stack-arg-probe -fno-builtin -nostdlib")); } } /* Optimization */ if (arg_unoptimized) { StringListAppend(&perm, &compile_args, Lit("-DUNOPTIMIZED=1")); if (arg_msvc) { StringListAppend(&perm, &compile_args, Lit("/Od")); } else { StringListAppend(&perm, &compile_and_link_args, Lit("-O0")); } } else { if (arg_msvc) { StringListAppend(&perm, &compile_args, Lit("/O2")); StringListAppend(&perm, &link_args, Lit("/LTCG")); } else { StringListAppend(&perm, &compile_and_link_args, Lit("-O3 -flto")); } } /* Debug info */ if (arg_debinfo) { StringListAppend(&perm, &compile_args, Lit("-DDEBINFO=1")); if (arg_msvc) { StringListAppend(&perm, &compile_args, Lit("/JMC /Zi")); } else { StringListAppend(&perm, &compile_and_link_args, Lit("-g")); } } /* Address sanitizer */ if (arg_asan) { if (!arg_crtlib) { Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with asan enabled")); OS_Exit(1); } StringListAppend(&perm, &compile_args, Lit("-DASAN=1")); if (arg_msvc) { StringListAppend(&perm, &compile_args, Lit("/fsanitize=address")); } else { StringListAppend(&perm, &compile_and_link_args, Lit("-fsanitize=address")); } } /* Developer mode */ if (arg_developer) { StringListAppend(&perm, &compile_args, Lit("-DDEVELOPER=1")); } /* Profiling */ if (arg_profiling) { if (!arg_crtlib) { Error(Lit("CRTLIB (C runtime library) must be enabled when compiling with profiling enabled")); OS_Exit(1); } if (arg_msvc) { Error(Lit("MSVC not supported with profiling enabled (Profiling relies on Clang attributes)")); OS_Exit(1); } StringListAppend(&perm, &compile_args, Lit("-DPROFILING=1")); /* Tracy include path */ if (tracy_src_dir_path.len == 0 || !OS_DirExists(tracy_src_dir_path)) { Error(StringF(&perm, Lit("Profiling is enabled but tracy directory \"%F\" does not exist (set by environment variable \"%F\")"), FmtStr(tracy_src_dir_path), FmtStr(tracy_env_var_name))); OS_Exit(1); } StringListAppend(&perm, &compile_args, StringF(&perm, Lit("-DTRACY_CLIENT_HEADER_PATH=\\\"%F\\\""), FmtStr(tracy_client_header_path))); StringListAppend(&perm, &compile_args, StringF(&perm, Lit("-DTRACY_CLIENT_SRC_PATH=\\\"%F\\\""), FmtStr(tracy_client_src_path))); } if (!arg_msvc) { String incbin_dir = StringReplace(&perm, out_inc_dir_path, Lit("\\"), Lit("/")); StringListAppend(&perm, &compile_args, StringF(&perm, Lit("-DINCBIN_DIR_RAW=\"%F\""), FmtStr(incbin_dir))); } } /* ========================== * * Examine build hash * ========================== */ { D_Tag build_hash_file = D_TagFromPath(&perm, build_hash_path, D_TagKind_File); U64 build_hash = D_HashString64Basis; { String args_str = StringFromStringList(&perm, Lit(" "), cli_args); String compile_time_str = Lit(__DATE__ __TIME__); build_hash = D_HashString64(build_hash, args_str); build_hash = D_HashString64(build_hash, compile_time_str); } U64 old_build_hash = 0; { String build_hash_file_data = D_ReadAll(&perm, build_hash_file); if (build_hash_file_data.len >= 8) { MemoryCopy((Byte *)&old_build_hash, build_hash_file_data.text, 8); } } if (build_hash != old_build_hash) { SH_Print(Lit("Builder exe or build args have changed, forcing complete rebuild.\n")); force_rebuild = true; String data = StringFromStruct(&build_hash); D_ClearWrite(build_hash_file, data); } } /* ========================== * * Begin build * ========================== */ hist = D_HistFromPath(&perm, hist_path); D_TagList link_files = { 0 }; /* ========================== * * Build step: Tar archives * ========================== */ AtomicI32 tar_success_flag = { 0 }; { AddSyncPoint(); D_TagList tar_input_dirs = { 0 }; if (should_embed_res_dir) { D_TagListAppend(&perm, &tar_input_dirs, res_dir); } for (D_TagListNode *n = tar_input_dirs.first; n; n = n->next) { D_Tag input_dir = n->tag; D_Tag tar_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/%F.tar"), FmtStr(out_inc_dir_path), FmtStr(D_GetName(input_dir))), D_TagKind_File); D_AddDependency(&store, tar_file, input_dir); if (should_embed_in_rc) { D_AddDependency(&store, rc_res_file, tar_file); } else { D_AddDependency(&store, inc_src_file, tar_file); } if (IsDirty(tar_file)) { BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg); bs_arg->cmd = StringF(&perm, Lit("cd %F && tar cvf %F ."), FmtStr(input_dir.full_path), FmtStr(tar_file.full_path)); bs_arg->failure_flag = &tar_success_flag; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(D_GetName(input_dir)), FmtStr(D_GetName(tar_file))); AddStep(step_name, &BuildStepSimpleCommand, bs_arg); } } } /* ========================== * * Build step: Compile RC files * ========================== */ AtomicI32 rc_success_flag = { 0 }; if (PlatformWindows) { AddSyncPoint(); D_Tag rc_input_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/rc.rc"), FmtStr(out_inc_dir_path)), D_TagKind_File); { D_Tag res_tar_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/%F.tar"), FmtStr(out_inc_dir_path), FmtStr(D_GetName(res_dir))), D_TagKind_File); D_AddDependency(&store, rc_input_file, icon_file); if (should_embed_in_rc && should_embed_res_dir) { D_AddDependency(&store, rc_input_file, res_tar_file); } if (IsDirty(rc_input_file)) { D_ClearWrite(rc_input_file, Lit("")); D_AppendWrite(rc_input_file, StringF(&perm, Lit("%F %F DISCARDABLE %F\n"), FmtStr(D_GetName(icon_file)), FmtStr(Lit("ICON")), FmtStr(D_GetName(icon_file)))); if (should_embed_in_rc && should_embed_res_dir) { D_AppendWrite(rc_input_file, StringF(&perm, Lit("%F %F DISCARDABLE %F\n"), FmtStr(D_GetName(res_tar_file)), FmtStr(Lit("RCDATA")), FmtStr(D_GetName(res_tar_file)))); } } } { String rc_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), rc_compile_args); D_AddDependency(&store, rc_res_file, rc_input_file); if (IsDirty(rc_res_file)) { BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg); bs_arg->cmd = StringF(&perm, rc_compile_args_fmt, FmtStr(rc_res_file.full_path), FmtStr(rc_input_file.full_path)); bs_arg->skip_flag = &tar_success_flag; bs_arg->failure_flag = &rc_success_flag; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(D_GetName(rc_input_file)), FmtStr(D_GetName(rc_res_file))); AddStep(step_name, &BuildStepSimpleCommand, bs_arg); } D_TagListAppend(&perm, &link_files, rc_res_file); } } /* ========================== * * Build step: Compile pch files * ========================== */ AtomicI32 pch_success_flag = { 0 }; D_TagList depfile_force_includes = { 0 }; { AddSyncPoint(); D_Tag pch_header_file = D_TagFromPath(&perm, Lit("src/common.h"), D_TagKind_File); D_Tag dep_file; { String name = D_GetName(pch_header_file); String dep_file_path = StringF(&perm, Lit("%F/%F.%F"), FmtStr(out_obj_dir_path), FmtStr(name), FmtStr(dep_file_extension)); dep_file = D_TagFromPath(&perm, dep_file_path, D_TagKind_DepFile); } D_Tag pch_c_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/%F.c_pch"), FmtStr(out_obj_dir_path), FmtStr(D_GetName(pch_header_file))), D_TagKind_File); D_AddDependency(&store, pch_c_file, pch_header_file); D_AddDependency(&store, pch_c_file, dep_file); D_Tag pch_cpp_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/%F.cpp_pch"), FmtStr(out_obj_dir_path), FmtStr(D_GetName(pch_header_file))), D_TagKind_File); D_AddDependency(&store, pch_cpp_file, pch_header_file); D_AddDependency(&store, pch_cpp_file, dep_file); if (arg_msvc) { D_TagListAppend(&perm, &depfile_force_includes, pch_header_file); D_Tag pch_c_src_gen_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/common.c"), FmtStr(out_obj_dir_path)), D_TagKind_File); D_Tag pch_c_src_gen_obj_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/common.c.obj"), FmtStr(out_obj_dir_path)), D_TagKind_File); D_Tag pch_cpp_src_gen_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/common.cpp"), FmtStr(out_obj_dir_path)), D_TagKind_File); D_Tag pch_cpp_src_gen_obj_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/common.cpp.obj"), FmtStr(out_obj_dir_path)), D_TagKind_File); StringListAppend(&perm, &c_compile_args, StringF(&perm, Lit("/Yu\"%F\" /FI\"%F\" /Fp\"%F\""), FmtStr(pch_header_file.full_path), FmtStr(pch_header_file.full_path), FmtStr(pch_c_file.full_path))); StringListAppend(&perm, &cpp_compile_args, StringF(&perm, Lit("/Yu\"%F\" /FI\"%F\" /Fp\"%F\""), FmtStr(pch_header_file.full_path), FmtStr(pch_header_file.full_path), FmtStr(pch_cpp_file.full_path))); StringListAppend(&perm, &pch_c_compile_args, StringF(&perm, Lit("/FI\"%F\" /Fp\"%F\" /Fo\"%F\""), FmtStr(pch_header_file.full_path), FmtStr(pch_c_file.full_path), FmtStr(pch_c_src_gen_obj_file.full_path))); StringListAppend(&perm, &pch_cpp_compile_args, StringF(&perm, Lit("/FI\"%F\" /Fp\"%F\" /Fo\"%F\""), FmtStr(pch_header_file.full_path), FmtStr(pch_cpp_file.full_path), FmtStr(pch_cpp_src_gen_obj_file.full_path))); D_TagListAppend(&perm, &link_files, pch_c_src_gen_obj_file); D_TagListAppend(&perm, &link_files, pch_cpp_src_gen_obj_file); String pch_c_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), pch_c_compile_args, compile_warnings, compile_and_link_args, compile_args); String pch_cpp_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), pch_cpp_compile_args, compile_warnings, compile_and_link_args, compile_args); /* C */ D_AddDependency(&store, pch_c_src_gen_obj_file, pch_c_src_gen_file); D_AddDependency(&store, pch_c_file, pch_c_src_gen_obj_file); if (IsDirty(pch_c_file)) { D_ClearWrite(pch_c_src_gen_file, Lit("")); BuildStepMsvcCompileCommandArg *bs_arg = ArenaPush(&perm, BuildStepMsvcCompileCommandArg); bs_arg->cmd = StringF(&perm, pch_c_compile_args_fmt, FmtStr(pch_header_file.full_path), FmtStr(pch_c_src_gen_file.full_path)); bs_arg->depfile_dependent = pch_header_file; bs_arg->output_depfile = dep_file; bs_arg->skip_flag = &rc_success_flag; bs_arg->failure_flag = &pch_success_flag; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(D_GetName(pch_header_file)), FmtStr(D_GetName(pch_c_file))); AddStep(step_name, &BuildStepMsvcCompileCommand, bs_arg); } /* Cpp */ D_AddDependency(&store, pch_cpp_src_gen_obj_file, pch_cpp_src_gen_file); D_AddDependency(&store, pch_cpp_file, pch_cpp_src_gen_obj_file); if (IsDirty(pch_cpp_file)) { D_ClearWrite(pch_cpp_src_gen_file, Lit("")); BuildStepMsvcCompileCommandArg *bs_arg = ArenaPush(&perm, BuildStepMsvcCompileCommandArg); bs_arg->cmd = StringF(&perm, pch_cpp_compile_args_fmt, FmtStr(pch_header_file.full_path), FmtStr(pch_cpp_src_gen_file.full_path)); bs_arg->depfile_dependent = pch_header_file; bs_arg->output_depfile = dep_file; bs_arg->skip_flag = &rc_success_flag; bs_arg->failure_flag = &pch_success_flag; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(D_GetName(pch_header_file)), FmtStr(D_GetName(pch_cpp_file))); AddStep(step_name, &BuildStepMsvcCompileCommand, bs_arg); } } else { StringListAppend(&perm, &c_compile_args, StringF(&perm, Lit("-include-pch %F"), FmtStr(pch_c_file.full_path))); StringListAppend(&perm, &cpp_compile_args, StringF(&perm, Lit("-include-pch %F"), FmtStr(pch_cpp_file.full_path))); String pch_c_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), pch_c_compile_args, compile_warnings, compile_and_link_args, compile_args); String pch_cpp_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), pch_cpp_compile_args, compile_warnings, compile_and_link_args, compile_args); /* C */ if (IsDirty(pch_c_file)) { BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg); bs_arg->cmd = StringF(&perm, pch_c_compile_args_fmt, FmtStr(pch_header_file.full_path), FmtStr(pch_c_file.full_path)); bs_arg->skip_flag = &rc_success_flag; bs_arg->failure_flag = &pch_success_flag; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(D_GetName(pch_header_file)), FmtStr(D_GetName(pch_c_file))); AddStep(step_name, &BuildStepSimpleCommand, bs_arg); } /* Cpp */ if (IsDirty(pch_cpp_file)) { BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg); bs_arg->cmd = StringF(&perm, pch_cpp_compile_args_fmt, FmtStr(pch_header_file.full_path), FmtStr(pch_cpp_file.full_path)); bs_arg->skip_flag = &rc_success_flag; bs_arg->failure_flag = &pch_success_flag; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(D_GetName(pch_header_file)), FmtStr(D_GetName(pch_cpp_file))); AddStep(step_name, &BuildStepSimpleCommand, bs_arg); } } } /* ========================== * * Build step: Compile src files * ========================== */ AtomicI32 src_success_flag = { 0 }; { D_TagList src_input_files = { 0 }; { D_Tag src_dir = D_TagFromPath(&perm, Lit("src"), D_TagKind_Dir); D_TagList src_files = D_GetDirContents(&perm, src_dir, false); for (D_TagListNode *n = src_files.first; n; n = n->next) { Bool ignore = true; D_Tag file = n->tag; String path = file.full_path; String name = D_GetName(file); String extension = StringPathExtension(name); Bool is_dir = file.kind == D_TagKind_Dir; Bool is_c = !is_dir && StringEqual(extension, Lit("c")); Bool is_cpp = !is_dir && !is_c && StringEqual(extension, Lit("cpp")); if (is_c || is_cpp) { if (StringBeginsWith(name, Lit("sys_")) || StringBeginsWith(name, Lit("sock_")) || StringBeginsWith(name, Lit("gp_")) || StringBeginsWith(name, Lit("playback_")) || StringBeginsWith(name, Lit("mp3_")) || StringBeginsWith(name, Lit("ttf_"))) { if (PlatformWindows) { ignore = !(StringEqual(name, Lit("sys_win32.c")) || StringEqual(name, Lit("sock_win32.c")) || StringEqual(name, Lit("gp_dx12.c")) || StringEqual(name, Lit("playback_wasapi.c")) || StringEqual(name, Lit("mp3_mmf.c")) || StringEqual(name, Lit("ttf_dwrite.cpp"))); } } else { ignore = false; } } if (!ignore) { D_TagListAppend(&perm, &src_input_files, file); } } } { AddSyncPoint(); String c_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), c_compile_args, compile_warnings, compile_and_link_args, compile_args); String cpp_compile_args_fmt = StringFromStringLists(&perm, Lit(" "), cpp_compile_args, compile_warnings, compile_and_link_args, compile_args); for (D_TagListNode *n = src_input_files.first; n; n = n->next) { D_Tag file = n->tag; String path = file.full_path; String name = D_GetName(file); String extension = StringPathExtension(name); Bool is_c = StringEqual(extension, Lit("c")); Bool is_cpp = !is_c && StringEqual(extension, Lit("cpp")); D_Tag dep_file; { String name_no_extension = StringPathNoExtension(name); String dep_file_path = StringF(&perm, Lit("%F/%F.%F"), FmtStr(out_obj_dir_path), FmtStr(name_no_extension), FmtStr(dep_file_extension)); dep_file = D_TagFromPath(&perm, dep_file_path, D_TagKind_DepFile); } D_Tag obj_file; { String name_no_extension = StringPathNoExtension(name); String obj_file_path = StringF(&perm, Lit("%F/%F.%F"), FmtStr(out_obj_dir_path), FmtStr(name_no_extension), FmtStr(obj_file_extension)); obj_file = D_TagFromPath(&perm, obj_file_path, D_TagKind_File); } D_AddDependency(&store, obj_file, file); D_AddDependency(&store, obj_file, dep_file); if (IsDirty(obj_file)) { String comp_cmd_fmt = is_c ? c_compile_args_fmt : cpp_compile_args_fmt; String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(name), FmtStr(D_GetName(obj_file))); if (arg_msvc) { BuildStepMsvcCompileCommandArg *bs_arg = ArenaPush(&perm, BuildStepMsvcCompileCommandArg); bs_arg->cmd = StringF(&perm, comp_cmd_fmt, FmtStr(file.full_path), FmtStr(obj_file.full_path)); bs_arg->depfile_dependent = obj_file; bs_arg->output_depfile = dep_file; bs_arg->depfile_force_includes = depfile_force_includes; bs_arg->skip_flag = &pch_success_flag; bs_arg->failure_flag = &src_success_flag; AddStep(step_name, &BuildStepMsvcCompileCommand, bs_arg); } else { BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg); bs_arg->cmd = StringF(&perm, comp_cmd_fmt, FmtStr(file.full_path), FmtStr(obj_file.full_path)); bs_arg->skip_flag = &pch_success_flag; bs_arg->failure_flag = &src_success_flag; AddStep(step_name, &BuildStepSimpleCommand, bs_arg); } } D_TagListAppend(&perm, &link_files, obj_file); } } } /* ========================== * * Build step: Link * ========================== */ { AddSyncPoint(); String link_files_str = { 0 }; { StringList link_files_quoted_list = { 0 }; for (D_TagListNode *n = link_files.first; n; n = n->next) { D_Tag file = n->tag; String path = StringF(&perm, Lit("\"%F\""), FmtStr(file.full_path)); StringListAppend(&perm, &link_files_quoted_list, path); D_AddDependency(&store, executable_file, file); } link_files_str = StringFromStringList(&perm, Lit(" "), link_files_quoted_list); } if (link_files_str.len > 0 && IsDirty(executable_file)) { String link_args_fmt = StringFromStringLists(&perm, Lit(" "), link_args, link_warnings, compile_and_link_args); BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg); bs_arg->cmd = StringF(&perm, link_args_fmt, FmtStr(link_files_str), FmtStr(executable_file.full_path)); bs_arg->skip_flag = &src_success_flag; String step_name = Lit("Link"); AddStep(step_name, &BuildStepSimpleCommand, bs_arg); } } /* ========================== * * Execute build steps * ========================== */ Bool success = true; I64 step_count = sl->count; if (step_count > 0) { KillRunningProcesses(); ExecuteSteps(); Step *s = sl->first; I64 step_i = 0; while (s) { ++step_i; { OS_Lock lock = OS_MutexLockS(&s->res_mutex); while (s->res_status == StepStatus_None) { OS_ConditionVariableWait(&s->res_cv, &lock); } OS_MutexUnlock(&lock); } String output = s->res_output; if (s->res_status == StepStatus_Success) { SH_PrintF(Lit("[%F/%F] %F\n"), FmtI64(step_i), FmtI64(step_count), FmtStr(s->name)); } else if (s->res_status == StepStatus_Failure) { success = false; SH_PrintF(Lit("[%F/%F] %F\n\n%F\n\n"), FmtI64(step_i), FmtI64(step_count), FmtStr(s->name), FmtStr(output)); } s = s->next; skip_cont: continue; } } else { SH_Print(Lit("No work to do\n")); } if (!D_Exists(executable_file)) { /* Create blank executible if build fails (since Visual Studio can get * confused if no build target exists) */ D_ClearWrite(executable_file, Lit("")); } #if 0 if (!success) { Error(Lit("Build failed\n")); } #endif T_ShutdownWorkers(); D_WriteStoreToHistFile(&store, hist_path); }