converted build system to simpler C
This commit is contained in:
@@ -0,0 +1,915 @@
|
||||
#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)
|
||||
{
|
||||
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 <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[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 <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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user