Skip to content

Instantly share code, notes, and snippets.

@namandixit
Last active January 8, 2026 07:04
Show Gist options
  • Select an option

  • Save namandixit/e6baf57058adf4b5e316568b61b71f02 to your computer and use it in GitHub Desktop.

Select an option

Save namandixit/e6baf57058adf4b5e316568b61b71f02 to your computer and use it in GitHub Desktop.
Build System Facilitator
/*
* 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