power_play/build.c
2025-08-11 04:06:34 -05:00

1125 lines
42 KiB
C

#include "buildit.h"
typedef struct StepList StepList;
////////////////////////////////
//~ Globals
Bool force_rebuild = 0;
Arena perm = { 0 };
D_Store store = { 0 };
D_Hist hist = { 0 };
StepList *sl = { 0 };
/* Args */
String arg_outdir = Lit("build");
Bool arg_msvc = 0;
Bool arg_rtc = 0;
Bool arg_asan = 0;
Bool arg_crtlib = 0;
Bool arg_debinfo = 0;
Bool arg_developer = 0;
Bool arg_profiling = 0;
Bool arg_unoptimized = 0;
////////////////////////////////
//~ Utilities
void Error(String msg)
{
SH_PrintF(Lit("ERROR: %F\n"), FmtStr(msg));
}
Bool IsDirty(D_Tag tag)
{
Bool res = force_rebuild ? 1 : 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, 1);
return res;
}
void KillRunningProcesses(void)
{
SH_RunCommand(Lit("taskkill /im PowerPlay.exe /f /fi \"STATUS eq RUNNING\""), 1);
}
////////////////////////////////
//~ Steplist operations
typedef struct StepList StepList;
typedef I32 StepStatus; enum
{
StepStatus_None,
StepStatus_Success,
StepStatus_Failure,
StepStatus_Skipped
};
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);
}
////////////////////////////////
//~ Output cleaning operations
/* 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 step operations
typedef struct BuildStepSimpleCommandArg BuildStepSimpleCommandArg;
struct BuildStepSimpleCommandArg
{
String cmd;
Atomic32 *skip_flag;
Atomic32 *failure_flag;
D_Tag delete_file_on_failure;
D_Tag dxc_depfile_dependent;
D_Tag dxc_depfile;
};
typedef struct BuildStepMsvcCompileCommandArg BuildStepMsvcCompileCommandArg;
struct BuildStepMsvcCompileCommandArg
{
String cmd;
Atomic32 *skip_flag;
Atomic32 *failure_flag;
D_Tag delete_file_on_failure;
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;
Atomic32 *skip_flag = arg->skip_flag;
Atomic32 *failure_flag = arg->failure_flag;
if (!skip_flag || !Atomic32Fetch(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 (!D_IsNil(arg->dxc_depfile))
{
String depfile_data = D_DepfileTextFromDxcOutput(scratch.arena, arg->dxc_depfile_dependent, result.output);
D_ClearWrite(arg->dxc_depfile, depfile_data);
}
if (result.error)
{
if (failure_flag)
{
Atomic32FetchSet(failure_flag, 1);
}
if (!D_IsNil(arg->delete_file_on_failure))
{
D_Delete(arg->delete_file_on_failure);
}
}
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)
{
Atomic32FetchSet(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;
Atomic32 *skip_flag = arg->skip_flag;
Atomic32 *failure_flag = arg->failure_flag;
if (!skip_flag || !Atomic32Fetch(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)
{
if (failure_flag)
{
Atomic32FetchSet(failure_flag, 1);
}
if (!D_IsNil(arg->delete_file_on_failure))
{
D_Delete(arg->delete_file_on_failure);
}
}
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)
{
Atomic32FetchSet(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 I32 ArgState; enum
{
ArgState_None,
ArgState_OutputDir
};
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 = 0;
if (StringEqual(arg, Lit("-msvc"))) arg_msvc = 1;
if (StringEqual(arg, Lit("-rtc"))) arg_rtc = 1;
if (StringEqual(arg, Lit("-asan"))) arg_asan = 1;
if (StringEqual(arg, Lit("-crtlib"))) arg_crtlib = 1;
if (StringEqual(arg, Lit("-debinfo"))) arg_debinfo = 1;
if (StringEqual(arg, Lit("-developer"))) arg_developer = 1;
if (StringEqual(arg, Lit("-profiling"))) arg_profiling = 1;
if (StringEqual(arg, Lit("-unoptimized"))) arg_unoptimized = 1;
} 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_dxc_dir_path = OS_GetAbsPath(&perm, StringF(&perm, Lit("%F/dxc/"), 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_dxc_dir_path))
{
OS_CreateDirAtAbsPath(out_dxc_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 dxc_dir = D_TagFromPath(&perm, out_dxc_dir_path, D_TagKind_Dir);
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/inc_core.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
String dxc_args_format = { 0 };
StringList c_compile_args = { 0 };
StringList 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 };
StringList dxc_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, &rc_compile_args, Lit("rc /fo\"%F\" \"%F\""));
StringListAppend(&perm, &link_args, Lit("link.exe /nologo %F /OUT:\"%F\" /OPT:REF /OPT:ICF"));
String warnings = Lit("/WX /Wall "
"/options:strict "
"/wd4820 /wd4201 /wd5220 /wd4514 /wd4244 /wd5045 /wd4242 /wd4061 /wd4189 /wd4723 /wd5246 /wd4324 /wd4464 /wd4577 /wd4100");
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, &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-assign-enum -Wno-switch-default "
"-Wno-reserved-identifier -Wno-reserved-macro-identifier "
"-Wno-missing-designated-field-initializers "
"-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);
}
//- Dxc
{
StringListAppend(&perm, &dxc_compile_args, Lit("-I src/ -H -WX -Ges"));
String dxc_args_define = StringFromStringList(&perm, Lit(" "), dxc_compile_args);
StringListAppend(&perm, &compile_args, StringF(&perm, Lit("-DDXC_ARGS=\"%F\""), FmtStr(dxc_args_define)));
}
//- Rtc
if (arg_rtc)
{
if (!arg_crtlib)
{
Error(Lit("CrtlibIsEnabled (C runtime library) Must be enabled when compiling with RtcIsEnabled (runtime checks)"));
OS_Exit(1);
}
StringListAppend(&perm, &compile_args, Lit("-DRtcIsEnabled=1"));
if (arg_msvc)
{
if (!arg_asan)
{
/* Enable /RtcIsEnabled option (not compatible with AsanIsEnabled) */
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("-DCrtlibIsEnabled=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("-DUnoptimizedIsEnabled=1"));
if (arg_msvc)
{
StringListAppend(&perm, &compile_args, Lit("/Od"));
}
else
{
StringListAppend(&perm, &compile_and_link_args, Lit("-O0"));
}
StringListAppend(&perm, &dxc_compile_args, Lit("-Od"));
}
else
{
if (arg_msvc)
{
StringListAppend(&perm, &compile_args, Lit("/O2 /GL "));
StringListAppend(&perm, &link_args, Lit("/LTCG"));
}
else
{
StringListAppend(&perm, &compile_and_link_args, Lit("-O3 -flto"));
}
StringListAppend(&perm, &dxc_compile_args, Lit("-O3"));
}
//- Debug info
if (arg_debinfo)
{
StringListAppend(&perm, &compile_args, Lit("-DDebinfoEnabled=1"));
if (arg_msvc)
{
StringListAppend(&perm, &compile_args, Lit("/JMC /Zi"));
StringListAppend(&perm, &link_args, Lit("/DEBUG:FULL"));
}
else
{
StringListAppend(&perm, &compile_and_link_args, Lit("-g"));
}
StringListAppend(&perm, &dxc_compile_args, Lit("-Zi -Qembed_debug"));
}
//- Address sanitizer
if (arg_asan)
{
if (!arg_crtlib)
{
Error(Lit("CrtlibIsEnabled (C runtime library) Must be enabled when compiling with asan enabled"));
OS_Exit(1);
}
StringListAppend(&perm, &compile_args, Lit("-DAsanIsEnabled=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("-DDeveloperIsEnabled=1"));
}
//- Profiling
if (arg_profiling)
{
if (!arg_crtlib)
{
Error(Lit("CrtlibIsEnabled (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("-DProfilingIsEnabled=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("-DTracyClientHeaderPath=\\\"%F\\\""), FmtStr(tracy_client_header_path)));
StringListAppend(&perm, &compile_args, StringF(&perm, Lit("-DTracyClientSrcPath=\\\"%F\\\""), FmtStr(tracy_client_src_path)));
}
//- Incbin
if (!arg_msvc)
{
String incbin_dir = StringReplace(&perm, out_inc_dir_path, Lit("\\"), Lit("/"));
StringListAppend(&perm, &compile_args, StringF(&perm, Lit("-DIncbinRawDir=\"%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 = 1;
String data = StringFromStruct(&build_hash);
D_ClearWrite(build_hash_file, data);
}
}
////////////////////////////////
//~ Build steps
hist = D_HistFromPath(&perm, hist_path);
D_TagList link_files = { 0 };
//- Build step: Compile shaders
Atomic32 shader_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 = { 0 };
D_GetDirContents(&perm, &src_files, src_dir, 1);
for (D_TagListNode *n = src_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_dir = file.kind == D_TagKind_Dir;
Bool is_rst = !is_dir && StringEqual(extension, Lit("rst"));
Bool is_knl = !is_dir && StringEqual(extension, Lit("knl"));
if (is_rst || is_knl)
{
D_TagListAppend(&perm, &src_input_files, file);
}
}
}
{
AddSyncPoint();
String dxc_args_bare = StringFromStringLists(&perm, Lit(" "), dxc_compile_args);
String dxc_compile_args_fmt = StringF(&perm, Lit("dxc %%F -E %%F -T %%F -Fo %%F %F"), FmtStr(dxc_args_bare));
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 name_no_extension = StringPathNoExtension(name);
String extension = StringPathExtension(name);
Bool is_rst = StringEqual(extension, Lit("rst"));
Bool is_knl = !is_rst && StringEqual(extension, Lit("knl"));
for (I32 kind = 0; kind < 3; ++kind)
{
String out_file_extension = { 0 };
String entry = { 0 };
String profile = { 0 };
if (kind == 0 && is_rst)
{
/* Vertex shader */
out_file_extension = Lit("vs");
entry = Lit("vs");
profile = Lit("vs_6_6");
}
else if (kind == 1 && is_rst)
{
/* Pixel shader */
out_file_extension = Lit("ps");
entry = Lit("ps");
profile = Lit("ps_6_6");
}
else if (kind == 2 && is_knl)
{
/* Compute shader */
out_file_extension = Lit("cs");
entry = Lit("cs");
profile = Lit("cs_6_6");
}
if (entry.len > 0)
{
D_Tag dep_file;
{
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 dxc_file;
{
String dxc_file_path = StringF(&perm, Lit("%F/%F.%F"), FmtStr(out_dxc_dir_path), FmtStr(name_no_extension), FmtStr(out_file_extension));
dxc_file = D_TagFromPath(&perm, dxc_file_path, D_TagKind_File);
}
D_AddDependency(&store, dxc_file, file);
D_AddDependency(&store, dxc_file, dep_file);
if (IsDirty(dxc_file))
{
String step_name = StringF(&perm, Lit("%F -> %F"), FmtStr(name), FmtStr(D_GetName(dxc_file)));
{
BuildStepSimpleCommandArg *bs_arg = ArenaPush(&perm, BuildStepSimpleCommandArg);
bs_arg->cmd = StringF(&perm, dxc_compile_args_fmt, FmtStr(file.full_path), FmtStr(entry), FmtStr(profile), FmtStr(dxc_file.full_path));
bs_arg->failure_flag = &shader_success_flag;
bs_arg->delete_file_on_failure = dxc_file;
bs_arg->dxc_depfile_dependent = dxc_file;
bs_arg->dxc_depfile = dep_file;
AddStep(step_name, &BuildStepSimpleCommand, bs_arg);
}
}
}
}
}
}
}
//- Build step: Tar archives
Atomic32 tar_success_flag = { 0 };
{
AddSyncPoint();
D_TagList tar_input_dirs = { 0 };
D_TagListAppend(&perm, &tar_input_dirs, dxc_dir);
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;
bs_arg->skip_flag = &shader_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
Atomic32 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_Tag dxc_tar_file = D_TagFromPath(&perm, StringF(&perm, Lit("%F/%F.tar"), FmtStr(out_inc_dir_path), FmtStr(D_GetName(dxc_dir))), D_TagKind_File);
D_AddDependency(&store, rc_input_file, icon_file);
D_AddDependency(&store, rc_input_file, dxc_tar_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))));
D_AppendWrite(rc_input_file, StringF(&perm, Lit("%F %F DISCARDABLE %F\n"), FmtStr(D_GetName(dxc_tar_file)), FmtStr(Lit("RCDATA")), FmtStr(D_GetName(dxc_tar_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 src files
Atomic32 src_success_flag = { 0 };
{
/* src_input_files will contain .c & .cpp files with names that match their parent directory */
D_TagList src_input_files = { 0 };
{
D_Tag top_level_dir = D_TagFromPath(&perm, Lit("src"), D_TagKind_Dir);
D_TagList top_level_files = { 0 };
D_GetDirContents(&perm, &top_level_files, top_level_dir, 0);
for (D_TagListNode *top_level_node = top_level_files.first; top_level_node; top_level_node = top_level_node->next)
{
D_Tag top_level_item = top_level_node->tag;
String top_level_item_name = D_GetName(top_level_item);
if (top_level_item.kind == D_TagKind_Dir)
{
D_TagList sub_files = { 0 };
D_GetDirContents(&perm, &sub_files, top_level_item, 0);
for (D_TagListNode *n = sub_files.first; n; n = n->next)
{
D_Tag file = n->tag;
String name = D_GetName(file);
String extension = StringPathExtension(name);
String name_no_extension = StringPathNoExtension(name);
if (StringEqual(name_no_extension, top_level_item_name))
{
Bool is_c = StringEqual(extension, Lit("c"));
Bool is_cpp = !is_c && StringEqual(extension, Lit("cpp"));
if (is_c || is_cpp)
{
D_TagListAppend(&perm, &src_input_files, file);
}
}
}
}
}
}
/* Build src files */
{
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)));
D_TagList depfile_force_includes = { 0 };
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 = &rc_success_flag;
bs_arg->failure_flag = &src_success_flag;
bs_arg->delete_file_on_failure = obj_file;
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 = &rc_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;
bs_arg->delete_file_on_failure = executable_file;
String step_name = Lit("Link");
AddStep(step_name, &BuildStepSimpleCommand, bs_arg);
}
}
////////////////////////////////
//~ Execute build steps
Bool success = 1;
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 = 0;
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 (success)
{
T_ShutdownWorkers();
D_WriteStoreToHistFile(&store, hist_path);
}
else
{
D_WriteStoreToHistFile(&store, hist_path);
Error(Lit("Build failed\n"));
OS_Exit(1);
}
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(""));
}
}