Skip to content

Instantly share code, notes, and snippets.

@namandixit
Last active February 10, 2025 06:42
Show Gist options
  • Save namandixit/e6baf57058adf4b5e316568b61b71f02 to your computer and use it in GitHub Desktop.
Save namandixit/e6baf57058adf4b5e316568b61b71f02 to your computer and use it in GitHub Desktop.
Build System Facilitator in C (Ideal for Unity Builds)
/*
* 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 <stdint.h>
#if !defined(BUILD_MARK_UNREACHABLE_CODE)
# define BUILD_MARK_UNREACHABLE_CODE()
#endif
#define BUILD__INCLUDE_DIR_COUNT 10
#define BUILD__LIBRARY_DIR_COUNT 10
#define BUILD__DEFINE_COUNT 50
#define BUILD__DISABLED_WARNING_COUNT 50
#define BUILD__LIBRARY_COUNT 20
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 enum Build_Language {
Build_Language_C,
Build_Language_CXX,
} Build_Language;
typedef struct String_Builder_List_Node String_Builder_List_Node;
typedef struct Build_Environment {
Build_Platform host;
Build_Platform target;
char *project_dir;
Build_Compiler compiler;
void *backend_data;
bool print_commands;
} Build_Environment;
#define BUILD__CONFIG_ADD_INCLUDE_DIRECTORY_FN(name) struct Build_Config* name (struct Build_Config *config, char *directory)
#define BUILD__CONFIG_ADD_LIBRARY_DIRECTORY_FN(name) struct Build_Config* name (struct Build_Config *config, char *directory)
#define BUILD__CONFIG_ADD_LIBRARY_FN(name) struct Build_Config* name (struct Build_Config *config, char *library)
#define BUILD__CONFIG_ADD_USER_LIBRARY_FN(name) struct Build_Config* name (struct Build_Config *config, 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, char *warning)
#define BUILD__CONFIG_DEFINE_FN(name) struct Build_Config* name (struct Build_Config *config, char *macro, 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_Environment *env;
char *name;
char *target_dir;
bool internal;
bool slow;
bool debug_information;
bool enable_optimizations;
char *include_dirs[BUILD__INCLUDE_DIR_COUNT];
size_t include_dir_count;
char *library_dirs[BUILD__LIBRARY_DIR_COUNT];
size_t library_dir_count;
char *libraries[BUILD__LIBRARY_COUNT];
size_t library_count;
char *define_keys[BUILD__DEFINE_COUNT];
char *define_vals[BUILD__DEFINE_COUNT];
size_t define_count;
bool enable_warnings;
bool warnings_are_errors;
char *disabled_warnings[Build_Compiler__COUNT][BUILD__DISABLED_WARNING_COUNT];
size_t disabled_warning_count[Build_Compiler__COUNT];
Build_Target_Type target_type;
bool windows_console_subsystem;
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 struct Build_Source {
char *filepath;
Build_Language lang;
uint32_t lang_version;
} Build_Source;
typedef struct Build_Metadata {
char *uuid;
char *time;
} Build_Metadata;
typedef struct Build_Object {
char *filepath;
Build_Metadata meta;
} Build_Object;
typedef struct Build_Executable {
char *name;
char *dir;
char *path;
Build_Metadata meta;
} Build_Executable;
typedef struct Build_Lock {
char *path;
} Build_Lock;
Build_Environment* buildEnvironmentCreate (Build_Platform host, Build_Platform target, Build_Compiler compiler, bool print_commands);
void buildEnvironmentDelete (Build_Environment *env);
Build_Config* buildConfigCreate (Build_Environment *env, char *name, char *target_dir, bool internal, bool slow);
Build_Config* buildConfigDuplicate (Build_Config *config, char *name);
bool buildPathIsNewerThan (Build_Environment *env, char *source, char *target);
char* buildPathDir (Build_Environment *env, char *dir);
char* buildPathFileIn (Build_Environment *env, char *dir, char *file);
char* buildPathFile (Build_Environment *env, char *file);
char* buildPathTargetDir (Build_Environment *env, char *dir);
char* buildPathTargetFileIn (Build_Environment *env, char *dir, char *file);
char* build_PathJoin (Build_Environment *env, ...);
#define buildPathJoin(env, ...) build_PathJoin(env, __VA_ARGS__, NULL)
Build_Object buildCompile (Build_Config *config, Build_Source src, bool print_include_tree);
Build_Executable buildLink (Build_Config *config, size_t obj_count, Build_Object *obj);
Build_Source buildSource (char *srcpath, Build_Language lang, uint32_t lang_version);
Build_Object buildObject (Build_Environment *env, char *path);
Build_Executable buildExecutableFind (Build_Environment *env, char *exename, char *exedir);
int32_t build_Execute (Build_Environment *env, Build_Executable exe, ...);
#define buildExecute(env, exe, ...) build_Execute(env, exe, __VA_ARGS__, NULL)
Build_Lock buildLock (Build_Environment *env, char *dir, char *lockfilename);
void buildUnlock (Build_Lock lock);
void buildDeleteFileByGlob (Build_Environment *env, char *dirname, char *glob_pattern);
#if defined(SDL_h_)
#define BUILD__SDL_COMMANDS_MAXIMUM_ARGS 1000
#define BUILD__SDL_ALLOCATIONS_MAXIMUM 10000
typedef struct Build__String_Builder Build__String_Builder;
typedef struct Build__SDL {
void *allocs[BUILD__SDL_ALLOCATIONS_MAXIMUM];
size_t alloc_count;
uint64_t perf_frequency;
uint64_t perf_counter_begin;
uint64_t perf_counter_end;
} Build__SDL;
typedef struct Build__SDL_Command {
char *args[BUILD__SDL_COMMANDS_MAXIMUM_ARGS];
size_t arg_count;
} Build__SDL_Command;
#define cmdAppend(_arg) do { \
SDL_assert_release(cmd->arg_count < BUILD__SDL_COMMANDS_MAXIMUM_ARGS); \
cmd->args[cmd->arg_count++] = (_arg); \
} while (0)
static inline
void* build_SDL_malloc (Build_Environment *env, size_t s)
{
void *p = SDL_calloc(1, s);
Build__SDL *bd = env->backend_data;
bd->allocs[bd->alloc_count++] = p;
return p;
}
static inline
char* build_SDL_strdup (Build_Environment *env, const char *str)
{
size_t l = SDL_strlen(str);
char *str2 = build_SDL_malloc(env, l+1);
SDL_memcpy(str2, str, l);
str2[l] = '\0';
return str2;
}
static inline
SDL_PRINTF_VARARG_FUNC(2)
char* build_SDL_strformat (Build_Environment *env, SDL_PRINTF_FORMAT_STRING char *fmt, ...)
{
SDL_IOStream *io = SDL_IOFromDynamicMem();
va_list args;
va_start(args, fmt);
SDL_IOvprintf(io, fmt, args);
va_end(args);
SDL_WriteU8(io, '\0');
SDL_PropertiesID sbp = SDL_GetIOProperties(io);
char *msg = SDL_GetPointerProperty(sbp, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL);
if (msg == NULL) {
SDL_Log("Couldn't format string: %s (fmt: %s)", SDL_GetError(), fmt);
SDL_assert_release(false && "Couldn't format string");
}
SDL_SetPointerProperty(sbp, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); // This transfers the ownership of msg to the app
SDL_CloseIO(io);
Build__SDL *bd = env->backend_data;
bd->allocs[bd->alloc_count++] = msg;
return msg;
}
Build_Environment* buildEnvironmentCreate (Build_Platform host, Build_Platform target, Build_Compiler compiler, bool print_commands)
{
if (host == Build_Platform_UNDEFINED) {
#if defined(_WIN32) && (defined(_M_X64) || defined(__x86_64__))
host = Build_Platform_WIN64;
#else
# error Host can't be determined for the platform
#endif
}
if (target == Build_Platform_UNDEFINED) {
target = host;
}
if (compiler == Build_Compiler_UNDEFINED) {
#if defined(_WIN32)
compiler = Build_Compiler_MSVC;
#else
# error Compiler can't be determined for the platform
#endif
}
Build_Environment *env = SDL_calloc(1, sizeof(*env)); // NOTE(naman): This one can't go through build_SDL_malloc, so clean it up manually
env->backend_data = SDL_calloc(1, sizeof(Build__SDL)); // NOTE(naman): This one can't go through build_SDL_malloc, so clean it up manually
env->host = host;
env->target = target;
const char *basepath = SDL_GetBasePath();
if (basepath == NULL) {
SDL_Log("Couldn't get the executable path: %s", SDL_GetError());
}
char *basepath_dup = build_SDL_strdup(env, basepath);
size_t basepath_duplen = SDL_strlen(basepath_dup);
for (size_t i = 0; i < basepath_duplen; i++) {
if (basepath_dup[i] == '\\') {
basepath_dup[i] = '/';
}
}
basepath_dup[basepath_duplen - 1] = '\0';
env->project_dir = basepath_dup;
env->compiler = compiler;
env->print_commands = print_commands;
SDL_Time seed;
if (!SDL_GetCurrentTime(&seed)) {
seed = 0; // Will use GetPerformanceCounter()
}
SDL_srand((uint64_t)seed);
Build__SDL *bd = env->backend_data;
bd->perf_frequency = SDL_GetPerformanceFrequency();
bd->perf_counter_begin = SDL_GetPerformanceCounter();
SDL_Log("Entering directory `%s'", env->project_dir);
return env;
}
void buildEnvironmentDelete (Build_Environment *env)
{
Build__SDL *bd = env->backend_data;
for (size_t i = 0; i < bd->alloc_count; i++) {
SDL_free(bd->allocs[i]);
}
bd->perf_counter_end = SDL_GetPerformanceCounter();
double perf_counter_elapsed = (double)(bd->perf_counter_end - bd->perf_counter_begin);
double milliseconds = (1000.0 * perf_counter_elapsed) / (double)bd->perf_frequency;
SDL_Log("Time taken for the build executable: %f ms", milliseconds/1000);
SDL_free(env->backend_data);
SDL_free(env);
}
#define SDL_malloc(...)
#define SDL_calloc(...)
#define SDL_realloc(...)
#define SDL_free(...)
#define SDL_strdup(...)
static inline
BUILD__CONFIG_ADD_INCLUDE_DIRECTORY_FN(buildConfigAddIncludeDirectory)
{
if (config->include_dir_count >= BUILD__INCLUDE_DIR_COUNT) {
SDL_Log("Too many include directories added for %s", config->name);
SDL_assert_release(false && "Too many include directories added");
}
if (directory == NULL) {
SDL_Log("Directory name is NULL");
SDL_assert_release(false && "Directory name is NULL");
}
config->include_dirs[config->include_dir_count] = build_SDL_strdup(config->env, directory);
config->include_dir_count++;
return config;
}
static inline
BUILD__CONFIG_ADD_LIBRARY_DIRECTORY_FN(buildConfigAddLibraryDirectory)
{
if (config->library_dir_count >= BUILD__LIBRARY_DIR_COUNT) {
SDL_Log("Too many library directories added for %s", config->name);
SDL_assert_release(false && "Too many library directories added");
}
if (directory == NULL) {
SDL_Log("Directory name is NULL");
SDL_assert_release(false && "Directory name is NULL");
}
config->library_dirs[config->library_dir_count] = build_SDL_strdup(config->env, directory);
config->library_dir_count++;
return config;
}
static inline
BUILD__CONFIG_ADD_LIBRARY_FN(buildConfigAddLibrary)
{
if (config->library_count >= BUILD__LIBRARY_COUNT) {
SDL_Log("Too many libraries added for %s", config->name);
SDL_assert_release(false && "Too many libraries added");
}
if (library == NULL) {
SDL_Log("Library name is NULL");
SDL_assert_release(false && "Library name is NULL");
}
config->libraries[config->library_count] = build_SDL_strdup(config->env, library);
config->library_count++;
return config;
}
static inline
BUILD__CONFIG_ADD_USER_LIBRARY_FN(buildConfigAddUserLibrary)
{
buildConfigAddLibrary(config, library);
char *dll_name = NULL;
char *dbg_name = NULL;
#if defined(_WIN32)
dll_name = build_SDL_strformat(config->env, "%s.dll", library);
dbg_name = build_SDL_strformat(config->env, "%s.pdb", library);
#else
# error Libraries can't be copied for current platform
#endif
{ // Clean old copies if they exist
char *dll_path = build_SDL_strformat(config->env, "%s/%s", config->target_dir, dll_name);
if (SDL_GetPathInfo(dll_path, NULL)) {
if (!SDL_RemovePath(dll_path)) {
SDL_Log("Couldn't delete %s when building %s: %s", dll_path, config->name, SDL_GetError());
SDL_assert_release(false && "Couldn't delete DLL");
}
if (dbg_name) {
char *dbg_path = build_SDL_strformat(config->env, "%s/%s", config->target_dir, dbg_name);
if (SDL_GetPathInfo(dbg_path, NULL)) {
if (!SDL_RemovePath(dbg_path)) {
SDL_Log("Couldn't delete %s when building %s: %s", dbg_path, config->name, SDL_GetError());
SDL_assert_release(false && "Couldn't delete PDB");
}
}
}
}
}
{ // Copy over the dll and dbg file
bool file_copied = false;
for (size_t i = 0; i < config->library_dir_count; i++) {
char *old_dll_path = build_SDL_strformat(config->env, "%s/%s", config->library_dirs[i], dll_name);
char *new_dll_path = build_SDL_strformat(config->env, "%s/%s", config->target_dir, dll_name);
if (SDL_GetPathInfo(old_dll_path, NULL)) {
if (!SDL_CopyFile(old_dll_path, new_dll_path)) {
SDL_Log("Couldn't copy %s to %s when building %s: %s", old_dll_path, new_dll_path, config->name, SDL_GetError());
SDL_assert_release(false && "Couldn't copy DLL");
}
if (dbg_name) {
char *old_dbg_path = build_SDL_strformat(config->env, "%s/%s", config->library_dirs[i], dbg_name);
char *new_dbg_path = build_SDL_strformat(config->env, "%s/%s", config->target_dir, dbg_name);
if (SDL_GetPathInfo(old_dbg_path, NULL)) {
if (!SDL_CopyFile(old_dbg_path, new_dbg_path)) {
SDL_Log("Couldn't copy %s to %s when building %s: %s", old_dbg_path, new_dbg_path, config->name, SDL_GetError());
SDL_assert_release(false && "Couldn't copy PDB");
}
}
}
file_copied = true;
break;
}
}
if (!file_copied) {
SDL_Log("%s DLL file couldn't be found in any library paths", library);
SDL_assert_release(false && "DLL not found");
}
}
return config;
}
static inline
BUILD__CONFIG_WARNINGS_ARE_ERRORS_FN(buildConfigWarningsAreErrors)
{
config->warnings_are_errors = warnings_are_errors;
return config;
}
static inline
BUILD__CONFIG_ENABLE_WARNINGS_FN(buildConfigEnableWarnings)
{
config->enable_warnings = enable_warnings;
return config;
}
static inline
BUILD__CONFIG_DISABLE_WARNING_FN(buildConfigDisableWarning)
{
if (config->disabled_warning_count[compiler] >= BUILD__DISABLED_WARNING_COUNT) {
SDL_Log("Too many warnings disabled for %s", config->name);
SDL_assert_release(false && "Too many warnings disabled");
}
if (warning == NULL) {
SDL_Log("Warning name is NULL");
SDL_assert_release(false && "Warning name is NULL");
}
config->disabled_warnings[compiler][config->disabled_warning_count[compiler]] = build_SDL_strdup(config->env, warning);
config->disabled_warning_count[compiler]++;
return config;
}
static inline
BUILD__CONFIG_DEFINE_FN(buildConfigDefine)
{
if (config->define_count >= BUILD__DEFINE_COUNT) {
SDL_Log("Too many defines added for %s", config->name);
SDL_assert_release(false && "Too many defines added");
}
if (macro == NULL) {
SDL_Log("Define macro name name is NULL");
SDL_assert_release(false && "Define macro name name is NULL");
}
config->define_keys[config->define_count] = build_SDL_strdup(config->env, macro);
config->define_vals[config->define_count] = value ? build_SDL_strdup(config->env, value) : "1";
config->define_count++;
return config;
}
static inline
BUILD__CONFIG_SET_TARGET_TYPE_FN(buildConfigSetTargetType)
{
config->target_type = target_type;
return config;
}
Build_Config* buildConfigCreate (Build_Environment *env, char *name, char *target_dir, bool internal, bool slow)
{
if (env == NULL) {
SDL_Log("Environment is NULL");
SDL_assert_release(false && "Environment is NULL");
}
if (name == NULL) {
SDL_Log("Build config name is NULL");
SDL_assert_release(false && "Build config name is NULL");
}
if (target_dir == NULL) {
SDL_Log("Build config target directory is NULL");
SDL_assert_release(false && "Build config target directory is NULL");
}
Build_Config *c = build_SDL_malloc(env, sizeof(*c));
c->env = env;
c->name = build_SDL_strdup(env, name);
c->target_dir = build_SDL_strdup(env, target_dir);
c->internal = internal;
c->slow = slow;
c->debug_information = internal;
c->enable_optimizations = !slow;
c->enable_warnings = true;
c->warnings_are_errors = false;
c->target_type = Build_Target_Type_EXE;
c->windows_console_subsystem = false;
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;
if (internal) {
c->Define(c, "BUILD_INTERNAL", NULL);
}
if (slow) {
c->Define(c, "BUILD_SLOW", NULL);
}
return c;
}
Build_Config* buildConfigDuplicate (Build_Config *config, char *name)
{
Build_Config *d = build_SDL_malloc(config->env, sizeof(*d));
memcpy(d, config, sizeof(*d));
d->name = build_SDL_strdup(config->env, name);
d->target_dir = build_SDL_strdup(config->env, config->target_dir);
return d;
}
bool buildPathIsNewerThan (Build_Environment *env, char *source, char *target)
{
(void)env;
SDL_PathInfo sinfo = {};
SDL_assert_release(SDL_GetPathInfo(source, &sinfo));
SDL_PathInfo tinfo = {};
if (!SDL_GetPathInfo(target, &tinfo)) {
// If target doesn't exist, then source is clearly newer
return true;
} else {
// Equal to is included to handle platforms which may not have proper support
// for modify_time (always returning 0, etc.)
return sinfo.modify_time >= tinfo.modify_time;
}
}
char* buildPathDir (Build_Environment *env, char *dir)
{
char *dirpath = build_SDL_strformat(env, "%s/%s", env->project_dir, dir);
if (!SDL_CreateDirectory(dirpath)) {
SDL_Log("Couldn't create directory %s: %s", dirpath, SDL_GetError());
SDL_assert_release(false && "Couldn't create directory");
}
return dirpath;
}
char* buildPathFileIn (Build_Environment *env, char *dir, char *file)
{
char *filepath = build_SDL_strformat(env, "%s/%s", buildPathDir(env, dir), file);
return filepath;
}
char* buildPathFile (Build_Environment *env, char *file)
{
char *filepath = build_SDL_strformat(env, "%s/%s", env->project_dir, file);
return filepath;
}
static inline
char* build_TargetString (Build_Environment *env)
{
switch (env->target) {
case Build_Platform_WIN64: return "win64";
case Build_Platform_UNDEFINED: default: BUILD_MARK_UNREACHABLE_CODE(); break;
}
return NULL;
}
char* buildPathTargetDir (Build_Environment *env, char *dir)
{
char *dirpath = build_SDL_strformat(env, "%s/%s/%s", env->project_dir, dir, build_TargetString(env));
if (!SDL_CreateDirectory(dirpath)) {
SDL_Log("Couldn't create directory %s: %s", dirpath, SDL_GetError());
SDL_assert_release(false && "Couldn't create target directory");
}
return dirpath;
}
char* buildPathTargetFileIn (Build_Environment *env, char *dir, char *file)
{
char *filepath = build_SDL_strformat(env, "%s/%s", buildPathTargetDir(env, dir), file);
return filepath;
}
char* build_PathJoin (Build_Environment *env, ...)
{
char *path = NULL;
va_list args;
va_start(args, env);
char *p = va_arg(args, char *);
while (p) {
if (path) {
path = build_SDL_strformat(env, "%s/%s", path, p);
} else {
path = build_SDL_strformat(env, "%s", p);
}
p = va_arg(args, char *);
}
va_end(args);
return path;
}
static inline
char* build_PathBasename (Build_Environment *env, char *path)
{
char *result = SDL_strrchr(path, '/');
if (result == NULL) {
result = SDL_strrchr(path, '\\');
}
return build_SDL_strdup(env, result + 1);
}
static inline
Build_Metadata build_CreateMetadata (Build_Config *config)
{
Build_Environment *env = config->env;
// 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
char uuid_array[] = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
for (size_t i = 0; i < sizeof(uuid_array)/sizeof(uuid_array[0]); i++) {
switch (uuid_array[i]) {
case 'x': {
uuid_array[i] = build_SDL_strformat(env, "%x", SDL_rand(0x10))[0];
} break;
case 'y': {
uuid_array[i] = build_SDL_strformat(env, "%x", SDL_rand(0xc - 0x8) + 0x8)[0];
} break;
default: break;
}
}
char *uuid = build_SDL_strdup(env, uuid_array);
SDL_Time ticks;
SDL_DateTime dt;
char *time = "<Time-Undetermined>";
if (SDL_GetCurrentTime(&ticks) && SDL_TimeToDateTime(ticks, &dt, true)) {
time = build_SDL_strformat(env,
"%d-%d-%d-%d:%d:%d.%dT%+f",
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.nanosecond,
((double)dt.utc_offset)/(60*60));
} else {
SDL_Log("Couldn't get the time: %s", SDL_GetError());
}
buildConfigDefine(config, "BUILD_UUID", build_SDL_strformat(env, "\"%s\"", uuid));
buildConfigDefine(config, "BUILD_TIME", build_SDL_strformat(env, "\"%s\"", time));
Build_Metadata meta = {.uuid = uuid, .time = time};
return meta;
}
static inline
int build_ExecuteCommand (Build_Environment *env, Build__SDL_Command *cmd)
{
(void)env;
cmdAppend(NULL);
if (env->print_commands) {
SDL_IOStream *io = SDL_IOFromDynamicMem();
for (size_t i = 0; i < cmd->arg_count - 1; i++) {
SDL_IOprintf(io, "%s ", cmd->args[i]);
}
SDL_WriteU8(io, '\0');
SDL_PropertiesID sbp = SDL_GetIOProperties(io);
char *msg = SDL_GetPointerProperty(sbp, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL);
if (msg == NULL) {
SDL_assert_release(false && "Couldn't format execution command");
}
SDL_DestroyProperties(sbp);
SDL_CloseIO(io);
SDL_Log("%s", msg);
SDL_free(msg);
}
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, cmd->args);
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true);
SDL_Process *process = SDL_CreateProcessWithProperties(props);
if (process == NULL) {
SDL_DestroyProperties(props);
SDL_assert_release(false && "Process could not be created");
return -1;
}
int exitcode = 0;
SDL_WaitProcess(process, true, &exitcode);
SDL_DestroyProcess(process);
SDL_DestroyProperties(props);
return exitcode;
}
Build_Object buildCompile (Build_Config *config, Build_Source src, bool print_include_tree)
{
Build_Object obj = {0};
Build_Metadata meta = build_CreateMetadata(config);
Build__SDL_Command *cmd = build_SDL_malloc(config->env, sizeof(*cmd));
switch (config->env->compiler) {
case Build_Compiler_MSVC: {
cmdAppend("cl");
cmdAppend("/nologo");
cmdAppend("/diagnostics:caret");
if (config->target_type == Build_Target_Type_DLL) {
cmdAppend("/LD");
}
char *lang_flag = NULL;
if (src.lang == Build_Language_C) {
cmdAppend("/TC");
lang_flag = "/std:c";
} else if (src.lang == Build_Language_CXX) {
cmdAppend("/TP");
lang_flag = "/std:c++";
}
lang_flag = build_SDL_strformat(config->env, "%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_Language_C) && (src.lang_version == 23)) {
lang_flag = "/std:clatest";
}
cmdAppend(lang_flag);
cmdAppend(src.filepath);
for (size_t i = 0; i < config->include_dir_count; i++) {
cmdAppend("/I");
cmdAppend(config->include_dirs[i]);
}
if (print_include_tree) {
cmdAppend("/showIncludes");
}
cmdAppend("/c"); // Compile without linking
cmdAppend("/presetPadding"); // Zero-initialize padding for stack based types
if (config->debug_information) {
cmdAppend("/Zi"); // Generate complete debugging information.
cmdAppend(build_SDL_strformat(config->env, "/Fd:%s.pdb", buildPathJoin(config->env, config->target_dir, "vcobj"))); // Machine code before linking
}
cmdAppend("/FC"); // Full path of source code files passed to cl.exe in diagnostic text.
if (config->enable_optimizations) {
cmdAppend("/O2");
cmdAppend("/Oi");
}
cmdAppend("/utf-8"); // Set source and execution character sets to UTF-8.
for (size_t i = 0; i < config->define_count; i++) {
cmdAppend(build_SDL_strformat(config->env, "/D%s=%s", config->define_keys[i], config->define_vals[i]));
}
if (config->target_type == Build_Target_Type_DLL) {
cmdAppend("/DBUILD_DLL");
}
cmdAppend("/GS-"); // Disable Buffer Security Check
cmdAppend("/jumptablerdata"); // Put switch case statement jump tables in the .rdata section.
if (config->enable_warnings) {
cmdAppend("/Wall"); // Enable all warnings
}
if (config->warnings_are_errors) {
cmdAppend("/WX"); // Treat warnings as errors.
}
for (size_t i = 0; i < config->disabled_warning_count[Build_Compiler_MSVC]; i++) {
cmdAppend(build_SDL_strformat(config->env, "/wd%s", config->disabled_warnings[Build_Compiler_MSVC][i]));
}
if (config->enable_warnings) {
cmdAppend("/analyze:WX-"); // Enables code analysis, don't treat as error even with /WX
cmdAppend("/analyze:autolog-"); // disable logging to files
}
char *objname = build_SDL_strformat(config->env, "%s.%s.obj", config->name, build_PathBasename(config->env, src.filepath));
char *objpath = buildPathJoin(config->env, config->target_dir, objname);
cmdAppend(build_SDL_strformat(config->env, "/Fo%s", objpath));
if (build_ExecuteCommand(config->env, cmd)) {
SDL_Log("MSVC compile() failed");
SDL_assert_release(false && "MSVC compile() failed");
}
obj = buildObject(config->env, objpath);
} break;
case Build_Compiler_CLANG: {
if (src.lang == Build_Language_C) {
cmdAppend("clang");
cmdAppend(build_SDL_strformat(config->env, "--std=c%u", src.lang_version));
} else if (src.lang == Build_Language_CXX) {
cmdAppend("clang++");
cmdAppend(build_SDL_strformat(config->env, "--std=c++%u", src.lang_version));
}
cmdAppend("-ferror-limit=100");
if (config->internal) { // NOTE(naman): Remember to add these to the linker option too
cmdAppend("-fsanitize=address");
cmdAppend("-fsanitize=undefined");
cmdAppend("-fsanitize=integer");
cmdAppend("-fno-sanitize=unsigned-shift-base"); // Disable Left shifting 0xFFFF.... kind of numbers
// TODO(naman): thread, memory, safe-stack and type may be available on Linux, etc.
}
cmdAppend(src.filepath);
for (size_t i = 0; i < config->include_dir_count; i++) {
cmdAppend("-I");
cmdAppend(config->include_dirs[i]);
}
if (print_include_tree) {
cmdAppend("-H");
}
cmdAppend("-c"); // Compile without linking
if (config->debug_information) {
cmdAppend("-g3"); // Generate complete debugging information.
}
if (config->enable_optimizations) {
cmdAppend("-O2");
cmdAppend("-msse2");
cmdAppend("-fdata-sections"); cmdAppend("-ffunction-sections"); // CFLAG for removing unreferenced globals/functions
cmdAppend("-fwrapv"); cmdAppend("-fno-strict-aliasing"); // Prevent unsafe optimizations
cmdAppend("-fno-delete-null-pointer-checks");
}
for (size_t i = 0; i < config->define_count; i++) {
cmdAppend(build_SDL_strformat(config->env, "-D%s=%s", config->define_keys[i], config->define_vals[i]));
}
if (config->target_type == Build_Target_Type_DLL) {
cmdAppend("-DBUILD_DLL");
}
if (config->enable_warnings) {
cmdAppend("-Weverything");
cmdAppend("-Wpedantic");
}
if (config->warnings_are_errors) {
cmdAppend("-Werror");
cmdAppend("-pedantic-errors");
}
for (size_t i = 0; i < config->disabled_warning_count[Build_Compiler_CLANG]; i++) {
cmdAppend(build_SDL_strformat(config->env, "-Wno-%s", config->disabled_warnings[Build_Compiler_CLANG][i]));
}
char *objname = build_SDL_strformat(config->env, "%s.%s.obj", config->name, build_PathBasename(config->env, src.filepath));
char *objpath = buildPathJoin(config->env, config->target_dir, objname);
cmdAppend("-o");
cmdAppend(objpath);
if (build_ExecuteCommand(config->env, cmd)) {
SDL_Log("Clang compile() failed");
SDL_assert_release(false && "Clang compile() failed");
}
obj = buildObject(config->env, objpath);
} break;
case Build_Compiler_UNDEFINED: case Build_Compiler__COUNT: default: BUILD_MARK_UNREACHABLE_CODE();
}
obj.meta = meta;
return obj;
}
Build_Executable buildLink (Build_Config *config, size_t obj_count, Build_Object *objs)
{
Build_Executable exe = {0};
Build_Metadata meta = build_CreateMetadata(config);
Build__SDL_Command *cmd = build_SDL_malloc(config->env, sizeof(*cmd));
switch (config->env->compiler) {
case Build_Compiler_MSVC: {
cmdAppend("cl");
cmdAppend("/nologo");
if (config->target_type == Build_Target_Type_DLL) {
cmdAppend("/LD");
}
for (size_t i = 0; i < obj_count; i++) {
cmdAppend(objs[i].filepath);
}
cmdAppend("/INCREMENTAL:NO");
cmdAppend("/utf-8");
char *exename = NULL;
if (config->target_type == Build_Target_Type_EXE) {
exename = build_SDL_strformat(config->env, "%s.exe", config->name);
cmdAppend(build_SDL_strformat(config->env, "/Fe%s/%s", buildPathJoin(config->env, config->target_dir), exename));
} else if (config->target_type == Build_Target_Type_DLL) {
char *dll = build_SDL_strformat(config->env, "%s.dll", buildPathJoin(config->env, config->target_dir, config->name));
cmdAppend(build_SDL_strformat(config->env, "/Fe%s", dll));
}
cmdAppend("/link");
cmdAppend("/NOLOGO");
if (config->debug_information) {
char *pdb_suffix = "";
if (config->target_type == Build_Target_Type_DLL) {
pdb_suffix = build_SDL_strformat(config->env, "%s.dll.", meta.uuid);
}
cmdAppend("/DEBUG");
cmdAppend(build_SDL_strformat(config->env,
"/PDB:%s.%spdb",
buildPathJoin(config->env, config->target_dir, config->name), pdb_suffix));
}
if (config->enable_optimizations) {
cmdAppend("/opt:icf");
cmdAppend("/opt:ref");
}
if (config->target_type == Build_Target_Type_EXE) {
cmdAppend("/fixed");
if (config->windows_console_subsystem) {
cmdAppend("/SUBSYSTEM:CONSOLE");
} else {
cmdAppend("/SUBSYSTEM:WINDOWS");
}
}
for (size_t i = 0; i < config->library_dir_count; i++) {
cmdAppend(build_SDL_strformat(config->env, "/LIBPATH:%s", config->library_dirs[i]));
}
for (size_t i = 0; i < config->library_count; i++) {
cmdAppend(build_SDL_strformat(config->env, "%s.lib", config->libraries[i]));
}
if (build_ExecuteCommand(config->env, cmd)) {
SDL_Log("MSVC link() failed");
SDL_assert_release(false && "MSVC link() failed");
}
exe = buildExecutableFind(config->env, exename, config->target_dir);
} break;
case Build_Compiler_CLANG: {
cmdAppend("clang");
cmdAppend("-fuse-ld=lld");
cmdAppend("-ferror-limit=100");
if (config->internal) { // NOTE(naman): Remember to add these to the linker option too
cmdAppend("-fsanitize=address");
cmdAppend("-fsanitize=undefined");
cmdAppend("-fsanitize=integer");
cmdAppend("-fno-sanitize=unsigned-shift-base"); // Disable Left shifting 0xFFFF.... kind of numbers
// TODO(naman): thread, memory, safe-stack and type may be available on Linux, etc.
}
for (size_t i = 0; i < obj_count; i++) {
cmdAppend(objs[i].filepath);
}
char *exename = NULL;
if (config->target_type == Build_Target_Type_EXE) {
exename = build_SDL_strformat(config->env, "%s.exe", config->name);
cmdAppend("-o");
cmdAppend(build_SDL_strformat(config->env, "%s/%s", buildPathJoin(config->env, config->target_dir), exename));
} else if (config->target_type == Build_Target_Type_DLL) {
cmdAppend("-shared");
cmdAppend("-o");
char *dll = build_SDL_strformat(config->env, "%s.dll", buildPathJoin(config->env, config->target_dir, config->name));
cmdAppend(dll);
}
if (config->debug_information) {
char *pdb_suffix = "";
if (config->target_type == Build_Target_Type_DLL) {
pdb_suffix = build_SDL_strformat(config->env, "%s.dll.", meta.uuid);
}
cmdAppend(build_SDL_strformat(config->env,
"-Wl,/debug,/pdb:%s.%spdb",
buildPathJoin(config->env, config->target_dir, config->name), pdb_suffix));
}
if (config->enable_optimizations) {
cmdAppend("-Wl,/opt:icf");
cmdAppend("-Wl,/opt:ref");
}
if (config->target_type == Build_Target_Type_EXE) {
cmdAppend("-Wl,/fixed");
if (config->windows_console_subsystem) {
cmdAppend("-Wl,/subsystem:console");
} else {
cmdAppend("-Wl,/subsystem:windows");
}
}
for (size_t i = 0; i < config->library_dir_count; i++) {
cmdAppend(build_SDL_strformat(config->env, "-L%s", config->library_dirs[i]));
}
for (size_t i = 0; i < config->library_count; i++) {
cmdAppend(build_SDL_strformat(config->env, "-l%s", config->libraries[i]));
}
if (build_ExecuteCommand(config->env, cmd)) {
SDL_Log("Clang link() failed");
SDL_assert_release(false && "Clang link() failed");
}
exe = buildExecutableFind(config->env, exename, config->target_dir);
} break;
case Build_Compiler_UNDEFINED: case Build_Compiler__COUNT: default: BUILD_MARK_UNREACHABLE_CODE();
}
exe.meta = meta;
return exe;
}
Build_Source buildSource (char *srcpath, Build_Language lang, uint32_t lang_version)
{
Build_Source src = {
.filepath = srcpath,
.lang = lang,
.lang_version = lang_version,
};
return src;
}
Build_Object buildObject (Build_Environment *env, char *path)
{
(void)env;
Build_Object obj = {
.filepath = path,
};
return obj;
}
Build_Executable buildExecutableFind (Build_Environment *env, char *exename, char *exedir)
{
Build_Executable exe = {
.name = exename,
.dir = exedir,
.path = build_SDL_strformat(env, "%s/%s", exedir, exename),
};
return exe;
}
int build_Execute (Build_Environment *env, Build_Executable exe, ...)
{
Build__SDL_Command *cmd = build_SDL_malloc(env, sizeof(*cmd));
cmdAppend(exe.path);
va_list args;
va_start(args, exe);
char *p = va_arg(args, char *);
while (p) {
cmdAppend(p);
p = va_arg(args, char *);
}
va_end(args);
int exitcode = build_ExecuteCommand(env, cmd);
return exitcode;
}
Build_Lock buildLock (Build_Environment *env, char *dir, char *lockfilename)
{
Build_Lock l = {
.path = buildPathJoin(env, dir, lockfilename),
};
SDL_IOStream *io = SDL_IOFromFile(l.path, "w");
if (io == NULL) {
SDL_Log("Couldn't open lock file %s: %s", l.path, SDL_GetError());
SDL_assert_release(false && "Couldn't open lock file");
}
if (SDL_WriteIO(io, lockfilename, SDL_strlen(lockfilename)) != SDL_strlen(lockfilename)) {
SDL_Log("Couldn't write to lock file %s: %s", l.path, SDL_GetError());
SDL_assert_release(false && "Couldn't write to lock file");
}
if (!SDL_FlushIO(io)) {
SDL_Log("Couldn't flush to the lock file %s: %s", l.path, SDL_GetError());
SDL_assert_release(false && "Couldn't flush to the lock file");
}
if (!SDL_CloseIO(io)) {
SDL_Log("Couldn't close the lock file %s: %s", l.path, SDL_GetError());
SDL_assert_release(false && "Couldn't close the lock file");
}
return l;
}
void buildUnlock (Build_Lock lock)
{
if (!SDL_RemovePath(lock.path)) {
SDL_Log("Couldn't delete the lock file %s: %s", lock.path, SDL_GetError());
SDL_assert_release(false && "Couldn't delete the lock file");
}
}
void buildDeleteFileByGlob (Build_Environment *env, char *dirname, char *glob_pattern)
{
int32_t count;
char **candidates = SDL_GlobDirectory(dirname, glob_pattern, SDL_GLOB_CASEINSENSITIVE, &count);
if (candidates == NULL) {
SDL_Log("Couldn't glob over the directory %s with pattern %s: %s", dirname, glob_pattern, SDL_GetError());
SDL_assert_release(false && "Couldn't glob over the directory");
}
for (size_t i = 0; i < (size_t)count; i++) {
char *n = buildPathJoin(env, dirname, candidates[i]);
if (!SDL_RemovePath(n)) {
SDL_Log("Couldn't delete the globbed file %s: %s", n, SDL_GetError());
SDL_assert_release(false && "Couldn't delete the globbed file");
}
}
}
#undef cmdAppend
#undef SDL_malloc
#undef SDL_calloc
#undef SDL_realloc
#undef SDL_free
#undef SDL_strdup
#else
# error "No supported build.h backend found"
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment