1332 lines
49 KiB
C
1332 lines
49 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;
|
|
}
|
|
static void GenerateProject(const char* projectName, const char* projectDir, const char* guid, const char* includeSearchPath, const char* extraDefs, const char* debuggerCommandArgs, const char Srcs[64][256], int SrcCount, const char* defCommon, const char* defDebug, const char* defProfile, const char* defRelease)
|
|
{
|
|
FilePathList* srcFiles = CreateFilePathList();
|
|
if (SrcCount > 0) {
|
|
for (int i = 0; i < SrcCount; i++) {
|
|
FilePathListAdd(srcFiles, Srcs[i]);
|
|
}
|
|
} else {
|
|
CollectFiles(projectDir, NULL, ".cpp", srcFiles);
|
|
CollectFiles(projectDir, NULL, ".c", srcFiles);
|
|
CollectFiles(projectDir, NULL, ".h", srcFiles);
|
|
CollectFiles(projectDir, NULL, ".hpp", srcFiles);
|
|
CollectFiles(projectDir, NULL, ".inl", srcFiles);
|
|
}
|
|
|
|
char* buf = (char*)ArenaPush(4 * 1024 * 1024);
|
|
int offset = 0;
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
"<Project DefaultTargets=\"Build\" ToolsVersion=\"17.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"
|
|
" <ItemGroup Label=\"ProjectConfigurations\">\n"
|
|
" <ProjectConfiguration Include=\"Debug|x64\">\n"
|
|
" <Configuration>Debug</Configuration>\n"
|
|
" <Platform>x64</Platform>\n"
|
|
" </ProjectConfiguration>\n"
|
|
" <ProjectConfiguration Include=\"Profile|x64\">\n"
|
|
" <Configuration>Profile</Configuration>\n"
|
|
" <Platform>x64</Platform>\n"
|
|
" </ProjectConfiguration>\n"
|
|
" <ProjectConfiguration Include=\"Release|x64\">\n"
|
|
" <Configuration>Release</Configuration>\n"
|
|
" <Platform>x64</Platform>\n"
|
|
" </ProjectConfiguration>\n"
|
|
" </ItemGroup>\n"
|
|
" <PropertyGroup Label=\"Globals\">\n"
|
|
" <ProjectGuid>{%s}</ProjectGuid>\n"
|
|
" <Keyword>MakeFileProj</Keyword>\n"
|
|
" </PropertyGroup>\n"
|
|
" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n", guid);
|
|
|
|
const char* configs[] = { "Debug", "Profile", "Release" };
|
|
for (int i = 0; i < 3; i++) {
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='%s|x64'\" Label=\"Configuration\">\n"
|
|
" <ConfigurationType>Makefile</ConfigurationType>\n"
|
|
" <UseDebugLibraries>false</UseDebugLibraries>\n"
|
|
" <PlatformToolset>v143</PlatformToolset>\n"
|
|
" </PropertyGroup>\n", configs[i]);
|
|
}
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n"
|
|
" <ImportGroup Label=\"ExtensionSettings\">\n"
|
|
" </ImportGroup>\n");
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
const char* cfg = configs[i];
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='%s|x64'\">\n"
|
|
" <NMakeBuildCommandLine>cd $(SolutionDir) & misc\\build.bat %s $(SolutionName)</NMakeBuildCommandLine>\n"
|
|
" <NMakeReBuildCommandLine>cd $(SolutionDir) & misc\\build.bat -clean %s $(SolutionName)</NMakeReBuildCommandLine>\n"
|
|
" <NMakeCleanCommandLine>cd $(SolutionDir) & misc\\build.bat -clean %s $(SolutionName)</NMakeCleanCommandLine>\n"
|
|
" <NMakeOutput>$(SolutionDir)bin\\x64-%s\\%s.exe</NMakeOutput>\n"
|
|
" <NMakeIncludeSearchPath>%s$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>\n"
|
|
" <IncludePath>%s$(IncludePath)</IncludePath>\n",
|
|
cfg, cfg, cfg, cfg, cfg, projectName, includeSearchPath, includeSearchPath);
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <NMakePreprocessorDefinitions>");
|
|
if (defCommon && defCommon[0]) offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, "%s", defCommon);
|
|
if (strcmp(cfg, "Debug") == 0 && defDebug && defDebug[0]) offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, "%s", defDebug);
|
|
else if (strcmp(cfg, "Profile") == 0 && defProfile && defProfile[0]) offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, "%s", defProfile);
|
|
else if (strcmp(cfg, "Release") == 0 && defRelease && defRelease[0]) offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, "%s", defRelease);
|
|
if (extraDefs && extraDefs[0]) offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, "%s", extraDefs);
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, "$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>\n"
|
|
" <AdditionalOptions>/std:c++20</AdditionalOptions>\n"
|
|
" <LanguageStandard>stdcpp20</LanguageStandard>\n");
|
|
|
|
if (debuggerCommandArgs && debuggerCommandArgs[0] != '\0') {
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <LocalDebuggerCommandArguments>%s</LocalDebuggerCommandArguments>\n", debuggerCommandArgs);
|
|
}
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <LocalDebuggerWorkingDirectory>$(SolutionDir)bin\\x64-%s\\</LocalDebuggerWorkingDirectory>\n"
|
|
" <IntDir>$(SolutionDir)Intermediate\\x64-%s\\%s\\</IntDir>\n"
|
|
" <OutDir>$(SolutionDir)bin\\x64-%s\\</OutDir>\n"
|
|
" </PropertyGroup>\n", cfg, cfg, projectName, cfg);
|
|
}
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, " <ItemGroup>\n");
|
|
int projectDirLen = (int)strlen(projectDir);
|
|
for (int i = 0; i < srcFiles->Count; i++) {
|
|
const char* path = srcFiles->Paths[i];
|
|
const char* relPath = path;
|
|
if (strncmp(path, projectDir, projectDirLen) == 0 && (path[projectDirLen] == '\\' || path[projectDirLen] == '/')) {
|
|
relPath = path + projectDirLen + 1;
|
|
}
|
|
|
|
if (EndsWithNoCase(path, ".cpp") || EndsWithNoCase(path, ".c")) {
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, " <ClCompile Include=\"%s\" />\n", relPath);
|
|
} else {
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, " <ClInclude Include=\"%s\" />\n", relPath);
|
|
}
|
|
}
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset, " </ItemGroup>\n");
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n"
|
|
" <ImportGroup Label=\"ExtensionTargets\">\n"
|
|
" </ImportGroup>\n"
|
|
"</Project>\n");
|
|
|
|
char outPath[MAX_PATH_LEN];
|
|
snprintf(outPath, sizeof(outPath), "%s\\%s.vcxproj", projectDir, projectName);
|
|
WriteEntireFile(outPath, buf, offset);
|
|
printf("Generated %s\n", outPath);
|
|
|
|
// --- Generate .vcxproj.filters ---
|
|
char filters[512][256];
|
|
int filterCount = 0;
|
|
|
|
for (int i = 0; i < srcFiles->Count; i++) {
|
|
const char* path = srcFiles->Paths[i];
|
|
const char* relPath = path;
|
|
if (strncmp(path, projectDir, projectDirLen) == 0 && (path[projectDirLen] == '\\' || path[projectDirLen] == '/')) {
|
|
relPath = path + projectDirLen + 1;
|
|
}
|
|
|
|
const char* lastSlash = strrchr(relPath, '\\');
|
|
if (!lastSlash) lastSlash = strrchr(relPath, '/');
|
|
if (lastSlash) {
|
|
char dir[256];
|
|
int len = (int)(lastSlash - relPath);
|
|
if (len >= 256) len = 255;
|
|
memcpy(dir, relPath, len);
|
|
dir[len] = '\0';
|
|
|
|
char cur[256];
|
|
int c = 0;
|
|
for (int k = 0; k < len; k++) {
|
|
cur[c++] = dir[k];
|
|
if (dir[k] == '\\' || dir[k] == '/') {
|
|
cur[c - 1] = '\0';
|
|
bool found = false;
|
|
for (int f = 0; f < filterCount; f++) {
|
|
if (_stricmp(filters[f], cur) == 0) { found = true; break; }
|
|
}
|
|
if (!found && filterCount < 512) {
|
|
strcpy_s(filters[filterCount++], 256, cur);
|
|
}
|
|
cur[c - 1] = '\\';
|
|
}
|
|
}
|
|
bool found = false;
|
|
for (int f = 0; f < filterCount; f++) {
|
|
if (_stricmp(filters[f], dir) == 0) { found = true; break; }
|
|
}
|
|
if (!found && filterCount < 512) {
|
|
strcpy_s(filters[filterCount++], 256, dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
char* filterBuf = (char*)ArenaPush(4 * 1024 * 1024);
|
|
int filterOffset = 0;
|
|
filterOffset += sprintf_s(filterBuf + filterOffset, (4 * 1024 * 1024) - filterOffset,
|
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
"<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"
|
|
" <ItemGroup>\n");
|
|
for (int i = 0; i < filterCount; i++) {
|
|
filterOffset += sprintf_s(filterBuf + filterOffset, (4 * 1024 * 1024) - filterOffset,
|
|
" <Filter Include=\"%s\">\n"
|
|
" </Filter>\n", filters[i]);
|
|
}
|
|
filterOffset += sprintf_s(filterBuf + filterOffset, (4 * 1024 * 1024) - filterOffset,
|
|
" </ItemGroup>\n"
|
|
" <ItemGroup>\n");
|
|
|
|
for (int i = 0; i < srcFiles->Count; i++) {
|
|
const char* path = srcFiles->Paths[i];
|
|
const char* relPath = path;
|
|
if (strncmp(path, projectDir, projectDirLen) == 0 && (path[projectDirLen] == '\\' || path[projectDirLen] == '/')) {
|
|
relPath = path + projectDirLen + 1;
|
|
}
|
|
|
|
const char* lastSlash = strrchr(relPath, '\\');
|
|
if (!lastSlash) lastSlash = strrchr(relPath, '/');
|
|
|
|
const char* type = (EndsWithNoCase(path, ".cpp") || EndsWithNoCase(path, ".c")) ? "ClCompile" : "ClInclude";
|
|
|
|
if (lastSlash) {
|
|
char dir[256];
|
|
int len = (int)(lastSlash - relPath);
|
|
if (len >= 256) len = 255;
|
|
memcpy(dir, relPath, len);
|
|
dir[len] = '\0';
|
|
|
|
filterOffset += sprintf_s(filterBuf + filterOffset, (4 * 1024 * 1024) - filterOffset,
|
|
" <%s Include=\"%s\">\n"
|
|
" <Filter>%s</Filter>\n"
|
|
" </%s>\n", type, relPath, dir, type);
|
|
} else {
|
|
filterOffset += sprintf_s(filterBuf + filterOffset, (4 * 1024 * 1024) - filterOffset,
|
|
" <%s Include=\"%s\" />\n", type, relPath);
|
|
}
|
|
}
|
|
filterOffset += sprintf_s(filterBuf + filterOffset, (4 * 1024 * 1024) - filterOffset,
|
|
" </ItemGroup>\n</Project>\n");
|
|
|
|
char filterOutPath[MAX_PATH_LEN];
|
|
snprintf(filterOutPath, sizeof(filterOutPath), "%s\\%s.vcxproj.filters", projectDir, projectName);
|
|
WriteEntireFile(filterOutPath, filterBuf, filterOffset);
|
|
printf("Generated %s\n", filterOutPath);
|
|
}
|
|
|
|
static void GenerateSolution(const char* slnName, const char** projectNames, const char** projectDirs, const char** projectGuids, int projCount)
|
|
{
|
|
char* buf = (char*)ArenaPush(4 * 1024 * 1024);
|
|
int offset = 0;
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
"Microsoft Visual Studio Solution File, Format Version 12.00\n"
|
|
"# Visual Studio Version 17\n"
|
|
"VisualStudioVersion = 17.0.31903.59\n"
|
|
"MinimumVisualStudioVersion = 10.0.40219.1\n");
|
|
|
|
for (int i = 0; i < projCount; i++) {
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
"Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"%s\", \"%s\\%s.vcxproj\", \"{%s}\"\n"
|
|
"EndProject\n", projectNames[i], projectDirs[i], projectNames[i], projectGuids[i]);
|
|
}
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
"Global\n"
|
|
" GlobalSection(SolutionConfigurationPlatforms) = preSolution\n"
|
|
" Debug|x64 = Debug|x64\n"
|
|
" Profile|x64 = Profile|x64\n"
|
|
" Release|x64 = Release|x64\n"
|
|
" EndGlobalSection\n"
|
|
" GlobalSection(ProjectConfigurationPlatforms) = postSolution\n");
|
|
|
|
for (int i = 0; i < projCount; i++) {
|
|
const char* configs[] = { "Debug", "Profile", "Release" };
|
|
for (int c = 0; c < 3; c++) {
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" {%s}.%s|x64.ActiveCfg = %s|x64\n"
|
|
" {%s}.%s|x64.Build.0 = %s|x64\n",
|
|
projectGuids[i], configs[c], configs[c],
|
|
projectGuids[i], configs[c], configs[c]);
|
|
}
|
|
}
|
|
|
|
offset += sprintf_s(buf + offset, (4 * 1024 * 1024) - offset,
|
|
" EndGlobalSection\n"
|
|
" GlobalSection(SolutionProperties) = preSolution\n"
|
|
" HideSolutionNode = FALSE\n"
|
|
" EndGlobalSection\n"
|
|
"EndGlobal\n");
|
|
|
|
char outPath[MAX_PATH_LEN];
|
|
snprintf(outPath, sizeof(outPath), "%s.sln", slnName);
|
|
WriteEntireFile(outPath, buf, offset);
|
|
printf("Generated %s\n", outPath);
|
|
}
|
|
|
|
typedef struct {
|
|
char Name[64];
|
|
char Dir[256];
|
|
char Guid[64];
|
|
char ExtraDef[1024];
|
|
char IncludeSearch[1024];
|
|
char DebugCmd[1024];
|
|
char Srcs[64][256];
|
|
int SrcCount;
|
|
} VSProject;
|
|
|
|
typedef struct {
|
|
char Name[64];
|
|
char Projects[16][64];
|
|
int ProjCount;
|
|
} VSSolution;
|
|
|
|
static void GenerateDeterministicGuid(const char* input, char* outputGuid) {
|
|
unsigned long long hash1 = 14695981039346656037ULL;
|
|
unsigned long long hash2 = 1099511628211ULL;
|
|
for (const char* c = input; *c; ++c) {
|
|
hash1 ^= (unsigned long long)(*c);
|
|
hash1 *= 1099511628211ULL;
|
|
hash2 ^= (unsigned long long)(*c);
|
|
hash2 *= 137ULL;
|
|
}
|
|
sprintf_s(outputGuid, 64, "%08x-%04x-%04x-%04x-%012llx",
|
|
(unsigned int)(hash1 >> 32),
|
|
(unsigned int)((hash1 >> 16) & 0xFFFF),
|
|
(unsigned int)(hash1 & 0xFFFF),
|
|
(unsigned int)((hash2 >> 48) & 0xFFFF),
|
|
hash2 & 0xFFFFFFFFFFFFULL);
|
|
}
|
|
|
|
static int CommandGenVS(int argc, char* argv[])
|
|
{
|
|
if (argc < 3) {
|
|
printf("[ERROR] Usage: build_system gen_vs <plan_file>\n");
|
|
return 1;
|
|
}
|
|
const char* planFile = argv[2];
|
|
FileContent planContent = ReadEntireFile(planFile);
|
|
if (!planContent.Data) {
|
|
printf("[ERROR] Could not read VS plan: %s\n", planFile);
|
|
return 1;
|
|
}
|
|
|
|
char globalInclude[1024] = {0};
|
|
char globalDefCommon[1024] = {0};
|
|
char globalDefDebug[1024] = {0};
|
|
char globalDefProfile[1024] = {0};
|
|
char globalDefRelease[1024] = {0};
|
|
VSProject projects[16];
|
|
int projCount = 0;
|
|
|
|
VSSolution solutions[8];
|
|
int slnCount = 0;
|
|
|
|
const char* pos = planContent.Data;
|
|
while (*pos) {
|
|
const char* lineStart = pos;
|
|
while (*pos && *pos != '\n' && *pos != '\r') pos++;
|
|
int lineLen = (int)(pos - lineStart);
|
|
|
|
if (lineLen > 0) {
|
|
if (lineLen > 15 && memcmp(lineStart, "GLOBAL_INCLUDE ", 15) == 0) {
|
|
int nLen = lineLen - 15;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(globalInclude, lineStart + 15, nLen);
|
|
globalInclude[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 11 && memcmp(lineStart, "DEF_COMMON ", 11) == 0) {
|
|
int nLen = lineLen - 11;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(globalDefCommon, lineStart + 11, nLen);
|
|
globalDefCommon[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 10 && memcmp(lineStart, "DEF_DEBUG ", 10) == 0) {
|
|
int nLen = lineLen - 10;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(globalDefDebug, lineStart + 10, nLen);
|
|
globalDefDebug[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 12 && memcmp(lineStart, "DEF_PROFILE ", 12) == 0) {
|
|
int nLen = lineLen - 12;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(globalDefProfile, lineStart + 12, nLen);
|
|
globalDefProfile[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 12 && memcmp(lineStart, "DEF_RELEASE ", 12) == 0) {
|
|
int nLen = lineLen - 12;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(globalDefRelease, lineStart + 12, nLen);
|
|
globalDefRelease[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 8 && memcmp(lineStart, "PROJECT ", 8) == 0) {
|
|
if (projCount < 16) {
|
|
VSProject* p = &projects[projCount++];
|
|
memset(p, 0, sizeof(VSProject));
|
|
char lineBuf[1024];
|
|
int nLen = lineLen - 8;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(lineBuf, lineStart + 8, nLen);
|
|
lineBuf[nLen] = '\0';
|
|
int scanned = sscanf_s(lineBuf, "%s %s %s", p->Name, (unsigned)_countof(p->Name), p->Dir, (unsigned)_countof(p->Dir), p->Guid, (unsigned)_countof(p->Guid));
|
|
if (scanned < 2) {
|
|
strcpy_s(p->Dir, sizeof(p->Dir), p->Name);
|
|
}
|
|
if (scanned < 3) {
|
|
GenerateDeterministicGuid(p->Name, p->Guid);
|
|
}
|
|
}
|
|
}
|
|
else if (lineLen > 12 && memcmp(lineStart, "PROJECT_SRC ", 12) == 0 && projCount > 0) {
|
|
VSProject* p = &projects[projCount - 1];
|
|
if (p->SrcCount < 64) {
|
|
int nLen = lineLen - 12;
|
|
if (nLen >= 256) nLen = 255;
|
|
memcpy(p->Srcs[p->SrcCount], lineStart + 12, nLen);
|
|
p->Srcs[p->SrcCount][nLen] = '\0';
|
|
p->SrcCount++;
|
|
}
|
|
}
|
|
else if (lineLen > 10 && memcmp(lineStart, "EXTRA_DEF ", 10) == 0 && projCount > 0) {
|
|
VSProject* p = &projects[projCount - 1];
|
|
int nLen = lineLen - 10;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(p->ExtraDef, lineStart + 10, nLen);
|
|
p->ExtraDef[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 15 && memcmp(lineStart, "INCLUDE_SEARCH ", 15) == 0 && projCount > 0) {
|
|
VSProject* p = &projects[projCount - 1];
|
|
int nLen = lineLen - 15;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(p->IncludeSearch, lineStart + 15, nLen);
|
|
p->IncludeSearch[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 10 && memcmp(lineStart, "DEBUG_CMD ", 10) == 0 && projCount > 0) {
|
|
VSProject* p = &projects[projCount - 1];
|
|
int nLen = lineLen - 10;
|
|
if (nLen >= 1024) nLen = 1023;
|
|
memcpy(p->DebugCmd, lineStart + 10, nLen);
|
|
p->DebugCmd[nLen] = '\0';
|
|
}
|
|
else if (lineLen > 9 && memcmp(lineStart, "SOLUTION ", 9) == 0) {
|
|
if (slnCount < 8) {
|
|
VSSolution* s = &solutions[slnCount++];
|
|
memset(s, 0, sizeof(VSSolution));
|
|
int nLen = lineLen - 9;
|
|
if (nLen >= 64) nLen = 63;
|
|
memcpy(s->Name, lineStart + 9, nLen);
|
|
s->Name[nLen] = '\0';
|
|
}
|
|
}
|
|
else if (lineLen > 9 && memcmp(lineStart, "SLN_PROJ ", 9) == 0 && slnCount > 0) {
|
|
VSSolution* s = &solutions[slnCount - 1];
|
|
if (s->ProjCount < 16) {
|
|
int nLen = lineLen - 9;
|
|
if (nLen >= 64) nLen = 63;
|
|
memcpy(s->Projects[s->ProjCount], lineStart + 9, nLen);
|
|
s->Projects[s->ProjCount][nLen] = '\0';
|
|
s->ProjCount++;
|
|
}
|
|
}
|
|
}
|
|
while (*pos == '\n' || *pos == '\r') pos++;
|
|
}
|
|
|
|
for (int i = 0; i < projCount; i++) {
|
|
VSProject* p = &projects[i];
|
|
const char* incs = p->IncludeSearch[0] != '\0' ? p->IncludeSearch : globalInclude;
|
|
GenerateProject(p->Name, p->Dir, p->Guid, incs, p->ExtraDef, p->DebugCmd, p->Srcs, p->SrcCount, globalDefCommon, globalDefDebug, globalDefProfile, globalDefRelease);
|
|
}
|
|
|
|
for (int i = 0; i < slnCount; i++) {
|
|
VSSolution* s = &solutions[i];
|
|
const char* pNames[16];
|
|
const char* pDirs[16];
|
|
const char* pGuids[16];
|
|
for (int j = 0; j < s->ProjCount; j++) {
|
|
pNames[j] = s->Projects[j];
|
|
for (int k = 0; k < projCount; k++) {
|
|
if (strcmp(projects[k].Name, s->Projects[j]) == 0) {
|
|
pDirs[j] = projects[k].Dir;
|
|
pGuids[j] = projects[k].Guid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
GenerateSolution(s->Name, pNames, pDirs, pGuids, s->ProjCount);
|
|
}
|
|
|
|
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(" gen_vs Generate Visual Studio project and solution files\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);
|
|
if (strcmp(cmd, "gen_vs") == 0) return CommandGenVS(argc, argv);
|
|
|
|
printf("[ERROR] Unknown command: %s\n", cmd);
|
|
return 1;
|
|
}
|