From f471c447d877fa7d2fc836a8b0de6cfcc11462a7 Mon Sep 17 00:00:00 2001 From: Patedam Date: Thu, 23 Jul 2026 17:18:20 -0400 Subject: [PATCH] converted build system to simpler C --- misc/build.bat | 6 +- tools/build_system.c | 915 ++++++++++++++++++++++++++++++++++ tools/build_system.cpp | 1058 ---------------------------------------- 3 files changed, 918 insertions(+), 1061 deletions(-) create mode 100644 tools/build_system.c delete mode 100644 tools/build_system.cpp diff --git a/misc/build.bat b/misc/build.bat index ade4e44..dee8a64 100644 --- a/misc/build.bat +++ b/misc/build.bat @@ -12,7 +12,7 @@ cd /d "%SCRIPT_DIR%.." set FBUILD=misc\fbuild.exe set BUILD_TOOL=misc\build_system.exe -set BUILD_SRC=tools\build.cpp +set BUILD_SRC=tools\build_system.c set TARGET_JULIET=0 set TARGET_ROMEO=0 @@ -89,8 +89,8 @@ if !NEED_RECOMPILE_BUILD_TOOL! equ 1 ( exit /b 1 ) ) - echo [BUILD] Compiling fast native build orchestrator [tools\build.cpp -^> misc\build.exe]... - %COMPILER% /nologo /std:c++20 /MT /O2 /EHa- /W4 "%BUILD_SRC%" /Fe"%BUILD_TOOL%" + echo [BUILD] Compiling fast native build orchestrator [tools\build_system.c -^> misc\build.exe]... + %COMPILER% /nologo /MT /O2 /W4 "%BUILD_SRC%" /Fe"%BUILD_TOOL%" if !ERRORLEVEL! neq 0 ( echo [BUILD FAILED] Could not compile build orchestrator. exit /b 1 diff --git a/tools/build_system.c b/tools/build_system.c new file mode 100644 index 0000000..d93bf80 --- /dev/null +++ b/tools/build_system.c @@ -0,0 +1,915 @@ +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PATH_LEN 1024 +#define MAX_FILES 4096 +#define MAX_HASH_ENTRIES 512 + +// ============================================================================ +// Arena Allocator +// ============================================================================ + +typedef struct +{ + char* base; + size_t size; + size_t offset; +} Arena; + +static Arena g_Arena; + +static void ArenaInit(size_t size) +{ + g_Arena.base = (char*)malloc(size); + assert(g_Arena.base != NULL); + g_Arena.size = size; + g_Arena.offset = 0; +} + +static void* ArenaPush(size_t size) +{ + // align to 8 bytes + size_t aligned_size = (size + 7) & ~7; + if (g_Arena.offset + aligned_size > g_Arena.size) + { + printf("[ERROR] Arena out of memory!\n"); + exit(1); + } + void* ptr = g_Arena.base + g_Arena.offset; + g_Arena.offset += aligned_size; + memset(ptr, 0, size); + return ptr; +} + +// ============================================================================ +// String helpers +// ============================================================================ + +static bool StringContains(const char* haystack, const char* needle) +{ + return strstr(haystack, needle) != NULL; +} + +static bool EndsWithNoCase(const char* str, const char* suffix) +{ + int strLen = (int)strlen(str); + int sufLen = (int)strlen(suffix); + if (sufLen > strLen) return false; + return _stricmp(str + strLen - sufLen, suffix) == 0; +} + +// ============================================================================ +// File I/O helpers +// ============================================================================ + +typedef struct +{ + char* Data; + long Size; +} FileContent; + +static FileContent ReadEntireFile(const char* path) +{ + FileContent result = { NULL, 0 }; + FILE* f = NULL; + fopen_s(&f, path, "rb"); + if (!f) return result; + + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (size > 0) + { + result.Data = (char*)ArenaPush(size + 1); + result.Size = size; + fread(result.Data, 1, size, f); + result.Data[size] = '\0'; + } + fclose(f); + return result; +} + +static bool WriteEntireFile(const char* path, const char* data, long size) +{ + FILE* f = NULL; + fopen_s(&f, path, "wb"); + if (!f) return false; + size_t written = fwrite(data, 1, size, f); + fclose(f); + return written == (size_t)size; +} + +static bool FileExists(const char* path) +{ + DWORD attribs = GetFileAttributesA(path); + return (attribs != INVALID_FILE_ATTRIBUTES) && !(attribs & FILE_ATTRIBUTE_DIRECTORY); +} + +static bool DirectoryExists(const char* path) +{ + DWORD attribs = GetFileAttributesA(path); + return (attribs != INVALID_FILE_ATTRIBUTES) && (attribs & FILE_ATTRIBUTE_DIRECTORY); +} + +static void EnsureDirectoryExists(const char* path) +{ + CreateDirectoryA(path, NULL); +} + +static int64_t GetFileModTimeInt64(const char* path) +{ + WIN32_FILE_ATTRIBUTE_DATA data; + if (GetFileAttributesExA(path, GetFileExInfoStandard, &data)) + { + ULARGE_INTEGER li; + li.LowPart = data.ftLastWriteTime.dwLowDateTime; + li.HighPart = data.ftLastWriteTime.dwHighDateTime; + return (int64_t)li.QuadPart; + } + return 0; +} + +// ============================================================================ +// FNV-1a hash +// ============================================================================ + +static uint64_t ComputeFnv1aHash(const char* data, long size) +{ + uint64_t hash = 14695981039346656037ULL; + for (long i = 0; i < size; i++) + { + hash ^= (uint64_t)(unsigned char)data[i]; + hash *= 1099511628211ULL; + } + return hash; +} + +// ============================================================================ +// Hash cache +// ============================================================================ + +typedef struct +{ + char Path[MAX_PATH_LEN]; + char HashStr[32]; +} HashEntry; + +typedef struct +{ + HashEntry* Entries; + int Count; +} HashCache; + +static HashCache* CreateHashCache() +{ + HashCache* cache = (HashCache*)ArenaPush(sizeof(HashCache)); + cache->Entries = (HashEntry*)ArenaPush(sizeof(HashEntry) * MAX_HASH_ENTRIES); + cache->Count = 0; + return cache; +} + +static const char* HashCacheFind(const HashCache* cache, const char* path) +{ + for (int i = 0; i < cache->Count; i++) + { + if (strcmp(cache->Entries[i].Path, path) == 0) + { + return cache->Entries[i].HashStr; + } + } + return NULL; +} + +static void HashCacheAdd(HashCache* cache, const char* path, const char* hash) +{ + if (cache->Count < MAX_HASH_ENTRIES) + { + strncpy_s(cache->Entries[cache->Count].Path, MAX_PATH_LEN, path, _TRUNCATE); + strncpy_s(cache->Entries[cache->Count].HashStr, 32, hash, _TRUNCATE); + cache->Count++; + } +} + +static void LoadHashCache(const char* cachePath, HashCache* cache) +{ + FileContent content = ReadEntireFile(cachePath); + if (!content.Data) return; + + const char* pos = content.Data; + while (*pos != '\0') + { + const char* lineStart = pos; + while (*pos != '\0' && *pos != '\n' && *pos != '\r') + pos++; + int lineLen = (int)(pos - lineStart); + + if (lineLen > 0) + { + const char* sep = NULL; + for (const char* c = lineStart; c < lineStart + lineLen; c++) + { + if (*c == '=') + { + sep = c; + break; + } + } + if (sep) + { + char key[MAX_PATH_LEN] = { 0 }; + int keyLen = (int)(sep - lineStart); + if (keyLen >= MAX_PATH_LEN) keyLen = MAX_PATH_LEN - 1; + memcpy(key, lineStart, keyLen); + + int valLen = (int)((lineStart + lineLen) - (sep + 1)); + char val[32] = { 0 }; + if (valLen > 0 && valLen < 32) + { + memcpy(val, sep + 1, valLen); + } + HashCacheAdd(cache, key, val); + } + } + while (*pos == '\n' || *pos == '\r') + pos++; + } +} + +static void SaveHashCache(const char* cachePath, const HashCache* cache) +{ + long bufSize = MAX_HASH_ENTRIES * (MAX_PATH_LEN + 32 + 2); + char* buffer = (char*)ArenaPush(bufSize); + int offset = 0; + + for (int i = 0; i < cache->Count; i++) + { + int written = sprintf_s(buffer + offset, bufSize - offset, "%s=%s\n", cache->Entries[i].Path, cache->Entries[i].HashStr); + if (written > 0) offset += written; + } + WriteEntireFile(cachePath, buffer, offset); +} + +// ============================================================================ +// Clean #pragma message lines and compute hash for a unity file +// ============================================================================ + +typedef struct +{ + char HashStr[32]; + bool WasCleaned; +} CleanResult; + +static CleanResult CleanAndHashUnityFile(const char* filePath) +{ + CleanResult result = { { 0 }, false }; + FileContent content = ReadEntireFile(filePath); + if (!content.Data) return result; + + if (StringContains(content.Data, "#pragma message")) + { + char* filtered = (char*)ArenaPush(content.Size + 1); + long filteredSize = 0; + + const char* pos = content.Data; + while (*pos != '\0') + { + const char* lineStart = pos; + while (*pos != '\0' && *pos != '\n') + pos++; + + bool skipLine = false; + for (const char* c = lineStart; c < pos; c++) + { + if (c + 15 <= content.Data + content.Size && memcmp(c, "#pragma message", 15) == 0) + { + skipLine = true; + break; + } + } + + if (!skipLine) + { + long lineLen = (long)(pos - lineStart); + memcpy(filtered + filteredSize, lineStart, lineLen); + filteredSize += lineLen; + if (*pos == '\n') filtered[filteredSize++] = '\n'; + } + if (*pos == '\n') pos++; + } + filtered[filteredSize] = '\0'; + + WriteEntireFile(filePath, filtered, filteredSize); + uint64_t hashVal = ComputeFnv1aHash(filtered, filteredSize); + sprintf_s(result.HashStr, sizeof(result.HashStr), "%llu", hashVal); + result.WasCleaned = true; + } + else + { + uint64_t hashVal = ComputeFnv1aHash(content.Data, content.Size); + sprintf_s(result.HashStr, sizeof(result.HashStr), "%llu", hashVal); + } + return result; +} + +// ============================================================================ +// Recursive directory scan +// ============================================================================ + +static int64_t GetNewestSourceTimeRecursive(const char* dirPath) +{ + int64_t newest = 0; + if (!DirectoryExists(dirPath)) return newest; + + char searchPath[MAX_PATH_LEN]; + snprintf(searchPath, sizeof(searchPath), "%s\\*", dirPath); + + WIN32_FIND_DATAA findData; + HANDLE hFind = FindFirstFileA(searchPath, &findData); + if (hFind == INVALID_HANDLE_VALUE) return newest; + + do + { + if (findData.cFileName[0] == '.' && + (findData.cFileName[1] == '\0' || (findData.cFileName[1] == '.' && findData.cFileName[2] == '\0'))) + { + continue; + } + + char fullPath[MAX_PATH_LEN]; + snprintf(fullPath, sizeof(fullPath), "%s\\%s", dirPath, findData.cFileName); + + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + int64_t subNewest = GetNewestSourceTimeRecursive(fullPath); + if (subNewest > newest) newest = subNewest; + } + else + { + if (EndsWithNoCase(findData.cFileName, ".cpp") || EndsWithNoCase(findData.cFileName, ".h") || + EndsWithNoCase(findData.cFileName, ".hpp") || EndsWithNoCase(findData.cFileName, ".inl") || + EndsWithNoCase(findData.cFileName, ".c")) + { + ULARGE_INTEGER li; + li.LowPart = findData.ftLastWriteTime.dwLowDateTime; + li.HighPart = findData.ftLastWriteTime.dwHighDateTime; + int64_t ft = (int64_t)li.QuadPart; + if (ft > newest) newest = ft; + } + } + } + while (FindNextFileA(hFind, &findData)); + + FindClose(hFind); + return newest; +} + +// ============================================================================ +// Collect unity files +// ============================================================================ + +typedef struct +{ + char (*Paths)[MAX_PATH_LEN]; + int Count; +} FilePathList; + +static FilePathList* CreateFilePathList() +{ + FilePathList* list = (FilePathList*)ArenaPush(sizeof(FilePathList)); + list->Paths = (char(*)[MAX_PATH_LEN])ArenaPush(MAX_FILES * MAX_PATH_LEN); + list->Count = 0; + return list; +} + +static void FilePathListAdd(FilePathList* list, const char* path) +{ + if (list->Count < MAX_FILES) + { + strncpy_s(list->Paths[list->Count], MAX_PATH_LEN, path, _TRUNCATE); + list->Count++; + } +} + +static void CollectUnityFiles(const char* dirPath, FilePathList* list) +{ + char searchPath[MAX_PATH_LEN]; + snprintf(searchPath, sizeof(searchPath), "%s\\*", dirPath); + + WIN32_FIND_DATAA findData; + HANDLE hFind = FindFirstFileA(searchPath, &findData); + if (hFind == INVALID_HANDLE_VALUE) return; + + do + { + if (findData.cFileName[0] == '.' && + (findData.cFileName[1] == '\0' || (findData.cFileName[1] == '.' && findData.cFileName[2] == '\0'))) + { + continue; + } + + char fullPath[MAX_PATH_LEN]; + snprintf(fullPath, sizeof(fullPath), "%s\\%s", dirPath, findData.cFileName); + + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + CollectUnityFiles(fullPath, list); + } + else + { + if (StringContains(findData.cFileName, "_Unity") && EndsWithNoCase(findData.cFileName, ".cpp")) + { + FilePathListAdd(list, fullPath); + } + } + } + while (FindNextFileA(hFind, &findData)); + + FindClose(hFind); +} + +// ============================================================================ +// Commands +// ============================================================================ + +typedef struct +{ + char StepName[256]; + char OutputFile[MAX_PATH_LEN]; + char Depends[MAX_PATH_LEN]; + char Command[MAX_FILES]; +} BuildStep; + +typedef struct +{ + char Name[64]; + char FastBuildTargets[512]; + BuildStep Steps[32]; + int StepCount; +} BuildConfig; + +static int ExecuteCmd(const char* cmdLine) +{ + STARTUPINFOA si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi = { 0 }; + DWORD exitCode = 1; + + char* cmdBuf = (char*)ArenaPush(MAX_FILES); + strcpy_s(cmdBuf, MAX_FILES, cmdLine); + + if (CreateProcessA(NULL, cmdBuf, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + { + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &exitCode); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else + { + printf("[ERROR] Failed to execute command.\n"); + return 1; + } + return (int)exitCode; +} + +typedef struct +{ + char Path[MAX_PATH_LEN]; + int64_t Time; +} DirCacheEntry; + +static DirCacheEntry g_DirCache[64]; +static int g_DirCacheCount = 0; + +static int64_t GetDirTime(const char* dir) +{ + for (int k = 0; k < g_DirCacheCount; k++) + { + if (_stricmp(g_DirCache[k].Path, dir) == 0) return g_DirCache[k].Time; + } + int64_t t = GetNewestSourceTimeRecursive(dir); + if (g_DirCacheCount < 64) + { + strncpy_s(g_DirCache[g_DirCacheCount].Path, MAX_PATH_LEN, dir, _TRUNCATE); + g_DirCache[g_DirCacheCount].Time = t; + g_DirCacheCount++; + } + return t; +} + +static char g_TimingsBuf[MAX_FILES * 2]; +static int g_TimingsLen = 0; + +static void AppendTiming(const char* stepName, double secs) +{ + g_TimingsLen += sprintf_s(g_TimingsBuf + g_TimingsLen, sizeof(g_TimingsBuf) - g_TimingsLen, "%s|%.3fs\n", stepName, secs); +} + +static int CommandRunPlan(int argc, char* argv[]) +{ + if (argc < 3) + { + printf("[ERROR] Usage: build_system run_plan \n"); + return 1; + } + const char* planFile = argv[2]; + + FileContent planContent = ReadEntireFile(planFile); + if (!planContent.Data) + { + printf("[ERROR] Could not read build plan: %s\n", planFile); + return 1; + } + + EnsureDirectoryExists("Intermediate"); + DeleteFileA("Intermediate\\step_timings.tmp"); + DeleteFileA("Intermediate\\built_outputs.tmp"); + + LARGE_INTEGER perfStart, perfEnd, perfFreq; + QueryPerformanceCounter(&perfStart); + QueryPerformanceFrequency(&perfFreq); + + // Parse plan file + BuildConfig configs[4]; + memset(configs, 0, sizeof(configs)); + int configCount = 0; + + const char* pos = planContent.Data; + while (*pos != '\0') + { + const char* lineStart = pos; + while (*pos != '\0' && *pos != '\n' && *pos != '\r') + pos++; + int lineLen = (int)(pos - lineStart); + + if (lineLen > 0) + { + if (lineLen > 7 && memcmp(lineStart, "CONFIG ", 7) == 0) + { + if (configCount < 4) + { + int nLen = lineLen - 7; + if (nLen >= (int)sizeof(configs[configCount].Name)) nLen = (int)sizeof(configs[configCount].Name) - 1; + memcpy(configs[configCount].Name, lineStart + 7, nLen); + configs[configCount].Name[nLen] = '\0'; + configCount++; + } + } + else if (configCount > 0) + { + BuildConfig* curCfg = &configs[configCount - 1]; + if (lineLen > 10 && memcmp(lineStart, "FASTBUILD ", 10) == 0) + { + int nLen = lineLen - 10; + if (nLen >= (int)sizeof(curCfg->FastBuildTargets)) nLen = (int)sizeof(curCfg->FastBuildTargets) - 1; + memcpy(curCfg->FastBuildTargets, lineStart + 10, nLen); + curCfg->FastBuildTargets[nLen] = '\0'; + } + else if (lineLen > 5 && memcmp(lineStart, "STEP ", 5) == 0) + { + if (curCfg->StepCount < 32) + { + int nLen = lineLen - 5; + if (nLen >= (int)sizeof(curCfg->Steps[curCfg->StepCount].StepName)) + nLen = (int)sizeof(curCfg->Steps[curCfg->StepCount].StepName) - 1; + memcpy(curCfg->Steps[curCfg->StepCount].StepName, lineStart + 5, nLen); + curCfg->Steps[curCfg->StepCount].StepName[nLen] = '\0'; + curCfg->StepCount++; + } + } + else if (lineLen > 7 && memcmp(lineStart, "OUTPUT ", 7) == 0 && curCfg->StepCount > 0) + { + int nLen = lineLen - 7; + if (nLen >= (int)sizeof(curCfg->Steps[curCfg->StepCount - 1].OutputFile)) + nLen = (int)sizeof(curCfg->Steps[curCfg->StepCount - 1].OutputFile) - 1; + memcpy(curCfg->Steps[curCfg->StepCount - 1].OutputFile, lineStart + 7, nLen); + curCfg->Steps[curCfg->StepCount - 1].OutputFile[nLen] = '\0'; + } + else if (lineLen > 8 && memcmp(lineStart, "DEPENDS ", 8) == 0 && curCfg->StepCount > 0) + { + int nLen = lineLen - 8; + if (nLen >= (int)sizeof(curCfg->Steps[curCfg->StepCount - 1].Depends)) + nLen = (int)sizeof(curCfg->Steps[curCfg->StepCount - 1].Depends) - 1; + memcpy(curCfg->Steps[curCfg->StepCount - 1].Depends, lineStart + 8, nLen); + curCfg->Steps[curCfg->StepCount - 1].Depends[nLen] = '\0'; + } + else if (lineLen > 8 && memcmp(lineStart, "COMMAND ", 8) == 0 && curCfg->StepCount > 0) + { + int nLen = lineLen - 8; + if (nLen >= (int)sizeof(curCfg->Steps[curCfg->StepCount - 1].Command)) + nLen = (int)sizeof(curCfg->Steps[curCfg->StepCount - 1].Command) - 1; + memcpy(curCfg->Steps[curCfg->StepCount - 1].Command, lineStart + 8, nLen); + curCfg->Steps[curCfg->StepCount - 1].Command[nLen] = '\0'; + } + } + } + while (*pos == '\n' || *pos == '\r') + pos++; + } + + bool anyBuilt = false; + + for (int i = 0; i < configCount; i++) + { + BuildConfig* cfg = &configs[i]; + printf("\n==============================================================================\n"); + printf("Generating Unity Files into Intermediate/ via FASTBuild [%s]...\n", cfg->Name); + printf("==============================================================================\n"); + + if (cfg->FastBuildTargets[0] != '\0') + { + char fbuildCmd[MAX_PATH_LEN]; + sprintf_s(fbuildCmd, sizeof(fbuildCmd), "misc\\fbuild.exe %s", cfg->FastBuildTargets); + + LARGE_INTEGER tStart, tEnd; + QueryPerformanceCounter(&tStart); + int res = ExecuteCmd(fbuildCmd); + QueryPerformanceCounter(&tEnd); + + char stepName[MAX_PATH_LEN]; + sprintf_s(stepName, sizeof(stepName), "FASTBuild Unity Gen [%s]", cfg->Name); + AppendTiming(stepName, (double)(tEnd.QuadPart - tStart.QuadPart) / (double)perfFreq.QuadPart); + + if (res != 0) + { + printf("[BUILD FAILED] FASTBuild failed for %s.\n", cfg->Name); + return res; + } + } + + HashCache* oldCache = CreateHashCache(); + LoadHashCache("Intermediate\\.unity_hashes_cache", oldCache); + + HashCache* newCache = CreateHashCache(); + bool unityChanged = false; + + FilePathList* unityFiles = CreateFilePathList(); + CollectUnityFiles("Intermediate", unityFiles); + + if (unityFiles->Count == 0) + { + unityChanged = true; + } + else + { + for (int k = 0; k < unityFiles->Count; k++) + { + CleanResult cr = CleanAndHashUnityFile(unityFiles->Paths[k]); + HashCacheAdd(newCache, unityFiles->Paths[k], cr.HashStr); + + const char* oldHash = HashCacheFind(oldCache, unityFiles->Paths[k]); + if (oldHash == NULL || strcmp(oldHash, cr.HashStr) != 0) + { + unityChanged = true; + } + } + } + SaveHashCache("Intermediate\\.unity_hashes_cache", newCache); + + if (unityChanged) + { + printf("[FASTBuild] Unity structure or source file list changed.\n"); + } + else + { + printf("[FASTBuild] No Unity file structure changes detected [up-to-date].\n"); + } + + printf("\n==============================================================================\n"); + printf("Compiling Targets [%s]...\n", cfg->Name); + printf("==============================================================================\n"); + + g_DirCacheCount = 0; + bool anyNeedBuild = false; + + for (int j = 0; j < cfg->StepCount; j++) + { + BuildStep* step = &cfg->Steps[j]; + int64_t maxTime = 0; + const char* cur = step->Depends; + while (*cur) + { + const char* nextPipe = strchr(cur, '|'); + char dirPath[MAX_PATH_LEN] = { 0 }; + if (nextPipe) + { + int dLen = (int)(nextPipe - cur); + if (dLen >= (int)sizeof(dirPath)) dLen = (int)sizeof(dirPath) - 1; + memcpy(dirPath, cur, dLen); + dirPath[dLen] = '\0'; + cur = nextPipe + 1; + } + else + { + strcpy_s(dirPath, sizeof(dirPath), cur); + cur += strlen(cur); + } + if (dirPath[0] != '\0') + { + int64_t t = GetDirTime(dirPath); + if (t > maxTime) maxTime = t; + } + } + + bool needCompile = unityChanged; + if (!needCompile) + { + if (!FileExists(step->OutputFile)) + { + needCompile = true; + } + else + { + int64_t outTime = GetFileModTimeInt64(step->OutputFile); + if (outTime < maxTime) needCompile = true; + } + } + + printf("\n--- %s ---\n", step->StepName); + if (!needCompile) + { + printf("[SKIP / UP-TO-DATE] %s [Binary up to date]\n", step->OutputFile); + } + else + { + printf("[REBUILDING] Compiling %s...\n", step->OutputFile); + anyNeedBuild = true; + + LARGE_INTEGER tStart, tEnd; + QueryPerformanceCounter(&tStart); + int res = ExecuteCmd(step->Command); + QueryPerformanceCounter(&tEnd); + + AppendTiming(step->StepName, (double)(tEnd.QuadPart - tStart.QuadPart) / (double)perfFreq.QuadPart); + + if (res != 0) + { + printf("[BUILD FAILED] Command failed with exit code %d.\n", res); + return res; + } + + char builtOut[MAX_PATH_LEN + 2]; + sprintf_s(builtOut, sizeof(builtOut), "%s\n", step->OutputFile); + + HANDLE hFile = CreateFileA("Intermediate\\built_outputs.tmp", FILE_APPEND_DATA, FILE_SHARE_READ, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD written = 0; + WriteFile(hFile, builtOut, (DWORD)strlen(builtOut), &written, NULL); + CloseHandle(hFile); + } + anyBuilt = true; + } + } + + if (!anyNeedBuild) + { + printf("\n[SKIP / UP-TO-DATE] All requested targets in [%s] are up-to-date.\n", cfg->Name); + } + } + + if (g_TimingsLen > 0) + { + WriteEntireFile("Intermediate\\step_timings.tmp", g_TimingsBuf, g_TimingsLen); + } + + QueryPerformanceCounter(&perfEnd); + double elapsedSeconds = (double)(perfEnd.QuadPart - perfStart.QuadPart) / (double)perfFreq.QuadPart; + + char elapsedBuf[64]; + if (elapsedSeconds < 60.0) + { + sprintf_s(elapsedBuf, sizeof(elapsedBuf), "%.3fs", elapsedSeconds); + } + else + { + int totalMs = (int)(elapsedSeconds * 1000.0); + int hours = totalMs / 3600000; + int minutes = (totalMs % 3600000) / 60000; + int seconds = (totalMs % 60000) / 1000; + int ms = totalMs % 1000; + sprintf_s(elapsedBuf, sizeof(elapsedBuf), "%02d:%02d:%02d.%03d", hours, minutes, seconds, ms); + } + + printf("\n==============================================================================\n"); + printf("[BUILD SUMMARY]\n"); + printf("==============================================================================\n"); + + if (g_TimingsLen > 0) + { + printf("Step Timings:\n"); + const char* pos = g_TimingsBuf; + while (*pos) + { + const char* lineStart = pos; + while (*pos && *pos != '\n') + pos++; + int len = (int)(pos - lineStart); + if (len > 0) + { + const char* pipe = strchr(lineStart, '|'); + if (pipe && pipe < pos) + { + char stepName[MAX_PATH_LEN] = { 0 }; + int nameLen = (int)(pipe - lineStart); + if (nameLen >= MAX_PATH_LEN) nameLen = MAX_PATH_LEN - 1; + memcpy(stepName, lineStart, nameLen); + + char durStr[64] = { 0 }; + int durLen = (int)(pos - (pipe + 1)); + if (durLen >= 64) durLen = 63; + memcpy(durStr, pipe + 1, durLen); + + printf(" - %-42s : %s\n", stepName, durStr); + } + } + if (*pos == '\n') pos++; + } + printf("------------------------------------------------------------------------------\n"); + } + + printf("Total Duration : %s\n", elapsedBuf); + printf("Output Binaries:\n"); + + if (anyBuilt) + { + FileContent outputsContent = ReadEntireFile("Intermediate\\built_outputs.tmp"); + if (outputsContent.Data) + { + const char* pos = outputsContent.Data; + while (*pos) + { + const char* lineStart = pos; + while (*pos && *pos != '\n' && *pos != '\r') + pos++; + int len = (int)(pos - lineStart); + if (len > 0) + { + char filePath[MAX_PATH_LEN] = { 0 }; + if (len >= MAX_PATH_LEN) len = MAX_PATH_LEN - 1; + memcpy(filePath, lineStart, len); + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExA(filePath, GetFileExInfoStandard, &fileData)) + { + ULARGE_INTEGER fileSize; + fileSize.LowPart = fileData.nFileSizeLow; + fileSize.HighPart = fileData.nFileSizeHigh; + double sizeMB = (double)fileSize.QuadPart / (1024.0 * 1024.0); + printf(" - %s (%.2f MB)\n", filePath, sizeMB); + } + } + while (*pos == '\n' || *pos == '\r') + pos++; + } + } + DeleteFileA("Intermediate\\built_outputs.tmp"); + } + else + { + printf(" - None built in this session (all requested targets up-to-date)\n"); + } + + printf("==============================================================================\n"); + printf("[ALL BUILDS SUCCEEDED] All requested targets processed successfully.\n"); + printf("==============================================================================\n"); + fflush(stdout); + + return 0; +} + +int main(int argc, char* argv[]) +{ + // 256 MB Bump allocator + ArenaInit(256 * 1024 * 1024); + + if (argc < 2) + { + printf("Usage: build_system [args...]\n"); + printf("Commands:\n"); + printf(" run_plan Run the build from a generated plan file\n"); + printf(" check_alive Returns 0 immediately to test execution\n"); + return 1; + } + + const char* cmd = argv[1]; + if (strcmp(cmd, "check_alive") == 0) return 0; + if (strcmp(cmd, "run_plan") == 0) return CommandRunPlan(argc, argv); + + printf("[ERROR] Unknown command: %s\n", cmd); + return 1; +} diff --git a/tools/build_system.cpp b/tools/build_system.cpp deleted file mode 100644 index 88cc177..0000000 --- a/tools/build_system.cpp +++ /dev/null @@ -1,1058 +0,0 @@ -// tools/build.cpp - Fast native C++ build orchestrator for Juliet project -// Replaces all PowerShell scripts and orchestrates the full build process -#ifndef NOMINMAX -#define NOMINMAX -#endif -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include -#include -#include -#include - -// ============================================================================ -// Minimal string / path helpers (no STL, no std::filesystem — pure speed) -// ============================================================================ - -static constexpr int MaxPath = 1024; -static constexpr int MaxFiles = 4096; -static constexpr int MaxHashEntries = 512; - -struct FixedString -{ - char Data[MaxPath]; - int Length; - - FixedString() - : Data{} - , Length(0) - { - } - - void Set(const char* str) - { - assert(str != nullptr); - Length = 0; - while (str[Length] != '\0' && Length < MaxPath - 1) - { - Data[Length] = str[Length]; - Length++; - } - Data[Length] = '\0'; - } - - void Set(const char* str, int len) - { - assert(str != nullptr); - assert(len >= 0 && len < MaxPath); - for (int i = 0; i < len; i++) - { - Data[i] = str[i]; - } - Data[len] = '\0'; - Length = len; - } - - void Append(const char* str) - { - assert(str != nullptr); - int i = 0; - while (str[i] != '\0' && Length < MaxPath - 1) - { - Data[Length++] = str[i++]; - } - Data[Length] = '\0'; - } - - void AppendChar(char c) - { - if (Length < MaxPath - 1) - { - Data[Length++] = c; - Data[Length] = '\0'; - } - } - - [[nodiscard]] bool Equals(const char* other) const - { - assert(other != nullptr); - return strcmp(Data, other) == 0; - } - - [[nodiscard]] bool EqualsNoCase(const char* other) const - { - assert(other != nullptr); - return _stricmp(Data, other) == 0; - } -}; - -[[nodiscard]] static bool StringContains(const char* haystack, const char* needle) -{ - assert(haystack != nullptr); - assert(needle != nullptr); - return strstr(haystack, needle) != nullptr; -} - -[[nodiscard]] static bool EndsWithNoCase(const char* str, const char* suffix) -{ - assert(str != nullptr); - assert(suffix != nullptr); - int strLen = static_cast(strlen(str)); - int sufLen = static_cast(strlen(suffix)); - if (sufLen > strLen) - { - return false; - } - return _stricmp(str + strLen - sufLen, suffix) == 0; -} - -// ============================================================================ -// File I/O helpers using Win32 -// ============================================================================ - -struct FileContent -{ - char* Data; - DWORD Size; -}; - -[[nodiscard]] static FileContent ReadEntireFile(const char* path) -{ - assert(path != nullptr); - FileContent result = { nullptr, 0 }; - HANDLE file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr); - if (file == INVALID_HANDLE_VALUE) - { - return result; - } - DWORD size = GetFileSize(file, nullptr); - if (size == INVALID_FILE_SIZE || size == 0) - { - CloseHandle(file); - return result; - } - char* buffer = static_cast(HeapAlloc(GetProcessHeap(), 0, size + 1)); - if (buffer == nullptr) - { - CloseHandle(file); - return result; - } - DWORD bytesRead = 0; - ReadFile(file, buffer, size, &bytesRead, nullptr); - CloseHandle(file); - buffer[bytesRead] = '\0'; - result.Data = buffer; - result.Size = bytesRead; - return result; -} - -static void FreeFileContent(FileContent& content) -{ - if (content.Data != nullptr) - { - HeapFree(GetProcessHeap(), 0, content.Data); - content.Data = nullptr; - content.Size = 0; - } -} - -static bool WriteEntireFile(const char* path, const char* data, DWORD size) -{ - assert(path != nullptr); - assert(data != nullptr); - HANDLE file = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, nullptr); - if (file == INVALID_HANDLE_VALUE) - { - return false; - } - DWORD written = 0; - WriteFile(file, data, size, &written, nullptr); - CloseHandle(file); - return written == size; -} - -[[nodiscard]] static bool FileExists(const char* path) -{ - assert(path != nullptr); - DWORD attribs = GetFileAttributesA(path); - return (attribs != INVALID_FILE_ATTRIBUTES) && !(attribs & FILE_ATTRIBUTE_DIRECTORY); -} - -[[nodiscard]] static bool DirectoryExists(const char* path) -{ - assert(path != nullptr); - DWORD attribs = GetFileAttributesA(path); - return (attribs != INVALID_FILE_ATTRIBUTES) && (attribs & FILE_ATTRIBUTE_DIRECTORY); -} - -static void EnsureDirectoryExists(const char* path) -{ - assert(path != nullptr); - CreateDirectoryA(path, nullptr); -} - -[[nodiscard]] static int64_t GetFileModTimeInt64(const char* path) -{ - assert(path != nullptr); - WIN32_FILE_ATTRIBUTE_DATA data; - if (GetFileAttributesExA(path, GetFileExInfoStandard, &data)) - { - ULARGE_INTEGER li; - li.LowPart = data.ftLastWriteTime.dwLowDateTime; - li.HighPart = data.ftLastWriteTime.dwHighDateTime; - return static_cast(li.QuadPart); - } - return 0; -} - -// ============================================================================ -// FNV-1a hash -// ============================================================================ - -[[nodiscard]] static uint64_t ComputeFnv1aHash(const char* data, DWORD size) -{ - assert(data != nullptr); - uint64_t hash = 14695981039346656037ULL; - for (DWORD i = 0; i < size; i++) - { - hash ^= static_cast(static_cast(data[i])); - hash *= 1099511628211ULL; - } - return hash; -} - -// ============================================================================ -// Hash cache for unity files -// ============================================================================ - -struct HashEntry -{ - FixedString Path; - char HashStr[32]; -}; - -struct HashCache -{ - HashEntry* Entries; - int Count; - - HashCache() - { - Entries = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HashEntry) * MaxHashEntries)); - assert(Entries != nullptr); - Count = 0; - } - - ~HashCache() - { - if (Entries != nullptr) - { - HeapFree(GetProcessHeap(), 0, Entries); - Entries = nullptr; - } - } - - HashCache(const HashCache&) = delete; - HashCache& operator=(const HashCache&) = delete; - - [[nodiscard]] const char* Find(const char* path) const - { - assert(path != nullptr); - for (int i = 0; i < Count; i++) - { - if (Entries[i].Path.Equals(path)) - { - return Entries[i].HashStr; - } - } - return nullptr; - } - - void Add(const char* path, const char* hash) - { - assert(path != nullptr); - assert(hash != nullptr); - if (Count < MaxHashEntries && Entries != nullptr) - { - Entries[Count].Path.Set(path); - strncpy_s(Entries[Count].HashStr, hash, sizeof(Entries[Count].HashStr) - 1); - Count++; - } - } -}; - -static void LoadHashCache(const char* cachePath, HashCache& cache) -{ - assert(cachePath != nullptr); - FileContent content = ReadEntireFile(cachePath); - if (content.Data == nullptr) - { - return; - } - - const char* pos = content.Data; - while (*pos != '\0') - { - const char* lineStart = pos; - while (*pos != '\0' && *pos != '\n' && *pos != '\r') - { - pos++; - } - int lineLen = static_cast(pos - lineStart); - - if (lineLen > 0) - { - const char* sep = nullptr; - for (const char* c = lineStart; c < lineStart + lineLen; c++) - { - if (*c == '=') - { - sep = c; - break; - } - } - if (sep != nullptr) - { - FixedString key; - key.Set(lineStart, static_cast(sep - lineStart)); - - int valLen = static_cast((lineStart + lineLen) - (sep + 1)); - char val[32] = {}; - if (valLen > 0 && valLen < 32) - { - memcpy(val, sep + 1, static_cast(valLen)); - val[valLen] = '\0'; - } - cache.Add(key.Data, val); - } - } - - while (*pos == '\n' || *pos == '\r') - { - pos++; - } - } - - FreeFileContent(content); -} - -static void SaveHashCache(const char* cachePath, const HashCache& cache) -{ - assert(cachePath != nullptr); - size_t bufSize = static_cast(MaxHashEntries) * (MaxPath + 32 + 2); - char* buffer = static_cast(HeapAlloc(GetProcessHeap(), 0, bufSize)); - if (buffer == nullptr) - { - return; - } - int offset = 0; - - for (int i = 0; i < cache.Count; i++) - { - int written = sprintf_s(buffer + offset, bufSize - static_cast(offset), "%s=%s\n", - cache.Entries[i].Path.Data, cache.Entries[i].HashStr); - if (written > 0) - { - offset += written; - } - } - - WriteEntireFile(cachePath, buffer, static_cast(offset)); - HeapFree(GetProcessHeap(), 0, buffer); -} - -// ============================================================================ -// Clean #pragma message lines and compute hash for a unity file -// ============================================================================ - -struct CleanResult -{ - char HashStr[32]; - bool WasCleaned; -}; - -[[nodiscard]] static CleanResult CleanAndHashUnityFile(const char* filePath) -{ - assert(filePath != nullptr); - CleanResult result = { {}, false }; - - FileContent content = ReadEntireFile(filePath); - if (content.Data == nullptr) - { - result.HashStr[0] = '\0'; - return result; - } - - bool hasPragma = StringContains(content.Data, "#pragma message"); - - if (hasPragma) - { - // Filter out #pragma message lines - char* filtered = static_cast(HeapAlloc(GetProcessHeap(), 0, content.Size + 1)); - assert(filtered != nullptr); - DWORD filteredSize = 0; - - const char* pos = content.Data; - while (*pos != '\0') - { - const char* lineStart = pos; - while (*pos != '\0' && *pos != '\n') - { - pos++; - } - bool skipLine = false; - for (const char* c = lineStart; c < pos; c++) - { - if (c + 15 <= content.Data + content.Size && memcmp(c, "#pragma message", 15) == 0) - { - skipLine = true; - break; - } - } - if (!skipLine) - { - DWORD lineLen = static_cast(pos - lineStart); - memcpy(filtered + filteredSize, lineStart, lineLen); - filteredSize += lineLen; - if (*pos == '\n') - { - filtered[filteredSize++] = '\n'; - } - } - if (*pos == '\n') - { - pos++; - } - } - filtered[filteredSize] = '\0'; - - WriteEntireFile(filePath, filtered, filteredSize); - - uint64_t hashVal = ComputeFnv1aHash(filtered, filteredSize); - sprintf_s(result.HashStr, "%llu", hashVal); - result.WasCleaned = true; - - HeapFree(GetProcessHeap(), 0, filtered); - } - else - { - uint64_t hashVal = ComputeFnv1aHash(content.Data, content.Size); - sprintf_s(result.HashStr, "%llu", hashVal); - } - - FreeFileContent(content); - return result; -} - -// ============================================================================ -// Recursive directory scan for newest source file time -// ============================================================================ - -[[nodiscard]] static int64_t GetNewestSourceTimeRecursive(const char* dirPath) -{ - assert(dirPath != nullptr); - int64_t newest = 0; - - if (!DirectoryExists(dirPath)) - { - return newest; - } - - FixedString searchPath; - searchPath.Set(dirPath); - searchPath.Append("\\*"); - - WIN32_FIND_DATAA findData; - HANDLE hFind = FindFirstFileA(searchPath.Data, &findData); - if (hFind == INVALID_HANDLE_VALUE) - { - return newest; - } - - do - { - if (findData.cFileName[0] == '.' && - (findData.cFileName[1] == '\0' || (findData.cFileName[1] == '.' && findData.cFileName[2] == '\0'))) - { - continue; - } - - FixedString fullPath; - fullPath.Set(dirPath); - fullPath.AppendChar('\\'); - fullPath.Append(findData.cFileName); - - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - int64_t subNewest = GetNewestSourceTimeRecursive(fullPath.Data); - if (subNewest > newest) - { - newest = subNewest; - } - } - else - { - if (EndsWithNoCase(findData.cFileName, ".cpp") || EndsWithNoCase(findData.cFileName, ".h") || - EndsWithNoCase(findData.cFileName, ".hpp") || EndsWithNoCase(findData.cFileName, ".inl") || - EndsWithNoCase(findData.cFileName, ".c")) - { - ULARGE_INTEGER li; - li.LowPart = findData.ftLastWriteTime.dwLowDateTime; - li.HighPart = findData.ftLastWriteTime.dwHighDateTime; - int64_t ft = static_cast(li.QuadPart); - if (ft > newest) - { - newest = ft; - } - } - } - } - while (FindNextFileA(hFind, &findData)); - - FindClose(hFind); - return newest; -} - -// ============================================================================ -// Collect unity files recursively from Intermediate/ -// ============================================================================ - -struct FilePathList -{ - FixedString* Paths; - int Count; - - FilePathList() - { - Paths = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FixedString) * MaxFiles)); - assert(Paths != nullptr); - Count = 0; - } - - ~FilePathList() - { - if (Paths != nullptr) - { - HeapFree(GetProcessHeap(), 0, Paths); - Paths = nullptr; - } - } - - FilePathList(const FilePathList&) = delete; - FilePathList& operator=(const FilePathList&) = delete; - - void Add(const char* path) - { - assert(path != nullptr); - if (Count < MaxFiles && Paths != nullptr) - { - Paths[Count].Set(path); - Count++; - } - } -}; - -static void CollectUnityFiles(const char* dirPath, FilePathList& list) -{ - assert(dirPath != nullptr); - FixedString searchPath; - searchPath.Set(dirPath); - searchPath.Append("\\*"); - - WIN32_FIND_DATAA findData; - HANDLE hFind = FindFirstFileA(searchPath.Data, &findData); - if (hFind == INVALID_HANDLE_VALUE) - { - return; - } - - do - { - if (findData.cFileName[0] == '.' && - (findData.cFileName[1] == '\0' || (findData.cFileName[1] == '.' && findData.cFileName[2] == '\0'))) - { - continue; - } - - FixedString fullPath; - fullPath.Set(dirPath); - fullPath.AppendChar('\\'); - fullPath.Append(findData.cFileName); - - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - CollectUnityFiles(fullPath.Data, list); - } - else - { - if (StringContains(findData.cFileName, "_Unity") && EndsWithNoCase(findData.cFileName, ".cpp")) - { - list.Add(fullPath.Data); - } - } - } - while (FindNextFileA(hFind, &findData)); - - FindClose(hFind); -} - -// ============================================================================ -// Commands -// ============================================================================ - -struct BuildStep { - char StepName[256]; - char OutputFile[MaxPath]; - char Depends[MaxPath]; - char Command[MaxFiles]; -}; - -struct BuildConfig { - char Name[64]; - char FastBuildTargets[512]; - BuildStep Steps[32]; - int StepCount; -}; - -static int CommandRunPlan(int argc, char* argv[]) -{ - if (argc < 3) - { - printf("[ERROR] Usage: build_system run_plan \n"); - return 1; - } - const char* planFile = argv[2]; - - FileContent planContent = ReadEntireFile(planFile); - if (!planContent.Data) - { - printf("[ERROR] Could not read build plan: %s\n", planFile); - return 1; - } - - EnsureDirectoryExists("Intermediate"); - DeleteFileA("Intermediate\\step_timings.tmp"); - DeleteFileA("Intermediate\\built_outputs.tmp"); - - LARGE_INTEGER perfStart, perfEnd, perfFreq; - QueryPerformanceCounter(&perfStart); - QueryPerformanceFrequency(&perfFreq); - - // Parse plan file - BuildConfig configs[4] = {}; - int configCount = 0; - - const char* pos = planContent.Data; - while (*pos != '\0') - { - const char* lineStart = pos; - while (*pos != '\0' && *pos != '\n' && *pos != '\r') pos++; - int lineLen = static_cast(pos - lineStart); - - if (lineLen > 0) - { - if (lineLen > 7 && memcmp(lineStart, "CONFIG ", 7) == 0) - { - if (configCount < 4) - { - int nLen = lineLen - 7; - if (nLen >= static_cast(sizeof(configs[configCount].Name))) nLen = static_cast(sizeof(configs[configCount].Name)) - 1; - memcpy(configs[configCount].Name, lineStart + 7, nLen); - configs[configCount].Name[nLen] = '\0'; - configCount++; - } - } - else if (configCount > 0) - { - BuildConfig& curCfg = configs[configCount - 1]; - if (lineLen > 10 && memcmp(lineStart, "FASTBUILD ", 10) == 0) - { - int nLen = lineLen - 10; - if (nLen >= static_cast(sizeof(curCfg.FastBuildTargets))) nLen = static_cast(sizeof(curCfg.FastBuildTargets)) - 1; - memcpy(curCfg.FastBuildTargets, lineStart + 10, nLen); - curCfg.FastBuildTargets[nLen] = '\0'; - } - else if (lineLen > 5 && memcmp(lineStart, "STEP ", 5) == 0) - { - if (curCfg.StepCount < 32) - { - int nLen = lineLen - 5; - if (nLen >= static_cast(sizeof(curCfg.Steps[curCfg.StepCount].StepName))) nLen = static_cast(sizeof(curCfg.Steps[curCfg.StepCount].StepName)) - 1; - memcpy(curCfg.Steps[curCfg.StepCount].StepName, lineStart + 5, nLen); - curCfg.Steps[curCfg.StepCount].StepName[nLen] = '\0'; - curCfg.StepCount++; - } - } - else if (lineLen > 7 && memcmp(lineStart, "OUTPUT ", 7) == 0 && curCfg.StepCount > 0) - { - int nLen = lineLen - 7; - if (nLen >= static_cast(sizeof(curCfg.Steps[curCfg.StepCount - 1].OutputFile))) nLen = static_cast(sizeof(curCfg.Steps[curCfg.StepCount - 1].OutputFile)) - 1; - memcpy(curCfg.Steps[curCfg.StepCount - 1].OutputFile, lineStart + 7, nLen); - curCfg.Steps[curCfg.StepCount - 1].OutputFile[nLen] = '\0'; - } - else if (lineLen > 8 && memcmp(lineStart, "DEPENDS ", 8) == 0 && curCfg.StepCount > 0) - { - int nLen = lineLen - 8; - if (nLen >= static_cast(sizeof(curCfg.Steps[curCfg.StepCount - 1].Depends))) nLen = static_cast(sizeof(curCfg.Steps[curCfg.StepCount - 1].Depends)) - 1; - memcpy(curCfg.Steps[curCfg.StepCount - 1].Depends, lineStart + 8, nLen); - curCfg.Steps[curCfg.StepCount - 1].Depends[nLen] = '\0'; - } - else if (lineLen > 8 && memcmp(lineStart, "COMMAND ", 8) == 0 && curCfg.StepCount > 0) - { - int nLen = lineLen - 8; - if (nLen >= static_cast(sizeof(curCfg.Steps[curCfg.StepCount - 1].Command))) nLen = static_cast(sizeof(curCfg.Steps[curCfg.StepCount - 1].Command)) - 1; - memcpy(curCfg.Steps[curCfg.StepCount - 1].Command, lineStart + 8, nLen); - curCfg.Steps[curCfg.StepCount - 1].Command[nLen] = '\0'; - } - } - } - while (*pos == '\n' || *pos == '\r') pos++; - } - FreeFileContent(planContent); - - bool anyBuilt = false; - char timingsBuf[MaxFiles * 2] = {}; - int timingsLen = 0; - - auto AppendTiming = [&](const char* stepName, double secs) - { - timingsLen += sprintf_s(timingsBuf + timingsLen, sizeof(timingsBuf) - timingsLen, "%s|%.3fs\n", stepName, secs); - }; - - auto ExecuteCmd = [](const char* cmdLine) -> int - { - STARTUPINFOA si; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - PROCESS_INFORMATION pi = {}; - DWORD exitCode = 1; - - char* cmdBuf = static_cast(HeapAlloc(GetProcessHeap(), 0, MaxFiles)); - if (!cmdBuf) return 1; - strcpy_s(cmdBuf, MaxFiles, cmdLine); - - if (CreateProcessA(nullptr, cmdBuf, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi)) - { - WaitForSingleObject(pi.hProcess, INFINITE); - GetExitCodeProcess(pi.hProcess, &exitCode); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - else - { - printf("[ERROR] Failed to execute command.\n"); - HeapFree(GetProcessHeap(), 0, cmdBuf); - return 1; - } - HeapFree(GetProcessHeap(), 0, cmdBuf); - return static_cast(exitCode); - }; - - for (int i = 0; i < configCount; i++) - { - BuildConfig& cfg = configs[i]; - printf("\n==============================================================================\n"); - printf("Generating Unity Files into Intermediate/ via FASTBuild [%s]...\n", cfg.Name); - printf("==============================================================================\n"); - - if (cfg.FastBuildTargets[0] != '\0') - { - char fbuildCmd[MaxPath]; - sprintf_s(fbuildCmd, sizeof(fbuildCmd), "misc\\fbuild.exe %s", cfg.FastBuildTargets); - - LARGE_INTEGER tStart, tEnd; - QueryPerformanceCounter(&tStart); - int res = ExecuteCmd(fbuildCmd); - QueryPerformanceCounter(&tEnd); - - char stepName[MaxPath]; - sprintf_s(stepName, sizeof(stepName), "FASTBuild Unity Gen [%s]", cfg.Name); - AppendTiming(stepName, static_cast(tEnd.QuadPart - tStart.QuadPart) / static_cast(perfFreq.QuadPart)); - - if (res != 0) - { - printf("[BUILD FAILED] FASTBuild failed for %s.\n", cfg.Name); - return res; - } - } - - HashCache oldCache; - LoadHashCache("Intermediate\\.unity_hashes_cache", oldCache); - - HashCache newCache; - bool unityChanged = false; - - FilePathList unityFiles; - CollectUnityFiles("Intermediate", unityFiles); - - if (unityFiles.Count == 0) - { - unityChanged = true; - } - else - { - for (int k = 0; k < unityFiles.Count; k++) - { - CleanResult cr = CleanAndHashUnityFile(unityFiles.Paths[k].Data); - newCache.Add(unityFiles.Paths[k].Data, cr.HashStr); - - const char* oldHash = oldCache.Find(unityFiles.Paths[k].Data); - if (oldHash == nullptr || strcmp(oldHash, cr.HashStr) != 0) - { - unityChanged = true; - } - } - } - SaveHashCache("Intermediate\\.unity_hashes_cache", newCache); - - if (unityChanged) - { - printf("[FASTBuild] Unity structure or source file list changed.\n"); - } - else - { - printf("[FASTBuild] No Unity file structure changes detected [up-to-date].\n"); - } - - printf("\n==============================================================================\n"); - printf("Compiling Targets [%s]...\n", cfg.Name); - printf("==============================================================================\n"); - - struct DirCacheEntry { FixedString Path; int64_t Time; }; - DirCacheEntry dirCache[64]; - int dirCacheCount = 0; - - auto GetDirTime = [&](const char* dir) -> int64_t - { - for (int k = 0; k < dirCacheCount; k++) - { - if (dirCache[k].Path.EqualsNoCase(dir)) return dirCache[k].Time; - } - int64_t t = GetNewestSourceTimeRecursive(dir); - if (dirCacheCount < 64) - { - dirCache[dirCacheCount].Path.Set(dir); - dirCache[dirCacheCount].Time = t; - dirCacheCount++; - } - return t; - }; - - bool anyNeedBuild = false; - - for (int j = 0; j < cfg.StepCount; j++) - { - BuildStep& step = cfg.Steps[j]; - int64_t maxTime = 0; - const char* cur = step.Depends; - while (*cur) - { - const char* nextPipe = strchr(cur, '|'); - char dirPath[MaxPath] = {}; - if (nextPipe) - { - int dLen = static_cast(nextPipe - cur); - if (dLen >= static_cast(sizeof(dirPath))) dLen = static_cast(sizeof(dirPath)) - 1; - memcpy(dirPath, cur, dLen); - dirPath[dLen] = '\0'; - cur = nextPipe + 1; - } - else - { - strcpy_s(dirPath, sizeof(dirPath), cur); - cur += strlen(cur); - } - if (dirPath[0] != '\0') - { - int64_t t = GetDirTime(dirPath); - if (t > maxTime) maxTime = t; - } - } - - bool needCompile = unityChanged; - if (!needCompile) - { - if (!FileExists(step.OutputFile)) - { - needCompile = true; - } - else - { - int64_t outTime = GetFileModTimeInt64(step.OutputFile); - if (outTime < maxTime) needCompile = true; - } - } - - printf("\n--- %s ---\n", step.StepName); - if (!needCompile) - { - printf("[SKIP / UP-TO-DATE] %s [Binary up to date]\n", step.OutputFile); - } - else - { - printf("[REBUILDING] Compiling %s...\n", step.OutputFile); - anyNeedBuild = true; - - LARGE_INTEGER tStart, tEnd; - QueryPerformanceCounter(&tStart); - int res = ExecuteCmd(step.Command); - QueryPerformanceCounter(&tEnd); - - AppendTiming(step.StepName, static_cast(tEnd.QuadPart - tStart.QuadPart) / static_cast(perfFreq.QuadPart)); - - if (res != 0) - { - printf("[BUILD FAILED] Command failed with exit code %d.\n", res); - return res; - } - - char builtOut[MaxPath + 2]; - sprintf_s(builtOut, "%s\n", step.OutputFile); - - HANDLE hFile = CreateFileA("Intermediate\\built_outputs.tmp", FILE_APPEND_DATA, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile != INVALID_HANDLE_VALUE) - { - DWORD written = 0; - WriteFile(hFile, builtOut, static_cast(strlen(builtOut)), &written, nullptr); - CloseHandle(hFile); - } - anyBuilt = true; - } - } - - if (!anyNeedBuild) - { - printf("\n[SKIP / UP-TO-DATE] All requested targets in [%s] are up-to-date.\n", cfg.Name); - } - } - - if (timingsLen > 0) - { - WriteEntireFile("Intermediate\\step_timings.tmp", timingsBuf, timingsLen); - } - - QueryPerformanceCounter(&perfEnd); - double elapsedSeconds = static_cast(perfEnd.QuadPart - perfStart.QuadPart) / static_cast(perfFreq.QuadPart); - - char elapsedBuf[64]; - if (elapsedSeconds < 60.0) - { - sprintf_s(elapsedBuf, "%.3fs", elapsedSeconds); - } - else - { - int totalMs = static_cast(elapsedSeconds * 1000.0); - int hours = totalMs / 3600000; - int minutes = (totalMs % 3600000) / 60000; - int seconds = (totalMs % 60000) / 1000; - int ms = totalMs % 1000; - sprintf_s(elapsedBuf, "%02d:%02d:%02d.%03d", hours, minutes, seconds, ms); - } - - printf("\n==============================================================================\n"); - printf("[BUILD SUMMARY]\n"); - printf("==============================================================================\n"); - - if (timingsLen > 0) - { - printf("Step Timings:\n"); - const char* pos = timingsBuf; - while (*pos) - { - const char* lineStart = pos; - while (*pos && *pos != '\n') pos++; - int len = static_cast(pos - lineStart); - if (len > 0) - { - const char* pipe = strchr(lineStart, '|'); - if (pipe && pipe < pos) - { - char stepName[MaxPath] = {}; - int nameLen = static_cast(pipe - lineStart); - memcpy(stepName, lineStart, nameLen); - - char durStr[64] = {}; - int durLen = static_cast(pos - (pipe + 1)); - memcpy(durStr, pipe + 1, durLen); - - printf(" - %-42s : %s\n", stepName, durStr); - } - } - if (*pos == '\n') pos++; - } - printf("------------------------------------------------------------------------------\n"); - } - - printf("Total Duration : %s\n", elapsedBuf); - printf("Output Binaries:\n"); - - if (anyBuilt) - { - FileContent outputsContent = ReadEntireFile("Intermediate\\built_outputs.tmp"); - if (outputsContent.Data) - { - const char* pos = outputsContent.Data; - while (*pos) - { - const char* lineStart = pos; - while (*pos && *pos != '\n' && *pos != '\r') pos++; - int len = static_cast(pos - lineStart); - if (len > 0) - { - char filePath[MaxPath] = {}; - memcpy(filePath, lineStart, len); - WIN32_FILE_ATTRIBUTE_DATA fileData; - if (GetFileAttributesExA(filePath, GetFileExInfoStandard, &fileData)) - { - ULARGE_INTEGER fileSize; - fileSize.LowPart = fileData.nFileSizeLow; - fileSize.HighPart = fileData.nFileSizeHigh; - double sizeMB = static_cast(fileSize.QuadPart) / (1024.0 * 1024.0); - printf(" - %s (%.2f MB)\n", filePath, sizeMB); - } - } - while (*pos == '\n' || *pos == '\r') pos++; - } - FreeFileContent(outputsContent); - } - DeleteFileA("Intermediate\\built_outputs.tmp"); - } - else - { - printf(" - None built in this session (all requested targets up-to-date)\n"); - } - - printf("==============================================================================\n"); - printf("[ALL BUILDS SUCCEEDED] All requested targets processed successfully.\n"); - printf("==============================================================================\n"); - fflush(stdout); - - return 0; -} - -// ============================================================================ -// Main entry point -// ============================================================================ - -int main(int argc, char* argv[]) -{ - if (argc < 2) - { - printf("Usage: build_system [args...]\n"); - printf("Commands:\n"); - printf(" run_plan Run the build from a generated plan file\n"); - printf(" check_alive Returns 0 immediately to test execution\n"); - return 1; - } - - const char* cmd = argv[1]; - if (strcmp(cmd, "check_alive") == 0) return 0; - if (strcmp(cmd, "run_plan") == 0) return CommandRunPlan(argc, argv); - - printf("[ERROR] Unknown command: %s\n", cmd); - return 1; -}