#ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #include #include #define MAX_PATH_LEN 1024 #define MAX_FILES 4096 #define MAX_HASH_ENTRIES 512 // ============================================================================ // Arena Allocator // ============================================================================ typedef struct { char* base; size_t size; size_t offset; } Arena; static Arena g_Arena; static void ArenaInit(size_t size) { g_Arena.base = (char*)malloc(size); assert(g_Arena.base != NULL); g_Arena.size = size; g_Arena.offset = 0; } static void* ArenaPush(size_t size) { // align to 8 bytes size_t aligned_size = (size + 7) & ~7; if (g_Arena.offset + aligned_size > g_Arena.size) { printf("[ERROR] Arena out of memory!\n"); exit(1); } void* ptr = g_Arena.base + g_Arena.offset; g_Arena.offset += aligned_size; memset(ptr, 0, size); return ptr; } // ============================================================================ // String helpers // ============================================================================ static bool StringContains(const char* haystack, const char* needle) { return strstr(haystack, needle) != NULL; } static bool EndsWithNoCase(const char* str, const char* suffix) { int strLen = (int)strlen(str); int sufLen = (int)strlen(suffix); if (sufLen > strLen) return false; return _stricmp(str + strLen - sufLen, suffix) == 0; } // ============================================================================ // File I/O helpers // ============================================================================ typedef struct { char* Data; long Size; } FileContent; static FileContent ReadEntireFile(const char* path) { FileContent result = { NULL, 0 }; FILE* f = NULL; fopen_s(&f, path, "rb"); if (!f) return result; fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); if (size > 0) { result.Data = (char*)ArenaPush(size + 1); result.Size = size; fread(result.Data, 1, size, f); result.Data[size] = '\0'; } fclose(f); return result; } static bool WriteEntireFile(const char* path, const char* data, long size) { FILE* f = NULL; fopen_s(&f, path, "wb"); if (!f) return false; size_t written = fwrite(data, 1, size, f); fclose(f); return written == (size_t)size; } static bool FileExists(const char* path) { DWORD attribs = GetFileAttributesA(path); return (attribs != INVALID_FILE_ATTRIBUTES) && !(attribs & FILE_ATTRIBUTE_DIRECTORY); } static bool DirectoryExists(const char* path) { DWORD attribs = GetFileAttributesA(path); return (attribs != INVALID_FILE_ATTRIBUTES) && (attribs & FILE_ATTRIBUTE_DIRECTORY); } static void EnsureDirectoryExists(const char* path) { 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 \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 [args...]\n"); printf("Commands:\n"); printf(" run_plan Run the build from a generated plan file\n"); printf(" check_alive Returns 0 immediately to test execution\n"); return 1; } const char* cmd = argv[1]; if (strcmp(cmd, "check_alive") == 0) return 0; if (strcmp(cmd, "run_plan") == 0) return CommandRunPlan(argc, argv); printf("[ERROR] Unknown command: %s\n", cmd); return 1; }