Files
Juliet/tools/build_system.c
T
2026-07-23 19:41:52 -04:00

869 lines
28 KiB
C

#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#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)
{
char tmp[MAX_PATH_LEN];
strncpy_s(tmp, MAX_PATH_LEN, path, _TRUNCATE);
for (int i = 0; tmp[i]; i++)
{
if (tmp[i] == '\\' || tmp[i] == '/')
{
char c = tmp[i];
tmp[i] = '\0';
CreateDirectoryA(tmp, NULL);
tmp[i] = c;
}
}
CreateDirectoryA(tmp, 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;
}
// ============================================================================
// 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 CollectFiles(const char* dirPath, const char* containsStr, const char* endsWithStr, 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)
{
CollectFiles(fullPath, containsStr, endsWithStr, list);
}
else
{
bool match = true;
if (containsStr && !StringContains(findData.cFileName, containsStr)) match = false;
if (endsWithStr && !EndsWithNoCase(findData.cFileName, endsWithStr)) match = false;
if (match)
{
FilePathListAdd(list, fullPath);
}
}
}
while (FindNextFileA(hFind, &findData));
FindClose(hFind);
}
typedef struct
{
char SourceDir[MAX_PATH_LEN];
char OutputDir[MAX_PATH_LEN];
char Prefix[256];
char* ExplicitFiles[64];
int ExplicitFileCount;
int MaxFiles;
} UnityConfig;
static bool GenerateUnityFiles(const UnityConfig* ucfg)
{
FilePathList* srcFiles = CreateFilePathList();
if (ucfg->SourceDir[0] != '\0')
{
CollectFiles(ucfg->SourceDir, NULL, ".cpp", srcFiles);
}
else
{
for (int i = 0; i < ucfg->ExplicitFileCount; i++)
{
FilePathListAdd(srcFiles, ucfg->ExplicitFiles[i]);
}
}
EnsureDirectoryExists(ucfg->OutputDir);
int fileIdx = 0;
int unityCount = 1;
char currentUnityPath[MAX_PATH_LEN];
bool anyChanged = false;
char* memBuf = (char*)ArenaPush(1024 * 1024);
int memOffset = 0;
for (int i = 0; i < srcFiles->Count; i++)
{
if (StringContains(srcFiles->Paths[i], "_Unity")) continue;
if (fileIdx == 0)
{
memOffset = 0;
}
char absPath[MAX_PATH_LEN];
GetFullPathNameA(srcFiles->Paths[i], MAX_PATH_LEN, absPath, NULL);
for (int k = 0; absPath[k]; k++) {
if (absPath[k] == '\\') absPath[k] = '/';
}
memOffset += sprintf_s(memBuf + memOffset, (1024 * 1024) - memOffset, "#include \"%s\"\n", absPath);
fileIdx++;
if (fileIdx >= ucfg->MaxFiles || i == srcFiles->Count - 1)
{
snprintf(currentUnityPath, sizeof(currentUnityPath), "%s\\%s%d.cpp", ucfg->OutputDir, ucfg->Prefix, unityCount);
FileContent exist = ReadEntireFile(currentUnityPath);
bool write = true;
if (exist.Data && exist.Size == memOffset)
{
if (memcmp(exist.Data, memBuf, memOffset) == 0)
{
write = false;
}
}
if (write)
{
WriteEntireFile(currentUnityPath, memBuf, memOffset);
anyChanged = true;
}
unityCount++;
fileIdx = 0;
}
}
while (true)
{
snprintf(currentUnityPath, sizeof(currentUnityPath), "%s\\%s%d.cpp", ucfg->OutputDir, ucfg->Prefix, unityCount);
if (FileExists(currentUnityPath))
{
DeleteFileA(currentUnityPath);
anyChanged = true;
unityCount++;
}
else
{
break;
}
}
return anyChanged;
}
// ============================================================================
// 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];
UnityConfig UnitySteps[16];
int UnityStepCount;
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 <plan_file.txt>\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 = (BuildConfig*)ArenaPush(4 * sizeof(BuildConfig));
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 > 6 && memcmp(lineStart, "UNITY ", 6) == 0)
{
if (curCfg->UnityStepCount < 16)
{
char lineBuf[1024];
int nLen = lineLen - 6;
if (nLen >= (int)sizeof(lineBuf)) nLen = (int)sizeof(lineBuf) - 1;
memcpy(lineBuf, lineStart + 6, nLen);
lineBuf[nLen] = '\0';
UnityConfig* ucfg = &curCfg->UnitySteps[curCfg->UnityStepCount];
ucfg->ExplicitFileCount = 0;
if (sscanf_s(lineBuf, "%s %s %s %d",
ucfg->SourceDir, (unsigned)_countof(ucfg->SourceDir),
ucfg->OutputDir, (unsigned)_countof(ucfg->OutputDir),
ucfg->Prefix, (unsigned)_countof(ucfg->Prefix),
&ucfg->MaxFiles) == 4)
{
curCfg->UnityStepCount++;
}
}
}
else if (lineLen > 15 && memcmp(lineStart, "UNITY_EXPLICIT ", 15) == 0)
{
if (curCfg->UnityStepCount < 16)
{
char lineBuf[2048];
int nLen = lineLen - 15;
if (nLen >= (int)sizeof(lineBuf)) nLen = (int)sizeof(lineBuf) - 1;
memcpy(lineBuf, lineStart + 15, nLen);
lineBuf[nLen] = '\0';
UnityConfig* ucfg = &curCfg->UnitySteps[curCfg->UnityStepCount];
ucfg->SourceDir[0] = '\0';
ucfg->ExplicitFileCount = 0;
char fileList[1024];
if (sscanf_s(lineBuf, "%s %s %d %s",
ucfg->OutputDir, (unsigned)_countof(ucfg->OutputDir),
ucfg->Prefix, (unsigned)_countof(ucfg->Prefix),
&ucfg->MaxFiles,
fileList, (unsigned)_countof(fileList)) == 4)
{
char* ctx = NULL;
char* token = strtok_s(fileList, "|", &ctx);
while(token)
{
if (ucfg->ExplicitFileCount < 64)
{
size_t tLen = strlen(token);
char* storedStr = (char*)ArenaPush(tLen + 1);
strcpy_s(storedStr, tLen + 1, token);
ucfg->ExplicitFiles[ucfg->ExplicitFileCount++] = storedStr;
}
token = strtok_s(NULL, "|", &ctx);
}
curCfg->UnityStepCount++;
}
}
}
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 [%s]...\n", cfg->Name);
printf("==============================================================================\n");
bool unityChanged = false;
if (cfg->UnityStepCount > 0)
{
LARGE_INTEGER tStart, tEnd;
QueryPerformanceCounter(&tStart);
for (int u = 0; u < cfg->UnityStepCount; u++)
{
if (GenerateUnityFiles(&cfg->UnitySteps[u]))
{
unityChanged = true;
}
}
QueryPerformanceCounter(&tEnd);
char stepName[MAX_PATH_LEN];
sprintf_s(stepName, sizeof(stepName), "Unity Gen [%s]", cfg->Name);
AppendTiming(stepName, (double)(tEnd.QuadPart - tStart.QuadPart) / (double)perfFreq.QuadPart);
}
if (unityChanged)
{
printf("[Unity] Source file list or include mapping changed.\n");
}
else
{
printf("[Unity] No 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 <command> [args...]\n");
printf("Commands:\n");
printf(" run_plan <plan_file> 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;
}