Last active
February 10, 2025 06:42
-
-
Save namandixit/e6baf57058adf4b5e316568b61b71f02 to your computer and use it in GitHub Desktop.
Build System Facilitator in C (Ideal for Unity Builds)
This file contains 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 <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