Made build_system create the unity build itself, removing fastbuild dependency for that

fastbuild still used for generating solution
This commit is contained in:
2026-07-23 19:41:52 -04:00
parent f471c447d8
commit 4096e9d1ba
13 changed files with 269 additions and 286 deletions
+192 -239
View File
@@ -126,7 +126,19 @@ static bool DirectoryExists(const char* path)
static void EnsureDirectoryExists(const char* path)
{
CreateDirectoryA(path, NULL);
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)
@@ -142,187 +154,6 @@ static int64_t GetFileModTimeInt64(const char* path)
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
@@ -403,7 +234,7 @@ static void FilePathListAdd(FilePathList* list, const char* path)
}
}
static void CollectUnityFiles(const char* dirPath, FilePathList* list)
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);
@@ -425,11 +256,14 @@ static void CollectUnityFiles(const char* dirPath, FilePathList* list)
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
CollectUnityFiles(fullPath, list);
CollectFiles(fullPath, containsStr, endsWithStr, list);
}
else
{
if (StringContains(findData.cFileName, "_Unity") && EndsWithNoCase(findData.cFileName, ".cpp"))
bool match = true;
if (containsStr && !StringContains(findData.cFileName, containsStr)) match = false;
if (endsWithStr && !EndsWithNoCase(findData.cFileName, endsWithStr)) match = false;
if (match)
{
FilePathListAdd(list, fullPath);
}
@@ -440,6 +274,101 @@ static void CollectUnityFiles(const char* dirPath, FilePathList* list)
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
// ============================================================================
@@ -454,10 +383,11 @@ typedef struct
typedef struct
{
char Name[64];
char FastBuildTargets[512];
BuildStep Steps[32];
int StepCount;
char Name[64];
UnityConfig UnitySteps[16];
int UnityStepCount;
BuildStep Steps[32];
int StepCount;
} BuildConfig;
static int ExecuteCmd(const char* cmdLine)
@@ -544,8 +474,7 @@ static int CommandRunPlan(int argc, char* argv[])
QueryPerformanceFrequency(&perfFreq);
// Parse plan file
BuildConfig configs[4];
memset(configs, 0, sizeof(configs));
BuildConfig* configs = (BuildConfig*)ArenaPush(4 * sizeof(BuildConfig));
int configCount = 0;
const char* pos = planContent.Data;
@@ -572,12 +501,65 @@ static int CommandRunPlan(int argc, char* argv[])
else if (configCount > 0)
{
BuildConfig* curCfg = &configs[configCount - 1];
if (lineLen > 10 && memcmp(lineStart, "FASTBUILD ", 10) == 0)
if (lineLen > 6 && memcmp(lineStart, "UNITY ", 6) == 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';
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)
{
@@ -627,66 +609,37 @@ static int CommandRunPlan(int argc, char* argv[])
{
BuildConfig* cfg = &configs[i];
printf("\n==============================================================================\n");
printf("Generating Unity Files into Intermediate/ via FASTBuild [%s]...\n", cfg->Name);
printf("Generating Unity Files [%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);
bool unityChanged = false;
if (cfg->UnityStepCount > 0)
{
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)
for (int u = 0; u < cfg->UnityStepCount; u++)
{
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)
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);
}
SaveHashCache("Intermediate\\.unity_hashes_cache", newCache);
if (unityChanged)
{
printf("[FASTBuild] Unity structure or source file list changed.\n");
printf("[Unity] Source file list or include mapping changed.\n");
}
else
{
printf("[FASTBuild] No Unity file structure changes detected [up-to-date].\n");
printf("[Unity] No changes detected [up-to-date].\n");
}
printf("\n==============================================================================\n");