Last active
January 8, 2026 07:04
-
-
Save namandixit/e6baf57058adf4b5e316568b61b71f02 to your computer and use it in GitHub Desktop.
Build System Facilitator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Creator: Naman Dixit | |
| * Notice: © Copyright 2024 Naman Dixit | |
| * License: BSD Zero Clause License | |
| * SPDX: 0BSD (https://spdx.org/licenses/0BSD.html) | |
| */ | |
| /* Example Commands to compile a build.c that includes this file: | |
| * * clang --std=c23 -Iassets\code\ -Iprovisions\sources -ferror-limit=500 build.c -o build.exe -Lprovisions\binaries\win64 -lSDL3 | |
| * * cl /TC /std:clatest /Iassets\code\ /Iprovisions\sources build.c /Febuild.exe /LIBPATH:provisions\binaries\win64 SDL3.lib | |
| */ | |
| #pragma once | |
| #include "std.h" | |
| #include "stdos.h" | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wcovered-switch-default\""); | |
| pragma_clang("clang diagnostic ignored \"-Wunsafe-buffer-usage\""); | |
| pragma_clang("clang diagnostic ignored \"-Wpadded\""); | |
| #if defined(ENV_LANG_C) | |
| pragma_clang("clang diagnostic ignored \"-Wpre-c23-compat\""); | |
| pragma_clang("clang diagnostic ignored \"-Wdeclaration-after-statement\""); | |
| #elif defined(ENV_LANG_CXX) | |
| pragma_clang("clang diagnostic ignored \"-Wc++98-compat-pedantic\""); | |
| pragma_clang("clang diagnostic ignored \"-Wmissing-field-initializers\""); | |
| #endif | |
| pragma_msvc("warning ( push )"); | |
| #if defined(ENV_LANG_C) | |
| #elif defined(ENV_LANG_CXX) | |
| pragma_msvc("warning ( disable: 4127 )"); // conditional expression is constant | |
| pragma_msvc("warning ( disable: 4820 )"); // Padding | |
| pragma_msvc("warning ( disable: 6386 )"); // Analyze: WRITE_OVERRUN | |
| pragma_msvc("warning ( disable: 6011 )"); // Analyze: DEREF_NULL_PTR | |
| #endif | |
| typedef enum Build_Platform { | |
| Build_Platform_UNDEFINED, | |
| Build_Platform_WIN64, | |
| // Build_Compiler_WOA64, | |
| } Build_Platform; | |
| typedef enum Build_Compiler { | |
| Build_Compiler_UNDEFINED, | |
| Build_Compiler_MSVC, | |
| Build_Compiler_CLANG, | |
| Build_Compiler_COUNT, | |
| } Build_Compiler; | |
| typedef enum Build_Target_Type { | |
| Build_Target_Type_EXE, | |
| Build_Target_Type_DLL, | |
| } Build_Target_Type; | |
| typedef struct Build_Space { | |
| Build_Platform host; | |
| Build_Platform target; | |
| Build_Compiler compiler; | |
| const Path *project_dir; | |
| Memory *parent_memory; | |
| Memory_Push mempush; | |
| Txt_Fmt_Pool tfp; | |
| RNG_State rng_state; | |
| Uint64 tick_rate; | |
| Uint64 tick_begin; | |
| Uint64 tick_end; | |
| Bool print_commands; | |
| } Build_Space; | |
| typedef struct Build_Metadata { | |
| const Char *uuid; | |
| const Char *time; | |
| } Build_Metadata; | |
| #define BUILD_CONFIG_ADD_INCLUDE_DIRECTORY_FN(name) struct Build_Config* name (struct Build_Config *config, const Path *directory) | |
| #define BUILD_CONFIG_ADD_LIBRARY_DIRECTORY_FN(name) struct Build_Config* name (struct Build_Config *config, const Path *directory) | |
| #define BUILD_CONFIG_ADD_LIBRARY_FN(name) struct Build_Config* name (struct Build_Config *config, const Char *library) | |
| #define BUILD_CONFIG_ADD_USER_LIBRARY_FN(name) struct Build_Config* name (struct Build_Config *config, const Char *library) | |
| #define BUILD_CONFIG_WARNINGS_ARE_ERRORS_FN(name) struct Build_Config* name (struct Build_Config *config, Bool warnings_are_errors) | |
| #define BUILD_CONFIG_ENABLE_WARNINGS_FN(name) struct Build_Config* name (struct Build_Config *config, Bool enable_warnings) | |
| #define BUILD_CONFIG_DISABLE_WARNING_FN(name) struct Build_Config* name (struct Build_Config *config, Build_Compiler compiler, const Char *warning) | |
| #define BUILD_CONFIG_DEFINE_FN(name) struct Build_Config* name (struct Build_Config *config, const Char *macro, const Char *value) | |
| #define BUILD_CONFIG_SET_TARGET_TYPE_FN(name) struct Build_Config* name (struct Build_Config *config, Build_Target_Type target_type) | |
| typedef struct Build_Config { | |
| Build_Space *space; | |
| const Char *name; | |
| const Path *target_dir; | |
| Bool internal; | |
| Bool slow; | |
| Bool sanitizers; | |
| Bool allow_hotloading; | |
| Bool debug_information; | |
| Bool enable_optimizations; | |
| Paths include_dirs; | |
| Paths library_dirs; | |
| segarOf(const Char *) libraries; | |
| segarOf(const Char *) define_keys; | |
| segarOf(const Char *) define_vals; | |
| Bool enable_warnings; | |
| Bool warnings_are_errors; | |
| segarOf(const Char *) disabled_warnings[Build_Compiler_COUNT]; | |
| Build_Target_Type target_type; | |
| Bool windows_console_subsystem; | |
| Build_Metadata meta; | |
| BUILD_CONFIG_ADD_INCLUDE_DIRECTORY_FN((*AddIncludeDirectory)); | |
| BUILD_CONFIG_ADD_LIBRARY_DIRECTORY_FN((*AddLibraryDirectory)); | |
| BUILD_CONFIG_ADD_LIBRARY_FN((*AddLibrary)); | |
| BUILD_CONFIG_ADD_USER_LIBRARY_FN((*AddUserLibrary)); | |
| BUILD_CONFIG_WARNINGS_ARE_ERRORS_FN((*WarningsAreErrors)); | |
| BUILD_CONFIG_ENABLE_WARNINGS_FN((*EnableWarnings)); | |
| BUILD_CONFIG_DISABLE_WARNING_FN((*DisableWarning)); | |
| BUILD_CONFIG_DEFINE_FN((*Define)); | |
| BUILD_CONFIG_SET_TARGET_TYPE_FN((*SetTargetType)); | |
| } Build_Config; | |
| typedef enum Build_Lang { | |
| Build_Lang_C, | |
| Build_Lang_CXX, | |
| } Build_Lang; | |
| typedef struct Build_Src { | |
| const Path *filepath; | |
| Build_Lang lang; | |
| Uint32 lang_version; | |
| } Build_Src; | |
| typedef struct Build_Obj { | |
| const Path *filepath; | |
| } Build_Obj; | |
| typedef struct Build_Lock { | |
| const Path *path; | |
| } Build_Lock; | |
| typedef struct Build_Space_Create_Params { Build_Platform host, target; Build_Compiler compiler; Bool print_commands; } Build_Space_Create_Params; | |
| Build_Space* buildSpace_Create (const Path *project_dir, Memory *mem, Build_Space_Create_Params params); | |
| #define buildSpaceCreate(_pd, _mem, ...) buildSpace_Create((_pd), (_mem), compound_init(Build_Space_Create_Params, {__VA_ARGS__})) | |
| void buildSpaceDelete (Build_Space *bs); | |
| typedef struct Build_Config_Create_Params { Bool internal, slow, sanitizers, allow_hotloading, console_app; } Build_Config_Create_Params; | |
| Build_Config* buildConfig_Create (Build_Space *bs, const Char *name, const Path *target_dir, Build_Config_Create_Params params); | |
| #define buildConfigCreate(_bs, _n, _td, ...) buildConfig_Create((_bs), (_n), (_td), compound_init(Build_Config_Create_Params, {__VA_ARGS__})) | |
| const Char* buildTargetName (Build_Space *bs); | |
| #define buildPathInProject(_bs, ...) pathJoin(&(_bs)->tfp, &(_bs)->mempush, (_bs)->project_dir, __VA_ARGS__) | |
| typedef struct Build_Compile_Params { Bool print_include_tree; } Build_Compile_Params; | |
| Build_Obj build_Compile (Build_Config *config, Build_Src src, Build_Compile_Params params); | |
| #define buildCompile(_c, _s, ...) build_Compile((_c), (_s), compound_init(Build_Compile_Params, {__VA_ARGS__})) | |
| typedef struct Build_Link_Params { Byte _placeholder; } Build_Link_Params; | |
| const Path* build_Link (Build_Config *config, Size obj_count, Build_Obj *obj, Build_Link_Params params); | |
| #define buildLink(_c, _oc, _os, ...) build_Link((_c), (_oc), (_os), compound_init(Build_Link_Params, {__VA_ARGS__})) | |
| Build_Src buildSrcMake (const Path *srcpath, Build_Lang lang, Uint32 lang_version); | |
| Build_Obj buildObjMake (Build_Space *bs, const Path *path); | |
| #if defined(SDL_h_) | |
| Build_Space* buildSpace_Create (const Path *project_dir, Memory *mem, Build_Space_Create_Params params) | |
| { | |
| if (params.host == Build_Platform_UNDEFINED) { | |
| #if defined(_WIN32) && (defined(_M_X64) || defined(__x86_64__)) | |
| params.host = Build_Platform_WIN64; | |
| #else | |
| # error Host can't be determined for the platform | |
| #endif | |
| } | |
| if (params.target == Build_Platform_UNDEFINED) { | |
| params.target = params.host; | |
| } | |
| if (params.compiler == Build_Compiler_UNDEFINED) { | |
| #if defined(_WIN32) | |
| params.compiler = Build_Compiler_MSVC; | |
| #else | |
| # error Compiler can't be determined for the platform | |
| #endif | |
| } | |
| Memory_Push mempush; | |
| memPushCreate(&mempush, mem, MiB(1u)); | |
| Build_Space *bs = cast_ptr(Build_Space *, memPushAllot(&mempush, sizeof(*bs))); | |
| bs->parent_memory = mem; | |
| bs->mempush = mempush; | |
| txtfmtPoolCreate(&bs->tfp, bs->parent_memory, 2 * HUNDRED * THOUSAND); | |
| bs->host = params.host; | |
| bs->target = params.target; | |
| bs->compiler = params.compiler; | |
| bs->project_dir = project_dir; | |
| bs->rng_state = rngMake(osGetTime(true).nanosecond); | |
| bs->tick_rate = osTickGetRate(); | |
| bs->tick_begin = osTickCounter(); | |
| bs->print_commands = params.print_commands; | |
| osLogOut("Entering directory `%s'", pathGetStr(bs->project_dir)); | |
| return bs; | |
| } | |
| void buildSpaceDelete (Build_Space *bs) | |
| { | |
| { | |
| bs->tick_end = osTickCounter(); | |
| double perf_counter_elapsed = cast_val(double, bs->tick_end - bs->tick_begin); | |
| double milliseconds = (1000.0 * perf_counter_elapsed) / cast_val(double, bs->tick_rate); | |
| osLogOut("Time taken for the build executable: %f ms", milliseconds/1000); | |
| } | |
| txtfmtPoolDelete(&bs->tfp); | |
| Memory_Push mempush = bs->mempush; | |
| memPushDelete(&mempush); | |
| } | |
| header_function | |
| BUILD_CONFIG_ADD_INCLUDE_DIRECTORY_FN(buildConfigAddIncludeDirectory) | |
| { | |
| osAssert(directory != nullptr, "Directory name shouldn't be nullptr"); | |
| segarAdd(&config->include_dirs, directory); | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_ADD_LIBRARY_DIRECTORY_FN(buildConfigAddLibraryDirectory) | |
| { | |
| osAssert(directory != nullptr, "Directory name shouldn't be nullptr"); | |
| segarAdd(&config->library_dirs, directory); | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_ADD_LIBRARY_FN(buildConfigAddLibrary) | |
| { | |
| osAssert(library != nullptr, "Library name shouldn't be nullptr"); | |
| segarAdd(&config->libraries, cast_const(const Char*, txtStr(txtNew(&config->space->mempush, library)))); | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_ADD_USER_LIBRARY_FN(buildConfigAddUserLibrary) | |
| { | |
| Build_Space *bs = config->space; | |
| buildConfigAddLibrary(config, library); | |
| #if defined(ENV_OS_WINDOWS) | |
| const Char *dll_name = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.dll", library)); | |
| #else | |
| # error Libraries can't be copied for current platform | |
| #endif | |
| #if defined(ENV_OS_WINDOWS) | |
| const Char *dbg_name = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.pdb", library)); | |
| #else | |
| const Char *dbg_name = nullptr; | |
| #endif | |
| Bool file_copied = false; | |
| for (Uint32 i = 0; !file_copied && (i < segarLen(&config->library_dirs)); i++) { | |
| const Path *src_dll_dir = segarGet(&config->library_dirs, i); | |
| const Path *dst_dll_dir = config->target_dir; | |
| const Path *src_dll_path = pathJoin(&bs->tfp, &bs->mempush, src_dll_dir, dll_name); | |
| const Path *dst_dll_path = pathJoin(&bs->tfp, &bs->mempush, dst_dll_dir, dll_name); | |
| if (!osDoesPathExists(src_dll_path)) continue; | |
| if (osPathGetChangedTime(src_dll_path) <= osPathGetChangedTime(dst_dll_path)) { // Source is not newer (might be old or of same age) | |
| file_copied = true; // Not needed | |
| break; | |
| } | |
| osLogOut("- Copying library: %s", library); | |
| if (osPathCopy(&bs->tfp, &bs->mempush, src_dll_path, dst_dll_path)) { | |
| if (dbg_name) { | |
| const Path *src_dbg_path = pathJoin(&bs->tfp, &bs->mempush, src_dll_dir, dbg_name); | |
| const Path *dst_dbg_path = pathJoin(&bs->tfp, &bs->mempush, dst_dll_dir, dbg_name); | |
| if (!osPathCopy(&bs->tfp, &bs->mempush, src_dbg_path, dst_dbg_path)) { | |
| osLogOut("Failed to copy debug symbols for library %s", dll_name); | |
| } | |
| } | |
| file_copied = true; | |
| break; | |
| } | |
| } | |
| osAssert(file_copied || config->allow_hotloading, "%s couldn't be copied from any library paths", library); | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_WARNINGS_ARE_ERRORS_FN(buildConfigWarningsAreErrors) | |
| { | |
| config->warnings_are_errors = warnings_are_errors; | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_ENABLE_WARNINGS_FN(buildConfigEnableWarnings) | |
| { | |
| config->enable_warnings = enable_warnings; | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_DISABLE_WARNING_FN(buildConfigDisableWarning) | |
| { | |
| osAssert(warning != nullptr, "Warning shouldn't be nullptr"); | |
| osAssert(compiler != Build_Compiler_UNDEFINED, "Compiler shouldn't be invalid when disabling warning"); | |
| segarAdd(&config->disabled_warnings[compiler], warning); | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_DEFINE_FN(buildConfigDefine) | |
| { | |
| osAssert(macro != nullptr, "Define macro name shouldn't be nullptr"); | |
| segarAdd(&config->define_keys, macro); | |
| segarAdd(&config->define_vals, value ? value : "1"); | |
| return config; | |
| } | |
| header_function | |
| BUILD_CONFIG_SET_TARGET_TYPE_FN(buildConfigSetTargetType) | |
| { | |
| config->target_type = target_type; | |
| return config; | |
| } | |
| header_function | |
| Build_Metadata buildMetadata_Init (Build_Config *config) | |
| { | |
| Build_Space *bs = config->space; | |
| // TODO(naman): Replace this with MAC address based UUID? Specially if SDL adds mac retrieval. | |
| // Current implementation swiped from https://gist.github.com/jrus/3197011 | |
| const Char *uuid = "00000000-0000-4000-8000-000000000000"; { | |
| Char uuid_array[] = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"; | |
| Char hex_digits[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; | |
| for (Size i = 0; i < elemin(uuid_array); i++) { | |
| switch (uuid_array[i]) { | |
| case 'x': uuid_array[i] = hex_digits[rngGet(&bs->rng_state) & 0xFull]; break; | |
| case 'y': uuid_array[i] = hex_digits[rngGet(&bs->rng_state) & 0x3ull + 0x8ull]; break; | |
| default: break; | |
| } | |
| } | |
| uuid = txtStr(txtNew(&bs->mempush, uuid_array)); | |
| } | |
| OS_Time t = osGetTime(true); | |
| const Char *time = "0000-00-00-00:00:00.000000000T+0.00"; { // Calculate time | |
| time = txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "%04hu-%02hhu-%02hhu-%02hhu:%02hhu:%02hhu.%09uT%+.2f", | |
| t.year, t.month, t.day, t.hour, t.minute, t.second, t.nanosecond, | |
| cast_val(Float64, t.utc_offset)/(60*60))); | |
| } | |
| buildConfigDefine(config, "BUILD_UUID", txtStr(txtNew(&bs->mempush, txtStr(txtFormat(&bs->tfp, &bs->mempush, "\"%s\"", uuid))))); | |
| buildConfigDefine(config, "BUILD_TIME", txtStr(txtNew(&bs->mempush, txtStr(txtFormat(&bs->tfp, &bs->mempush, "\"%s\"", time))))); | |
| Build_Metadata meta = {.uuid = uuid, .time = time}; | |
| return meta; | |
| } | |
| Build_Config* buildConfig_Create (Build_Space *bs, const Char *name, const Path *target_dir, Build_Config_Create_Params params) | |
| { | |
| osAssert(bs != nullptr, "Build_Space shouldn't be NULL"); | |
| osAssert(name != nullptr, "Build config name shouldn't be NULL"); | |
| osAssert(target_dir != nullptr, "Build config's target directory shouldn't be NULL"); | |
| Build_Config *c = cast_ptr(Build_Config *, memPushAllot(&bs->mempush, sizeof(*c))); | |
| c->space = bs; | |
| c->name = txtStr(txtNew(&bs->mempush, name)); | |
| c->target_dir = target_dir; | |
| c->internal = params.internal; | |
| c->slow = params.slow; | |
| c->sanitizers = params.sanitizers; | |
| c->allow_hotloading = params.allow_hotloading; | |
| c->windows_console_subsystem = params.console_app; | |
| c->debug_information = params.internal; | |
| c->enable_optimizations = !params.slow; | |
| c->enable_warnings = true; | |
| c->warnings_are_errors = false; | |
| c->target_type = Build_Target_Type_EXE; | |
| c->AddIncludeDirectory = &buildConfigAddIncludeDirectory; | |
| c->AddLibraryDirectory = &buildConfigAddLibraryDirectory; | |
| c->AddLibrary = &buildConfigAddLibrary; | |
| c->AddUserLibrary = &buildConfigAddUserLibrary; | |
| c->WarningsAreErrors = &buildConfigWarningsAreErrors; | |
| c->EnableWarnings = &buildConfigEnableWarnings; | |
| c->DisableWarning = &buildConfigDisableWarning; | |
| c->Define = &buildConfigDefine; | |
| c->SetTargetType = &buildConfigSetTargetType; | |
| c->include_dirs = segarMake(&bs->mempush); | |
| c->library_dirs = segarMake(&bs->mempush); | |
| c->libraries = segarMake(&bs->mempush); | |
| c->define_keys = segarMake(&bs->mempush); | |
| c->define_vals = segarMake(&bs->mempush); | |
| for (Uint i = 0; i < Build_Compiler_COUNT; i++) { | |
| c->disabled_warnings[i] = segarMake(&bs->mempush); | |
| } | |
| if (params.internal) { | |
| c->Define(c, "BUILD_INTERNAL", nullptr); | |
| } | |
| if (params.slow) { | |
| c->Define(c, "BUILD_SLOW", nullptr); | |
| } | |
| if (params.sanitizers) { | |
| c->Define(c, "BUILD_SANITIZERS", nullptr); | |
| } | |
| c->meta = buildMetadata_Init(c); | |
| return c; | |
| } | |
| const Char* buildTargetName (Build_Space *bs) | |
| { | |
| switch (bs->target) { | |
| case Build_Platform_WIN64: return "win64"; | |
| case Build_Platform_UNDEFINED: default: debugUnreachable(); break; | |
| } | |
| return "unknown"; | |
| } | |
| header_function | |
| const Path* buildPath_OfSanitizerDLL (Build_Config *config) | |
| { | |
| switch (config->space->compiler) { | |
| case Build_Compiler_MSVC: { | |
| const Char *tools_dir_env_val = osEnvironmentVariableGet("VCToolsInstallDir"); | |
| osAssert(tools_dir_env_val != nullptr, "Environment variable 'VCToolsInstallDir' not set"); | |
| const Path *bin_dir = pathCreate(&config->space->mempush, tools_dir_env_val); | |
| osAssert(bin_dir != nullptr, "Couldn't get MSVC install path to copy sanitizer DLL"); | |
| switch (config->space->target) { | |
| case Build_Platform_WIN64: { | |
| const Path *path = pathJoin(&config->space->tfp, &config->space->mempush, | |
| bin_dir, "bin/Hostx64/x64/clang_rt.asan_dbg_dynamic-x86_64.dll"); | |
| return path; | |
| } break; | |
| case Build_Platform_UNDEFINED: default: debugBreak(); break; | |
| } | |
| } break; | |
| case Build_Compiler_CLANG: { | |
| OS_Command cmd = osCommandInit(&config->space->mempush); | |
| osCommandAppend(&cmd, "clang"); | |
| osCommandAppend(&cmd, "-print-file-name=clang_rt.asan_dynamic-x86_64.dll"); | |
| Txt output; | |
| osAssert(osCommandExecute(&cmd, &config->space->mempush, &output) == 0, | |
| "Couldn't create process to get Clang ASAN DLL path"); | |
| osAssert(txtLen(output) != 0, "Couldn't get Clang ASAN DLL path"); | |
| // Remove trailing newline | |
| if (output.str[txtLen(output) - 1] == '\n') { | |
| output.str[txtLen(output) - 1] = '\0'; | |
| } | |
| const Path *path = pathCreate(&config->space->mempush, txtStr(output)); | |
| return path; | |
| } break; | |
| case Build_Compiler_UNDEFINED: case Build_Compiler_COUNT: default: debugBreak(); break; | |
| } | |
| debugBreak(); | |
| return nullptr; | |
| } | |
| header_function | |
| int buildSanitizer_Enable (Build_Config *config, OS_Command *cmd) | |
| { | |
| int result = 0; | |
| if (config->sanitizers) { | |
| switch (config->space->compiler) { | |
| case Build_Compiler_MSVC: { | |
| osCommandAppend(cmd, "/fsanitize=address"); | |
| } break; | |
| case Build_Compiler_CLANG: { | |
| osCommandAppend(cmd, "-fsanitize=address"); | |
| osCommandAppend(cmd, "-fsanitize=undefined"); | |
| osCommandAppend(cmd, "-fsanitize=integer"); | |
| osCommandAppend(cmd, "-fno-sanitize=unsigned-shift-base"); // Prevents left shifting 0xFFFF.... kind of numbers | |
| osCommandAppend(cmd, "-fno-sanitize=unsigned-integer-overflow"); // Perfectly well-defined, needed for loops | |
| // TODO(naman): thread, memory, safe-stack and type may be available on Linux, etc. | |
| } break; | |
| case Build_Compiler_UNDEFINED: case Build_Compiler_COUNT: default: debugBreak(); break; | |
| } | |
| const Path* src_path = buildPath_OfSanitizerDLL(config); | |
| const Path *dst_path = pathJoin(&config->space->tfp, &config->space->mempush, | |
| config->target_dir, "clang_rt.asan_dynamic-x86_64.dll"); | |
| if (osPathGetChangedTime(src_path) > osPathGetChangedTime(dst_path)) { | |
| osLogOut("- Copying library: address sanitizer"); | |
| osAssert(osPathCopy(&config->space->tfp, &config->space->mempush, | |
| src_path, dst_path), "ASAN DLL Copy failed"); | |
| } | |
| } | |
| return result; | |
| } | |
| Build_Obj build_Compile (Build_Config *config, Build_Src src, Build_Compile_Params params) | |
| { | |
| Build_Space *bs = config->space; | |
| OS_Command cmd = osCommandInit(&config->space->mempush); | |
| Build_Obj obj = valzero(typeof(obj)); | |
| switch (bs->compiler) { | |
| case Build_Compiler_MSVC: { | |
| osCommandAppend(&cmd, "cl"); | |
| osCommandAppend(&cmd, "/nologo"); | |
| osCommandAppend(&cmd, "/diagnostics:caret"); | |
| osCommandAppend(&cmd, "/Zc:preprocessor"); | |
| osCommandAppend(&cmd, "/EHsc"); | |
| // Better floating point determinism (use /fp:strict if we want to change rounding modes or trap floating-point exceptions) | |
| // Floating-point fused contractions (FMA, etc.) aren't generated from VS 2022 and later | |
| osCommandAppend(&cmd, "/fp:precise"); | |
| if (config->target_type == Build_Target_Type_DLL) { | |
| osCommandAppend(&cmd, "/LD"); | |
| } | |
| const Char *lang_flag = nullptr; | |
| if (src.lang == Build_Lang_C) { | |
| osCommandAppend(&cmd, "/TC"); | |
| lang_flag = "/std:c"; | |
| } else if (src.lang == Build_Lang_CXX) { | |
| osCommandAppend(&cmd, "/TP"); | |
| lang_flag = "/std:c++"; | |
| osCommandAppend(&cmd, "/Zc:__cplusplus"); | |
| } | |
| lang_flag = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s%u", lang_flag, src.lang_version)); | |
| // FIXME(naman): Remove this special case when MSVC adds proper support for C23 (might come with C++26) | |
| if ((src.lang == Build_Lang_C) && (src.lang_version == 23)) { | |
| lang_flag = "/std:clatest"; | |
| } | |
| osCommandAppend(&cmd, lang_flag); | |
| buildSanitizer_Enable(config, &cmd); | |
| osCommandAppend(&cmd, pathGetStr(src.filepath)); | |
| for (Uint32 i = 0; i < segarLen(&config->include_dirs); i++) { | |
| osCommandAppend(&cmd, "/I"); | |
| osCommandAppend(&cmd, pathGetStr(segarGet(&config->include_dirs, i))); | |
| } | |
| if (params.print_include_tree) { | |
| osCommandAppend(&cmd, "/showIncludes"); | |
| } | |
| osCommandAppend(&cmd, "/c"); // Compile without linking | |
| osCommandAppend(&cmd, "/presetPadding"); // Zero-initialize padding for stack based types | |
| const Char *objname = txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "%s.obj", txtStr(pathGetName(&bs->mempush, src.filepath)))); | |
| const Path *objpath = pathJoin(&bs->tfp, &bs->mempush, config->target_dir, config->name, objname); | |
| osAssert(osCreateDirectory(pathGetParent(&bs->mempush, objpath)), "Couldn't create folder for object file"); | |
| if (config->debug_information) { | |
| osCommandAppend(&cmd, "/Zi"); // Generate complete debugging information. | |
| // Machine code before linking (should be ignored) | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/Fd:%s.pdb", pathGetStr(objpath)))); | |
| } | |
| osCommandAppend(&cmd, "/FC"); // Full path of source code files passed to cl.exe in diagnostic text. | |
| if (config->enable_optimizations) { | |
| osCommandAppend(&cmd, "/O2"); | |
| osCommandAppend(&cmd, "/Oi"); | |
| } | |
| osCommandAppend(&cmd, "/utf-8"); // Set source and execution Character sets to UTF-8. | |
| osAssert(segarLen(&config->define_keys) == segarLen(&config->define_vals), | |
| "Number of macros and their values is not equal"); | |
| for (Uint32 i = 0; i < segarLen(&config->define_keys); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/D%s=%s", | |
| segarGet(&config->define_keys, i), | |
| segarGet(&config->define_vals, i)))); | |
| } | |
| if (config->target_type == Build_Target_Type_DLL) { | |
| osCommandAppend(&cmd, "/DBUILD_DLL"); | |
| } | |
| osCommandAppend(&cmd, "/GS-"); // Disable Buffer Security Check | |
| osCommandAppend(&cmd, "/jumptablerdata"); // Put switch case statement jump tables in the .rdata section. | |
| if (config->enable_warnings) { | |
| osCommandAppend(&cmd, "/Wall"); // Enable all warnings | |
| } | |
| if (config->warnings_are_errors) { | |
| osCommandAppend(&cmd, "/WX"); // Treat warnings as errors. | |
| } | |
| for (Uint32 i = 0; i < segarLen(&config->disabled_warnings[Build_Compiler_MSVC]); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/wd%s", | |
| segarGet(&config->disabled_warnings[Build_Compiler_MSVC], i)))); | |
| } | |
| if (config->enable_warnings) { | |
| osCommandAppend(&cmd, "/analyze:WX-"); // Enables code analysis, don't treat as error even with /WX | |
| osCommandAppend(&cmd, "/analyze:autolog-"); // disable logging to files | |
| } | |
| osCommandAppend(&cmd, "/Zc:gotoScope-"); // No error on skipping variable initialization with goto (unless destructor is not allowed) | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/Fo%s", pathGetStr(objpath)))); | |
| osAssert(osCommandExecute(&cmd, &bs->mempush, nullptr) == 0, "MSVC compile() failed"); | |
| obj = buildObjMake(bs, objpath); | |
| } break; | |
| case Build_Compiler_CLANG: { | |
| if (src.lang == Build_Lang_C) { | |
| osCommandAppend(&cmd, "clang"); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "--std=c%u", src.lang_version))); | |
| } else if (src.lang == Build_Lang_CXX) { | |
| osCommandAppend(&cmd, "clang++"); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "--std=c++%u", src.lang_version))); | |
| } | |
| osCommandAppend(&cmd, "-ferror-limit=100"); | |
| osCommandAppend(&cmd, "-fstack-usage"); // Outpur list of functions with each's stack usage | |
| osCommandAppend(&cmd, "-MD"); // Output a list of all dependencies | |
| osCommandAppend(&cmd, "-fno-omit-frame-pointer"); | |
| osCommandAppend(&cmd, "-ffp-eval-method=source"); // Use the floating-point type declared in the source as the evaluation method | |
| osCommandAppend(&cmd, "-ffp-contract=off"); // Don't form fused floating-point contractions, such as fused multiply-add (FMA) | |
| buildSanitizer_Enable(config, &cmd); | |
| osCommandAppend(&cmd, pathGetStr(src.filepath)); | |
| for (Uint32 i = 0; i < segarLen(&config->include_dirs); i++) { | |
| osCommandAppend(&cmd, "-I"); | |
| osCommandAppend(&cmd, pathGetStr(segarGet(&config->include_dirs, i))); | |
| } | |
| if (params.print_include_tree) { | |
| osCommandAppend(&cmd, "-H"); | |
| } | |
| osCommandAppend(&cmd, "-c"); // Compile without linking | |
| if (config->debug_information) { | |
| osCommandAppend(&cmd, "-g3"); // Generate complete debugging information. | |
| } | |
| if (config->enable_optimizations) { | |
| osCommandAppend(&cmd, "-O2"); | |
| osCommandAppend(&cmd, "-msse2"); | |
| osCommandAppend(&cmd, "-fdata-sections"); osCommandAppend(&cmd, "-ffunction-sections"); // CFLAG for removing unreferenced globals/functions | |
| osCommandAppend(&cmd, "-fwrapv"); osCommandAppend(&cmd, "-fno-strict-aliasing"); // Prevent unsafe optimizations | |
| osCommandAppend(&cmd, "-fno-delete-null-pointer-checks"); | |
| } | |
| osAssert(segarLen(&config->define_keys) == segarLen(&config->define_vals), | |
| "Number of macros and their values is not equal"); | |
| for (Uint32 i = 0; i < segarLen(&config->define_keys); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "-D%s=%s", | |
| segarGet(&config->define_keys, i), | |
| segarGet(&config->define_vals, i)))); | |
| } | |
| if (config->target_type == Build_Target_Type_DLL) { | |
| osCommandAppend(&cmd, "-DBUILD_DLL"); | |
| } | |
| if (config->enable_warnings) { | |
| osCommandAppend(&cmd, "-Weverything"); | |
| osCommandAppend(&cmd, "-Wpedantic"); | |
| } | |
| if (config->warnings_are_errors) { | |
| osCommandAppend(&cmd, "-Werror"); | |
| osCommandAppend(&cmd, "-pedantic-errors"); | |
| } | |
| for (Uint32 i = 0; i < segarLen(&config->disabled_warnings[Build_Compiler_CLANG]); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "-Wno-%s", | |
| segarGet(&config->disabled_warnings[Build_Compiler_CLANG], i)))); | |
| } | |
| const Char *objname = txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "%s.obj", txtStr(pathGetName(&bs->mempush, src.filepath)))); | |
| const Path *objpath = pathJoin(&bs->tfp, &bs->mempush, config->target_dir, config->name, objname); | |
| osAssert(osCreateDirectory(pathGetParent(&bs->mempush, objpath)), "Couldn't create folder for object file"); | |
| osCommandAppend(&cmd, "-o"); | |
| osCommandAppend(&cmd, pathGetStr(objpath)); | |
| osAssert(osCommandExecute(&cmd, &bs->mempush, nullptr) == 0, "Clang compile() failed"); | |
| obj = buildObjMake(bs, objpath); | |
| } break; | |
| case Build_Compiler_UNDEFINED: case Build_Compiler_COUNT: default: debugUnreachable(); | |
| } | |
| return obj; | |
| } | |
| const Path* build_Link (Build_Config *config, Size obj_count, Build_Obj *objs, Build_Link_Params params [[maybe_unused]]) | |
| { | |
| Build_Space *bs = config->space; | |
| OS_Command cmd = osCommandInit(&bs->mempush); | |
| const Path *outpath = nullptr; | |
| switch (bs->compiler) { | |
| case Build_Compiler_MSVC: { | |
| osCommandAppend(&cmd, "cl"); | |
| osCommandAppend(&cmd, "/nologo"); | |
| if (config->target_type == Build_Target_Type_DLL) { | |
| osCommandAppend(&cmd, "/LD"); | |
| } | |
| for (Size i = 0; i < obj_count; i++) { | |
| osCommandAppend(&cmd, pathGetStr(objs[i].filepath)); | |
| } | |
| osCommandAppend(&cmd, "/INCREMENTAL:NO"); | |
| osCommandAppend(&cmd, "/utf-8"); | |
| buildSanitizer_Enable(config, &cmd); | |
| if (config->target_type == Build_Target_Type_EXE) { | |
| const Char *exename = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.exe", config->name)); | |
| outpath = pathJoin(&bs->tfp, &bs->mempush, config->target_dir, exename); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/Fe%s", pathGetStr(outpath)))); | |
| } else if (config->target_type == Build_Target_Type_DLL) { | |
| const Char *dllname = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.dll", config->name)); | |
| outpath = pathJoin(&bs->tfp, &bs->mempush, config->target_dir, dllname); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/Fe%s", pathGetStr(outpath)))); | |
| } | |
| osCommandAppend(&cmd, "/link"); | |
| osCommandAppend(&cmd, "/NOLOGO"); | |
| osCommandAppend(&cmd, "/MAP"); | |
| if (config->debug_information) { | |
| const Char *pdb_name; | |
| if (config->target_type == Build_Target_Type_DLL) { | |
| // The config->name is given twice so that the files can be deleted easily by just comparing the suffix | |
| pdb_name = txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "%s.%s.%s.dll.pdb", config->name, config->meta.uuid, config->name)); | |
| } else { | |
| pdb_name = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.pdb", config->name)); | |
| } | |
| osCommandAppend(&cmd, "/DEBUG"); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "/PDB:%s", | |
| pathGetStr(pathJoin(&bs->tfp, &bs->mempush, | |
| config->target_dir, pdb_name))))); | |
| } | |
| if (config->enable_optimizations) { | |
| osCommandAppend(&cmd, "/opt:icf"); | |
| osCommandAppend(&cmd, "/opt:ref"); | |
| } | |
| if (config->target_type == Build_Target_Type_EXE) { | |
| osCommandAppend(&cmd, "/fixed"); | |
| if (config->windows_console_subsystem) { | |
| osCommandAppend(&cmd, "/SUBSYSTEM:CONSOLE"); | |
| } else { | |
| osCommandAppend(&cmd, "/SUBSYSTEM:WINDOWS"); | |
| } | |
| } | |
| for (Uint32 i = 0; i < segarLen(&config->library_dirs); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "/LIBPATH:%s", pathGetStr(segarGet(&config->library_dirs, i))))); | |
| } | |
| for (Uint32 i = 0; i < segarLen(&config->libraries); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.lib", segarGet(&config->libraries, i)))); | |
| } | |
| Sint linking_command_return_code = osCommandExecute(&cmd, &bs->mempush, nullptr); | |
| if (!config->allow_hotloading) { | |
| osAssert(linking_command_return_code == 0, "MSVC link() failed"); | |
| } else if (linking_command_return_code) { | |
| osLogErr("MSVC link() failed"); | |
| } | |
| } break; | |
| case Build_Compiler_CLANG: { | |
| osCommandAppend(&cmd, "clang"); | |
| osCommandAppend(&cmd, "-fuse-ld=lld"); | |
| osCommandAppend(&cmd, "-ferror-limit=100"); | |
| osCommandAppend(&cmd, "-Wl,/errorlimit:0"); | |
| osCommandAppend(&cmd, "-fno-omit-frame-pointer"); | |
| buildSanitizer_Enable(config, &cmd); | |
| for (Size i = 0; i < obj_count; i++) { | |
| osCommandAppend(&cmd, pathGetStr(objs[i].filepath)); | |
| } | |
| if (config->target_type == Build_Target_Type_EXE) { | |
| const Char *exename = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.exe", config->name)); | |
| outpath = pathJoin(&bs->tfp, &bs->mempush, config->target_dir, exename); | |
| osCommandAppend(&cmd, "-o"); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s", pathGetStr(outpath)))); | |
| } else if (config->target_type == Build_Target_Type_DLL) { | |
| osCommandAppend(&cmd, "-shared"); | |
| const Char *dllname = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.dll", config->name)); | |
| outpath = pathJoin(&bs->tfp, &bs->mempush, config->target_dir, dllname); | |
| osCommandAppend(&cmd, "-o"); | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s", pathGetStr(outpath)))); | |
| } | |
| if (config->debug_information) { | |
| const Char *pdb_name; | |
| if (config->target_type == Build_Target_Type_DLL) { | |
| // The config->name is given twice so that the files can be deleted easily by just comparing the suffix | |
| pdb_name = txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "%s.%s.%s.dll.pdb", config->name, config->meta.uuid, config->name)); | |
| } else { | |
| pdb_name = txtStr(txtFormat(&bs->tfp, &bs->mempush, "%s.pdb", config->name)); | |
| } | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, "-Wl,/debug,/pdb:%s", | |
| pathGetStr(pathJoin(&bs->tfp, &bs->mempush, | |
| config->target_dir, pdb_name))))); | |
| } | |
| if (config->enable_optimizations) { | |
| osCommandAppend(&cmd, "-Wl,/opt:icf"); | |
| osCommandAppend(&cmd, "-Wl,/opt:ref"); | |
| } | |
| if (config->target_type == Build_Target_Type_EXE) { | |
| osCommandAppend(&cmd, "-Wl,/fixed"); | |
| if (config->windows_console_subsystem) { | |
| osCommandAppend(&cmd, "-Wl,/subsystem:console"); | |
| } else { | |
| osCommandAppend(&cmd, "-Wl,/subsystem:windows"); | |
| } | |
| } | |
| for (Uint32 i = 0; i < segarLen(&config->library_dirs); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "-L%s", pathGetStr(segarGet(&config->library_dirs, i))))); | |
| } | |
| for (Uint32 i = 0; i < segarLen(&config->libraries); i++) { | |
| osCommandAppend(&cmd, txtStr(txtFormat(&bs->tfp, &bs->mempush, | |
| "-l%s", segarGet(&config->libraries, i)))); | |
| } | |
| Sint linking_command_return_code = osCommandExecute(&cmd, &bs->mempush, nullptr); | |
| if (!config->allow_hotloading) { | |
| osAssert(linking_command_return_code == 0, "Clang link() failed"); | |
| } else if (linking_command_return_code) { | |
| osLogErr("Clang link() failed"); | |
| } | |
| } break; | |
| case Build_Compiler_UNDEFINED: case Build_Compiler_COUNT: default: debugUnreachable(); | |
| } | |
| return outpath; | |
| } | |
| Build_Src buildSrcMake (const Path *srcpath, Build_Lang lang, Uint32 lang_version) | |
| { | |
| Build_Src src = { | |
| .filepath = srcpath, | |
| .lang = lang, | |
| .lang_version = lang_version, | |
| }; | |
| return src; | |
| } | |
| Build_Obj buildObjMake (Build_Space *bs, const Path *path) | |
| { | |
| (void)bs; | |
| Build_Obj obj = { | |
| .filepath = path, | |
| }; | |
| return obj; | |
| } | |
| #else | |
| # error "No supported build.h backend found" | |
| #endif | |
| pragma_clang("clang diagnostic pop"); | |
| pragma_msvc("warning ( pop )"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment