/* MIT License mate.h - A single-header library for compiling your C code in C Version - 2025-05-16 (0.1.10): https://github.com/TomasBorquez/mate.h Guide on the `README.md` */ #pragma once #ifdef MATE_IMPLEMENTATION # define BASE_IMPLEMENTATION #endif // --- BASE.H START --- /* MIT License base.h - Better cross-platform STD Version - 2025-05-15 (0.1.18): https://github.com/TomasBorquez/base.h Usage: #define BASE_IMPLEMENTATION #include "base.h" More on the the `README.md` */ #ifdef __cplusplus extern "C" { #endif /* --- Platform MACROS and includes --- */ #if defined(__clang__) # define COMPILER_CLANG #elif defined(_MSC_VER) # define COMPILER_MSVC #elif defined(__GNUC__) # define COMPILER_GCC #elif defined(__TINYC__) # define COMPILER_TCC #else # error "The codebase only supports GCC, Clang, TCC and MSVC" #endif #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) # define NORETURN __attribute__((noreturn)) # define UNLIKELY(x) __builtin_expect(!!(x), 0) # define ALLOC_ATTR2(sz, al) __attribute__((malloc, alloc_size(sz), alloc_align(al))) # define ALLOC_ATTR(sz) __attribute__((malloc, alloc_size(sz))) # define FORMAT_CHECK(fmt_pos, args_pos) __attribute__((format(printf, fmt_pos, args_pos))) # define WARN_UNUSED __attribute__((warn_unused_result)) #elif defined(COMPILER_MSVC) # define NORETURN __declspec(noreturn) # define UNLIKELY(x) x # define ALLOC_ATTR2(sz, al) # define ALLOC_ATTR(sz) # define FORMAT_CHECK(fmt_pos, args_pos) # define WARN_UNUSED _Check_return_ #else # define NORETURN __declspec(noreturn) # define UNLIKELY(x) x # define ALLOC_ATTR2(sz, al) # define ALLOC_ATTR(sz) # define FORMAT_CHECK(fmt_pos, args_pos) # define WARN_UNUSED #endif #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) # define PLATFORM_WIN #elif defined(__linux__) || defined(__gnu_linux__) # define PLATFORM_LINUX #elif defined(__APPLE__) || defined(__MACH__) # define PLATFORM_MACOS #else # error "The codebase only supports linux, macos and windows" #endif #if defined(COMPILER_CLANG) # define FILE_NAME __FILE_NAME__ #else # define FILE_NAME __FILE__ #endif #if defined(PLATFORM_WIN) # define WIN32_LEAN_AND_MEAN # include #else # define _POSIX_C_SOURCE 200809L # define _GNU_SOURCE # include # include # include # include # include # include #endif #if defined(__STDC_VERSION__) # if (__STDC_VERSION__ >= 202311L) # define C_STANDARD_C23 # define C_STANDARD "C23" # elif (__STDC_VERSION__ >= 201710L) # define C_STANDARD_C17 # define C_STANDARD "C17" # elif (__STDC_VERSION__ >= 201112L) # define C_STANDARD_C11 # define C_STANDARD "C11" # elif (__STDC_VERSION__ >= 199901L) # define C_STANDARD_C99 # define C_STANDARD "C99" # else # error "Why C89 if you have C99" # endif #else # if defined(COMPILER_MSVC) # if defined(_MSC_VER) && _MSC_VER >= 1920 // >= Visual Studio 2019 # define C_STANDARD_C17 # define C_STANDARD "C17" # else # define C_STANDARD_C11 # define C_STANDARD "C11" # endif # endif #endif #include #include #include #include #include #include #include #include #include /* --- Platform Specific --- */ #if defined(PLATFORM_WIN) /* Process functions */ # define popen _popen # define pclose _pclose /* File I/O functions */ # define fileno _fileno # define fdopen _fdopen # define access _access # define unlink _unlink # define isatty _isatty # define dup _dup # define dup2 _dup2 # define ftruncate _chsize # define fsync _commit /* Directory functions */ # define mkdir(path, mode) _mkdir(path) # define rmdir _rmdir # define getcwd _getcwd # define chdir _chdir /* Process/Threading */ # define getpid _getpid # define execvp _execvp # define execve _execve # define sleep(x) Sleep((x) * 1000) # define usleep(x) Sleep((x) / 1000) /* String functions */ # define strcasecmp _stricmp # define strncasecmp _strnicmp # define strdup _strdup /* File modes */ # define R_OK 4 # define W_OK 2 # define X_OK 0 /* Windows doesn't have explicit X_OK */ # define F_OK 0 /* File descriptors */ # define STDIN_FILENO 0 # define STDOUT_FILENO 1 # define STDERR_FILENO 2 /* Some functions need complete replacements */ # if defined(COMPILER_MSVC) # define snprintf _snprintf # define vsnprintf _vsnprintf # endif #endif /* --- Types and MACRO types --- */ // Unsigned int types typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; // Signed int types typedef int8_t s8; typedef int16_t s16; typedef int32_t s32; typedef int64_t s64; // Regular int types typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; // Floating point types typedef float f32; typedef double f64; typedef struct { size_t length; // Does not include null terminator char *data; } String; // Maximum values for integer types #define U8_MAX UINT8_MAX #define U16_MAX UINT16_MAX #define U32_MAX UINT32_MAX #define U64_MAX UINT64_MAX #define S8_MAX INT8_MAX #define S8_MIN INT8_MIN #define S16_MAX INT16_MAX #define S16_MIN INT16_MIN #define S32_MAX INT32_MAX #define S32_MIN INT32_MIN #define S64_MAX INT64_MAX #define S64_MIN INT64_MIN #define I8_MAX INT8_MAX #define I8_MIN INT8_MIN #define I16_MAX INT16_MAX #define I16_MIN INT16_MIN #define I32_MAX INT32_MAX #define I32_MIN INT32_MIN #define I64_MAX INT64_MAX #define I64_MIN INT64_MIN #define TYPE_INIT(type) (type) void _custom_assert(const char *expr, const char *file, unsigned line, const char *format, ...) FORMAT_CHECK(4, 5); #define Assert(expression, ...) (void)((!!(expression)) || (_custom_assert(#expression, __FILE__, __LINE__, __VA_ARGS__), 0)) /* --- Vector --- */ // TODO: `VecSort` implement some sorting algorithm for sorting the vector #define VEC_TYPE(typeName, valueType) \ typedef struct { \ valueType *data; \ size_t length; \ size_t capacity; \ } typeName #define VecReserve(vector, count) \ do { \ vector.capacity = count; \ vector.data = Malloc(count * sizeof(*vector.data)); \ } while (0) #define VecPush(vector, value) vecPush((void **)&(vector).data, &(vector).length, &(vector).capacity, sizeof(*vector.data), &(value)); #define VecPop(vector) vecPop((vector).data, &(vector).length, sizeof(*vector.data)); #define VecShift(vector) vecShift((void **)&(vector).data, &(vector).length, sizeof(*vector.data)) #define VecUnshift(vector, value) vecUnshift((void **)&(vector).data, &(vector).length, &(vector).capacity, sizeof(*vector.data), &(value)) #define VecInsert(vector, value, index) vecInsert((void **)&(vector).data, &(vector).length, &(vector).capacity, sizeof(*vector.data), &(value), index) #define VecAt(vector, index) (*(__typeof__(*vector.data) *)vecAt((void **)&(vector).data, &(vector).length, index, sizeof(*vector.data))) #define VecAtPtr(vector, index) (vecAt((void **)&(vector).data, &(vector).length, index, sizeof(*vector.data))) #define VecFree(vector) vecFree((void **)&(vector).data, &(vector).length, &(vector).capacity) #define VecForEach(vector, it) for (__typeof__(*vector.data) *it = vector.data; it < vector.data + vector.length; it++) /* --- Time and Platforms --- */ i64 TimeNow(); void WaitTime(i64 ms); String GetCompiler(); String GetPlatform(); /* --- Error --- */ typedef i32 errno_t; enum GeneralError { SUCCESS = 0, }; String ErrToStr(errno_t err); /* --- Arena --- */ typedef struct __ArenaChunk { struct __ArenaChunk *next; size_t cap; char buffer[]; } __ArenaChunk; typedef struct { __ArenaChunk *current; size_t offset; __ArenaChunk *root; size_t chunkSize; } Arena; // This makes sure right alignment on 86/64 bits #define DEFAULT_ALIGNMENT (2 * sizeof(void *)) Arena *ArenaCreate(size_t chunkSize); ALLOC_ATTR2(2, 3) void *ArenaAllocAligned(Arena *arena, size_t size, size_t align); ALLOC_ATTR(2) char *ArenaAllocChars(Arena *arena, size_t count); ALLOC_ATTR(2) void *ArenaAlloc(Arena *arena, size_t size); void ArenaFree(Arena *arena); void ArenaReset(Arena *arena); /* --- Memory Allocations --- */ void *Realloc(void *block, size_t size); void *Malloc(size_t size); void Free(void *address); /* --- String and Macros --- */ #define STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0])) // NOTE: Inspired from clay.h #define ENSURE_STRING_LITERAL(x) ("" x "") // NOTE: If an error led you here, it's because `S` can only be used with string literals, i.e. `S("SomeString")` and not `S(yourString)` - for that use `s()` #define S(string) (TYPE_INIT(String){.length = STRING_LENGTH(ENSURE_STRING_LITERAL(string)), .data = (string)}) String s(char *msg); String F(Arena *arena, const char *format, ...) FORMAT_CHECK(2, 3); VEC_TYPE(StringVector, String); #define StringVectorPushMany(vector, ...) \ do { \ char *values[] = {__VA_ARGS__}; \ size_t count = sizeof(values) / sizeof(char *); \ for (size_t i = 0; i < count; i++) { \ String value = s(values[i]); \ VecPush(vector, value); \ } \ } while (0) void SetMaxStrSize(size_t size); String StrNew(Arena *arena, char *str); String StrNewSize(Arena *arena, char *str, size_t len); // Without null terminator void StrCopy(String destination, String source); StringVector StrSplit(Arena *arena, String string, String delimiter); StringVector StrSplitNewLine(Arena *arena, String str); bool StrEq(String string1, String string2); String StrConcat(Arena *arena, String string1, String string2); void StrToLower(String string1); void StrToUpper(String string1); bool StrIsNull(String string); void StrTrim(String *string); String StrSlice(Arena *arena, String str, size_t start, size_t end); String NormalizePath(Arena *arena, String path); String NormalizeExePath(Arena *arena, String path); String NormalizeExtension(Arena *arena, String path); String NormalizeStaticLibPath(Arena *arena, String path); String NormalizePathStart(Arena *arena, String path); String NormalizePathEnd(Arena *arena, String path); typedef struct { size_t capacity; String buffer; } StringBuilder; StringBuilder StringBuilderCreate(Arena *arena); StringBuilder StringBuilderReserve(Arena *arena, size_t capacity); void StringBuilderAppend(Arena *arena, StringBuilder *builder, String *string); /* --- Random --- */ void RandomInit(); u64 RandomGetSeed(); void RandomSetSeed(u64 newSeed); i32 RandomInteger(i32 min, i32 max); f32 RandomFloat(f32 min, f32 max); /* --- File System --- */ #define MAX_FILES 200 typedef struct { char *name; char *extension; i64 size; i64 createTime; i64 modifyTime; } File; char *GetCwd(); void SetCwd(char *destination); bool Mkdir(String path); // NOTE: Mkdir if not exist StringVector ListDir(Arena *arena, String path); enum FileStatsError { FILE_GET_ATTRIBUTES_FAILED = 100, FILE_STATS_FILE_NOT_EXIST }; WARN_UNUSED errno_t FileStats(String path, File *file); enum FileReadError { FILE_NOT_EXIST = 200, FILE_OPEN_FAILED, FILE_GET_SIZE_FAILED, FILE_READ_FAILED }; WARN_UNUSED errno_t FileRead(Arena *arena, String path, String *result); enum FileWriteError { FILE_WRITE_OPEN_FAILED = 300, FILE_WRITE_ACCESS_DENIED, FILE_WRITE_NO_MEMORY, FILE_WRITE_NOT_FOUND, FILE_WRITE_DISK_FULL, FILE_WRITE_IO_ERROR }; WARN_UNUSED errno_t FileWrite(String path, String data); // enum FileWriteError - Same error enum since it uses `FileWrite("")` under the hood WARN_UNUSED errno_t FileReset(String path); enum FileAddError { FILE_ADD_OPEN_FAILED = 400, FILE_ADD_ACCESS_DENIED, FILE_ADD_NO_MEMORY, FILE_ADD_NOT_FOUND, FILE_ADD_DISK_FULL, FILE_ADD_IO_ERROR }; WARN_UNUSED errno_t FileAdd(String path, String data); // NOTE: Adds `\n` at the end always enum FileDeleteError { FILE_DELETE_ACCESS_DENIED = 500, FILE_DELETE_NOT_FOUND, FILE_DELETE_IO_ERROR }; WARN_UNUSED errno_t FileDelete(String path); enum FileRenameError { FILE_RENAME_ACCESS_DENIED = 600, FILE_RENAME_NOT_FOUND, FILE_RENAME_IO_ERROR }; WARN_UNUSED errno_t FileRename(String oldPath, String newPath); /* --- Logger --- */ #define _RESET "\x1b[0m" #define _GRAY "\x1b[0;36m" #define _RED "\x1b[0;31m" #define _GREEN "\x1b[0;32m" #define _ORANGE "\x1b[0;33m" void LogInit(); void LogInfo(const char *format, ...) FORMAT_CHECK(1, 2); void LogWarn(const char *format, ...) FORMAT_CHECK(1, 2); void LogError(const char *format, ...) FORMAT_CHECK(1, 2); void LogSuccess(const char *format, ...) FORMAT_CHECK(1, 2); /* --- Math --- */ #define Min(a, b) (((a) < (b)) ? (a) : (b)) #define Max(a, b) (((a) > (b)) ? (a) : (b)) #define Clamp(a, x, b) (((x) < (a)) ? (a) : ((b) < (x)) ? (b) : (x)) #define Swap(a, b) \ do { \ a ^= b; \ b ^= a; \ a ^= b; \ } while (0); /* --- Defer Macros --- */ #if defined(DEFER_MACRO) // NOTE: Optional since not all compilers support it and not all C versions do either /* - GCC implementation - NOTE: Must use C23 (depending on the platform) */ # if defined(COMPILER_GCC) # define defer __DEFER(__COUNTER__) # define __DEFER(N) __DEFER_(N) # define __DEFER_(N) __DEFER__(__DEFER_FUNCTION_##N, __DEFER_VARIABLE_##N) # define __DEFER__(F, V) \ auto void F(int *); \ [[gnu::cleanup(F)]] int V; \ auto void F(int *) /* - Clang implementation - NOTE: Must compile with flag `-fblocks` */ # elif defined(COMPILER_CLANG) typedef void (^const __df_t)(void); [[maybe_unused]] static inline void __df_cb(__df_t *__fp) { (*__fp)(); } # define defer __DEFER(__COUNTER__) # define __DEFER(N) __DEFER_(N) # define __DEFER_(N) __DEFER__(__DEFER_VARIABLE_##N) # define __DEFER__(V) [[gnu::cleanup(__df_cb)]] __df_t V = ^void(void) /* -- MSVC implementation -- NOTE: Not available yet in MSVC, use `_try/_finally` */ # elif defined(COMPILER_MSVC) # error "Not available yet in MSVC, use `_try/_finally`" # endif #endif /* --- INI Parser --- */ typedef struct { String key; String value; } IniEntry; VEC_TYPE(IniEntryVector, IniEntry); typedef struct { IniEntryVector data; Arena *arena; } IniFile; WARN_UNUSED errno_t IniParse(String path, IniFile *result); WARN_UNUSED errno_t IniWrite(String path, IniFile *iniFile); // NOTE: Updates/Creates .ini file void IniFree(IniFile *iniFile); String IniGet(IniFile *ini, String key); String IniSet(IniFile *ini, String key, String value); i32 IniGetInt(IniFile *ini, String key); i64 IniGetLong(IniFile *ini, String key); f64 IniGetDouble(IniFile *ini, String key); bool IniGetBool(IniFile *ini, String key); /* MIT License base.h - Implementation of base.h https://github.com/TomasBorquez/base.h */ #if defined(BASE_IMPLEMENTATION) // --- Vector Implementation --- static void vecPush(void **data, size_t *length, size_t *capacity, size_t element_size, void *value) { // WARNING: Vector must always be initialized to zero `Vector vector = {0}` Assert(*length <= *capacity, "VecPush: Possible memory corruption or vector not initialized, `Vector vector = {0}`"); Assert(!(*length > 0 && *data == NULL), "VecPush: Possible memory corruption, data should be NULL only if length == 0"); if (*length >= *capacity) { if (*capacity == 0) *capacity = 128; else *capacity *= 2; *data = Realloc(*data, *capacity * element_size); } void *address = (char *)(*data) + (*length * element_size); memcpy(address, value, element_size); (*length)++; } static void *vecPop(void *data, size_t *length, size_t element_size) { Assert(*length > 0, "VecPop: Cannot pop from empty vector"); (*length)--; return (char *)data + (*length * element_size); } static void vecShift(void **data, size_t *length, size_t element_size) { Assert(*length != 0, "VecShift: Length should at least be >= 1"); memmove(*data, (char *)(*data) + element_size, ((*length) - 1) * element_size); (*length)--; } static void vecUnshift(void **data, size_t *length, size_t *capacity, size_t element_size, const void *value) { if (*length >= *capacity) { if (*capacity == 0) *capacity = 2; else *capacity *= 2; *data = Realloc(*data, *capacity * element_size); } if (*length > 0) { memmove((char *)(*data) + element_size, *data, (*length) * element_size); } memcpy(*data, value, element_size); (*length)++; } static void vecInsert(void **data, size_t *length, size_t *capacity, size_t element_size, void *value, size_t index) { Assert(index <= *length, "VecInsert: Index out of bounds for insertion"); if (*length >= *capacity) { if (*capacity == 0) *capacity = 2; else *capacity *= 2; *data = Realloc(*data, *capacity * element_size); } if (index < *length) { memmove((char *)(*data) + ((index + 1) * element_size), (char *)(*data) + (index * element_size), (*length - index) * element_size); } memcpy((char *)(*data) + (index * element_size), value, element_size); (*length)++; } static void *vecAt(void **data, size_t *length, size_t index, size_t elementSize) { Assert(index >= 0 && index < *length, "VecAt: Index out of bounds"); void *address = (char *)(*data) + (index * elementSize); return address; } static void vecFree(void **data, size_t *length, size_t *capacity) { free(*data); *data = NULL; *length = 0; *capacity = 0; } // --- Time and Platforms Implementation --- # if !defined(PLATFORM_WIN) # if !defined(EINVAL) # define EINVAL 22 // Invalid argument # endif # if !defined(ERANGE) # define ERANGE 34 // Result too large # endif WARN_UNUSED errno_t memcpy_s(void *dest, size_t destSize, const void *src, size_t count) { if (dest == NULL) { return EINVAL; } if (src == NULL || destSize < count) { memset(dest, 0, destSize); return EINVAL; } memcpy(dest, src, count); return 0; } WARN_UNUSED errno_t fopen_s(FILE **streamptr, const char *filename, const char *mode) { if (streamptr == NULL) { return EINVAL; } *streamptr = NULL; if (filename == NULL || mode == NULL) { return EINVAL; } if (*filename == '\0') { return EINVAL; } const char *valid_modes = "rwa+btx"; size_t mode_len = strlen(mode); if (mode_len == 0) { return EINVAL; } for (size_t i = 0; i < mode_len; i++) { if (strchr(valid_modes, mode[i]) == NULL) { return EINVAL; } } *streamptr = fopen(filename, mode); if (*streamptr == NULL) { return errno; } return 0; } # endif String GetCompiler() { # if defined(COMPILER_CLANG) return S("clang"); # elif defined(COMPILER_GCC) return S("gcc"); # elif defined(COMPILER_TCC) return S("tcc"); # elif defined(COMPILER_MSVC) return S("cl.exe"); # endif } String GetPlatform() { # if defined(PLATFORM_WIN) return S("windows"); # elif defined(PLATFORM_LINUX) return S("linux"); # elif defined(PLATFORM_MACOS) return S("macos"); # endif } i64 TimeNow() { # if defined(PLATFORM_WIN) FILETIME ft; GetSystemTimeAsFileTime(&ft); LARGE_INTEGER li; li.LowPart = ft.dwLowDateTime; li.HighPart = ft.dwHighDateTime; // Convert Windows FILETIME (100-nanosecond intervals since January 1, 1601) // to UNIX timestamp in milliseconds i64 currentTime = (li.QuadPart - 116444736000000000LL) / 10000; # else struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); i64 currentTime = (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL); # endif Assert(currentTime != -1, "TimeNow: currentTime should never be -1"); return currentTime; } void WaitTime(i64 ms) { # if defined(PLATFORM_WIN) sleep(ms); # else struct timespec ts; ts.tv_sec = ms / 1000; ts.tv_nsec = (ms % 1000) * 1000000; nanosleep(&ts, NULL); # endif } /* --- Error Implementation --- */ String FileErrToStr(errno_t err) { if (err < 100) { # if defined(PLATFORM_WIN) char buffer[256]; DWORD msgLen = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL); // Trim message if (msgLen > 0) { while (msgLen > 0 && (buffer[msgLen - 1] == '\r' || buffer[msgLen - 1] == '\n')) { buffer[--msgLen] = '\0'; } if (msgLen > 0 && buffer[msgLen - 1] == '.') { buffer[--msgLen] = '\0'; } return s(buffer); } return S("Unknown system error"); # else return s(strerror(err)); # endif } switch (err) { // FileStats errors (100-199) case FILE_GET_ATTRIBUTES_FAILED: return S("Failed to get file attributes"); case FILE_STATS_FILE_NOT_EXIST: return S("File does not exist"); // FileRead errors (200-299) case FILE_NOT_EXIST: return S("File does not exist"); case FILE_OPEN_FAILED: return S("Failed to open file for reading"); case FILE_GET_SIZE_FAILED: return S("Failed to get file size"); case FILE_READ_FAILED: return S("Failed to read file"); // FileWrite errors (300-399) case FILE_WRITE_OPEN_FAILED: return S("Failed to open file for writing"); case FILE_WRITE_ACCESS_DENIED: return S("Access denied when writing file"); case FILE_WRITE_NO_MEMORY: return S("Not enough memory to write file"); case FILE_WRITE_NOT_FOUND: return S("File not found for writing"); case FILE_WRITE_DISK_FULL: return S("Disk full when writing file"); case FILE_WRITE_IO_ERROR: return S("I/O error when writing file"); // FileAdd errors (400-499) case FILE_ADD_OPEN_FAILED: return S("Failed to open file for appending"); case FILE_ADD_ACCESS_DENIED: return S("Access denied when appending to file"); case FILE_ADD_NO_MEMORY: return S("Not enough memory to append to file"); case FILE_ADD_NOT_FOUND: return S("File not found for appending"); case FILE_ADD_DISK_FULL: return S("Disk full when appending to file"); case FILE_ADD_IO_ERROR: return S("I/O error when appending to file"); // FileDelete errors (500-599) case FILE_DELETE_ACCESS_DENIED: return S("Access denied when deleting file"); case FILE_DELETE_NOT_FOUND: return S("File not found for deletion"); case FILE_DELETE_IO_ERROR: return S("I/O error when deleting file"); // FileRename errors (600-699) case FILE_RENAME_ACCESS_DENIED: return S("Access denied when renaming file"); case FILE_RENAME_NOT_FOUND: return S("File not found for renaming"); case FILE_RENAME_IO_ERROR: return S("I/O error when renaming file"); default: return S("Unknown file error"); } } void _custom_assert(const char *expr, const char *file, unsigned line, const char *format, ...) { printf("%sAssertion failed: %s, file %s, line %u %s\n", _RED, expr, file, line, _RESET); if (format) { va_list args; va_start(args, format); LogError(format, args); va_start(args, format); } abort(); } /* --- Arena Implementation --- */ // Allocate or iterate to next chunk that can fit `bytes` void __ArenaNextChunk(Arena *arena, size_t bytes) { __ArenaChunk *next = arena->current ? arena->current->next : NULL; while (next) { arena->current = next; if (arena->current->cap > bytes) { return; } next = arena->current->next; } next = (__ArenaChunk *)Malloc(sizeof(__ArenaChunk) + bytes); next->cap = bytes; next->next = NULL; if (arena->current) arena->current->next = next; arena->current = next; } void *ArenaAllocAligned(Arena *arena, size_t size, size_t al) { void *current_pos = arena->current->buffer + arena->offset; intptr_t mask = al - 1; intptr_t misalignment = ((intptr_t)current_pos & mask); intptr_t padding = misalignment ? al - misalignment : 0; arena->offset += padding; void *result; if (arena->offset + size > arena->current->cap) { size_t bytes = size > arena->chunkSize ? size : arena->chunkSize; __ArenaNextChunk(arena, bytes); current_pos = arena->current->buffer; misalignment = ((intptr_t)current_pos & mask); padding = misalignment ? al - misalignment : 0; arena->offset = padding; result = arena->current->buffer + arena->offset; arena->offset += size; } else { result = arena->current->buffer + arena->offset; arena->offset += size; } if (size) memset(result, 0, size); return result; } char *ArenaAllocChars(Arena *arena, size_t count) { return (char *)ArenaAllocAligned(arena, count, 1); } void *ArenaAlloc(Arena *arena, const size_t size) { return ArenaAllocAligned(arena, size, DEFAULT_ALIGNMENT); } void ArenaFree(Arena *arena) { __ArenaChunk *chunk = arena->root; while (chunk) { __ArenaChunk *next = chunk->next; free(chunk); chunk = next; } free(arena); } void ArenaReset(Arena *arena) { arena->current = arena->root; arena->offset = 0; } Arena *ArenaCreate(size_t chunkSize) { Arena *res = (Arena *)Malloc(sizeof(Arena)); memset(res, 0, sizeof(*res)); res->chunkSize = chunkSize; __ArenaNextChunk(res, chunkSize); res->root = res->current; return res; } /* Memory Allocations */ // TODO: Add hashmap that checks for unfreed values only on DEBUG, __FILE__ and __LINE__ void *Malloc(size_t size) { Assert(size > 0, "Malloc: size cant be negative"); void *address = malloc(size); Assert(address != NULL, "Malloc: failed, returned address should never be NULL"); return address; } void *Realloc(void *block, size_t size) { Assert(size > 0, "Realloc: size cant be negative"); void *address = realloc(block, size); Assert(address != NULL, "Realloc: failed, returned address should never be NULL"); return address; } void Free(void *address) { free(address); } /* String Implementation */ static size_t maxStringSize = 10000; static size_t strLength(char *str, size_t maxSize) { if (str == NULL) { return 0; } size_t len = 0; while (len < maxSize && str[len] != '\0') { len++; } return len; } static void addNullTerminator(char *str, size_t len) { str[len] = '\0'; } bool StrIsNull(String str) { return str.data == NULL; } void SetMaxStrSize(size_t size) { maxStringSize = size; } String StrNewSize(Arena *arena, char *str, size_t len) { Assert(str != NULL, "StrNewSize: failed, can't give a NULL str"); const size_t memorySize = sizeof(char) * len + 1; // NOTE: Includes null terminator char *allocatedString = ArenaAllocChars(arena, memorySize); memcpy(allocatedString, str, memorySize); addNullTerminator(allocatedString, len); return (String){len, allocatedString}; } String StrNew(Arena *arena, char *str) { const size_t len = strLength(str, maxStringSize); if (len == 0) { return (String){0, NULL}; } const size_t memorySize = sizeof(char) * len + 1; // NOTE: Includes null terminator char *allocatedString = ArenaAllocChars(arena, memorySize); memcpy(allocatedString, str, memorySize); addNullTerminator(allocatedString, len); return (String){len, allocatedString}; } String s(char *msg) { if (msg == NULL) { return (String){0}; } return (String){ .length = strlen(msg), .data = msg, }; } String StrConcat(Arena *arena, String string1, String string2) { if (StrIsNull(string1)) { const size_t len = string2.length; const size_t memorySize = sizeof(char) * len; char *allocatedString = ArenaAllocChars(arena, memorySize); errno_t err = memcpy_s(allocatedString, memorySize, string2.data, string2.length); Assert(err == SUCCESS, "StrConcat: memcpy_s failed, err: %d", err); addNullTerminator(allocatedString, len); return (String){len, allocatedString}; } if (StrIsNull(string2)) { const size_t len = string1.length; const size_t memorySize = sizeof(char) * len; char *allocatedString = ArenaAllocChars(arena, memorySize); errno_t err = memcpy_s(allocatedString, memorySize, string1.data, string1.length); Assert(err == SUCCESS, "StrConcat: memcpy_s failed, err: %d", err); addNullTerminator(allocatedString, len); return (String){len, allocatedString}; } const size_t len = string1.length + string2.length; const size_t memorySize = sizeof(char) * len + 1; // NOTE: Includes null terminator char *allocatedString = ArenaAllocChars(arena, memorySize); errno_t err = memcpy_s(allocatedString, memorySize, string1.data, string1.length); Assert(err == SUCCESS, "StrConcat: memcpy_s failed, err: %d", err); err = memcpy_s(allocatedString + string1.length, memorySize, string2.data, string2.length); Assert(err == SUCCESS, "StrConcat: memcpy_s failed, err: %d", err); addNullTerminator(allocatedString, len); return (String){len, allocatedString}; } void StrCopy(String destination, String source) { Assert(!StrIsNull(destination), "StrCopy: destination should never be NULL"); Assert(!StrIsNull(source), "StrCopy: source should never be NULL"); Assert(destination.length >= source.length, "destination length should never smaller than source length"); errno_t err = memcpy_s(destination.data, destination.length, source.data, source.length); Assert(err == SUCCESS, "StrCopy: memcpy_s failed, err: %d", err); destination.length = source.length; addNullTerminator(destination.data, destination.length); } bool StrEq(String string1, String string2) { if (string1.length != string2.length) { return false; } if (memcmp(string1.data, string2.data, string1.length) != 0) { return false; } return true; } StringVector StrSplit(Arena *arena, String str, String delimiter) { Assert(!StrIsNull(str), "StrSplit: str should never be NULL"); Assert(!StrIsNull(delimiter), "StrSplit: delimiter should never be NULL"); char *start = str.data; const char *end = str.data + str.length; char *curr = start; StringVector result = {0}; if (delimiter.length == 0) { for (size_t i = 0; i < str.length; i++) { String currString = StrNewSize(arena, str.data + i, 1); VecPush(result, currString); } return result; } while (curr < end) { char *match = NULL; for (char *search = curr; search <= end - delimiter.length; search++) { if (memcmp(search, delimiter.data, delimiter.length) == 0) { match = search; break; } } if (!match) { String currString = StrNewSize(arena, curr, end - curr); VecPush(result, currString); break; } size_t len = match - curr; String currString = StrNewSize(arena, curr, len); VecPush(result, currString); curr = match + delimiter.length; } return result; } StringVector StrSplitNewLine(Arena *arena, String str) { Assert(!StrIsNull(str), "SplitNewLine: str should never be NULL"); char *start = str.data; const char *end = str.data + str.length; char *curr = start; StringVector result = {0}; while (curr < end) { char *pos = curr; while (pos < end && *pos != '\n') { pos++; } size_t len = pos - curr; if (pos < end && pos > curr && *(pos - 1) == '\r') { len--; } String currString = StrNewSize(arena, curr, len); VecPush(result, currString); if (pos < end) { curr = pos + 1; } else { break; } } return result; } void StrToUpper(String str) { for (size_t i = 0; i < str.length; ++i) { char currChar = str.data[i]; str.data[i] = toupper(currChar); } } void StrToLower(String str) { for (size_t i = 0; i < str.length; ++i) { char currChar = str.data[i]; str.data[i] = tolower(currChar); } } bool isSpace(char character) { return character == ' ' || character == '\n' || character == '\t' || character == '\r'; } void StrTrim(String *str) { char *firstChar = NULL; char *lastChar = NULL; if (str->length == 0) { return; } if (str->length == 1) { if (isSpace(str->data[0])) { str->data[0] = '\0'; str->length = 0; } return; } for (size_t i = 0; i < str->length; ++i) { char *currChar = &str->data[i]; if (isSpace(*currChar)) { continue; } if (firstChar == NULL) { firstChar = currChar; } lastChar = currChar; } if (firstChar == NULL || lastChar == NULL) { str->data[0] = '\0'; str->length = 0; addNullTerminator(str->data, 0); return; } size_t len = (lastChar - firstChar) + 1; errno_t err = memcpy_s(str->data, str->length, firstChar, len); Assert(err == SUCCESS, "str->rim: memcpy_s failed, err: %d", err); str->length = len; addNullTerminator(str->data, len); } String StrSlice(Arena *arena, String str, size_t start, size_t end) { Assert(start >= 0, "StrSlice: start index must be non-negative"); Assert(start <= str.length, "StrSlice: start index out of bounds"); if (end < 0) { end = str.length + end; } Assert(end >= start, "StrSlice: end must be greater than or equal to start"); Assert(end <= str.length, "StrSlice: end index out of bounds"); size_t len = end - start; return StrNewSize(arena, str.data + start, len); } String F(Arena *arena, const char *format, ...) { va_list args; va_start(args, format); size_t size = vsnprintf(NULL, 0, format, args) + 1; // +1 for null terminator va_end(args); char *buffer = ArenaAllocChars(arena, size); va_start(args, format); vsnprintf(buffer, size, format, args); va_end(args); return (String){.length = size - 1, .data = buffer}; } String normSlashes(String path) { # if defined(PLATFORM_WIN) for (size_t i = 0; i < path.length; i++) { if (path.data[i] == '/') { path.data[i] = '\\'; } } # else for (size_t i = 0; i < path.length; i++) { if (path.data[i] == '\\') { path.data[i] = '/'; } } # endif return path; } String NormalizePath(Arena *arena, String path) { String result; if (path.length >= 2 && path.data[0] == '.' && (path.data[1] == '/' || path.data[1] == '\\')) { result = StrNewSize(arena, path.data + 2, path.length - 2); } else { result = StrNewSize(arena, path.data, path.length); } return normSlashes(result); } String NormalizeExePath(Arena *arena, String path) { String platform = GetPlatform(); String result; if (path.length >= 2 && path.data[0] == '.' && (path.data[1] == '/' || path.data[1] == '\\')) { result = StrNewSize(arena, path.data + 2, path.length - 2); } else { result = StrNewSize(arena, path.data, path.length); } bool hasExe = false; String exeExtension = S(".exe"); if (result.length >= exeExtension.length) { String resultEnd = StrSlice(arena, result, result.length - exeExtension.length, result.length); if (StrEq(resultEnd, exeExtension)) { hasExe = true; } } if (StrEq(platform, S("windows"))) { if (!hasExe) { result = StrConcat(arena, result, exeExtension); } return normSlashes(result); } if (hasExe) { result = StrSlice(arena, result, 0, result.length - exeExtension.length); } return normSlashes(result); } String NormalizeExtension(Arena *arena, String path) { String result; if (path.length >= 2 && path.data[0] == '.' && (path.data[1] == '/' || path.data[1] == '\\')) { result = StrNewSize(arena, path.data + 2, path.length - 2); } else { result = StrNewSize(arena, path.data, path.length); } size_t filenameStart = 0; for (size_t i = 0; i < result.length; i++) { if (result.data[i] == '/' || result.data[i] == '\\') { filenameStart = i + 1; } } size_t lastDotIndex = 0; for (size_t i = 0; i < result.length; i++) { if (result.data[i] == '.') { lastDotIndex = i; } } if (lastDotIndex <= filenameStart) { return normSlashes(result); } result = StrSlice(arena, result, filenameStart, lastDotIndex); return normSlashes(result); } String NormalizeStaticLibPath(Arena *arena, String path) { String platform = GetPlatform(); String result; if (path.length >= 2 && path.data[0] == '.' && (path.data[1] == '/' || path.data[1] == '\\')) { result = StrNewSize(arena, path.data + 2, path.length - 2); } else { result = StrNewSize(arena, path.data, path.length); } bool hasLibExt = false; String libExtension; String aExtension = S(".a"); String libWinExtension = S(".lib"); if (result.length >= aExtension.length) { String resultEnd = StrSlice(arena, result, result.length - aExtension.length, result.length); if (StrEq(resultEnd, aExtension)) { hasLibExt = true; libExtension = aExtension; } } if (!hasLibExt && result.length >= libWinExtension.length) { String resultEnd = StrSlice(arena, result, result.length - libWinExtension.length, result.length); if (StrEq(resultEnd, libWinExtension)) { hasLibExt = true; libExtension = libWinExtension; } } if (StrEq(platform, S("windows"))) { if (hasLibExt && !StrEq(libExtension, libWinExtension)) { result = StrSlice(arena, result, 0, result.length - libExtension.length); hasLibExt = false; } if (!hasLibExt) { result = StrConcat(arena, result, libWinExtension); } return normSlashes(result); } if (hasLibExt && !StrEq(libExtension, aExtension)) { result = StrSlice(arena, result, 0, result.length - libExtension.length); hasLibExt = false; } if (!hasLibExt) { result = StrConcat(arena, result, aExtension); } return normSlashes(result); } String NormalizePathStart(Arena *arena, String path) { String result; if (path.length >= 2 && path.data[0] == '.' && (path.data[1] == '/' || path.data[1] == '\\')) { result = StrNewSize(arena, path.data + 2, path.length - 2); } else { result = StrNewSize(arena, path.data, path.length); } return result; } String NormalizePathEnd(Arena *arena, String path) { size_t lastSlashIndex = 0; for (size_t i = 0; i < path.length; i++) { if (path.data[i] == '/' || path.data[i] == '\\') { lastSlashIndex = i + 1; } } return StrNewSize(arena, path.data + lastSlashIndex, path.length - lastSlashIndex); } StringBuilder StringBuilderCreate(Arena *arena) { StringBuilder result = {0}; char *data = ArenaAllocChars(arena, 128); result.capacity = 128; result.buffer = (String){.data = data, .length = 0}; return result; } StringBuilder StringBuilderReserve(Arena *arena, size_t capacity) { StringBuilder result = {0}; char *data = ArenaAllocChars(arena, capacity); result.capacity = capacity; result.buffer = (String){.data = data, .length = 0}; return result; } void StringBuilderAppend(Arena *arena, StringBuilder *builder, String *string) { size_t newLength = builder->buffer.length + string->length; if (newLength + 1 >= builder->capacity) { size_t newCapacity = (newLength + 1) * 2; char *data = ArenaAllocChars(arena, newCapacity); memcpy(data, builder->buffer.data, builder->buffer.length); builder->buffer.data = data; builder->capacity = newCapacity; } memcpy(builder->buffer.data + builder->buffer.length, string->data, string->length); builder->buffer.length = newLength; builder->buffer.data[builder->buffer.length] = '\0'; } /* Random Implemenation */ static u64 seed = 0; u64 RandomGetSeed() { return seed; } void RandomSetSeed(u64 newSeed) { seed = newSeed; srand(seed); } i32 RandomInteger(i32 min, i32 max) { Assert(min <= max, "RandomInteger: min should always be less than or equal to max"); Assert(max - min <= INT32_MAX - 1, "RandomInteger: range too large"); i32 range = max - min + 1; // Calculate scaling factor to avoid modulo bias u32 buckets = RAND_MAX / range; u32 limit = buckets * range; // Reject numbers that would create bias u32 r; do { r = rand(); } while (r >= limit); return min + (r / buckets); } f32 RandomFloat(f32 min, f32 max) { Assert(min <= max, "RandomFloat: min must be less than or equal to max"); f32 normalized = (f32)rand() / (f32)RAND_MAX; return min + normalized * (max - min); } /* File System Implementation */ # if defined(PLATFORM_WIN) char *GetCwd() { static char currentPath[MAX_PATH]; DWORD length = GetCurrentDirectory(MAX_PATH, currentPath); if (length == 0) { LogError("GetCwd: failed getting current directory, err: %lu", GetLastError()); currentPath[0] = '\0'; } return currentPath; } void SetCwd(char *destination) { bool result = SetCurrentDirectory(destination); if (!result) { LogError("SetCwd: failed setting cwd for %s, err: %lu", destination, GetLastError()); } GetCwd(); } errno_t FileStats(String path, File *result) { if (GetFileAttributesA(path.data) == INVALID_FILE_ATTRIBUTES) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { return FILE_STATS_FILE_NOT_EXIST; } return FILE_GET_ATTRIBUTES_FAILED; } WIN32_FILE_ATTRIBUTE_DATA fileAttr = {0}; if (!GetFileAttributesExA(path.data, GetFileExInfoStandard, &fileAttr)) { return FILE_GET_ATTRIBUTES_FAILED; } char *nameStart = strrchr(path.data, '\\'); if (!nameStart) { nameStart = strrchr(path.data, '/'); } if (nameStart) { nameStart++; } else { nameStart = path.data; } result->name = strdup(nameStart); char *extStart = strrchr(nameStart, '.'); if (extStart) { result->extension = strdup(extStart + 1); } else { result->extension = strdup(""); } LARGE_INTEGER fileSize; fileSize.HighPart = fileAttr.nFileSizeHigh; fileSize.LowPart = fileAttr.nFileSizeLow; result->size = fileSize.QuadPart; LARGE_INTEGER createTime, modifyTime; createTime.LowPart = fileAttr.ftCreationTime.dwLowDateTime; createTime.HighPart = fileAttr.ftCreationTime.dwHighDateTime; modifyTime.LowPart = fileAttr.ftLastWriteTime.dwLowDateTime; modifyTime.HighPart = fileAttr.ftLastWriteTime.dwHighDateTime; const i64 WINDOWS_TICK = 10000000; const i64 SEC_TO_UNIX_EPOCH = 11644473600LL; result->createTime = createTime.QuadPart / WINDOWS_TICK - SEC_TO_UNIX_EPOCH; result->modifyTime = modifyTime.QuadPart / WINDOWS_TICK - SEC_TO_UNIX_EPOCH; return SUCCESS; } errno_t FileRead(Arena *arena, String path, String *result) { HANDLE hFile = INVALID_HANDLE_VALUE; hFile = CreateFileA(path.data, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { return FILE_NOT_EXIST; } return FILE_OPEN_FAILED; } LARGE_INTEGER fileSize; if (!GetFileSizeEx(hFile, &fileSize)) { CloseHandle(hFile); return FILE_GET_SIZE_FAILED; } DWORD bytesRead; char *buffer = ArenaAllocChars(arena, fileSize.QuadPart); if (!ReadFile(hFile, buffer, (DWORD)fileSize.QuadPart, &bytesRead, NULL) || bytesRead != fileSize.QuadPart) { CloseHandle(hFile); return FILE_READ_FAILED; } *result = (String){.length = bytesRead, .data = buffer}; CloseHandle(hFile); return SUCCESS; } errno_t FileWrite(String path, String data) { HANDLE hFile = INVALID_HANDLE_VALUE; hFile = CreateFileA(path.data, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: return FILE_WRITE_ACCESS_DENIED; case ERROR_FILE_NOT_FOUND: return FILE_WRITE_NOT_FOUND; default: return FILE_WRITE_OPEN_FAILED; } } DWORD bytesWritten; if (!WriteFile(hFile, data.data, (DWORD)data.length, &bytesWritten, NULL) || bytesWritten != data.length) { DWORD error = GetLastError(); CloseHandle(hFile); switch (error) { case ERROR_DISK_FULL: return FILE_WRITE_DISK_FULL; default: return FILE_WRITE_IO_ERROR; } } CloseHandle(hFile); return SUCCESS; } errno_t FileAdd(String path, String data) { HANDLE hFile = INVALID_HANDLE_VALUE; hFile = CreateFileA(path.data, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: return FILE_ADD_ACCESS_DENIED; case ERROR_FILE_NOT_FOUND: return FILE_ADD_NOT_FOUND; default: return FILE_ADD_OPEN_FAILED; } } if (SetFilePointer(hFile, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) { CloseHandle(hFile); return FILE_ADD_IO_ERROR; } char *newData = Malloc(data.length + 1); // NOTE: +1 for \n memcpy(newData, data.data, data.length); newData[data.length] = '\n'; DWORD newLength = (DWORD)data.length + 1; DWORD bytesWritten; if (!WriteFile(hFile, newData, newLength, &bytesWritten, NULL) || bytesWritten != newLength) { DWORD error = GetLastError(); CloseHandle(hFile); Free(newData); switch (error) { case ERROR_DISK_FULL: return FILE_ADD_DISK_FULL; default: return FILE_ADD_IO_ERROR; } } CloseHandle(hFile); Free(newData); return SUCCESS; } errno_t FileDelete(String path) { if (!DeleteFileA(path.data)) { DWORD error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: return FILE_DELETE_ACCESS_DENIED; case ERROR_FILE_NOT_FOUND: return FILE_DELETE_NOT_FOUND; default: return FILE_DELETE_IO_ERROR; } } return SUCCESS; } errno_t FileRename(String oldPath, String newPath) { if (!MoveFileEx(oldPath.data, newPath.data, MOVEFILE_REPLACE_EXISTING)) { DWORD error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: return FILE_RENAME_ACCESS_DENIED; case ERROR_FILE_NOT_FOUND: return FILE_RENAME_NOT_FOUND; default: return FILE_RENAME_IO_ERROR; } } return SUCCESS; } bool Mkdir(String path) { bool result = CreateDirectory(path.data, NULL); if (result != false) { return true; } u64 error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) { return true; } LogError("Mkdir: failed for %s, err: %llu", path.data, error); return false; } StringVector ListDir(Arena *arena, String path) { StringVector result = {0}; WIN32_FIND_DATA findData; HANDLE hFind = INVALID_HANDLE_VALUE; char searchPath[MAX_PATH]; snprintf(searchPath, MAX_PATH, "%s\\*", path.data); hFind = FindFirstFile(searchPath, &findData); if (hFind == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); LogError("ListDir: failed for %s, err: %lu", path.data, error); return result; } do { if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0) { continue; } String entry = StrNew(arena, findData.cFileName); VecPush(result, entry); } while (FindNextFile(hFind, &findData) != 0); DWORD error = GetLastError(); if (error != ERROR_NO_MORE_FILES) { LogError("ListDir: failed reading directory %s, err: %lu", path.data, error); } FindClose(hFind); return result; } # else char *GetCwd() { static char currentPath[PATH_MAX]; if (getcwd(currentPath, PATH_MAX) == NULL) { LogError("GetCwd: failed getting current directory, err: %s", strerror(errno)); currentPath[0] = '\0'; } return currentPath; } void SetCwd(char *destination) { if (chdir(destination) != 0) { LogError("SetCwd: failed setting cwd for %s, err: %s", destination, strerror(errno)); } GetCwd(); } errno_t FileStats(String path, File *result) { struct stat fileStat; if (stat(path.data, &fileStat) != 0) { if (errno == ENOENT) { return FILE_STATS_FILE_NOT_EXIST; } return FILE_GET_ATTRIBUTES_FAILED; } char *nameStart = strrchr(path.data, '/'); if (nameStart) { nameStart++; } else { nameStart = path.data; } result->name = strdup(nameStart); char *extStart = strrchr(nameStart, '.'); if (extStart) { result->extension = strdup(extStart + 1); } else { result->extension = strdup(""); } result->size = fileStat.st_size; result->createTime = fileStat.st_ctime; // Creation time (may be change time on some Unix systems) result->modifyTime = fileStat.st_mtime; // Modification time return SUCCESS; } errno_t FileRead(Arena *arena, String path, String *result) { FILE *file = fopen(path.data, "r"); if (!file) { int error = errno; if (error == ENOENT) { return FILE_NOT_EXIST; } return FILE_OPEN_FAILED; } if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return FILE_GET_SIZE_FAILED; } long fileSize = ftell(file); if (fileSize == -1) { fclose(file); return FILE_GET_SIZE_FAILED; } rewind(file); char *buffer = ArenaAllocChars(arena, fileSize); size_t bytesRead = fread(buffer, 1, fileSize, file); if (bytesRead != (size_t)fileSize) { fclose(file); return FILE_READ_FAILED; } *result = (String){.length = bytesRead, .data = buffer}; fclose(file); return SUCCESS; } errno_t FileWrite(String path, String data) { i32 fd = -1; fd = open(path.data, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { i32 error = errno; switch (error) { case EACCES: return FILE_WRITE_ACCESS_DENIED; case ENOENT: return FILE_WRITE_NOT_FOUND; default: return FILE_WRITE_OPEN_FAILED; } } ssize_t bytesWritten = write(fd, data.data, data.length); if (bytesWritten != data.length) { int error = errno; close(fd); switch (error) { case ENOSPC: return FILE_WRITE_DISK_FULL; default: return FILE_WRITE_IO_ERROR; } } close(fd); return SUCCESS; } errno_t FileAdd(String path, String data) { i32 fd = -1; fd = open(path.data, O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd == -1) { int error = errno; switch (error) { case EACCES: return FILE_ADD_ACCESS_DENIED; case ENOENT: return FILE_ADD_NOT_FOUND; default: return FILE_ADD_OPEN_FAILED; } } char *newData = Malloc(data.length + 1); // +1 for \n memcpy(newData, data.data, data.length); newData[data.length] = '\n'; size_t newLength = data.length + 1; ssize_t bytesWritten = write(fd, newData, newLength); if (bytesWritten != newLength) { int error = errno; close(fd); Free(newData); switch (error) { case ENOSPC: return FILE_ADD_DISK_FULL; default: return FILE_ADD_IO_ERROR; } } close(fd); Free(newData); return SUCCESS; } errno_t FileDelete(String path) { if (unlink(path.data) != 0) { int error = errno; switch (error) { case EACCES: return FILE_DELETE_ACCESS_DENIED; case ENOENT: return FILE_DELETE_NOT_FOUND; default: return FILE_DELETE_IO_ERROR; } } return SUCCESS; } errno_t FileRename(String oldPath, String newPath) { if (rename(oldPath.data, newPath.data) != 0) { errno_t error = errno; switch (error) { case EACCES: return FILE_RENAME_ACCESS_DENIED; case ENOENT: return FILE_RENAME_NOT_FOUND; default: return FILE_RENAME_IO_ERROR; } } return SUCCESS; } bool Mkdir(String path) { struct stat st; if (stat(path.data, &st) == 0 && S_ISDIR(st.st_mode)) { return true; // Directory already exists } if (mkdir(path.data, 0755) != 0) { if (errno != EEXIST) { LogError("Mkdir: failed to create directory for %s, err: %s", path.data, strerror(errno)); return false; } } return true; } StringVector ListDir(Arena *arena, String path) { StringVector result = {0}; DIR *dir = opendir(path.data); if (dir == NULL) { errno_t error = errno; LogError("ListDir: failed opening directory for %s, err: %d", path.data, error); return result; } errno = 0; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } String entryStr = StrNew(arena, entry->d_name); VecPush(result, entryStr); errno = 0; } if (errno != 0) { errno_t error = errno; LogError("ListDir: failed reading directory %s, err: %d", path.data, error); } closedir(dir); return result; } # endif errno_t FileReset(String path) { return FileWrite(path, S("")); } /* Logger Implemenation */ void LogInfo(const char *format, ...) { printf("%s[INFO]: ", _GRAY); va_list args; va_start(args, format); vprintf(format, args); va_end(args); printf("%s\n", _RESET); } void LogWarn(const char *format, ...) { printf("%s[WARN]: ", _ORANGE); va_list args; va_start(args, format); vprintf(format, args); va_end(args); printf("%s\n", _RESET); } void LogError(const char *format, ...) { printf("%s[ERROR]: ", _RED); va_list args; va_start(args, format); vprintf(format, args); va_end(args); printf("%s\n", _RESET); } void LogSuccess(const char *format, ...) { printf("%s[SUCCESS]: ", _GREEN); va_list args; va_start(args, format); vprintf(format, args); va_end(args); printf("%s\n", _RESET); } void LogInit() { # if defined(PLATFORM_WIN) HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwMode = 0; GetConsoleMode(hOut, &dwMode); dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(hOut, dwMode); # endif } /* --- INI Parser Implementation --- */ errno_t IniParse(String path, IniFile *result) { File stats = {0}; errno_t err = FileStats(path, &stats); if (err == FILE_STATS_FILE_NOT_EXIST) { LogWarn("IniParse: %s does not exist, creating...", path.data); errno_t fileResetErr = FileReset(path); Assert(fileResetErr == SUCCESS, "IniParse: Failed creating file for path %s, err: %d", path.data, fileResetErr); result->arena = ArenaCreate(sizeof(String) * 10); // Initialize arena return SUCCESS; } if (err != SUCCESS) { return err; } String buffer; result->arena = ArenaCreate(stats.size * 4); err = FileRead(result->arena, path, &buffer); if (err != SUCCESS) { return err; } StringVector iniSplit = StrSplitNewLine(result->arena, buffer); VecForEach(iniSplit, currLine) { if (currLine->length == 0 || currLine->data[0] == ';') { continue; } size_t equalPos = (size_t)-1; for (size_t j = 0; j < currLine->length; j++) { if (currLine->data[j] == '=') { equalPos = j; break; } } if (equalPos == (size_t)-1) { continue; } String key = StrSlice(result->arena, *currLine, 0, equalPos); String value = StrSlice(result->arena, *currLine, equalPos + 1, currLine->length); IniEntry entry = {.key = key, .value = value}; VecPush(result->data, entry); } VecFree(iniSplit); return SUCCESS; } errno_t IniWrite(String path, IniFile *iniFile) { errno_t err = FileReset(path); if (err != SUCCESS) { return err; } VecForEach(iniFile->data, entry) { String value = F(iniFile->arena, "%s=%s", entry->key.data, entry->value.data); err = FileAdd(path, value); if (err != SUCCESS) { return err; } } return SUCCESS; } void IniFree(IniFile *iniFile) { ArenaFree(iniFile->arena); VecFree(iniFile->data); } String IniGet(IniFile *ini, String key) { VecForEach(ini->data, entry) { if (StrEq(entry->key, key)) { return entry->value; } } return (String){0}; } String IniSet(IniFile *ini, String key, String value) { VecForEach(ini->data, entry) { if (StrEq(entry->key, key)) { entry->value = value; return entry->value; } } IniEntry newEntry; newEntry.key = key; newEntry.value = value; VecPush(ini->data, newEntry); return newEntry.value; } i32 IniGetInt(IniFile *ini, String key) { String value = IniGet(ini, key); if (StrIsNull(value)) { return 0; } char *endPtr; i32 result = (i32)strtol(value.data, &endPtr, 10); if (endPtr == value.data) { LogWarn("IniGetLong: Failed to convert [key: %s, value: %s] to int", key.data, value.data); return 0; } return result; } i64 IniGetLong(IniFile *ini, String key) { String value = IniGet(ini, key); if (StrIsNull(value)) { return 0; } char *endPtr; i64 result = strtoll(value.data, &endPtr, 10); if (endPtr == value.data) { LogWarn("IniGetLong: Failed to convert [key: %s, value: %s] to long", key.data, value.data); return 0; } return result; } f64 IniGetDouble(IniFile *ini, String key) { String value = IniGet(ini, key); if (StrIsNull(value)) { return 0.0; } char *endPtr; f64 result = strtod(value.data, &endPtr); if (endPtr == value.data) { LogWarn("IniGetLong: Failed to convert [key: %s, value: %s] to double", key.data, value.data); return 0.0; } return result; } bool IniGetBool(IniFile *ini, String key) { String value = IniGet(ini, key); if (StrIsNull(value)) { return false; } return StrEq(value, S("true")); } #endif #ifdef __cplusplus } #endif // --- BASE.H END --- // --- MATE.H START --- /* MIT License mate.h - Mate Definitions start here Guide on the `README.md` */ typedef struct { i64 lastBuild; bool samuraiBuild; bool firstBuild; } MateCache; typedef struct { char *compiler; char *buildDirectory; char *mateSource; char *mateExe; } MateOptions; typedef struct { String compiler; // Paths String buildDirectory; String mateSource; String mateExe; // Cache MateCache mateCache; IniFile cache; // Misc Arena *arena; bool initConfig; i64 startTime; i64 totalTime; } MateConfig; typedef struct { String output; String flags; String arFlags; String ninjaBuildPath; } StaticLib; typedef struct { String output; String flags; String linkerFlags; String ninjaBuildPath; } Executable; enum WarningsFlags { FLAG_WARNINGS_MINIMAL = 1, // -Wall FLAG_WARNINGS, // -Wall -Wextra FLAG_WARNINGS_VERBOSE, // -Wall -Wextra -Wpedantic }; enum DebugFlags { FLAG_DEBUG_MINIMAL = 1, // -g1 FLAG_DEBUG_MEDIUM, // -g/g2 FLAG_DEBUG, // -g3 }; enum OptimizationFlags { FLAG_OPTIMIZATION_NONE = 1, // -O0 FLAG_OPTIMIZATION_BASIC, // -O1 FLAG_OPTIMIZATION, // -O2 FLAG_OPTIMIZATION_SIZE, // -Os FLAG_OPTIMIZATION_AGGRESSIVE // -O3 }; enum StandardFlags { FLAG_STD_C99 = 1, // -std=c99 FLAG_STD_C11, // -std=c11 FLAG_STD_C17, // -std=c17 FLAG_STD_C23, // -std=c23 FLAG_STD_C2X // -std=c2x }; typedef struct { char *output; char *flags; char *linkerFlags; char *includes; char *libs; u8 warnings; u8 debug; u8 optimization; u8 std; } ExecutableOptions; typedef struct { char *output; char *flags; char *arFlags; char *includes; char *libs; u8 warnings; u8 debug; u8 optimization; u8 std; } StaticLibOptions; void StartBuild(); void EndBuild(); void CreateConfig(MateOptions options); String CreateExecutable(ExecutableOptions executableOptions); String InstallExecutable(); void ResetExecutable(); String CreateStaticLib(StaticLibOptions staticLibOptions); String InstallStaticLib(); void ResetStaticLib(); WARN_UNUSED errno_t RunCommand(String command); enum CreateCompileCommandsError { COMPILE_COMMANDS_FAILED_OPEN_FILE = 1000, COMPILE_COMMANDS_FAILED_COMPDB }; WARN_UNUSED errno_t CreateCompileCommands(String ninjaBuildPath); #define AddLibraryPaths(...) \ do { \ StringVector vector = {0}; \ StringVectorPushMany(vector, __VA_ARGS__); \ addLibraryPaths(&vector); \ } while (0) static void addLibraryPaths(StringVector *vector); #define AddIncludePaths(...) \ do { \ StringVector vector = {0}; \ StringVectorPushMany(vector, __VA_ARGS__); \ addIncludePaths(&vector); \ } while (0) static void addIncludePaths(StringVector *vector); #define LinkSystemLibraries(...) \ do { \ StringVector vector = {0}; \ StringVectorPushMany(vector, __VA_ARGS__); \ linkSystemLibraries(&vector); \ } while (0) static void linkSystemLibraries(StringVector *vector); #define AddFile(source) addFile(S(source)); static void addFile(String source); #define RemoveFile(source) removeFile(S(source)); static bool removeFile(String source); static void reBuild(); static bool needRebuild(); static void setDefaultState(); // Utility platform/compiler functions bool isWindows(); bool isLinux(); bool isMSVC(); bool isGCC(); bool isClang(); bool isTCC(); // --- SAMURAI START --- /* * This code comes from Samurai (https://github.com/michaelforney/samurai) * Copyright © 2017-2021 Michael Forney * Licensed under ISC license, with portions under Apache License 2.0 and MIT licenses. * See LICENSE-SAMURAI.txt for the full license text. */ #define SAMURAI_AMALGAM "#define _POSIX_C_SOURCE 200809L\n" \ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "#include \n"\ "struct node;\n"\ "struct buildoptions {\n"\ " size_t maxjobs, maxfail;\n"\ " _Bool verbose, explain, keepdepfile, keeprsp, dryrun;\n"\ " const char *statusfmt;\n"\ " double maxload;\n"\ "};\n"\ "extern struct buildoptions buildopts;\n"\ "void buildreset(void);\n"\ "void buildadd(struct node *);\n"\ "void build(void);\n"\ "struct edge;\n"\ "void depsinit(const char *);\n"\ "void depsclose(void);\n"\ "void depsload(struct edge *);\n"\ "void depsrecord(struct edge *);\n"\ "struct evalstring;\n"\ "struct string;\n"\ "struct rule {\n"\ " char *name;\n"\ " struct treenode *bindings;\n"\ "};\n"\ "struct pool {\n"\ " char *name;\n"\ " int numjobs, maxjobs;\n"\ " struct edge *work;\n"\ "};\n"\ "void envinit(void);\n"\ "struct environment *mkenv(struct environment *);\n"\ "struct string *envvar(struct environment *, char *);\n"\ "void envaddvar(struct environment *, char *, struct string *);\n"\ "struct string *enveval(struct environment *, struct evalstring *);\n"\ "struct rule *envrule(struct environment *, char *);\n"\ "void envaddrule(struct environment *, struct rule *);\n"\ "struct rule *mkrule(char *);\n"\ "void ruleaddvar(struct rule *, char *, struct evalstring *);\n"\ "struct pool *mkpool(char *);\n"\ "struct pool *poolget(char *);\n"\ "struct string *edgevar(struct edge *, char *, _Bool);\n"\ "extern struct environment *rootenv;\n"\ "extern struct rule phonyrule;\n"\ "extern struct pool consolepool;\n"\ "enum {\n"\ " MTIME_UNKNOWN = -1,\n"\ " MTIME_MISSING = -2,\n"\ "};\n"\ "struct node {\n"\ " struct string *path, *shellpath;\n"\ " int64_t mtime, logmtime;\n"\ " struct edge *gen, **use;\n"\ " size_t nuse;\n"\ " uint64_t hash;\n"\ " int32_t id;\n"\ " _Bool dirty;\n"\ "};\n"\ "struct edge {\n"\ " struct rule *rule;\n"\ " struct pool *pool;\n"\ " struct environment *env;\n"\ " struct node **out, **in;\n"\ " size_t nout, nin;\n"\ " size_t outimpidx;\n"\ " size_t inimpidx, inorderidx;\n"\ " uint64_t hash;\n"\ " size_t nblock;\n"\ " size_t nprune;\n"\ " enum {\n"\ " FLAG_WORK = 1 << 0,\n"\ " FLAG_HASH = 1 << 1,\n"\ " FLAG_DIRTY_IN = 1 << 3,\n"\ " FLAG_DIRTY_OUT = 1 << 4,\n"\ " FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT,\n"\ " FLAG_CYCLE = 1 << 5,\n"\ " FLAG_DEPS = 1 << 6,\n"\ " } flags;\n"\ " struct edge *worknext;\n"\ " struct edge *allnext;\n"\ "};\n"\ "void graphinit(void);\n"\ "struct node *mknode(struct string *);\n"\ "struct node *nodeget(const char *, size_t);\n"\ "void nodestat(struct node *);\n"\ "struct string *nodepath(struct node *, _Bool);\n"\ "void nodeuse(struct node *, struct edge *);\n"\ "struct edge *mkedge(struct environment *parent);\n"\ "void edgehash(struct edge *);\n"\ "void edgeadddeps(struct edge *e, struct node **deps, size_t ndeps);\n"\ "extern struct edge *alledges;\n"\ "struct hashtablekey {\n"\ " uint64_t hash;\n"\ " const char *str;\n"\ " size_t len;\n"\ "};\n"\ "void htabkey(struct hashtablekey *, const char *, size_t);\n"\ "struct hashtable *mkhtab(size_t);\n"\ "void delhtab(struct hashtable *, void(void *));\n"\ "void **htabput(struct hashtable *, struct hashtablekey *);\n"\ "void *htabget(struct hashtable *, struct hashtablekey *);\n"\ "uint64_t murmurhash64a(const void *, size_t);\n"\ "struct node;\n"\ "void loginit(const char *);\n"\ "void logclose(void);\n"\ "void logrecord(struct node *);\n"\ "struct environment;\n"\ "struct node;\n"\ "struct parseoptions {\n"\ " _Bool dupbuildwarn;\n"\ "};\n"\ "void parseinit(void);\n"\ "void parse(const char *, struct environment *);\n"\ "extern struct parseoptions parseopts;\n"\ "enum {\n"\ " ninjamajor = 1,\n"\ " ninjaminor = 9,\n"\ "};\n"\ "void defaultnodes(void(struct node *));\n"\ "extern const char *argv0;\n"\ "#define ARGBEGIN \\\n"\ " for (;;) { \\\n"\ " if (argc > 0) ++argv, --argc; \\\n"\ " if (argc == 0 || (*argv)[0] != '-') break; \\\n"\ " if ((*argv)[1] == '-' && !(*argv)[2]) { \\\n"\ " ++argv, --argc; \\\n"\ " break; \\\n"\ " } \\\n"\ " for (char *opt_ = &(*argv)[1], done_ = 0; !done_ && *opt_; ++opt_) { \\\n"\ " switch (*opt_)\n"\ "#define ARGEND \\\n"\ " } \\\n"\ " }\n"\ "#define EARGF(x) (done_ = 1, *++opt_ ? opt_ : argv[1] ? --argc, *++argv : ((x), abort(), (char *)0))\n"\ "enum token {\n"\ " BUILD,\n"\ " DEFAULT,\n"\ " INCLUDE,\n"\ " POOL,\n"\ " RULE,\n"\ " SUBNINJA,\n"\ " VARIABLE,\n"\ "};\n"\ "struct scanner {\n"\ " FILE *f;\n"\ " const char *path;\n"\ " int chr, line, col;\n"\ "};\n"\ "extern struct evalstring **paths;\n"\ "extern size_t npaths;\n"\ "void scaninit(struct scanner *, const char *);\n"\ "void scanclose(struct scanner *);\n"\ "void scanerror(struct scanner *, const char *, ...);\n"\ "int scankeyword(struct scanner *, char **);\n"\ "char *scanname(struct scanner *);\n"\ "struct evalstring *scanstring(struct scanner *, _Bool);\n"\ "void scanpaths(struct scanner *);\n"\ "void scanchar(struct scanner *, int);\n"\ "int scanpipe(struct scanner *, int);\n"\ "_Bool scanindent(struct scanner *);\n"\ "void scannewline(struct scanner *);\n"\ "struct tool {\n"\ " const char *name;\n"\ " int (*run)(int, char *[]);\n"\ "};\n"\ "const struct tool *toolget(const char *);\n"\ "struct treenode {\n"\ " char *key;\n"\ " void *value;\n"\ " struct treenode *child[2];\n"\ " int height;\n"\ "};\n"\ "void deltree(struct treenode *, void(void *), void(void *));\n"\ "struct treenode *treefind(struct treenode *, const char *);\n"\ "void *treeinsert(struct treenode **, char *, void *);\n"\ "struct buffer {\n"\ " char *data;\n"\ " size_t len, cap;\n"\ "};\n"\ "struct string {\n"\ " size_t n;\n"\ " char s[];\n"\ "};\n"\ "struct evalstring {\n"\ " char *var;\n"\ " struct string *str;\n"\ " struct evalstring *next;\n"\ "};\n"\ "#define LEN(a) (sizeof(a) / sizeof((a)[0]))\n"\ "void warn(const char *, ...);\n"\ "void fatal(const char *, ...);\n"\ "void *xmalloc(size_t);\n"\ "void *xreallocarray(void *, size_t, size_t);\n"\ "char *xmemdup(const char *, size_t);\n"\ "int xasprintf(char **, const char *, ...);\n"\ "void bufadd(struct buffer *buf, char c);\n"\ "struct string *mkstr(size_t n);\n"\ "void delevalstr(void *);\n"\ "void canonpath(struct string *);\n"\ "int writefile(const char *, struct string *);\n"\ "struct string;\n"\ "void osgetcwd(char *, size_t);\n"\ "void oschdir(const char *);\n"\ "int osmkdirs(struct string *, _Bool);\n"\ "int64_t osmtime(const char *);\n"\ "struct job {\n"\ " struct string *cmd;\n"\ " struct edge *edge;\n"\ " struct buffer buf;\n"\ " size_t next;\n"\ " pid_t pid;\n"\ " int fd;\n"\ " bool failed;\n"\ "};\n"\ "struct buildoptions buildopts = {.maxfail = 1};\n"\ "static struct edge *work;\n"\ "static size_t nstarted, nfinished, ntotal;\n"\ "static bool consoleused;\n"\ "static struct timespec starttime;\n"\ "void buildreset(void) {\n"\ " struct edge *e;\n"\ " for (e = alledges; e; e = e->allnext)\n"\ " e->flags &= ~FLAG_WORK;\n"\ "}\n"\ "static bool isnewer(struct node *n1, struct node *n2) {\n"\ " return n1 && n1->mtime > n2->mtime;\n"\ "}\n"\ "static bool isdirty(struct node *n, struct node *newest, bool generator, bool restat) {\n"\ " struct edge *e;\n"\ " e = n->gen;\n"\ " if (e->rule == &phonyrule) {\n"\ " if (e->nin > 0 || n->mtime != MTIME_MISSING) return false;\n"\ " if (buildopts.explain) warn(\"explain %s: phony and no inputs\", n->path->s);\n"\ " return true;\n"\ " }\n"\ " if (n->mtime == MTIME_MISSING) {\n"\ " if (buildopts.explain) warn(\"explain %s: missing\", n->path->s);\n"\ " return true;\n"\ " }\n"\ " if (isnewer(newest, n) && (!restat || n->logmtime == MTIME_MISSING)) {\n"\ " if (buildopts.explain) {\n"\ " warn(\"explain %s: older than input '%s': %\" PRId64 \" vs %\" PRId64, n->path->s, newest->path->s, n->mtime, newest->mtime);\n"\ " }\n"\ " return true;\n"\ " }\n"\ " if (n->logmtime == MTIME_MISSING) {\n"\ " if (!generator) {\n"\ " if (buildopts.explain) warn(\"explain %s: no record in .ninja_log\", n->path->s);\n"\ " return true;\n"\ " }\n"\ " } else if (newest && n->logmtime < newest->mtime) {\n"\ " if (buildopts.explain) {\n"\ " warn(\"explain %s: recorded mtime is older than input '%s': %\" PRId64 \" vs %\" PRId64, n->path->s, newest->path->s, n->logmtime, newest->mtime);\n"\ " }\n"\ " return true;\n"\ " }\n"\ " if (generator) return false;\n"\ " edgehash(e);\n"\ " if (e->hash == n->hash) return false;\n"\ " if (buildopts.explain) warn(\"explain %s: command line changed\", n->path->s);\n"\ " return true;\n"\ "}\n"\ "static void queue(struct edge *e) {\n"\ " struct edge **front = &work;\n"\ " if (e->pool && e->rule != &phonyrule) {\n"\ " if (e->pool->numjobs == e->pool->maxjobs) front = &e->pool->work;\n"\ " else ++e->pool->numjobs;\n"\ " }\n"\ " e->worknext = *front;\n"\ " *front = e;\n"\ "}\n"\ "void buildadd(struct node *n) {\n"\ " struct edge *e;\n"\ " struct node *newest;\n"\ " size_t i;\n"\ " bool generator, restat;\n"\ " e = n->gen;\n"\ " if (!e) {\n"\ " if (n->mtime == MTIME_UNKNOWN) nodestat(n);\n"\ " if (n->mtime == MTIME_MISSING) fatal(\"file is missing and not created by any action: '%s'\", n->path->s);\n"\ " n->dirty = false;\n"\ " return;\n"\ " }\n"\ " if (e->flags & FLAG_CYCLE) fatal(\"dependency cycle involving '%s'\", n->path->s);\n"\ " if (e->flags & FLAG_WORK) return;\n"\ " e->flags |= FLAG_CYCLE | FLAG_WORK;\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " n->dirty = false;\n"\ " if (n->mtime == MTIME_UNKNOWN) nodestat(n);\n"\ " }\n"\ " depsload(e);\n"\ " e->nblock = 0;\n"\ " newest = NULL;\n"\ " for (i = 0; i < e->nin; ++i) {\n"\ " n = e->in[i];\n"\ " buildadd(n);\n"\ " if (i < e->inorderidx) {\n"\ " if (n->dirty) e->flags |= FLAG_DIRTY_IN;\n"\ " if (n->mtime != MTIME_MISSING && !isnewer(newest, n)) newest = n;\n"\ " }\n"\ " if (n->dirty || (n->gen && n->gen->nblock > 0)) ++e->nblock;\n"\ " }\n"\ " generator = edgevar(e, \"generator\", true);\n"\ " restat = edgevar(e, \"restat\", true);\n"\ " for (i = 0; i < e->nout && !(e->flags & FLAG_DIRTY_OUT); ++i) {\n"\ " n = e->out[i];\n"\ " if (isdirty(n, newest, generator, restat)) {\n"\ " n->dirty = true;\n"\ " e->flags |= FLAG_DIRTY_OUT;\n"\ " }\n"\ " }\n"\ " if (e->flags & FLAG_DIRTY) {\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " if (buildopts.explain && !n->dirty) {\n"\ " if (e->flags & FLAG_DIRTY_IN) warn(\"explain %s: input is dirty\", n->path->s);\n"\ " else if (e->flags & FLAG_DIRTY_OUT) warn(\"explain %s: output of generating action is dirty\", n->path->s);\n"\ " }\n"\ " n->dirty = true;\n"\ " }\n"\ " }\n"\ " if (!(e->flags & FLAG_DIRTY_OUT)) e->nprune = e->nblock;\n"\ " if (e->flags & FLAG_DIRTY) {\n"\ " if (e->nblock == 0) queue(e);\n"\ " if (e->rule != &phonyrule) ++ntotal;\n"\ " }\n"\ " e->flags &= ~FLAG_CYCLE;\n"\ "}\n"\ "static size_t formatstatus(char *buf, size_t len) {\n"\ " const char *fmt;\n"\ " size_t ret = 0;\n"\ " int n;\n"\ " struct timespec endtime;\n"\ " for (fmt = buildopts.statusfmt; *fmt; ++fmt) {\n"\ " if (*fmt != '%' || *++fmt == '%') {\n"\ " if (len > 1) {\n"\ " *buf++ = *fmt;\n"\ " --len;\n"\ " }\n"\ " ++ret;\n"\ " continue;\n"\ " }\n"\ " n = 0;\n"\ " switch (*fmt) {\n"\ " case 's':\n"\ " n = snprintf(buf, len, \"%zu\", nstarted);\n"\ " break;\n"\ " case 'f':\n"\ " n = snprintf(buf, len, \"%zu\", nfinished);\n"\ " break;\n"\ " case 't':\n"\ " n = snprintf(buf, len, \"%zu\", ntotal);\n"\ " break;\n"\ " case 'r':\n"\ " n = snprintf(buf, len, \"%zu\", nstarted - nfinished);\n"\ " break;\n"\ " case 'u':\n"\ " n = snprintf(buf, len, \"%zu\", ntotal - nstarted);\n"\ " break;\n"\ " case 'p':\n"\ " n = snprintf(buf, len, \"%3zu%%\", 100 * nfinished / ntotal);\n"\ " break;\n"\ " case 'o':\n"\ " if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) {\n"\ " warn(\"clock_gettime:\");\n"\ " break;\n"\ " }\n"\ " n = snprintf(buf, len, \"%.1f\", nfinished / ((endtime.tv_sec - starttime.tv_sec) + 0.000000001 * (endtime.tv_nsec - starttime.tv_nsec)));\n"\ " break;\n"\ " case 'e':\n"\ " if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) {\n"\ " warn(\"clock_gettime:\");\n"\ " break;\n"\ " }\n"\ " n = snprintf(buf, len, \"%.3f\", (endtime.tv_sec - starttime.tv_sec) + 0.000000001 * (endtime.tv_nsec - starttime.tv_nsec));\n"\ " break;\n"\ " default:\n"\ " fatal(\"unknown placeholder '%%%c' in $NINJA_STATUS\", *fmt);\n"\ " continue;\n"\ " }\n"\ " if (n < 0) fatal(\"snprintf:\");\n"\ " ret += n;\n"\ " if ((size_t)n > len) n = len;\n"\ " buf += n;\n"\ " len -= n;\n"\ " }\n"\ " if (len > 0) *buf = '\\0';\n"\ " return ret;\n"\ "}\n"\ "static void printstatus(struct edge *e, struct string *cmd) {\n"\ " struct string *description;\n"\ " char status[256];\n"\ " description = buildopts.verbose ? NULL : edgevar(e, \"description\", true);\n"\ " if (!description || description->n == 0) description = cmd;\n"\ " formatstatus(status, sizeof(status));\n"\ " fputs(status, stdout);\n"\ " puts(description->s);\n"\ "}\n"\ "static int jobstart(struct job *j, struct edge *e) {\n"\ " extern char **environ;\n"\ " size_t i;\n"\ " struct node *n;\n"\ " struct string *rspfile, *content;\n"\ " int fd[2];\n"\ " posix_spawn_file_actions_t actions;\n"\ " char *argv[] = {\"/bin/sh\", \"-c\", NULL, NULL};\n"\ " ++nstarted;\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " if (n->mtime == MTIME_MISSING) {\n"\ " if (osmkdirs(n->path, true) < 0) goto err0;\n"\ " }\n"\ " }\n"\ " rspfile = edgevar(e, \"rspfile\", false);\n"\ " if (rspfile) {\n"\ " content = edgevar(e, \"rspfile_content\", true);\n"\ " if (writefile(rspfile->s, content) < 0) goto err0;\n"\ " }\n"\ " if (pipe(fd) < 0) {\n"\ " warn(\"pipe:\");\n"\ " goto err1;\n"\ " }\n"\ " j->edge = e;\n"\ " j->cmd = edgevar(e, \"command\", true);\n"\ " j->fd = fd[0];\n"\ " argv[2] = j->cmd->s;\n"\ " if (!consoleused) printstatus(e, j->cmd);\n"\ " if ((errno = posix_spawn_file_actions_init(&actions))) {\n"\ " warn(\"posix_spawn_file_actions_init:\");\n"\ " goto err2;\n"\ " }\n"\ " if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0]))) {\n"\ " warn(\"posix_spawn_file_actions_addclose:\");\n"\ " goto err3;\n"\ " }\n"\ " if (e->pool != &consolepool) {\n"\ " if ((errno = posix_spawn_file_actions_addopen(&actions, 0, \"/dev/null\", O_RDONLY, 0))) {\n"\ " warn(\"posix_spawn_file_actions_addopen:\");\n"\ " goto err3;\n"\ " }\n"\ " if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1))) {\n"\ " warn(\"posix_spawn_file_actions_adddup2:\");\n"\ " goto err3;\n"\ " }\n"\ " if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 2))) {\n"\ " warn(\"posix_spawn_file_actions_adddup2:\");\n"\ " goto err3;\n"\ " }\n"\ " if ((errno = posix_spawn_file_actions_addclose(&actions, fd[1]))) {\n"\ " warn(\"posix_spawn_file_actions_addclose:\");\n"\ " goto err3;\n"\ " }\n"\ " }\n"\ " if ((errno = posix_spawn(&j->pid, argv[0], &actions, NULL, argv, environ))) {\n"\ " warn(\"posix_spawn %s:\", j->cmd->s);\n"\ " goto err3;\n"\ " }\n"\ " posix_spawn_file_actions_destroy(&actions);\n"\ " close(fd[1]);\n"\ " j->failed = false;\n"\ " if (e->pool == &consolepool) consoleused = true;\n"\ " return j->fd;\n"\ "err3:\n"\ " posix_spawn_file_actions_destroy(&actions);\n"\ "err2:\n"\ " close(fd[0]);\n"\ " close(fd[1]);\n"\ "err1:\n"\ " if (rspfile && !buildopts.keeprsp) remove(rspfile->s);\n"\ "err0:\n"\ " return -1;\n"\ "}\n"\ "static void nodedone(struct node *n, bool prune) {\n"\ " struct edge *e;\n"\ " size_t i, j;\n"\ " for (i = 0; i < n->nuse; ++i) {\n"\ " e = n->use[i];\n"\ " if (!(e->flags & FLAG_WORK)) continue;\n"\ " if (!(e->flags & (prune ? FLAG_DIRTY_OUT : FLAG_DIRTY)) && --e->nprune == 0) {\n"\ " for (j = 0; j < e->nout; ++j)\n"\ " nodedone(e->out[j], true);\n"\ " if (e->flags & FLAG_DIRTY && e->rule != &phonyrule) --ntotal;\n"\ " } else if (--e->nblock == 0) {\n"\ " queue(e);\n"\ " }\n"\ " }\n"\ "}\n"\ "static bool shouldprune(struct edge *e, struct node *n, int64_t old) {\n"\ " struct node *in, *newest;\n"\ " size_t i;\n"\ " if (old != n->mtime) return false;\n"\ " newest = NULL;\n"\ " for (i = 0; i < e->inorderidx; ++i) {\n"\ " in = e->in[i];\n"\ " nodestat(in);\n"\ " if (in->mtime != MTIME_MISSING && !isnewer(newest, in)) newest = in;\n"\ " }\n"\ " if (newest) n->logmtime = newest->mtime;\n"\ " return true;\n"\ "}\n"\ "static void edgedone(struct edge *e) {\n"\ " struct node *n;\n"\ " size_t i;\n"\ " struct string *rspfile;\n"\ " bool restat;\n"\ " int64_t old;\n"\ " restat = edgevar(e, \"restat\", true);\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " old = n->mtime;\n"\ " nodestat(n);\n"\ " n->logmtime = n->mtime == MTIME_MISSING ? 0 : n->mtime;\n"\ " nodedone(n, restat && shouldprune(e, n, old));\n"\ " }\n"\ " rspfile = edgevar(e, \"rspfile\", false);\n"\ " if (rspfile && !buildopts.keeprsp) remove(rspfile->s);\n"\ " edgehash(e);\n"\ " depsrecord(e);\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " n->hash = e->hash;\n"\ " logrecord(n);\n"\ " }\n"\ "}\n"\ "static void jobdone(struct job *j) {\n"\ " int status;\n"\ " struct edge *e, *new;\n"\ " struct pool *p;\n"\ " ++nfinished;\n"\ " if (waitpid(j->pid, &status, 0) < 0) {\n"\ " warn(\"waitpid %d:\", j->pid);\n"\ " j->failed = true;\n"\ " } else if (WIFEXITED(status)) {\n"\ " if (WEXITSTATUS(status) != 0) {\n"\ " warn(\"job failed with status %d: %s\", WEXITSTATUS(status), j->cmd->s);\n"\ " j->failed = true;\n"\ " }\n"\ " } else if (WIFSIGNALED(status)) {\n"\ " warn(\"job terminated due to signal %d: %s\", WTERMSIG(status), j->cmd->s);\n"\ " j->failed = true;\n"\ " } else {\n"\ " warn(\"job status unknown: %s\", j->cmd->s);\n"\ " j->failed = true;\n"\ " }\n"\ " close(j->fd);\n"\ " if (j->buf.len && (!consoleused || j->failed)) fwrite(j->buf.data, 1, j->buf.len, stdout);\n"\ " j->buf.len = 0;\n"\ " e = j->edge;\n"\ " if (e->pool) {\n"\ " p = e->pool;\n"\ " if (p == &consolepool) consoleused = false;\n"\ " if (p->work) {\n"\ " new = p->work;\n"\ " p->work = p->work->worknext;\n"\ " new->worknext = work;\n"\ " work = new;\n"\ " } else {\n"\ " --p->numjobs;\n"\ " }\n"\ " }\n"\ " if (!j->failed) edgedone(e);\n"\ "}\n"\ "static bool jobwork(struct job *j) {\n"\ " char *newdata;\n"\ " size_t newcap;\n"\ " ssize_t n;\n"\ " if (j->buf.cap - j->buf.len < BUFSIZ / 2) {\n"\ " newcap = j->buf.cap + BUFSIZ;\n"\ " newdata = realloc(j->buf.data, newcap);\n"\ " if (!newdata) {\n"\ " warn(\"realloc:\");\n"\ " goto kill;\n"\ " }\n"\ " j->buf.cap = newcap;\n"\ " j->buf.data = newdata;\n"\ " }\n"\ " n = read(j->fd, j->buf.data + j->buf.len, j->buf.cap - j->buf.len);\n"\ " if (n > 0) {\n"\ " j->buf.len += n;\n"\ " return true;\n"\ " }\n"\ " if (n == 0) goto done;\n"\ " warn(\"read:\");\n"\ "kill:\n"\ " kill(j->pid, SIGTERM);\n"\ " j->failed = true;\n"\ "done:\n"\ " jobdone(j);\n"\ " return false;\n"\ "}\n"\ "static double queryload(void) {\n"\ "#ifdef HAVE_GETLOADAVG\n"\ " double load;\n"\ " if (getloadavg(&load, 1) == -1) {\n"\ " warn(\"getloadavg:\");\n"\ " load = 100.0;\n"\ " }\n"\ " return load;\n"\ "#else\n"\ " return 0;\n"\ "#endif\n"\ "}\n"\ "void build(void) {\n"\ " struct job *jobs = NULL;\n"\ " struct pollfd *fds = NULL;\n"\ " size_t i, next = 0, jobslen = 0, maxjobs = buildopts.maxjobs, numjobs = 0, numfail = 0;\n"\ " struct edge *e;\n"\ " if (ntotal == 0) {\n"\ " warn(\"nothing to do\");\n"\ " return;\n"\ " }\n"\ " clock_gettime(CLOCK_MONOTONIC, &starttime);\n"\ " formatstatus(NULL, 0);\n"\ " nstarted = 0;\n"\ " for (;;) {\n"\ " if (buildopts.maxload) maxjobs = queryload() > buildopts.maxload ? 1 : buildopts.maxjobs;\n"\ " while (work && numjobs < maxjobs && numfail < buildopts.maxfail) {\n"\ " e = work;\n"\ " work = work->worknext;\n"\ " if (e->rule != &phonyrule && buildopts.dryrun) {\n"\ " ++nstarted;\n"\ " printstatus(e, edgevar(e, \"command\", true));\n"\ " ++nfinished;\n"\ " }\n"\ " if (e->rule == &phonyrule || buildopts.dryrun) {\n"\ " for (i = 0; i < e->nout; ++i)\n"\ " nodedone(e->out[i], false);\n"\ " continue;\n"\ " }\n"\ " if (next == jobslen) {\n"\ " jobslen = jobslen ? jobslen * 2 : 8;\n"\ " if (jobslen > buildopts.maxjobs) jobslen = buildopts.maxjobs;\n"\ " jobs = xreallocarray(jobs, jobslen, sizeof(jobs[0]));\n"\ " fds = xreallocarray(fds, jobslen, sizeof(fds[0]));\n"\ " for (i = next; i < jobslen; ++i) {\n"\ " jobs[i].buf.data = NULL;\n"\ " jobs[i].buf.len = 0;\n"\ " jobs[i].buf.cap = 0;\n"\ " jobs[i].next = i + 1;\n"\ " fds[i].fd = -1;\n"\ " fds[i].events = POLLIN;\n"\ " }\n"\ " }\n"\ " fds[next].fd = jobstart(&jobs[next], e);\n"\ " if (fds[next].fd < 0) {\n"\ " warn(\"job failed to start\");\n"\ " ++numfail;\n"\ " } else {\n"\ " next = jobs[next].next;\n"\ " ++numjobs;\n"\ " }\n"\ " }\n"\ " if (numjobs == 0) break;\n"\ " if (poll(fds, jobslen, 5000) < 0) fatal(\"poll:\");\n"\ " for (i = 0; i < jobslen; ++i) {\n"\ " if (!fds[i].revents || jobwork(&jobs[i])) continue;\n"\ " --numjobs;\n"\ " jobs[i].next = next;\n"\ " fds[i].fd = -1;\n"\ " next = i;\n"\ " if (jobs[i].failed) ++numfail;\n"\ " }\n"\ " }\n"\ " for (i = 0; i < jobslen; ++i)\n"\ " free(jobs[i].buf.data);\n"\ " free(jobs);\n"\ " free(fds);\n"\ " if (numfail > 0) {\n"\ " if (numfail < buildopts.maxfail) fatal(\"cannot make progress due to previous errors\");\n"\ " else if (numfail > 1) fatal(\"subcommands failed\");\n"\ " else fatal(\"subcommand failed\");\n"\ " }\n"\ " ntotal = 0;\n"\ "}\n"\ "#define MAX_RECORD_SIZE (1 << 19)\n"\ "struct nodearray {\n"\ " struct node **node;\n"\ " size_t len;\n"\ "};\n"\ "struct entry {\n"\ " struct node *node;\n"\ " struct nodearray deps;\n"\ " int64_t mtime;\n"\ "};\n"\ "static const char depsname[] = \".ninja_deps\";\n"\ "static const char depstmpname[] = \".ninja_deps.tmp\";\n"\ "static const char depsheader[] = \"# ninjadeps\\n\";\n"\ "static const uint32_t depsver = 4;\n"\ "static FILE *depsfile;\n"\ "static struct entry *entries;\n"\ "static size_t entrieslen, entriescap;\n"\ "static void depswrite(const void *p, size_t n, size_t m) {\n"\ " if (fwrite(p, n, m, depsfile) != m) fatal(\"deps log write:\");\n"\ "}\n"\ "static bool recordid(struct node *n) {\n"\ " uint32_t sz, chk;\n"\ " if (n->id != -1) return false;\n"\ " if (entrieslen == INT32_MAX) fatal(\"too many nodes\");\n"\ " n->id = entrieslen++;\n"\ " sz = (n->path->n + 7) & ~3;\n"\ " if (sz + 4 >= MAX_RECORD_SIZE) fatal(\"ID record too large\");\n"\ " depswrite(&sz, 4, 1);\n"\ " depswrite(n->path->s, 1, n->path->n);\n"\ " depswrite((char[4]){0}, 1, sz - n->path->n - 4);\n"\ " chk = ~n->id;\n"\ " depswrite(&chk, 4, 1);\n"\ " return true;\n"\ "}\n"\ "static void recorddeps(struct node *out, struct nodearray *deps, int64_t mtime) {\n"\ " uint32_t sz, m;\n"\ " size_t i;\n"\ " sz = 12 + deps->len * 4;\n"\ " if (sz + 4 >= MAX_RECORD_SIZE) fatal(\"deps record too large\");\n"\ " sz |= 0x80000000;\n"\ " depswrite(&sz, 4, 1);\n"\ " depswrite(&out->id, 4, 1);\n"\ " m = mtime & 0xffffffff;\n"\ " depswrite(&m, 4, 1);\n"\ " m = (mtime >> 32) & 0xffffffff;\n"\ " depswrite(&m, 4, 1);\n"\ " for (i = 0; i < deps->len; ++i)\n"\ " depswrite(&deps->node[i]->id, 4, 1);\n"\ "}\n"\ "void depsinit(const char *builddir) {\n"\ " char *depspath = (char *)depsname, *depstmppath = (char *)depstmpname;\n"\ " uint32_t *buf, cap, ver, sz, id;\n"\ " size_t len, i, j, nrecord;\n"\ " bool isdep;\n"\ " struct string *path;\n"\ " struct node *n;\n"\ " struct edge *e;\n"\ " struct entry *entry, *oldentries;\n"\ " if (depsfile) fclose(depsfile);\n"\ " entrieslen = 0;\n"\ " cap = BUFSIZ;\n"\ " buf = xmalloc(cap);\n"\ " if (builddir) xasprintf(&depspath, \"%s/%s\", builddir, depsname);\n"\ " depsfile = fopen(depspath, \"r+\");\n"\ " if (!depsfile) {\n"\ " if (errno != ENOENT) fatal(\"open %s:\", depspath);\n"\ " goto rewrite;\n"\ " }\n"\ " if (!fgets((char *)buf, sizeof(depsheader), depsfile)) goto rewrite;\n"\ " if (strcmp((char *)buf, depsheader) != 0) {\n"\ " warn(\"invalid deps log header\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (fread(&ver, sizeof(ver), 1, depsfile) != 1) {\n"\ " warn(ferror(depsfile) ? \"deps log read:\" : \"deps log truncated\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (ver != depsver) {\n"\ " warn(\"unknown deps log version\");\n"\ " goto rewrite;\n"\ " }\n"\ " for (nrecord = 0;; ++nrecord) {\n"\ " if (fread(&sz, sizeof(sz), 1, depsfile) != 1) break;\n"\ " isdep = sz & 0x80000000;\n"\ " sz &= 0x7fffffff;\n"\ " if (sz > MAX_RECORD_SIZE) {\n"\ " warn(\"deps record too large\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (sz > cap) {\n"\ " do\n"\ " cap *= 2;\n"\ " while (sz > cap);\n"\ " free(buf);\n"\ " buf = xmalloc(cap);\n"\ " }\n"\ " if (fread(buf, sz, 1, depsfile) != 1) {\n"\ " warn(ferror(depsfile) ? \"deps log read:\" : \"deps log truncated\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (sz % 4) {\n"\ " warn(\"invalid size, must be multiple of 4: %\" PRIu32, sz);\n"\ " goto rewrite;\n"\ " }\n"\ " if (isdep) {\n"\ " if (sz < 12) {\n"\ " warn(\"invalid size, must be at least 12: %\" PRIu32, sz);\n"\ " goto rewrite;\n"\ " }\n"\ " sz -= 12;\n"\ " id = buf[0];\n"\ " if (id >= entrieslen) {\n"\ " warn(\"invalid node ID: %\" PRIu32, id);\n"\ " goto rewrite;\n"\ " }\n"\ " entry = &entries[id];\n"\ " entry->mtime = (int64_t)buf[2] << 32 | buf[1];\n"\ " e = entry->node->gen;\n"\ " if (!e || !edgevar(e, \"deps\", true)) continue;\n"\ " sz /= 4;\n"\ " free(entry->deps.node);\n"\ " entry->deps.len = sz;\n"\ " entry->deps.node = xreallocarray(NULL, sz, sizeof(n));\n"\ " for (i = 0; i < sz; ++i) {\n"\ " id = buf[3 + i];\n"\ " if (id >= entrieslen) {\n"\ " warn(\"invalid node ID: %\" PRIu32, id);\n"\ " goto rewrite;\n"\ " }\n"\ " entry->deps.node[i] = entries[id].node;\n"\ " }\n"\ " } else {\n"\ " if (sz <= 4) {\n"\ " warn(\"invalid size, must be greater than 4: %\" PRIu32, sz);\n"\ " goto rewrite;\n"\ " }\n"\ " if (entrieslen != ~buf[sz / 4 - 1]) {\n"\ " warn(\"corrupt deps log, bad checksum\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (entrieslen == INT32_MAX) {\n"\ " warn(\"too many nodes in deps log\");\n"\ " goto rewrite;\n"\ " }\n"\ " len = sz - 4;\n"\ " while (((char *)buf)[len - 1] == '\\0')\n"\ " --len;\n"\ " path = mkstr(len);\n"\ " memcpy(path->s, buf, len);\n"\ " path->s[len] = '\\0';\n"\ " n = mknode(path);\n"\ " if (entrieslen >= entriescap) {\n"\ " entriescap = entriescap ? entriescap * 2 : 1024;\n"\ " entries = xreallocarray(entries, entriescap, sizeof(entries[0]));\n"\ " }\n"\ " n->id = entrieslen;\n"\ " entries[entrieslen++] = (struct entry){.node = n};\n"\ " }\n"\ " }\n"\ " if (ferror(depsfile)) {\n"\ " warn(\"deps log read:\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (nrecord <= 1000 || nrecord < 3 * entrieslen) {\n"\ " if (builddir) free(depspath);\n"\ " free(buf);\n"\ " return;\n"\ " }\n"\ "rewrite:\n"\ " free(buf);\n"\ " if (depsfile) fclose(depsfile);\n"\ " if (builddir) xasprintf(&depstmppath, \"%s/%s\", builddir, depstmpname);\n"\ " depsfile = fopen(depstmppath, \"w\");\n"\ " if (!depsfile) fatal(\"open %s:\", depstmppath);\n"\ " depswrite(depsheader, 1, sizeof(depsheader) - 1);\n"\ " depswrite(&depsver, 1, sizeof(depsver));\n"\ " for (i = 0; i < entrieslen; ++i)\n"\ " entries[i].node->id = -1;\n"\ " oldentries = xreallocarray(NULL, entrieslen, sizeof(entries[0]));\n"\ " memcpy(oldentries, entries, entrieslen * sizeof(entries[0]));\n"\ " len = entrieslen;\n"\ " entrieslen = 0;\n"\ " for (i = 0; i < len; ++i) {\n"\ " entry = &oldentries[i];\n"\ " if (!entry->deps.len) continue;\n"\ " recordid(entry->node);\n"\ " entries[entry->node->id] = *entry;\n"\ " for (j = 0; j < entry->deps.len; ++j)\n"\ " recordid(entry->deps.node[j]);\n"\ " recorddeps(entry->node, &entry->deps, entry->mtime);\n"\ " }\n"\ " free(oldentries);\n"\ " fflush(depsfile);\n"\ " if (ferror(depsfile)) fatal(\"deps log write failed\");\n"\ " if (rename(depstmppath, depspath) < 0) fatal(\"deps log rename:\");\n"\ " if (builddir) {\n"\ " free(depstmppath);\n"\ " free(depspath);\n"\ " }\n"\ "}\n"\ "void depsclose(void) {\n"\ " fflush(depsfile);\n"\ " if (ferror(depsfile)) fatal(\"deps log write failed\");\n"\ " fclose(depsfile);\n"\ "}\n"\ "static struct nodearray *depsparse(const char *name, bool allowmissing) {\n"\ " static struct buffer buf;\n"\ " static struct nodearray deps;\n"\ " static size_t depscap;\n"\ " struct string *in, *out = NULL;\n"\ " FILE *f;\n"\ " int c, n;\n"\ " bool sawcolon;\n"\ " deps.len = 0;\n"\ " f = fopen(name, \"r\");\n"\ " if (!f) {\n"\ " if (errno == ENOENT && allowmissing) return &deps;\n"\ " return NULL;\n"\ " }\n"\ " sawcolon = false;\n"\ " buf.len = 0;\n"\ " c = getc(f);\n"\ " for (;;) {\n"\ " while (isalnum(c) || strchr(\"$+,-./@\\\\_\", c)) {\n"\ " switch (c) {\n"\ " case '\\\\':\n"\ " n = 0;\n"\ " do {\n"\ " c = getc(f);\n"\ " if (++n % 2 == 0) bufadd(&buf, '\\\\');\n"\ " } while (c == '\\\\');\n"\ " if ((c == ' ' || c == '\\t') && n % 2 != 0) break;\n"\ " for (; n > 2; n -= 2)\n"\ " bufadd(&buf, '\\\\');\n"\ " switch (c) {\n"\ " case '#':\n"\ " break;\n"\ " case '\\n':\n"\ " c = ' ';\n"\ " continue;\n"\ " default:\n"\ " bufadd(&buf, '\\\\');\n"\ " continue;\n"\ " }\n"\ " break;\n"\ " case '$':\n"\ " c = getc(f);\n"\ " if (c != '$') {\n"\ " warn(\"bad depfile '%s': contains variable reference\", name);\n"\ " goto err;\n"\ " }\n"\ " break;\n"\ " }\n"\ " bufadd(&buf, c);\n"\ " c = getc(f);\n"\ " }\n"\ " if (sawcolon) {\n"\ " if (!isspace(c) && c != EOF) {\n"\ " warn(\"bad depfile '%s': '%c' is not a valid target character\", name, c);\n"\ " goto err;\n"\ " }\n"\ " if (buf.len > 0) {\n"\ " if (deps.len == depscap) {\n"\ " depscap = deps.node ? depscap * 2 : 32;\n"\ " deps.node = xreallocarray(deps.node, depscap, sizeof(deps.node[0]));\n"\ " }\n"\ " in = mkstr(buf.len);\n"\ " memcpy(in->s, buf.data, buf.len);\n"\ " in->s[buf.len] = '\\0';\n"\ " deps.node[deps.len++] = mknode(in);\n"\ " }\n"\ " if (c == '\\n') {\n"\ " sawcolon = false;\n"\ " do\n"\ " c = getc(f);\n"\ " while (c == '\\n');\n"\ " }\n"\ " if (c == EOF) break;\n"\ " } else {\n"\ " while (isblank(c))\n"\ " c = getc(f);\n"\ " if (c == EOF) break;\n"\ " if (c != ':') {\n"\ " warn(\"bad depfile '%s': expected ':', saw '%c'\", name, c);\n"\ " goto err;\n"\ " }\n"\ " if (!out) {\n"\ " out = mkstr(buf.len);\n"\ " memcpy(out->s, buf.data, buf.len);\n"\ " out->s[buf.len] = '\\0';\n"\ " } else if (out->n != buf.len || memcmp(buf.data, out->s, buf.len) != 0) {\n"\ " warn(\"bad depfile '%s': multiple outputs: %.*s != %s\", name, (int)buf.len, buf.data, out->s);\n"\ " goto err;\n"\ " }\n"\ " sawcolon = true;\n"\ " c = getc(f);\n"\ " }\n"\ " buf.len = 0;\n"\ " for (;;) {\n"\ " if (c == '\\\\') {\n"\ " if (getc(f) != '\\n') {\n"\ " warn(\"bad depfile '%s': '\\\\' only allowed before newline\", name);\n"\ " goto err;\n"\ " }\n"\ " } else if (!isblank(c)) {\n"\ " break;\n"\ " }\n"\ " c = getc(f);\n"\ " }\n"\ " }\n"\ " if (ferror(f)) {\n"\ " warn(\"depfile read '%s':\", name);\n"\ " goto err;\n"\ " }\n"\ " fclose(f);\n"\ " return &deps;\n"\ "err:\n"\ " fclose(f);\n"\ " return NULL;\n"\ "}\n"\ "void depsload(struct edge *e) {\n"\ " struct string *deptype, *depfile;\n"\ " struct nodearray *deps = NULL;\n"\ " struct node *n;\n"\ " if (e->flags & FLAG_DEPS) return;\n"\ " e->flags |= FLAG_DEPS;\n"\ " n = e->out[0];\n"\ " deptype = edgevar(e, \"deps\", true);\n"\ " if (deptype) {\n"\ " if (n->id != -1 && n->mtime <= entries[n->id].mtime) deps = &entries[n->id].deps;\n"\ " else if (buildopts.explain) warn(\"explain %s: missing or outdated record in .ninja_deps\", n->path->s);\n"\ " } else {\n"\ " depfile = edgevar(e, \"depfile\", false);\n"\ " if (!depfile) return;\n"\ " deps = depsparse(depfile->s, false);\n"\ " if (buildopts.explain && !deps) warn(\"explain %s: missing or invalid depfile\", n->path->s);\n"\ " }\n"\ " if (deps) {\n"\ " edgeadddeps(e, deps->node, deps->len);\n"\ " } else {\n"\ " n->dirty = true;\n"\ " e->flags |= FLAG_DIRTY_OUT;\n"\ " }\n"\ "}\n"\ "void depsrecord(struct edge *e) {\n"\ " struct string *deptype, *depfile;\n"\ " struct nodearray *deps;\n"\ " struct node *out, *n;\n"\ " struct entry *entry;\n"\ " size_t i;\n"\ " bool update;\n"\ " deptype = edgevar(e, \"deps\", true);\n"\ " if (!deptype || deptype->n == 0) return;\n"\ " if (strcmp(deptype->s, \"gcc\") != 0) {\n"\ " warn(\"unsuported deps type: %s\", deptype->s);\n"\ " return;\n"\ " }\n"\ " depfile = edgevar(e, \"depfile\", false);\n"\ " if (!depfile || depfile->n == 0) {\n"\ " warn(\"deps but no depfile\");\n"\ " return;\n"\ " }\n"\ " out = e->out[0];\n"\ " deps = depsparse(depfile->s, true);\n"\ " if (!buildopts.keepdepfile) remove(depfile->s);\n"\ " if (!deps) return;\n"\ " update = false;\n"\ " entry = NULL;\n"\ " if (recordid(out)) {\n"\ " update = true;\n"\ " } else {\n"\ " entry = &entries[out->id];\n"\ " if (entry->mtime != out->mtime || entry->deps.len != deps->len) update = true;\n"\ " for (i = 0; i < deps->len && !update; ++i) {\n"\ " if (entry->deps.node[i] != deps->node[i]) update = true;\n"\ " }\n"\ " }\n"\ " for (i = 0; i < deps->len; ++i) {\n"\ " n = deps->node[i];\n"\ " if (recordid(n)) update = true;\n"\ " }\n"\ " if (update) {\n"\ " recorddeps(out, deps, out->mtime);\n"\ " if (fflush(depsfile) < 0) fatal(\"deps log flush:\");\n"\ " }\n"\ "}\n"\ "struct environment {\n"\ " struct environment *parent;\n"\ " struct treenode *bindings;\n"\ " struct treenode *rules;\n"\ " struct environment *allnext;\n"\ "};\n"\ "struct environment *rootenv;\n"\ "struct rule phonyrule = {.name = \"phony\"};\n"\ "struct pool consolepool = {.name = \"console\", .maxjobs = 1};\n"\ "static struct treenode *pools;\n"\ "static struct environment *allenvs;\n"\ "static void addpool(struct pool *);\n"\ "static void delpool(void *);\n"\ "static void delrule(void *);\n"\ "void envinit(void) {\n"\ " struct environment *env;\n"\ " while (allenvs) {\n"\ " env = allenvs;\n"\ " allenvs = env->allnext;\n"\ " deltree(env->bindings, free, free);\n"\ " deltree(env->rules, NULL, delrule);\n"\ " free(env);\n"\ " }\n"\ " deltree(pools, NULL, delpool);\n"\ " rootenv = mkenv(NULL);\n"\ " envaddrule(rootenv, &phonyrule);\n"\ " pools = NULL;\n"\ " addpool(&consolepool);\n"\ "}\n"\ "static void addvar(struct treenode **tree, char *var, void *val) {\n"\ " char *old;\n"\ " old = treeinsert(tree, var, val);\n"\ " if (old) free(old);\n"\ "}\n"\ "struct environment *mkenv(struct environment *parent) {\n"\ " struct environment *env;\n"\ " env = xmalloc(sizeof(*env));\n"\ " env->parent = parent;\n"\ " env->bindings = NULL;\n"\ " env->rules = NULL;\n"\ " env->allnext = allenvs;\n"\ " allenvs = env;\n"\ " return env;\n"\ "}\n"\ "struct string *envvar(struct environment *env, char *var) {\n"\ " struct treenode *n;\n"\ " do {\n"\ " n = treefind(env->bindings, var);\n"\ " if (n) return n->value;\n"\ " env = env->parent;\n"\ " } while (env);\n"\ " return NULL;\n"\ "}\n"\ "void envaddvar(struct environment *env, char *var, struct string *val) {\n"\ " addvar(&env->bindings, var, val);\n"\ "}\n"\ "static struct string *merge(struct evalstring *str, size_t n) {\n"\ " struct string *result;\n"\ " struct evalstring *p;\n"\ " char *s;\n"\ " result = mkstr(n);\n"\ " s = result->s;\n"\ " for (p = str; p; p = p->next) {\n"\ " if (!p->str) continue;\n"\ " memcpy(s, p->str->s, p->str->n);\n"\ " s += p->str->n;\n"\ " }\n"\ " *s = '\\0';\n"\ " return result;\n"\ "}\n"\ "struct string *enveval(struct environment *env, struct evalstring *str) {\n"\ " size_t n;\n"\ " struct evalstring *p;\n"\ " struct string *res;\n"\ " n = 0;\n"\ " for (p = str; p; p = p->next) {\n"\ " if (p->var) p->str = envvar(env, p->var);\n"\ " if (p->str) n += p->str->n;\n"\ " }\n"\ " res = merge(str, n);\n"\ " delevalstr(str);\n"\ " return res;\n"\ "}\n"\ "void envaddrule(struct environment *env, struct rule *r) {\n"\ " if (treeinsert(&env->rules, r->name, r)) fatal(\"rule '%s' redefined\", r->name);\n"\ "}\n"\ "struct rule *envrule(struct environment *env, char *name) {\n"\ " struct treenode *n;\n"\ " do {\n"\ " n = treefind(env->rules, name);\n"\ " if (n) return n->value;\n"\ " env = env->parent;\n"\ " } while (env);\n"\ " return NULL;\n"\ "}\n"\ "static struct string *pathlist(struct node **nodes, size_t n, char sep, bool escape) {\n"\ " size_t i, len;\n"\ " struct string *path, *result;\n"\ " char *s;\n"\ " if (n == 0) return NULL;\n"\ " if (n == 1) return nodepath(nodes[0], escape);\n"\ " for (i = 0, len = 0; i < n; ++i)\n"\ " len += nodepath(nodes[i], escape)->n;\n"\ " result = mkstr(len + n - 1);\n"\ " s = result->s;\n"\ " for (i = 0; i < n; ++i) {\n"\ " path = nodepath(nodes[i], escape);\n"\ " memcpy(s, path->s, path->n);\n"\ " s += path->n;\n"\ " *s++ = sep;\n"\ " }\n"\ " *--s = '\\0';\n"\ " return result;\n"\ "}\n"\ "struct rule *mkrule(char *name) {\n"\ " struct rule *r;\n"\ " r = xmalloc(sizeof(*r));\n"\ " r->name = name;\n"\ " r->bindings = NULL;\n"\ " return r;\n"\ "}\n"\ "static void delrule(void *ptr) {\n"\ " struct rule *r = ptr;\n"\ " if (r == &phonyrule) return;\n"\ " deltree(r->bindings, free, delevalstr);\n"\ " free(r->name);\n"\ " free(r);\n"\ "}\n"\ "void ruleaddvar(struct rule *r, char *var, struct evalstring *val) {\n"\ " addvar(&r->bindings, var, val);\n"\ "}\n"\ "struct string *edgevar(struct edge *e, char *var, bool escape) {\n"\ " static void *const cycle = (void *)&cycle;\n"\ " struct evalstring *str, *p;\n"\ " struct treenode *n;\n"\ " size_t len;\n"\ " if (strcmp(var, \"in\") == 0) return pathlist(e->in, e->inimpidx, ' ', escape);\n"\ " if (strcmp(var, \"in_newline\") == 0) return pathlist(e->in, e->inimpidx, '\\n', escape);\n"\ " if (strcmp(var, \"out\") == 0) return pathlist(e->out, e->outimpidx, ' ', escape);\n"\ " n = treefind(e->env->bindings, var);\n"\ " if (n) return n->value;\n"\ " n = treefind(e->rule->bindings, var);\n"\ " if (!n) return envvar(e->env->parent, var);\n"\ " if (n->value == cycle) fatal(\"cycle in rule variable involving '%s'\", var);\n"\ " str = n->value;\n"\ " n->value = cycle;\n"\ " len = 0;\n"\ " for (p = str; p; p = p->next) {\n"\ " if (p->var) p->str = edgevar(e, p->var, escape);\n"\ " if (p->str) len += p->str->n;\n"\ " }\n"\ " n->value = str;\n"\ " return merge(str, len);\n"\ "}\n"\ "static void addpool(struct pool *p) {\n"\ " if (treeinsert(&pools, p->name, p)) fatal(\"pool '%s' redefined\", p->name);\n"\ "}\n"\ "struct pool *mkpool(char *name) {\n"\ " struct pool *p;\n"\ " p = xmalloc(sizeof(*p));\n"\ " p->name = name;\n"\ " p->numjobs = 0;\n"\ " p->maxjobs = 0;\n"\ " p->work = NULL;\n"\ " addpool(p);\n"\ " return p;\n"\ "}\n"\ "static void delpool(void *ptr) {\n"\ " struct pool *p = ptr;\n"\ " if (p == &consolepool) return;\n"\ " free(p->name);\n"\ " free(p);\n"\ "}\n"\ "struct pool *poolget(char *name) {\n"\ " struct treenode *n;\n"\ " n = treefind(pools, name);\n"\ " if (!n) fatal(\"unknown pool '%s'\", name);\n"\ " return n->value;\n"\ "}\n"\ "static struct hashtable *allnodes;\n"\ "struct edge *alledges;\n"\ "static void delnode(void *p) {\n"\ " struct node *n = p;\n"\ " if (n->shellpath != n->path) free(n->shellpath);\n"\ " free(n->use);\n"\ " free(n->path);\n"\ " free(n);\n"\ "}\n"\ "void graphinit(void) {\n"\ " struct edge *e;\n"\ " delhtab(allnodes, delnode);\n"\ " while (alledges) {\n"\ " e = alledges;\n"\ " alledges = e->allnext;\n"\ " free(e->out);\n"\ " free(e->in);\n"\ " free(e);\n"\ " }\n"\ " allnodes = mkhtab(1024);\n"\ "}\n"\ "struct node *mknode(struct string *path) {\n"\ " void **v;\n"\ " struct node *n;\n"\ " struct hashtablekey k;\n"\ " htabkey(&k, path->s, path->n);\n"\ " v = htabput(allnodes, &k);\n"\ " if (*v) {\n"\ " free(path);\n"\ " return *v;\n"\ " }\n"\ " n = xmalloc(sizeof(*n));\n"\ " n->path = path;\n"\ " n->shellpath = NULL;\n"\ " n->gen = NULL;\n"\ " n->use = NULL;\n"\ " n->nuse = 0;\n"\ " n->mtime = MTIME_UNKNOWN;\n"\ " n->logmtime = MTIME_MISSING;\n"\ " n->hash = 0;\n"\ " n->id = -1;\n"\ " *v = n;\n"\ " return n;\n"\ "}\n"\ "struct node *nodeget(const char *path, size_t len) {\n"\ " struct hashtablekey k;\n"\ " if (!len) len = strlen(path);\n"\ " htabkey(&k, path, len);\n"\ " return htabget(allnodes, &k);\n"\ "}\n"\ "void nodestat(struct node *n) {\n"\ " n->mtime = osmtime(n->path->s);\n"\ "}\n"\ "struct string *nodepath(struct node *n, bool escape) {\n"\ " char *s, *d;\n"\ " int nquote;\n"\ " if (!escape) return n->path;\n"\ " if (n->shellpath) return n->shellpath;\n"\ " escape = false;\n"\ " nquote = 0;\n"\ " for (s = n->path->s; *s; ++s) {\n"\ " if (!isalnum(*(unsigned char *)s) && !strchr(\"_+-./\", *s)) escape = true;\n"\ " if (*s == '\\'') ++nquote;\n"\ " }\n"\ " if (escape) {\n"\ " n->shellpath = mkstr(n->path->n + 2 + 3 * nquote);\n"\ " d = n->shellpath->s;\n"\ " *d++ = '\\'';\n"\ " for (s = n->path->s; *s; ++s) {\n"\ " *d++ = *s;\n"\ " if (*s == '\\'') {\n"\ " *d++ = '\\\\';\n"\ " *d++ = '\\'';\n"\ " *d++ = '\\'';\n"\ " }\n"\ " }\n"\ " *d++ = '\\'';\n"\ " } else {\n"\ " n->shellpath = n->path;\n"\ " }\n"\ " return n->shellpath;\n"\ "}\n"\ "void nodeuse(struct node *n, struct edge *e) {\n"\ " if (!(n->nuse & (n->nuse - 1))) n->use = xreallocarray(n->use, n->nuse ? n->nuse * 2 : 1, sizeof(e));\n"\ " n->use[n->nuse++] = e;\n"\ "}\n"\ "struct edge *mkedge(struct environment *parent) {\n"\ " struct edge *e;\n"\ " e = xmalloc(sizeof(*e));\n"\ " e->env = mkenv(parent);\n"\ " e->pool = NULL;\n"\ " e->out = NULL;\n"\ " e->nout = 0;\n"\ " e->in = NULL;\n"\ " e->nin = 0;\n"\ " e->flags = 0;\n"\ " e->allnext = alledges;\n"\ " alledges = e;\n"\ " return e;\n"\ "}\n"\ "void edgehash(struct edge *e) {\n"\ " static const char sep[] = \";rspfile=\";\n"\ " struct string *cmd, *rsp, *s;\n"\ " if (e->flags & FLAG_HASH) return;\n"\ " e->flags |= FLAG_HASH;\n"\ " cmd = edgevar(e, \"command\", true);\n"\ " if (!cmd) fatal(\"rule '%s' has no command\", e->rule->name);\n"\ " rsp = edgevar(e, \"rspfile_content\", true);\n"\ " if (rsp && rsp->n > 0) {\n"\ " s = mkstr(cmd->n + sizeof(sep) - 1 + rsp->n);\n"\ " memcpy(s->s, cmd->s, cmd->n);\n"\ " memcpy(s->s + cmd->n, sep, sizeof(sep) - 1);\n"\ " memcpy(s->s + cmd->n + sizeof(sep) - 1, rsp->s, rsp->n);\n"\ " s->s[s->n] = '\\0';\n"\ " e->hash = murmurhash64a(s->s, s->n);\n"\ " free(s);\n"\ " } else {\n"\ " e->hash = murmurhash64a(cmd->s, cmd->n);\n"\ " }\n"\ "}\n"\ "static struct edge *mkphony(struct node *n) {\n"\ " struct edge *e;\n"\ " e = mkedge(rootenv);\n"\ " e->rule = &phonyrule;\n"\ " e->inimpidx = 0;\n"\ " e->inorderidx = 0;\n"\ " e->outimpidx = 1;\n"\ " e->nout = 1;\n"\ " e->out = xmalloc(sizeof(n));\n"\ " e->out[0] = n;\n"\ " return e;\n"\ "}\n"\ "void edgeadddeps(struct edge *e, struct node **deps, size_t ndeps) {\n"\ " struct node **order, *n;\n"\ " size_t norder, i;\n"\ " for (i = 0; i < ndeps; ++i) {\n"\ " n = deps[i];\n"\ " if (!n->gen) n->gen = mkphony(n);\n"\ " nodeuse(n, e);\n"\ " }\n"\ " e->in = xreallocarray(e->in, e->nin + ndeps, sizeof(e->in[0]));\n"\ " order = e->in + e->inorderidx;\n"\ " norder = e->nin - e->inorderidx;\n"\ " memmove(order + ndeps, order, norder * sizeof(e->in[0]));\n"\ " memcpy(order, deps, ndeps * sizeof(e->in[0]));\n"\ " e->inorderidx += ndeps;\n"\ " e->nin += ndeps;\n"\ "}\n"\ "struct hashtable {\n"\ " size_t len, cap;\n"\ " struct hashtablekey *keys;\n"\ " void **vals;\n"\ "};\n"\ "void htabkey(struct hashtablekey *k, const char *s, size_t n) {\n"\ " k->str = s;\n"\ " k->len = n;\n"\ " k->hash = murmurhash64a(s, n);\n"\ "}\n"\ "struct hashtable *mkhtab(size_t cap) {\n"\ " struct hashtable *h;\n"\ " size_t i;\n"\ " assert(!(cap & (cap - 1)));\n"\ " h = xmalloc(sizeof(*h));\n"\ " h->len = 0;\n"\ " h->cap = cap;\n"\ " h->keys = xreallocarray(NULL, cap, sizeof(h->keys[0]));\n"\ " h->vals = xreallocarray(NULL, cap, sizeof(h->vals[0]));\n"\ " for (i = 0; i < cap; ++i)\n"\ " h->keys[i].str = NULL;\n"\ " return h;\n"\ "}\n"\ "void delhtab(struct hashtable *h, void del(void *)) {\n"\ " size_t i;\n"\ " if (!h) return;\n"\ " if (del) {\n"\ " for (i = 0; i < h->cap; ++i) {\n"\ " if (h->keys[i].str) del(h->vals[i]);\n"\ " }\n"\ " }\n"\ " free(h->keys);\n"\ " free(h->vals);\n"\ " free(h);\n"\ "}\n"\ "static bool keyequal(struct hashtablekey *k1, struct hashtablekey *k2) {\n"\ " if (k1->hash != k2->hash || k1->len != k2->len) return false;\n"\ " return memcmp(k1->str, k2->str, k1->len) == 0;\n"\ "}\n"\ "static size_t keyindex(struct hashtable *h, struct hashtablekey *k) {\n"\ " size_t i;\n"\ " i = k->hash & (h->cap - 1);\n"\ " while (h->keys[i].str && !keyequal(&h->keys[i], k))\n"\ " i = (i + 1) & (h->cap - 1);\n"\ " return i;\n"\ "}\n"\ "void **htabput(struct hashtable *h, struct hashtablekey *k) {\n"\ " struct hashtablekey *oldkeys;\n"\ " void **oldvals;\n"\ " size_t i, j, oldcap;\n"\ " if (h->cap / 2 < h->len) {\n"\ " oldkeys = h->keys;\n"\ " oldvals = h->vals;\n"\ " oldcap = h->cap;\n"\ " h->cap *= 2;\n"\ " h->keys = xreallocarray(NULL, h->cap, sizeof(h->keys[0]));\n"\ " h->vals = xreallocarray(NULL, h->cap, sizeof(h->vals[0]));\n"\ " for (i = 0; i < h->cap; ++i)\n"\ " h->keys[i].str = NULL;\n"\ " for (i = 0; i < oldcap; ++i) {\n"\ " if (oldkeys[i].str) {\n"\ " j = keyindex(h, &oldkeys[i]);\n"\ " h->keys[j] = oldkeys[i];\n"\ " h->vals[j] = oldvals[i];\n"\ " }\n"\ " }\n"\ " free(oldkeys);\n"\ " free(oldvals);\n"\ " }\n"\ " i = keyindex(h, k);\n"\ " if (!h->keys[i].str) {\n"\ " h->keys[i] = *k;\n"\ " h->vals[i] = NULL;\n"\ " ++h->len;\n"\ " }\n"\ " return &h->vals[i];\n"\ "}\n"\ "void *htabget(struct hashtable *h, struct hashtablekey *k) {\n"\ " size_t i;\n"\ " i = keyindex(h, k);\n"\ " return h->keys[i].str ? h->vals[i] : NULL;\n"\ "}\n"\ "uint64_t murmurhash64a(const void *ptr, size_t len) {\n"\ " const uint64_t seed = 0xdecafbaddecafbadull;\n"\ " const uint64_t m = 0xc6a4a7935bd1e995ull;\n"\ " uint64_t h, k, n;\n"\ " const uint8_t *p, *end;\n"\ " int r = 47;\n"\ " h = seed ^ (len * m);\n"\ " n = len & ~0x7ull;\n"\ " end = ptr;\n"\ " end += n;\n"\ " for (p = ptr; p != end; p += 8) {\n"\ " memcpy(&k, p, sizeof(k));\n"\ " k *= m;\n"\ " k ^= k >> r;\n"\ " k *= m;\n"\ " h ^= k;\n"\ " h *= m;\n"\ " }\n"\ " switch (len & 0x7) {\n"\ " case 7:\n"\ " h ^= (uint64_t)p[6] << 48;\n"\ " case 6:\n"\ " h ^= (uint64_t)p[5] << 40;\n"\ " case 5:\n"\ " h ^= (uint64_t)p[4] << 32;\n"\ " case 4:\n"\ " h ^= (uint64_t)p[3] << 24;\n"\ " case 3:\n"\ " h ^= (uint64_t)p[2] << 16;\n"\ " case 2:\n"\ " h ^= (uint64_t)p[1] << 8;\n"\ " case 1:\n"\ " h ^= (uint64_t)p[0];\n"\ " h *= m;\n"\ " }\n"\ " h ^= h >> r;\n"\ " h *= m;\n"\ " h ^= h >> r;\n"\ " return h;\n"\ "}\n"\ "static FILE *logfile;\n"\ "static const char *logname = \".ninja_log\";\n"\ "static const char *logtmpname = \".ninja_log.tmp\";\n"\ "static const char *logfmt = \"# ninja log v%d\\n\";\n"\ "static const int logver = 5;\n"\ "static char *nextfield(char **end) {\n"\ " char *s = *end;\n"\ " if (!*s) {\n"\ " warn(\"corrupt build log: missing field\");\n"\ " return NULL;\n"\ " }\n"\ " *end += strcspn(*end, \"\\t\\n\");\n"\ " if (**end) *(*end)++ = '\\0';\n"\ " return s;\n"\ "}\n"\ "void loginit(const char *builddir) {\n"\ " int ver;\n"\ " char *logpath = (char *)logname, *logtmppath = (char *)logtmpname, *p, *s;\n"\ " size_t nline, nentry, i;\n"\ " struct edge *e;\n"\ " struct node *n;\n"\ " int64_t mtime;\n"\ " struct buffer buf = {0};\n"\ " nline = 0;\n"\ " nentry = 0;\n"\ " if (logfile) fclose(logfile);\n"\ " if (builddir) xasprintf(&logpath, \"%s/%s\", builddir, logname);\n"\ " logfile = fopen(logpath, \"r+\");\n"\ " if (!logfile) {\n"\ " if (errno != ENOENT) fatal(\"open %s:\", logpath);\n"\ " goto rewrite;\n"\ " }\n"\ " setvbuf(logfile, NULL, _IOLBF, 0);\n"\ " if (fscanf(logfile, logfmt, &ver) < 1) goto rewrite;\n"\ " if (ver != logver) goto rewrite;\n"\ " for (;;) {\n"\ " if (buf.cap - buf.len < BUFSIZ) {\n"\ " buf.cap = buf.cap ? buf.cap * 2 : BUFSIZ;\n"\ " buf.data = xreallocarray(buf.data, buf.cap, 1);\n"\ " }\n"\ " buf.data[buf.cap - 2] = '\\0';\n"\ " if (!fgets(buf.data + buf.len, buf.cap - buf.len, logfile)) break;\n"\ " if (buf.data[buf.cap - 2] && buf.data[buf.cap - 2] != '\\n') {\n"\ " buf.len = buf.cap - 1;\n"\ " continue;\n"\ " }\n"\ " ++nline;\n"\ " p = buf.data;\n"\ " buf.len = 0;\n"\ " if (!nextfield(&p)) continue;\n"\ " if (!nextfield(&p)) continue;\n"\ " s = nextfield(&p);\n"\ " if (!s) continue;\n"\ " mtime = strtoll(s, &s, 10);\n"\ " if (*s) {\n"\ " warn(\"corrupt build log: invalid mtime\");\n"\ " continue;\n"\ " }\n"\ " s = nextfield(&p);\n"\ " if (!s) continue;\n"\ " n = nodeget(s, 0);\n"\ " if (!n || !n->gen) continue;\n"\ " if (n->logmtime == MTIME_MISSING) ++nentry;\n"\ " n->logmtime = mtime;\n"\ " s = nextfield(&p);\n"\ " if (!s) continue;\n"\ " n->hash = strtoull(s, &s, 16);\n"\ " if (*s) {\n"\ " warn(\"corrupt build log: invalid hash for '%s'\", n->path->s);\n"\ " continue;\n"\ " }\n"\ " }\n"\ " free(buf.data);\n"\ " if (ferror(logfile)) {\n"\ " warn(\"build log read:\");\n"\ " goto rewrite;\n"\ " }\n"\ " if (nline <= 100 || nline <= 3 * nentry) {\n"\ " if (builddir) free(logpath);\n"\ " return;\n"\ " }\n"\ "rewrite:\n"\ " if (logfile) fclose(logfile);\n"\ " if (builddir) xasprintf(&logtmppath, \"%s/%s\", builddir, logtmpname);\n"\ " logfile = fopen(logtmppath, \"w\");\n"\ " if (!logfile) fatal(\"open %s:\", logtmppath);\n"\ " setvbuf(logfile, NULL, _IOLBF, 0);\n"\ " fprintf(logfile, logfmt, logver);\n"\ " if (nentry > 0) {\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " if (!n->hash) continue;\n"\ " logrecord(n);\n"\ " }\n"\ " }\n"\ " }\n"\ " fflush(logfile);\n"\ " if (ferror(logfile)) fatal(\"build log write failed\");\n"\ " if (rename(logtmppath, logpath) < 0) fatal(\"build log rename:\");\n"\ " if (builddir) {\n"\ " free(logpath);\n"\ " free(logtmppath);\n"\ " }\n"\ "}\n"\ "void logclose(void) {\n"\ " fflush(logfile);\n"\ " if (ferror(logfile)) fatal(\"build log write failed\");\n"\ " fclose(logfile);\n"\ "}\n"\ "void logrecord(struct node *n) {\n"\ " fprintf(logfile, \"0\\t0\\t%\" PRId64 \"\\t%s\\t%\" PRIx64 \"\\n\", n->logmtime, n->path->s, n->hash);\n"\ "}\n"\ "struct parseoptions parseopts;\n"\ "static struct node **deftarg;\n"\ "static size_t ndeftarg;\n"\ "void parseinit(void) {\n"\ " free(deftarg);\n"\ " deftarg = NULL;\n"\ " ndeftarg = 0;\n"\ "}\n"\ "static void parselet(struct scanner *s, struct evalstring **val) {\n"\ " scanchar(s, '=');\n"\ " *val = scanstring(s, false);\n"\ " scannewline(s);\n"\ "}\n"\ "static void parserule(struct scanner *s, struct environment *env) {\n"\ " struct rule *r;\n"\ " char *var;\n"\ " struct evalstring *val;\n"\ " bool hascommand = false, hasrspfile = false, hasrspcontent = false;\n"\ " r = mkrule(scanname(s));\n"\ " scannewline(s);\n"\ " while (scanindent(s)) {\n"\ " var = scanname(s);\n"\ " parselet(s, &val);\n"\ " ruleaddvar(r, var, val);\n"\ " if (!val) continue;\n"\ " if (strcmp(var, \"command\") == 0) hascommand = true;\n"\ " else if (strcmp(var, \"rspfile\") == 0) hasrspfile = true;\n"\ " else if (strcmp(var, \"rspfile_content\") == 0) hasrspcontent = true;\n"\ " }\n"\ " if (!hascommand) fatal(\"rule '%s' has no command\", r->name);\n"\ " if (hasrspfile != hasrspcontent) fatal(\"rule '%s' has rspfile and no rspfile_content or vice versa\", r->name);\n"\ " envaddrule(env, r);\n"\ "}\n"\ "static void parseedge(struct scanner *s, struct environment *env) {\n"\ " struct edge *e;\n"\ " struct evalstring *str, **path;\n"\ " char *name;\n"\ " struct string *val;\n"\ " struct node *n;\n"\ " size_t i;\n"\ " int p;\n"\ " e = mkedge(env);\n"\ " scanpaths(s);\n"\ " e->outimpidx = npaths;\n"\ " if (scanpipe(s, 1)) scanpaths(s);\n"\ " e->nout = npaths;\n"\ " if (e->nout == 0) scanerror(s, \"expected output path\");\n"\ " scanchar(s, ':');\n"\ " name = scanname(s);\n"\ " e->rule = envrule(env, name);\n"\ " if (!e->rule) fatal(\"undefined rule '%s'\", name);\n"\ " free(name);\n"\ " scanpaths(s);\n"\ " e->inimpidx = npaths - e->nout;\n"\ " p = scanpipe(s, 1 | 2);\n"\ " if (p == 1) {\n"\ " scanpaths(s);\n"\ " p = scanpipe(s, 2);\n"\ " }\n"\ " e->inorderidx = npaths - e->nout;\n"\ " if (p == 2) scanpaths(s);\n"\ " e->nin = npaths - e->nout;\n"\ " scannewline(s);\n"\ " while (scanindent(s)) {\n"\ " name = scanname(s);\n"\ " parselet(s, &str);\n"\ " val = enveval(env, str);\n"\ " envaddvar(e->env, name, val);\n"\ " }\n"\ " e->out = xreallocarray(NULL, e->nout, sizeof(e->out[0]));\n"\ " for (i = 0, path = paths; i < e->nout; ++path) {\n"\ " val = enveval(e->env, *path);\n"\ " canonpath(val);\n"\ " n = mknode(val);\n"\ " if (n->gen) {\n"\ " if (!parseopts.dupbuildwarn) fatal(\"multiple rules generate '%s'\", n->path->s);\n"\ " warn(\"multiple rules generate '%s'\", n->path->s);\n"\ " --e->nout;\n"\ " if (i < e->outimpidx) --e->outimpidx;\n"\ " } else {\n"\ " n->gen = e;\n"\ " e->out[i] = n;\n"\ " ++i;\n"\ " }\n"\ " }\n"\ " e->in = xreallocarray(NULL, e->nin, sizeof(e->in[0]));\n"\ " for (i = 0; i < e->nin; ++i, ++path) {\n"\ " val = enveval(e->env, *path);\n"\ " canonpath(val);\n"\ " n = mknode(val);\n"\ " e->in[i] = n;\n"\ " nodeuse(n, e);\n"\ " }\n"\ " npaths = 0;\n"\ " val = edgevar(e, \"pool\", true);\n"\ " if (val) e->pool = poolget(val->s);\n"\ "}\n"\ "static void parseinclude(struct scanner *s, struct environment *env, bool newscope) {\n"\ " struct evalstring *str;\n"\ " struct string *path;\n"\ " str = scanstring(s, true);\n"\ " if (!str) scanerror(s, \"expected include path\");\n"\ " scannewline(s);\n"\ " path = enveval(env, str);\n"\ " if (newscope) env = mkenv(env);\n"\ " parse(path->s, env);\n"\ " free(path);\n"\ "}\n"\ "static void parsedefault(struct scanner *s, struct environment *env) {\n"\ " struct string *path;\n"\ " struct node *n;\n"\ " size_t i;\n"\ " scanpaths(s);\n"\ " deftarg = xreallocarray(deftarg, ndeftarg + npaths, sizeof(*deftarg));\n"\ " for (i = 0; i < npaths; ++i) {\n"\ " path = enveval(env, paths[i]);\n"\ " canonpath(path);\n"\ " n = nodeget(path->s, path->n);\n"\ " if (!n) fatal(\"unknown target '%s'\", path->s);\n"\ " free(path);\n"\ " deftarg[ndeftarg++] = n;\n"\ " }\n"\ " scannewline(s);\n"\ " npaths = 0;\n"\ "}\n"\ "static void parsepool(struct scanner *s, struct environment *env) {\n"\ " struct pool *p;\n"\ " struct evalstring *val;\n"\ " struct string *str;\n"\ " char *var, *end;\n"\ " p = mkpool(scanname(s));\n"\ " scannewline(s);\n"\ " while (scanindent(s)) {\n"\ " var = scanname(s);\n"\ " parselet(s, &val);\n"\ " if (strcmp(var, \"depth\") == 0) {\n"\ " str = enveval(env, val);\n"\ " p->maxjobs = strtol(str->s, &end, 10);\n"\ " if (*end) fatal(\"invalid pool depth '%s'\", str->s);\n"\ " free(str);\n"\ " } else {\n"\ " fatal(\"unexpected pool variable '%s'\", var);\n"\ " }\n"\ " }\n"\ " if (!p->maxjobs) fatal(\"pool '%s' has no depth\", p->name);\n"\ "}\n"\ "static void checkversion(const char *ver) {\n"\ " int major, minor = 0;\n"\ " if (sscanf(ver, \"%d.%d\", &major, &minor) < 1) fatal(\"invalid ninja_required_version\");\n"\ " if (major > ninjamajor || (major == ninjamajor && minor > ninjaminor)) fatal(\"ninja_required_version %s is newer than %d.%d\", ver, ninjamajor, ninjaminor);\n"\ "}\n"\ "void parse(const char *name, struct environment *env) {\n"\ " struct scanner s;\n"\ " char *var;\n"\ " struct string *val;\n"\ " struct evalstring *str;\n"\ " scaninit(&s, name);\n"\ " for (;;) {\n"\ " switch (scankeyword(&s, &var)) {\n"\ " case RULE:\n"\ " parserule(&s, env);\n"\ " break;\n"\ " case BUILD:\n"\ " parseedge(&s, env);\n"\ " break;\n"\ " case INCLUDE:\n"\ " parseinclude(&s, env, false);\n"\ " break;\n"\ " case SUBNINJA:\n"\ " parseinclude(&s, env, true);\n"\ " break;\n"\ " case DEFAULT:\n"\ " parsedefault(&s, env);\n"\ " break;\n"\ " case POOL:\n"\ " parsepool(&s, env);\n"\ " break;\n"\ " case VARIABLE:\n"\ " parselet(&s, &str);\n"\ " val = enveval(env, str);\n"\ " if (strcmp(var, \"ninja_required_version\") == 0) checkversion(val->s);\n"\ " envaddvar(env, var, val);\n"\ " break;\n"\ " case EOF:\n"\ " scanclose(&s);\n"\ " return;\n"\ " }\n"\ " }\n"\ "}\n"\ "void defaultnodes(void fn(struct node *)) {\n"\ " struct edge *e;\n"\ " struct node *n;\n"\ " size_t i;\n"\ " if (ndeftarg > 0) {\n"\ " for (i = 0; i < ndeftarg; ++i)\n"\ " fn(deftarg[i]);\n"\ " } else {\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " n = e->out[i];\n"\ " if (n->nuse == 0) fn(n);\n"\ " }\n"\ " }\n"\ " }\n"\ "}\n"\ "const char *argv0;\n"\ "static void usage(void) {\n"\ " fprintf(stderr, \"usage: %s [-C dir] [-f buildfile] [-j maxjobs] [-k maxfail] [-l maxload] [-n]\\n\", argv0);\n"\ " exit(2);\n"\ "}\n"\ "static char *getbuilddir(void) {\n"\ " struct string *builddir;\n"\ " builddir = envvar(rootenv, \"builddir\");\n"\ " if (!builddir) return NULL;\n"\ " if (osmkdirs(builddir, false) < 0) exit(1);\n"\ " return builddir->s;\n"\ "}\n"\ "static void debugflag(const char *flag) {\n"\ " if (strcmp(flag, \"explain\") == 0) buildopts.explain = true;\n"\ " else if (strcmp(flag, \"keepdepfile\") == 0) buildopts.keepdepfile = true;\n"\ " else if (strcmp(flag, \"keeprsp\") == 0) buildopts.keeprsp = true;\n"\ " else fatal(\"unknown debug flag '%s'\", flag);\n"\ "}\n"\ "static void loadflag(const char *flag) {\n"\ "#ifdef HAVE_GETLOADAVG\n"\ " double value;\n"\ " char *end;\n"\ " errno = 0;\n"\ " value = strtod(flag, &end);\n"\ " if (*end || value < 0 || errno != 0) fatal(\"invalid -l parameter\");\n"\ " buildopts.maxload = value;\n"\ "#else\n"\ " warn(\"job scheduling based on load average is not supported\");\n"\ "#endif\n"\ "}\n"\ "static void warnflag(const char *flag) {\n"\ " if (strcmp(flag, \"dupbuild=err\") == 0) parseopts.dupbuildwarn = false;\n"\ " else if (strcmp(flag, \"dupbuild=warn\") == 0) parseopts.dupbuildwarn = true;\n"\ " else fatal(\"unknown warning flag '%s'\", flag);\n"\ "}\n"\ "static void jobsflag(const char *flag) {\n"\ " long num;\n"\ " char *end;\n"\ " num = strtol(flag, &end, 10);\n"\ " if (*end || num < 0) fatal(\"invalid -j parameter\");\n"\ " buildopts.maxjobs = num > 0 ? num : -1;\n"\ "}\n"\ "static void parseenvargs(char *env) {\n"\ " char *arg, *argvbuf[64], **argv = argvbuf;\n"\ " int argc;\n"\ " if (!env) return;\n"\ " env = xmemdup(env, strlen(env) + 1);\n"\ " argc = 1;\n"\ " argv[0] = NULL;\n"\ " arg = strtok(env, \" \");\n"\ " while (arg) {\n"\ " if ((size_t)argc >= LEN(argvbuf) - 1) fatal(\"too many arguments in SAMUFLAGS\");\n"\ " argv[argc++] = arg;\n"\ " arg = strtok(NULL, \" \");\n"\ " }\n"\ " argv[argc] = NULL;\n"\ " ARGBEGIN {\n"\ " case 'j':\n"\ " jobsflag(EARGF(usage()));\n"\ " break;\n"\ " case 'v':\n"\ " buildopts.verbose = true;\n"\ " break;\n"\ " case 'l':\n"\ " loadflag(EARGF(usage()));\n"\ " break;\n"\ " default:\n"\ " fatal(\"invalid option in SAMUFLAGS\");\n"\ " }\n"\ " ARGEND\n"\ " free(env);\n"\ "}\n"\ "static const char *progname(const char *arg, const char *def) {\n"\ " const char *slash;\n"\ " if (!arg) return def;\n"\ " slash = strrchr(arg, '/');\n"\ " return slash ? slash + 1 : arg;\n"\ "}\n"\ "int main(int argc, char *argv[]) {\n"\ " char *builddir, *manifest = \"build.ninja\", *end, *arg;\n"\ " const struct tool *tool = NULL;\n"\ " struct node *n;\n"\ " long num;\n"\ " int tries;\n"\ " argv0 = progname(argv[0], \"samu\");\n"\ " parseenvargs(getenv(\"SAMUFLAGS\"));\n"\ " ARGBEGIN {\n"\ " case '-':\n"\ " arg = EARGF(usage());\n"\ " if (strcmp(arg, \"version\") == 0) {\n"\ " printf(\"%d.%d.0\\n\", ninjamajor, ninjaminor);\n"\ " return 0;\n"\ " } else if (strcmp(arg, \"verbose\") == 0) {\n"\ " buildopts.verbose = true;\n"\ " } else {\n"\ " usage();\n"\ " }\n"\ " break;\n"\ " case 'C':\n"\ " arg = EARGF(usage());\n"\ " warn(\"entering directory '%s'\", arg);\n"\ " oschdir(arg);\n"\ " break;\n"\ " case 'd':\n"\ " debugflag(EARGF(usage()));\n"\ " break;\n"\ " case 'f':\n"\ " manifest = EARGF(usage());\n"\ " break;\n"\ " case 'j':\n"\ " jobsflag(EARGF(usage()));\n"\ " break;\n"\ " case 'k':\n"\ " num = strtol(EARGF(usage()), &end, 10);\n"\ " if (*end) fatal(\"invalid -k parameter\");\n"\ " buildopts.maxfail = num > 0 ? num : -1;\n"\ " break;\n"\ " case 'l':\n"\ " loadflag(EARGF(usage()));\n"\ " break;\n"\ " case 'n':\n"\ " buildopts.dryrun = true;\n"\ " break;\n"\ " case 't':\n"\ " tool = toolget(EARGF(usage()));\n"\ " goto argdone;\n"\ " case 'v':\n"\ " buildopts.verbose = true;\n"\ " break;\n"\ " case 'w':\n"\ " warnflag(EARGF(usage()));\n"\ " break;\n"\ " default:\n"\ " usage();\n"\ " }\n"\ " ARGEND\n"\ "argdone:\n"\ " if (!buildopts.maxjobs) {\n"\ "#ifdef _SC_NPROCESSORS_ONLN\n"\ " long nproc = sysconf(_SC_NPROCESSORS_ONLN);\n"\ " switch (nproc) {\n"\ " case -1:\n"\ " case 0:\n"\ " case 1:\n"\ " buildopts.maxjobs = 2;\n"\ " break;\n"\ " case 2:\n"\ " buildopts.maxjobs = 3;\n"\ " break;\n"\ " default:\n"\ " buildopts.maxjobs = nproc + 2;\n"\ " break;\n"\ " }\n"\ "#else\n"\ " buildopts.maxjobs = 2;\n"\ "#endif\n"\ " }\n"\ " buildopts.statusfmt = getenv(\"NINJA_STATUS\");\n"\ " if (!buildopts.statusfmt) buildopts.statusfmt = \"[%s/%t] \";\n"\ " setvbuf(stdout, NULL, _IOLBF, 0);\n"\ " tries = 0;\n"\ "retry:\n"\ " graphinit();\n"\ " envinit();\n"\ " parseinit();\n"\ " parse(manifest, rootenv);\n"\ " if (tool) return tool->run(argc, argv);\n"\ " builddir = getbuilddir();\n"\ " loginit(builddir);\n"\ " depsinit(builddir);\n"\ " n = nodeget(manifest, 0);\n"\ " if (n && n->gen) {\n"\ " buildadd(n);\n"\ " if (n->dirty) {\n"\ " build();\n"\ " if (n->gen->flags & FLAG_DIRTY_OUT || n->gen->nprune > 0) {\n"\ " if (++tries > 100) fatal(\"manifest '%s' dirty after 100 tries\", manifest);\n"\ " if (!buildopts.dryrun) goto retry;\n"\ " }\n"\ " buildreset();\n"\ " }\n"\ " }\n"\ " if (argc) {\n"\ " for (; *argv; ++argv) {\n"\ " n = nodeget(*argv, 0);\n"\ " if (!n) fatal(\"unknown target '%s'\", *argv);\n"\ " buildadd(n);\n"\ " }\n"\ " } else {\n"\ " defaultnodes(buildadd);\n"\ " }\n"\ " build();\n"\ " logclose();\n"\ " depsclose();\n"\ " return 0;\n"\ "}\n"\ "struct evalstring **paths;\n"\ "size_t npaths;\n"\ "static struct buffer buf;\n"\ "void scaninit(struct scanner *s, const char *path) {\n"\ " s->path = path;\n"\ " s->line = 1;\n"\ " s->col = 1;\n"\ " s->f = fopen(path, \"r\");\n"\ " if (!s->f) fatal(\"open %s:\", path);\n"\ " s->chr = getc(s->f);\n"\ "}\n"\ "void scanclose(struct scanner *s) {\n"\ " fclose(s->f);\n"\ "}\n"\ "void scanerror(struct scanner *s, const char *fmt, ...) {\n"\ " extern const char *argv0;\n"\ " va_list ap;\n"\ " fprintf(stderr, \"%s: %s:%d:%d: \", argv0, s->path, s->line, s->col);\n"\ " va_start(ap, fmt);\n"\ " vfprintf(stderr, fmt, ap);\n"\ " va_end(ap);\n"\ " putc('\\n', stderr);\n"\ " exit(1);\n"\ "}\n"\ "static int next(struct scanner *s) {\n"\ " if (s->chr == '\\n') {\n"\ " ++s->line;\n"\ " s->col = 1;\n"\ " } else {\n"\ " ++s->col;\n"\ " }\n"\ " s->chr = getc(s->f);\n"\ " return s->chr;\n"\ "}\n"\ "static int issimplevar(int c) {\n"\ " return isalnum(c) || c == '_' || c == '-';\n"\ "}\n"\ "static int isvar(int c) {\n"\ " return issimplevar(c) || c == '.';\n"\ "}\n"\ "static bool newline(struct scanner *s) {\n"\ " switch (s->chr) {\n"\ " case '\\r':\n"\ " if (next(s) != '\\n') scanerror(s, \"expected '\\\\n' after '\\\\r'\");\n"\ " case '\\n':\n"\ " next(s);\n"\ " return true;\n"\ " }\n"\ " return false;\n"\ "}\n"\ "static bool singlespace(struct scanner *s) {\n"\ " switch (s->chr) {\n"\ " case '$':\n"\ " next(s);\n"\ " if (newline(s)) return true;\n"\ " ungetc(s->chr, s->f);\n"\ " s->chr = '$';\n"\ " return false;\n"\ " case ' ':\n"\ " next(s);\n"\ " return true;\n"\ " }\n"\ " return false;\n"\ "}\n"\ "static bool space(struct scanner *s) {\n"\ " if (!singlespace(s)) return false;\n"\ " while (singlespace(s))\n"\ " ;\n"\ " return true;\n"\ "}\n"\ "static bool comment(struct scanner *s) {\n"\ " if (s->chr != '#') return false;\n"\ " do\n"\ " next(s);\n"\ " while (!newline(s));\n"\ " return true;\n"\ "}\n"\ "static void name(struct scanner *s) {\n"\ " buf.len = 0;\n"\ " while (isvar(s->chr)) {\n"\ " bufadd(&buf, s->chr);\n"\ " next(s);\n"\ " }\n"\ " if (!buf.len) scanerror(s, \"expected name\");\n"\ " bufadd(&buf, '\\0');\n"\ " space(s);\n"\ "}\n"\ "int scankeyword(struct scanner *s, char **var) {\n"\ " static const struct {\n"\ " const char *name;\n"\ " int value;\n"\ " } keywords[] = {\n"\ " {\"build\", BUILD},\n"\ " {\"default\", DEFAULT},\n"\ " {\"include\", INCLUDE},\n"\ " {\"pool\", POOL},\n"\ " {\"rule\", RULE},\n"\ " {\"subninja\", SUBNINJA},\n"\ " };\n"\ " int low = 0, high = LEN(keywords) - 1, mid, cmp;\n"\ " for (;;) {\n"\ " switch (s->chr) {\n"\ " case ' ':\n"\ " space(s);\n"\ " if (!comment(s) && !newline(s)) scanerror(s, \"unexpected indent\");\n"\ " break;\n"\ " case '#':\n"\ " comment(s);\n"\ " break;\n"\ " case '\\r':\n"\ " case '\\n':\n"\ " newline(s);\n"\ " break;\n"\ " case EOF:\n"\ " return EOF;\n"\ " default:\n"\ " name(s);\n"\ " while (low <= high) {\n"\ " mid = (low + high) / 2;\n"\ " cmp = strcmp(buf.data, keywords[mid].name);\n"\ " if (cmp == 0) return keywords[mid].value;\n"\ " if (cmp < 0) high = mid - 1;\n"\ " else low = mid + 1;\n"\ " }\n"\ " *var = xmemdup(buf.data, buf.len);\n"\ " return VARIABLE;\n"\ " }\n"\ " }\n"\ "}\n"\ "char *scanname(struct scanner *s) {\n"\ " name(s);\n"\ " return xmemdup(buf.data, buf.len);\n"\ "}\n"\ "static void addstringpart(struct evalstring ***end, bool var) {\n"\ " struct evalstring *p;\n"\ " p = xmalloc(sizeof(*p));\n"\ " p->next = NULL;\n"\ " **end = p;\n"\ " if (var) {\n"\ " bufadd(&buf, '\\0');\n"\ " p->var = xmemdup(buf.data, buf.len);\n"\ " } else {\n"\ " p->var = NULL;\n"\ " p->str = mkstr(buf.len);\n"\ " memcpy(p->str->s, buf.data, buf.len);\n"\ " p->str->s[buf.len] = '\\0';\n"\ " }\n"\ " *end = &p->next;\n"\ " buf.len = 0;\n"\ "}\n"\ "static void escape(struct scanner *s, struct evalstring ***end) {\n"\ " switch (s->chr) {\n"\ " case '$':\n"\ " case ' ':\n"\ " case ':':\n"\ " bufadd(&buf, s->chr);\n"\ " next(s);\n"\ " break;\n"\ " case '{':\n"\ " if (buf.len > 0) addstringpart(end, false);\n"\ " while (isvar(next(s)))\n"\ " bufadd(&buf, s->chr);\n"\ " if (s->chr != '}') scanerror(s, \"invalid variable name\");\n"\ " next(s);\n"\ " addstringpart(end, true);\n"\ " break;\n"\ " case '\\r':\n"\ " case '\\n':\n"\ " newline(s);\n"\ " space(s);\n"\ " break;\n"\ " default:\n"\ " if (buf.len > 0) addstringpart(end, false);\n"\ " while (issimplevar(s->chr)) {\n"\ " bufadd(&buf, s->chr);\n"\ " next(s);\n"\ " }\n"\ " if (!buf.len) scanerror(s, \"invalid $ escape\");\n"\ " addstringpart(end, true);\n"\ " }\n"\ "}\n"\ "struct evalstring *scanstring(struct scanner *s, bool path) {\n"\ " struct evalstring *str = NULL, **end = &str;\n"\ " buf.len = 0;\n"\ " for (;;) {\n"\ " switch (s->chr) {\n"\ " case '$':\n"\ " next(s);\n"\ " escape(s, &end);\n"\ " break;\n"\ " case ':':\n"\ " case '|':\n"\ " case ' ':\n"\ " if (path) goto out;\n"\ " default:\n"\ " bufadd(&buf, s->chr);\n"\ " next(s);\n"\ " break;\n"\ " case '\\r':\n"\ " case '\\n':\n"\ " case EOF:\n"\ " goto out;\n"\ " }\n"\ " }\n"\ "out:\n"\ " if (buf.len > 0) addstringpart(&end, 0);\n"\ " if (path) space(s);\n"\ " return str;\n"\ "}\n"\ "void scanpaths(struct scanner *s) {\n"\ " static size_t max;\n"\ " struct evalstring *str;\n"\ " while ((str = scanstring(s, true))) {\n"\ " if (npaths == max) {\n"\ " max = max ? max * 2 : 32;\n"\ " paths = xreallocarray(paths, max, sizeof(paths[0]));\n"\ " }\n"\ " paths[npaths++] = str;\n"\ " }\n"\ "}\n"\ "void scanchar(struct scanner *s, int c) {\n"\ " if (s->chr != c) scanerror(s, \"expected '%c'\", c);\n"\ " next(s);\n"\ " space(s);\n"\ "}\n"\ "int scanpipe(struct scanner *s, int n) {\n"\ " if (s->chr != '|') return 0;\n"\ " next(s);\n"\ " if (s->chr != '|') {\n"\ " if (!(n & 1)) scanerror(s, \"expected '||'\");\n"\ " space(s);\n"\ " return 1;\n"\ " }\n"\ " if (!(n & 2)) scanerror(s, \"unexpected '||'\");\n"\ " next(s);\n"\ " space(s);\n"\ " return 2;\n"\ "}\n"\ "bool scanindent(struct scanner *s) {\n"\ " bool indent;\n"\ " for (;;) {\n"\ " indent = space(s);\n"\ " if (!comment(s)) return indent && !newline(s);\n"\ " }\n"\ "}\n"\ "void scannewline(struct scanner *s) {\n"\ " if (!newline(s)) scanerror(s, \"expected newline\");\n"\ "}\n"\ "static int cleanpath(struct string *path) {\n"\ " if (path) {\n"\ " if (remove(path->s) == 0) {\n"\ " printf(\"remove %s\\n\", path->s);\n"\ " } else if (errno != ENOENT) {\n"\ " warn(\"remove %s:\", path->s);\n"\ " return -1;\n"\ " }\n"\ " }\n"\ " return 0;\n"\ "}\n"\ "static int cleanedge(struct edge *e) {\n"\ " int ret = 0;\n"\ " size_t i;\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " if (cleanpath(e->out[i]->path) < 0) ret = -1;\n"\ " }\n"\ " if (cleanpath(edgevar(e, \"rspfile\", false)) < 0) ret = -1;\n"\ " if (cleanpath(edgevar(e, \"depfile\", false)) < 0) ret = -1;\n"\ " return ret;\n"\ "}\n"\ "static int cleantarget(struct node *n) {\n"\ " int ret = 0;\n"\ " size_t i;\n"\ " if (!n->gen || n->gen->rule == &phonyrule) return 0;\n"\ " if (cleanpath(n->path) < 0) ret = -1;\n"\ " for (i = 0; i < n->gen->nin; ++i) {\n"\ " if (cleantarget(n->gen->in[i]) < 0) ret = -1;\n"\ " }\n"\ " return ret;\n"\ "}\n"\ "static int clean(int argc, char *argv[]) {\n"\ " int ret = 0;\n"\ " bool cleangen = false, cleanrule = false;\n"\ " struct edge *e;\n"\ " struct node *n;\n"\ " struct rule *r;\n"\ " ARGBEGIN {\n"\ " case 'g':\n"\ " cleangen = true;\n"\ " break;\n"\ " case 'r':\n"\ " cleanrule = true;\n"\ " break;\n"\ " default:\n"\ " fprintf(stderr, \"usage: %s ... -t clean [-gr] [targets...]\\n\", argv0);\n"\ " return 2;\n"\ " }\n"\ " ARGEND\n"\ " if (cleanrule) {\n"\ " if (!argc) fatal(\"expected a rule to clean\");\n"\ " for (; *argv; ++argv) {\n"\ " r = envrule(rootenv, *argv);\n"\ " if (!r) {\n"\ " warn(\"unknown rule '%s'\", *argv);\n"\ " ret = 1;\n"\ " continue;\n"\ " }\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " if (e->rule != r) continue;\n"\ " if (cleanedge(e) < 0) ret = 1;\n"\ " }\n"\ " }\n"\ " } else if (argc > 0) {\n"\ " for (; *argv; ++argv) {\n"\ " n = nodeget(*argv, 0);\n"\ " if (!n) {\n"\ " warn(\"unknown target '%s'\", *argv);\n"\ " ret = 1;\n"\ " continue;\n"\ " }\n"\ " if (cleantarget(n) < 0) ret = 1;\n"\ " }\n"\ " } else {\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " if (e->rule == &phonyrule) continue;\n"\ " if (!cleangen && edgevar(e, \"generator\", true)) continue;\n"\ " if (cleanedge(e) < 0) ret = 1;\n"\ " }\n"\ " }\n"\ " return ret;\n"\ "}\n"\ "static void targetcommands(struct node *n) {\n"\ " struct edge *e = n->gen;\n"\ " struct string *command;\n"\ " size_t i;\n"\ " if (!e || (e->flags & FLAG_WORK)) return;\n"\ " e->flags |= FLAG_WORK;\n"\ " for (i = 0; i < e->nin; ++i)\n"\ " targetcommands(e->in[i]);\n"\ " command = edgevar(e, \"command\", true);\n"\ " if (command && command->n) puts(command->s);\n"\ "}\n"\ "static int commands(int argc, char *argv[]) {\n"\ " struct node *n;\n"\ " if (argc > 1) {\n"\ " while (*++argv) {\n"\ " n = nodeget(*argv, 0);\n"\ " if (!n) fatal(\"unknown target '%s'\", *argv);\n"\ " targetcommands(n);\n"\ " }\n"\ " } else {\n"\ " defaultnodes(targetcommands);\n"\ " }\n"\ " if (fflush(stdout) || ferror(stdout)) fatal(\"write failed\");\n"\ " return 0;\n"\ "}\n"\ "static void printquoted(const char *s, size_t n, bool join) {\n"\ " size_t i;\n"\ " char c;\n"\ " for (i = 0; i < n; ++i) {\n"\ " c = s[i];\n"\ " switch (c) {\n"\ " case '\"':\n"\ " case '\\\\':\n"\ " putchar('\\\\');\n"\ " break;\n"\ " case '\\n':\n"\ " if (join) c = ' ';\n"\ " break;\n"\ " case '\\0':\n"\ " return;\n"\ " }\n"\ " putchar(c);\n"\ " }\n"\ "}\n"\ "static int compdb(int argc, char *argv[]) {\n"\ " char dir[1024], *p;\n"\ " struct edge *e;\n"\ " struct string *cmd, *rspfile, *content;\n"\ " bool expandrsp = false, first = true;\n"\ " int i;\n"\ " size_t off;\n"\ " ARGBEGIN {\n"\ " case 'x':\n"\ " expandrsp = true;\n"\ " break;\n"\ " default:\n"\ " fprintf(stderr, \"usage: %s ... -t compdb [-x] [rules...]\\n\", argv0);\n"\ " return 2;\n"\ " }\n"\ " ARGEND\n"\ " osgetcwd(dir, sizeof(dir));\n"\ " putchar('[');\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " if (e->nin == 0) continue;\n"\ " for (i = 0; i < argc; ++i) {\n"\ " if (strcmp(e->rule->name, argv[i]) == 0) {\n"\ " if (first) first = false;\n"\ " else putchar(',');\n"\ " printf(\"\\n {\\n \\\"directory\\\": \\\"\");\n"\ " printquoted(dir, -1, false);\n"\ " printf(\"\\\",\\n \\\"command\\\": \\\"\");\n"\ " cmd = edgevar(e, \"command\", true);\n"\ " rspfile = expandrsp ? edgevar(e, \"rspfile\", true) : NULL;\n"\ " p = rspfile ? strstr(cmd->s, rspfile->s) : NULL;\n"\ " if (!p || p == cmd->s || p[-1] != '@') {\n"\ " printquoted(cmd->s, cmd->n, false);\n"\ " } else {\n"\ " off = p - cmd->s;\n"\ " printquoted(cmd->s, off - 1, false);\n"\ " content = edgevar(e, \"rspfile_content\", true);\n"\ " printquoted(content->s, content->n, true);\n"\ " off += rspfile->n;\n"\ " printquoted(cmd->s + off, cmd->n - off, false);\n"\ " }\n"\ " printf(\"\\\",\\n \\\"file\\\": \\\"\");\n"\ " printquoted(e->in[0]->path->s, -1, false);\n"\ " printf(\"\\\",\\n \\\"output\\\": \\\"\");\n"\ " printquoted(e->out[0]->path->s, -1, false);\n"\ " printf(\"\\\"\\n }\");\n"\ " break;\n"\ " }\n"\ " }\n"\ " }\n"\ " puts(\"\\n]\");\n"\ " if (fflush(stdout) || ferror(stdout)) fatal(\"write failed\");\n"\ " return 0;\n"\ "}\n"\ "static void graphnode(struct node *n) {\n"\ " struct edge *e = n->gen;\n"\ " size_t i;\n"\ " const char *style;\n"\ " printf(\"\\\"%p\\\" [label=\\\"\", (void *)n);\n"\ " printquoted(n->path->s, n->path->n, false);\n"\ " printf(\"\\\"]\\n\");\n"\ " if (!e || (e->flags & FLAG_WORK)) return;\n"\ " e->flags |= FLAG_WORK;\n"\ " for (i = 0; i < e->nin; ++i)\n"\ " graphnode(e->in[i]);\n"\ " if (e->nin == 1 && e->nout == 1) {\n"\ " printf(\"\\\"%p\\\" -> \\\"%p\\\" [label=\\\"%s\\\"]\\n\", (void *)e->in[0], (void *)e->out[0], e->rule->name);\n"\ " } else {\n"\ " printf(\"\\\"%p\\\" [label=\\\"%s\\\", shape=ellipse]\\n\", (void *)e, e->rule->name);\n"\ " for (i = 0; i < e->nout; ++i)\n"\ " printf(\"\\\"%p\\\" -> \\\"%p\\\"\\n\", (void *)e, (void *)e->out[i]);\n"\ " for (i = 0; i < e->nin; ++i) {\n"\ " style = i >= e->inorderidx ? \" style=dotted\" : \"\";\n"\ " printf(\"\\\"%p\\\" -> \\\"%p\\\" [arrowhead=none%s]\\n\", (void *)e->in[i], (void *)e, style);\n"\ " }\n"\ " }\n"\ "}\n"\ "static int graph(int argc, char *argv[]) {\n"\ " struct node *n;\n"\ " puts(\"digraph ninja {\");\n"\ " puts(\"rankdir=\\\"LR\\\"\");\n"\ " puts(\"node [fontsize=10, shape=box, height=0.25]\");\n"\ " puts(\"edge [fontsize=10]\");\n"\ " if (argc > 1) {\n"\ " while (*++argv) {\n"\ " n = nodeget(*argv, 0);\n"\ " if (!n) fatal(\"unknown target '%s'\", *argv);\n"\ " graphnode(n);\n"\ " }\n"\ " } else {\n"\ " defaultnodes(graphnode);\n"\ " }\n"\ " puts(\"}\");\n"\ " if (fflush(stdout) || ferror(stdout)) fatal(\"write failed\");\n"\ " return 0;\n"\ "}\n"\ "static int query(int argc, char *argv[]) {\n"\ " struct node *n;\n"\ " struct edge *e;\n"\ " char *path;\n"\ " int i;\n"\ " size_t j, k;\n"\ " if (argc == 1) {\n"\ " fprintf(stderr, \"usage: %s ... -t query target...\\n\", argv0);\n"\ " exit(2);\n"\ " }\n"\ " for (i = 1; i < argc; ++i) {\n"\ " path = argv[i];\n"\ " n = nodeget(path, 0);\n"\ " if (!n) fatal(\"unknown target '%s'\", path);\n"\ " printf(\"%s:\\n\", argv[i]);\n"\ " e = n->gen;\n"\ " if (e) {\n"\ " printf(\" input: %s\\n\", e->rule->name);\n"\ " for (j = 0; j < e->nin; ++j)\n"\ " printf(\" %s\\n\", e->in[j]->path->s);\n"\ " }\n"\ " puts(\" outputs:\");\n"\ " for (j = 0; j < n->nuse; ++j) {\n"\ " e = n->use[j];\n"\ " for (k = 0; k < e->nout; ++k)\n"\ " printf(\" %s\\n\", e->out[k]->path->s);\n"\ " }\n"\ " }\n"\ " return 0;\n"\ "}\n"\ "static void targetsdepth(struct node *n, size_t depth, size_t indent) {\n"\ " struct edge *e = n->gen;\n"\ " size_t i;\n"\ " for (i = 0; i < indent; ++i)\n"\ " printf(\" \");\n"\ " if (e) {\n"\ " printf(\"%s: %s\\n\", n->path->s, e->rule->name);\n"\ " if (depth != 1) {\n"\ " for (i = 0; i < e->nin; ++i)\n"\ " targetsdepth(e->in[i], depth - 1, indent + 1);\n"\ " }\n"\ " } else {\n"\ " puts(n->path->s);\n"\ " }\n"\ "}\n"\ "static void targetsusage(void) {\n"\ " fprintf(stderr,\n"\ " \"usage: %s ... -t targets [depth [maxdepth]]\\n\"\n"\ " \" %s ... -t targets rule [rulename]\\n\"\n"\ " \" %s ... -t targets all\\n\",\n"\ " argv0,\n"\ " argv0,\n"\ " argv0);\n"\ " exit(2);\n"\ "}\n"\ "static int targets(int argc, char *argv[]) {\n"\ " struct edge *e;\n"\ " size_t depth = 1, i;\n"\ " char *end, *mode, *name;\n"\ " if (argc > 3) targetsusage();\n"\ " mode = argv[1];\n"\ " if (!mode || strcmp(mode, \"depth\") == 0) {\n"\ " if (argc == 3) {\n"\ " depth = strtol(argv[2], &end, 10);\n"\ " if (*end) targetsusage();\n"\ " }\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " for (i = 0; i < e->nout; ++i) {\n"\ " if (e->out[i]->nuse == 0) targetsdepth(e->out[i], depth, 0);\n"\ " }\n"\ " }\n"\ " } else if (strcmp(mode, \"rule\") == 0) {\n"\ " name = argv[2];\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " if (!name) {\n"\ " for (i = 0; i < e->nin; ++i) {\n"\ " if (!e->in[i]->gen) puts(e->in[i]->path->s);\n"\ " }\n"\ " } else if (strcmp(e->rule->name, name) == 0) {\n"\ " for (i = 0; i < e->nout; ++i)\n"\ " puts(e->out[i]->path->s);\n"\ " }\n"\ " }\n"\ " } else if (strcmp(mode, \"all\") == 0 && argc == 2) {\n"\ " for (e = alledges; e; e = e->allnext) {\n"\ " for (i = 0; i < e->nout; ++i)\n"\ " printf(\"%s: %s\\n\", e->out[i]->path->s, e->rule->name);\n"\ " }\n"\ " } else {\n"\ " targetsusage();\n"\ " }\n"\ " if (fflush(stdout) || ferror(stdout)) fatal(\"write failed\");\n"\ " return 0;\n"\ "}\n"\ "static const struct tool tools[] = {\n"\ " {\"clean\", clean},\n"\ " {\"commands\", commands},\n"\ " {\"compdb\", compdb},\n"\ " {\"graph\", graph},\n"\ " {\"query\", query},\n"\ " {\"targets\", targets},\n"\ "};\n"\ "const struct tool *toolget(const char *name) {\n"\ " const struct tool *t;\n"\ " size_t i;\n"\ " t = NULL;\n"\ " for (i = 0; i < LEN(tools); ++i) {\n"\ " if (strcmp(name, tools[i].name) == 0) {\n"\ " t = &tools[i];\n"\ " break;\n"\ " }\n"\ " }\n"\ " if (!t) fatal(\"unknown tool '%s'\", name);\n"\ " return t;\n"\ "}\n"\ "#define MAXH (sizeof(void *) * 8 * 3 / 2)\n"\ "void deltree(struct treenode *n, void delkey(void *), void delval(void *)) {\n"\ " if (!n) return;\n"\ " if (delkey) delkey(n->key);\n"\ " if (delval) delval(n->value);\n"\ " deltree(n->child[0], delkey, delval);\n"\ " deltree(n->child[1], delkey, delval);\n"\ " free(n);\n"\ "}\n"\ "static inline int height(struct treenode *n) {\n"\ " return n ? n->height : 0;\n"\ "}\n"\ "static int rot(struct treenode **p, struct treenode *x, int dir) {\n"\ " struct treenode *y = x->child[dir];\n"\ " struct treenode *z = y->child[!dir];\n"\ " int hx = x->height;\n"\ " int hz = height(z);\n"\ " if (hz > height(y->child[dir])) {\n"\ " x->child[dir] = z->child[!dir];\n"\ " y->child[!dir] = z->child[dir];\n"\ " z->child[!dir] = x;\n"\ " z->child[dir] = y;\n"\ " x->height = hz;\n"\ " y->height = hz;\n"\ " z->height = hz + 1;\n"\ " } else {\n"\ " x->child[dir] = z;\n"\ " y->child[!dir] = x;\n"\ " x->height = hz + 1;\n"\ " y->height = hz + 2;\n"\ " z = y;\n"\ " }\n"\ " *p = z;\n"\ " return z->height - hx;\n"\ "}\n"\ "static int balance(struct treenode **p) {\n"\ " struct treenode *n = *p;\n"\ " int h0 = height(n->child[0]);\n"\ " int h1 = height(n->child[1]);\n"\ " if (h0 - h1 + 1u < 3u) {\n"\ " int old = n->height;\n"\ " n->height = h0 < h1 ? h1 + 1 : h0 + 1;\n"\ " return n->height - old;\n"\ " }\n"\ " return rot(p, n, h0 < h1);\n"\ "}\n"\ "struct treenode *treefind(struct treenode *n, const char *key) {\n"\ " int c;\n"\ " while (n) {\n"\ " c = strcmp(key, n->key);\n"\ " if (c == 0) return n;\n"\ " n = n->child[c > 0];\n"\ " }\n"\ " return NULL;\n"\ "}\n"\ "void *treeinsert(struct treenode **rootp, char *key, void *value) {\n"\ " struct treenode **a[MAXH], *n = *rootp, *r;\n"\ " void *old;\n"\ " int i = 0, c;\n"\ " a[i++] = rootp;\n"\ " while (n) {\n"\ " c = strcmp(key, n->key);\n"\ " if (c == 0) {\n"\ " old = n->value;\n"\ " n->value = value;\n"\ " return old;\n"\ " }\n"\ " a[i++] = &n->child[c > 0];\n"\ " n = n->child[c > 0];\n"\ " }\n"\ " r = xmalloc(sizeof(*r));\n"\ " r->key = key;\n"\ " r->value = value;\n"\ " r->child[0] = r->child[1] = NULL;\n"\ " r->height = 1;\n"\ " *a[--i] = r;\n"\ " while (i && balance(a[--i]))\n"\ " ;\n"\ " return NULL;\n"\ "}\n"\ "extern const char *argv0;\n"\ "static void vwarn(const char *fmt, va_list ap) {\n"\ " fprintf(stderr, \"%s: \", argv0);\n"\ " vfprintf(stderr, fmt, ap);\n"\ " if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {\n"\ " putc(' ', stderr);\n"\ " perror(NULL);\n"\ " } else {\n"\ " putc('\\n', stderr);\n"\ " }\n"\ "}\n"\ "void warn(const char *fmt, ...) {\n"\ " va_list ap;\n"\ " va_start(ap, fmt);\n"\ " vwarn(fmt, ap);\n"\ " va_end(ap);\n"\ "}\n"\ "void fatal(const char *fmt, ...) {\n"\ " va_list ap;\n"\ " va_start(ap, fmt);\n"\ " vwarn(fmt, ap);\n"\ " va_end(ap);\n"\ " exit(1);\n"\ "}\n"\ "void *xmalloc(size_t n) {\n"\ " void *p;\n"\ " p = malloc(n);\n"\ " if (!p) fatal(\"malloc:\");\n"\ " return p;\n"\ "}\n"\ "static void *reallocarray_(void *p, size_t n, size_t m) {\n"\ " if (m && n > SIZE_MAX / m) {\n"\ " errno = ENOMEM;\n"\ " return NULL;\n"\ " }\n"\ " return realloc(p, n * m);\n"\ "}\n"\ "void *xreallocarray(void *p, size_t n, size_t m) {\n"\ " p = reallocarray_(p, n, m);\n"\ " if (!p) fatal(\"reallocarray:\");\n"\ " return p;\n"\ "}\n"\ "char *xmemdup(const char *s, size_t n) {\n"\ " char *p;\n"\ " p = xmalloc(n);\n"\ " memcpy(p, s, n);\n"\ " return p;\n"\ "}\n"\ "int xasprintf(char **s, const char *fmt, ...) {\n"\ " va_list ap;\n"\ " int ret;\n"\ " size_t n;\n"\ " va_start(ap, fmt);\n"\ " ret = vsnprintf(NULL, 0, fmt, ap);\n"\ " va_end(ap);\n"\ " if (ret < 0) fatal(\"vsnprintf:\");\n"\ " n = ret + 1;\n"\ " *s = xmalloc(n);\n"\ " va_start(ap, fmt);\n"\ " ret = vsnprintf(*s, n, fmt, ap);\n"\ " va_end(ap);\n"\ " if (ret < 0 || (size_t)ret >= n) fatal(\"vsnprintf:\");\n"\ " return ret;\n"\ "}\n"\ "void bufadd(struct buffer *buf, char c) {\n"\ " if (buf->len >= buf->cap) {\n"\ " buf->cap = buf->cap ? buf->cap * 2 : 1 << 8;\n"\ " buf->data = realloc(buf->data, buf->cap);\n"\ " if (!buf->data) fatal(\"realloc:\");\n"\ " }\n"\ " buf->data[buf->len++] = c;\n"\ "}\n"\ "struct string *mkstr(size_t n) {\n"\ " struct string *str;\n"\ " str = xmalloc(sizeof(*str) + n + 1);\n"\ " str->n = n;\n"\ " return str;\n"\ "}\n"\ "void delevalstr(void *ptr) {\n"\ " struct evalstring *str = ptr, *p;\n"\ " while (str) {\n"\ " p = str;\n"\ " str = str->next;\n"\ " if (p->var) free(p->var);\n"\ " else free(p->str);\n"\ " free(p);\n"\ " }\n"\ "}\n"\ "void canonpath(struct string *path) {\n"\ " char *component[60];\n"\ " int n;\n"\ " char *s, *d, *end;\n"\ " if (path->n == 0) fatal(\"empty path\");\n"\ " s = d = path->s;\n"\ " end = path->s + path->n;\n"\ " n = 0;\n"\ " if (*s == '/') {\n"\ " ++s;\n"\ " ++d;\n"\ " }\n"\ " while (s < end) {\n"\ " switch (s[0]) {\n"\ " case '/':\n"\ " ++s;\n"\ " continue;\n"\ " case '.':\n"\ " switch (s[1]) {\n"\ " case '\\0':\n"\ " case '/':\n"\ " s += 2;\n"\ " continue;\n"\ " case '.':\n"\ " if (s[2] != '/' && s[2] != '\\0') break;\n"\ " if (n > 0) {\n"\ " d = component[--n];\n"\ " } else {\n"\ " *d++ = s[0];\n"\ " *d++ = s[1];\n"\ " *d++ = s[2];\n"\ " }\n"\ " s += 3;\n"\ " continue;\n"\ " }\n"\ " }\n"\ " if (n == LEN(component)) fatal(\"path has too many components: %s\", path->s);\n"\ " component[n++] = d;\n"\ " while (*s != '/' && *s != '\\0')\n"\ " *d++ = *s++;\n"\ " *d++ = *s++;\n"\ " }\n"\ " if (d == path->s) {\n"\ " *d++ = '.';\n"\ " *d = '\\0';\n"\ " } else {\n"\ " *--d = '\\0';\n"\ " }\n"\ " path->n = d - path->s;\n"\ "}\n"\ "int writefile(const char *name, struct string *s) {\n"\ " FILE *f;\n"\ " int ret;\n"\ " f = fopen(name, \"w\");\n"\ " if (!f) {\n"\ " warn(\"open %s:\", name);\n"\ " return -1;\n"\ " }\n"\ " ret = 0;\n"\ " if (s && (fwrite(s->s, 1, s->n, f) != s->n || fflush(f) != 0)) {\n"\ " warn(\"write %s:\", name);\n"\ " ret = -1;\n"\ " }\n"\ " fclose(f);\n"\ " return ret;\n"\ "}\n"\ "void osgetcwd(char *buf, size_t len) {\n"\ " if (!getcwd(buf, len)) fatal(\"getcwd:\");\n"\ "}\n"\ "void oschdir(const char *dir) {\n"\ " if (chdir(dir) < 0) fatal(\"chdir %s:\", dir);\n"\ "}\n"\ "int osmkdirs(struct string *path, bool parent) {\n"\ " int ret;\n"\ " struct stat st;\n"\ " char *s, *end;\n"\ " ret = 0;\n"\ " end = path->s + path->n;\n"\ " for (s = end - parent; s > path->s; --s) {\n"\ " if (*s != '/' && *s) continue;\n"\ " *s = '\\0';\n"\ " if (stat(path->s, &st) == 0) break;\n"\ " if (errno != ENOENT) {\n"\ " warn(\"stat %s:\", path->s);\n"\ " ret = -1;\n"\ " break;\n"\ " }\n"\ " }\n"\ " if (s > path->s && s < end) *s = '/';\n"\ " while (++s <= end - parent) {\n"\ " if (*s != '\\0') continue;\n"\ " if (ret == 0 && mkdir(path->s, 0777) < 0 && errno != EEXIST) {\n"\ " warn(\"mkdir %s:\", path->s);\n"\ " ret = -1;\n"\ " }\n"\ " if (s < end) *s = '/';\n"\ " }\n"\ " return ret;\n"\ "}\n"\ "int64_t osmtime(const char *name) {\n"\ " struct stat st;\n"\ " if (stat(name, &st) < 0) {\n"\ " if (errno != ENOENT) fatal(\"stat %s:\", name);\n"\ " return MTIME_MISSING;\n"\ " } else {\n"\ "#ifdef __APPLE__\n"\ " return (int64_t)st.st_mtime * 1000000000 + st.st_mtimensec;\n"\ "#elif defined(__sun)\n"\ " return (int64_t)st.st_mtim.__tv_sec * 1000000000 + st.st_mtim.__tv_nsec;\n"\ "#else\n"\ " return (int64_t)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;\n"\ "#endif\n"\ " }\n"\ "}\n"\ "" // --- SAMURAI END --- /* MIT License mate.h - Mate Implementations start here Guide on the `README.md` */ #ifdef MATE_IMPLEMENTATION // TODO: state -> mateState static MateConfig state = {0}; // TODO: add all to mateState static String includes = {0}; static String libs = {0}; static StringVector sources = {0}; static Executable executable = {0}; static StaticLib staticLib = {0}; static const String MSVCStr = {.data = "cl.exe", .length = 6}; bool isMSVC() { return StrEq(state.compiler, MSVCStr); } static const String GCCStr = {.data = "gcc", .length = 3}; bool isGCC() { return StrEq(state.compiler, GCCStr); } static const String ClangStr = {.data = "clang", .length = 5}; bool isClang() { return StrEq(state.compiler, ClangStr); } static const String TCCStr = {.data = "tcc", .length = 3}; bool isTCC() { return StrEq(state.compiler, TCCStr); } bool isLinux() { #if defined(PLATFORM_LINUX) return true; #else return false; #endif } bool isWindows() { #if defined(PLATFORM_WIN) return true; #else return false; #endif } static String fixPathExe(String str) { String path = NormalizeExePath(state.arena, str); #if defined(PLATFORM_WIN) return F(state.arena, "%s\\%s", GetCwd(), path.data); #else return F(state.arena, "%s/%s", GetCwd(), path.data); #endif } static String fixPath(String str) { String path = NormalizePath(state.arena, str); #if defined(PLATFORM_WIN) return F(state.arena, "%s\\%s", GetCwd(), path.data); #else return F(state.arena, "%s/%s", GetCwd(), path.data); #endif } String ConvertNinjaPath(String str) { #if defined(PLATFORM_WIN) String copy = StrNewSize(state.arena, str.data, str.length + 1); memmove(©.data[2], ©.data[1], str.length - 1); copy.data[1] = '$'; copy.data[2] = ':'; return copy; #else return str; #endif } static void setDefaultState() { state.arena = ArenaCreate(20000 * sizeof(String)); state.compiler = GetCompiler(); state.mateExe = fixPathExe(S("./mate")); state.mateSource = fixPath(S("./mate.c")); state.buildDirectory = fixPath(S("./build")); } static MateConfig parseMateConfig(MateOptions options) { MateConfig result; result.mateExe = StrNew(state.arena, options.mateExe); result.compiler = StrNew(state.arena, options.compiler); result.mateSource = StrNew(state.arena, options.mateSource); result.buildDirectory = StrNew(state.arena, options.buildDirectory); return result; } void CreateConfig(MateOptions options) { setDefaultState(); MateConfig config = parseMateConfig(options); if (!StrIsNull(config.mateExe)) { state.mateExe = fixPathExe(config.mateExe); } if (!StrIsNull(config.mateSource)) { state.mateSource = fixPath(config.mateSource); } if (!StrIsNull(config.buildDirectory)) { state.buildDirectory = fixPath(config.buildDirectory); } if (!StrIsNull(config.compiler)) { state.compiler = config.compiler; } state.initConfig = true; } static void readCache() { String mateCachePath = F(state.arena, "%s/mate-cache.ini", state.buildDirectory.data); errno_t err = IniParse(mateCachePath, &state.cache); Assert("MateReadCache: failed reading MateCache at %s, err: %d", mateCachePath.data, err); state.mateCache.lastBuild = IniGetLong(&state.cache, S("modify-time")); if (state.mateCache.lastBuild == 0) { state.mateCache.firstBuild = true; state.mateCache.lastBuild = TimeNow() / 1000; String modifyTime = F(state.arena, "%ld", state.mateCache.lastBuild); IniSet(&state.cache, S("modify-time"), modifyTime); } #if defined(PLATFORM_WIN) if (state.mateCache.firstBuild) { errno_t ninjaCheck = RunCommand(S("ninja --version > nul 2> nul")); Assert(ninjaCheck == SUCCESS, "MateReadCache: Ninja build system not found. Please install Ninja and add it to your PATH."); } #else state.mateCache.samuraiBuild = IniGetBool(&state.cache, S("samurai-build")); if (state.mateCache.samuraiBuild == false) { Assert(state.mateCache.firstBuild, "MateCache: This is not the first build and samurai is not compiled, could be a cache error, delete `./build` folder and rebuild `./mate.c`"); String samuraiAmalgam = s(SAMURAI_AMALGAM); String sourcePath = F(state.arena, "%s/samurai.c", state.buildDirectory.data); errno_t errFileWrite = FileWrite(sourcePath, samuraiAmalgam); Assert(errFileWrite == SUCCESS, "MateReadCache: failed writing samurai source code to path %s", sourcePath.data); String outputPath = F(state.arena, "%s/samurai", state.buildDirectory.data); String compileCommand = F(state.arena, "%s \"%s\" -o \"%s\" -lrt -std=c99", state.compiler.data, sourcePath.data, outputPath.data); errno_t err = RunCommand(compileCommand); Assert(err == SUCCESS, "MateReadCache: Error meanwhile compiling samurai at %s, if you are seeing this please make an issue at github.com/TomasBorquez/mate.h", sourcePath.data); LogSuccess("Successfully compiled samurai"); state.mateCache.samuraiBuild = true; IniSet(&state.cache, S("samurai-build"), S("true")); } #endif err = IniWrite(mateCachePath, &state.cache); Assert(err == SUCCESS, "MateReadCache: Failed writing cache, err: %d", err); } void StartBuild() { LogInit(); if (!state.initConfig) { setDefaultState(); } state.initConfig = true; state.startTime = TimeNow(); Mkdir(state.buildDirectory); readCache(); reBuild(); } static bool needRebuild() { File stats = {0}; errno_t err = FileStats(state.mateSource, &stats); Assert(err == SUCCESS, "Aborting rebuild: Could not read fileStats for %s, error: %d", state.mateSource.data, err); if (stats.modifyTime <= state.mateCache.lastBuild) { return false; } String mateCachePath = F(state.arena, "%s/mate-cache.ini", state.buildDirectory.data); String modifyTime = F(state.arena, "%ld", stats.modifyTime); IniSet(&state.cache, S("modify-time"), modifyTime); err = IniWrite(mateCachePath, &state.cache); Assert(err == SUCCESS, "Aborting rebuild: Could not write cache for path %s, error: %d", mateCachePath.data, err); return true; } void reBuild() { if (state.mateCache.firstBuild || !needRebuild()) { return; } String mateExeNew = NormalizeExePath(state.arena, F(state.arena, "%s/mate-new", state.buildDirectory.data)); String mateExeOld = NormalizeExePath(state.arena, F(state.arena, "%s/mate-old", state.buildDirectory.data)); String mateExe = NormalizeExePath(state.arena, state.mateExe); String compileCommand; if (isMSVC()) { compileCommand = F(state.arena, "cl.exe \"%s\" /Fe:\"%s\"", state.mateSource.data, mateExeNew.data); } else { compileCommand = F(state.arena, "%s \"%s\" -o \"%s\"", state.compiler.data, state.mateSource.data, mateExeNew.data); } LogWarn("%s changed rebuilding...", state.mateSource.data); errno_t rebuildErr = RunCommand(compileCommand); Assert(rebuildErr == SUCCESS, "MateRebuild: failed command %s, err: %d", compileCommand.data, rebuildErr); errno_t renameErr = FileRename(mateExe, mateExeOld); Assert(renameErr == SUCCESS, "MateRebuild: failed renaming original executable failed, err: %d", renameErr); renameErr = FileRename(mateExeNew, mateExe); Assert(renameErr == SUCCESS, "MateRebuild: failed renaming new executable into old: %d", renameErr); LogInfo("Rebuild finished, running %s", mateExe.data); errno_t err = RunCommand(mateExe); exit(err); } void defaultStaticLib() { staticLib.output = S(""); staticLib.flags = S(""); staticLib.arFlags = S("rcs"); } static StaticLib parseStaticLibOptions(StaticLibOptions options) { StaticLib result = {0}; result.output = StrNew(state.arena, options.output); Assert(!StrIsNull(result.output), "MateParseStaticLibOptions: failed, StaticLibOptions.output should never be null, please define the output name like this: \n" "\n" "CreateStaticLib((StaticLibOptions) { .output = \"libexample\"});"); result.flags = StrNew(state.arena, options.flags); result.arFlags = StrNew(state.arena, options.arFlags); return result; } String CreateStaticLib(StaticLibOptions staticLibOptions) { Assert(!isMSVC(), "CreateStaticLib: MSVC compiler not yet implemented for static libraries"); Assert(state.initConfig, "CreateStaticLib: before creating a static library you must use StartBuild(), like this: \n" "\n" "StartBuild()\n" "{\n" " // ...\n" "}\n" "EndBuild()"); defaultStaticLib(); StaticLib options = parseStaticLibOptions(staticLibOptions); String staticLibOutput = NormalizeStaticLibPath(state.arena, options.output); staticLib.output = NormalizePath(state.arena, staticLibOutput); String flagsStr = options.flags; if (flagsStr.data == NULL) { flagsStr = S(""); } if (staticLibOptions.warnings != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (staticLibOptions.warnings) { case FLAG_WARNINGS_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Wall"); break; case FLAG_WARNINGS: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Wall -Wextra"); break; case FLAG_WARNINGS_VERBOSE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Wall -Wextra -Wpedantic"); break; } #elif defined(COMPILER_MSVC) switch (staticLibOptions.warnings) { case FLAG_WARNINGS_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/W3"); break; case FLAG_WARNINGS: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/W4"); break; case FLAG_WARNINGS_VERBOSE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Wall"); break; } #endif } if (staticLibOptions.debug != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (staticLibOptions.debug) { case FLAG_DEBUG_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-g1"); break; case FLAG_DEBUG_MEDIUM: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-g2"); break; case FLAG_DEBUG: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-g3"); break; } #elif defined(COMPILER_MSVC) switch (staticLibOptions.debug) { case FLAG_DEBUG_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Zi"); break; case FLAG_DEBUG_MEDIUM: case FLAG_DEBUG: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/ZI"); break; } #endif } if (staticLibOptions.optimization != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (staticLibOptions.optimization) { case FLAG_OPTIMIZATION_NONE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O0"); break; case FLAG_OPTIMIZATION_BASIC: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O1"); break; case FLAG_OPTIMIZATION: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O2"); break; case FLAG_OPTIMIZATION_SIZE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Os"); break; case FLAG_OPTIMIZATION_AGGRESSIVE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O3"); break; } #elif defined(COMPILER_MSVC) switch (staticLibOptions.optimization) { case FLAG_OPTIMIZATION_NONE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Od"); break; case FLAG_OPTIMIZATION_BASIC: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/O1"); break; case FLAG_OPTIMIZATION: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/O2"); break; case FLAG_OPTIMIZATION_SIZE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/O1"); break; case FLAG_OPTIMIZATION_AGGRESSIVE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Ox"); break; } #endif } if (staticLibOptions.std != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (staticLibOptions.std) { case FLAG_STD_C99: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c99"); break; case FLAG_STD_C11: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c11"); break; case FLAG_STD_C17: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c17"); break; case FLAG_STD_C23: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c2x"); break; case FLAG_STD_C2X: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c2x"); break; } #elif defined(COMPILER_MSVC) switch (staticLibOptions.std) { case FLAG_STD_C99: case FLAG_STD_C11: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/std:c11"); break; case FLAG_STD_C17: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/std:c17"); break; case FLAG_STD_C23: case FLAG_STD_C2X: // NOTE: MSVC doesn't have C23 yet flagsStr = F(state.arena, "%s %s", flagsStr.data, "/std:clatest"); break; } #endif } if (!StrIsNull(flagsStr)) { staticLib.flags = flagsStr; } if (!StrIsNull(options.arFlags)) { staticLib.arFlags = options.arFlags; } String optionIncludes = s(staticLibOptions.includes); if (!StrIsNull(optionIncludes)) { includes = optionIncludes; } String optionLibs = s(staticLibOptions.libs); if (!StrIsNull(optionLibs)) { includes = optionLibs; } staticLib.ninjaBuildPath = F(state.arena, "%s/static-%s.ninja", state.buildDirectory.data, NormalizeExtension(state.arena, staticLib.output).data); return staticLib.ninjaBuildPath; } void defaultExecutable() { String executableOutput = NormalizeExePath(state.arena, S("main")); executable.output = NormalizePath(state.arena, executableOutput); executable.linkerFlags = S(""); executable.flags = S(""); } static Executable parseExecutableOptions(ExecutableOptions options) { Executable result; result.output = StrNew(state.arena, options.output); result.flags = StrNew(state.arena, options.flags); result.linkerFlags = StrNew(state.arena, options.linkerFlags); return result; } String CreateExecutable(ExecutableOptions executableOptions) { Assert(state.initConfig, "CreateExecutable: before creating an executable you must use StartBuild(), like this: \n" "\n" "StartBuild()\n" "{\n" " // ...\n" "}\n" "EndBuild()"); defaultExecutable(); Executable options = parseExecutableOptions(executableOptions); if (!StrIsNull(options.output)) { String executableOutput = NormalizeExePath(state.arena, options.output); executable.output = NormalizePath(state.arena, executableOutput); } String flagsStr = options.flags; if (flagsStr.data == NULL) { flagsStr = S(""); } if (executableOptions.warnings != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (executableOptions.warnings) { case FLAG_WARNINGS_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Wall"); break; case FLAG_WARNINGS: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Wall -Wextra"); break; case FLAG_WARNINGS_VERBOSE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Wall -Wextra -Wpedantic"); break; } #elif defined(COMPILER_MSVC) switch (executableOptions.warnings) { case FLAG_WARNINGS_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/W3"); break; case FLAG_WARNINGS: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/W4"); break; case FLAG_WARNINGS_VERBOSE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Wall"); break; } #endif } if (executableOptions.debug != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (executableOptions.debug) { case FLAG_DEBUG_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-g1"); break; case FLAG_DEBUG_MEDIUM: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-g2"); break; case FLAG_DEBUG: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-g3"); break; } #elif defined(COMPILER_MSVC) switch (executableOptions.debug) { case FLAG_DEBUG_MINIMAL: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Zi"); break; case FLAG_DEBUG_MEDIUM: case FLAG_DEBUG: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/ZI"); break; } #endif } if (executableOptions.optimization != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (executableOptions.optimization) { case FLAG_OPTIMIZATION_NONE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O0"); break; case FLAG_OPTIMIZATION_BASIC: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O1"); break; case FLAG_OPTIMIZATION: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O2"); break; case FLAG_OPTIMIZATION_SIZE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-Os"); break; case FLAG_OPTIMIZATION_AGGRESSIVE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-O3"); break; } #elif defined(COMPILER_MSVC) switch (executableOptions.optimization) { case FLAG_OPTIMIZATION_NONE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Od"); break; case FLAG_OPTIMIZATION_BASIC: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/O1"); break; case FLAG_OPTIMIZATION: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/O2"); break; case FLAG_OPTIMIZATION_SIZE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/O1"); break; case FLAG_OPTIMIZATION_AGGRESSIVE: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/Ox"); break; } #endif } if (executableOptions.std != 0) { #if defined(COMPILER_GCC) || defined(COMPILER_CLANG) switch (executableOptions.std) { case FLAG_STD_C99: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c99"); break; case FLAG_STD_C11: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c11"); break; case FLAG_STD_C17: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c17"); break; case FLAG_STD_C23: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c2x"); break; case FLAG_STD_C2X: flagsStr = F(state.arena, "%s %s", flagsStr.data, "-std=c2x"); break; } #elif defined(COMPILER_MSVC) switch (executableOptions.std) { case FLAG_STD_C99: case FLAG_STD_C11: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/std:c11"); break; case FLAG_STD_C17: flagsStr = F(state.arena, "%s %s", flagsStr.data, "/std:c17"); break; case FLAG_STD_C23: case FLAG_STD_C2X: // NOTE: MSVC doesn't have C23 yet flagsStr = F(state.arena, "%s %s", flagsStr.data, "/std:clatest"); break; } #endif } if (!StrIsNull(flagsStr)) { executable.flags = flagsStr; } if (!StrIsNull(options.linkerFlags)) { executable.linkerFlags = options.linkerFlags; } String optionIncludes = s(executableOptions.includes); if (!StrIsNull(optionIncludes)) { includes = optionIncludes; } String optionLibs = s(executableOptions.libs); if (!StrIsNull(optionLibs)) { includes = optionLibs; } executable.ninjaBuildPath = F(state.arena, "%s/exe-%s.ninja", state.buildDirectory.data, NormalizeExtension(state.arena, executable.output).data); return executable.ninjaBuildPath; } errno_t CreateCompileCommands(String ninjaBuildPath) { FILE *ninjaPipe; FILE *outputFile; char buffer[4096]; size_t bytes_read; String compileCommandsPath = NormalizePath(state.arena, F(state.arena, "%s/compile_commands.json", state.buildDirectory.data)); errno_t err = fopen_s(&outputFile, compileCommandsPath.data, "w"); if (err != SUCCESS || outputFile == NULL) { LogError("CreateCompileCommands: Failed to open file %s, err: %d", compileCommandsPath.data, err); return COMPILE_COMMANDS_FAILED_OPEN_FILE; } String compdbCommand; if (state.mateCache.samuraiBuild == true) { String samuraiOutputPath = F(state.arena, "%s/samurai", state.buildDirectory.data); compdbCommand = NormalizePath(state.arena, F(state.arena, "%s -f %s -t compdb", samuraiOutputPath.data, ninjaBuildPath.data)); } if (state.mateCache.samuraiBuild == false) { compdbCommand = NormalizePath(state.arena, F(state.arena, "ninja -f %s -t compdb", ninjaBuildPath.data)); } ninjaPipe = popen(compdbCommand.data, "r"); if (ninjaPipe == NULL) { LogError("CreateCompileCommands: Failed to run compdb command, %s", compdbCommand.data); fclose(outputFile); return COMPILE_COMMANDS_FAILED_COMPDB; } while ((bytes_read = fread(buffer, 1, sizeof(buffer), ninjaPipe)) > 0) { fwrite(buffer, 1, bytes_read, outputFile); } fclose(outputFile); errno_t status = pclose(ninjaPipe); if (status != SUCCESS) { LogError("CreateCompileCommands: Command failed with status %d\n", status); return COMPILE_COMMANDS_FAILED_COMPDB; } LogSuccess("Successfully created %s", NormalizePathEnd(state.arena, compileCommandsPath).data); return SUCCESS; } static bool globMatch(String pattern, String text) { if (pattern.length == 1 && pattern.data[0] == '*') { return true; } size_t p = 0; size_t t = 0; size_t starP = -1; size_t starT = -1; while (t < text.length) { if (p < pattern.length && pattern.data[p] == text.data[t]) { p++; t++; } else if (p < pattern.length && pattern.data[p] == '*') { starP = p; starT = t; p++; } else if (starP != (size_t)-1) { p = starP + 1; t = ++starT; } else { return false; } } while (p < pattern.length && pattern.data[p] == '*') { p++; } return p == pattern.length; } static void addFile(String source) { bool isGlob = false; for (size_t i = 0; i < source.length; i++) { if (source.data[i] == '*') { isGlob = true; break; } } Assert(source.length > 2 && source.data[0] == '.' && source.data[1] == '/', "AddFile: failed to a add file, to add a file it has to " "contain the relative path, for example AddFile(\"./main.c\")"); Assert(source.data[source.length - 1] != '/', "AddFile: failed to add a file, you can't add a slash at the end of a path.\n" "For example, valid: AddFile(\"./main.c\"), invalid: AddFile(\"./main.c/\")"); if (!isGlob) { VecPush(sources, source); return; } String directory = {0}; i32 lastSlash = -1; for (size_t i = 0; i < source.length; i++) { if (source.data[i] == '/') { lastSlash = i; } } directory = StrSlice(state.arena, source, 0, lastSlash); String pattern = StrSlice(state.arena, source, lastSlash + 1, source.length); StringVector files = ListDir(state.arena, directory); for (size_t i = 0; i < files.length; i++) { String file = VecAt(files, i); if (globMatch(pattern, file)) { String finalSource = F(state.arena, "%s/%s", directory.data, file.data); VecPush(sources, finalSource); } } } static bool removeFile(String source) { Assert(sources.length > 0, "RemoveFile: Before removing a file you must first add a file, use: AddFile()"); for (size_t i = 0; i < sources.length; i++) { String *currValue = VecAtPtr(sources, i); if (StrEq(source, *currValue)) { currValue->data = NULL; currValue->length = 0; return true; } } return false; } // TODO: Create something like NormalizeOutput static StringVector outputTransformer(StringVector vector) { StringVector result = {0}; if (isMSVC()) { for (size_t i = 0; i < vector.length; i++) { String currentExecutable = VecAt(vector, i); if (StrIsNull(currentExecutable)) { VecPush(result, S("")); continue; } size_t lastCharIndex = 0; for (size_t j = currentExecutable.length - 1; j > 0; j--) { char currentChar = currentExecutable.data[j]; if (currentChar == '/') { lastCharIndex = j; break; } } Assert(lastCharIndex != 0, "MateOutputTransformer: failed to transform %s, to an object file", currentExecutable.data); char *filenameStart = currentExecutable.data + lastCharIndex + 1; size_t filenameLength = currentExecutable.length - (lastCharIndex + 1); String objOutput = StrNewSize(state.arena, filenameStart, filenameLength + 2); objOutput.data[objOutput.length - 3] = 'o'; objOutput.data[objOutput.length - 2] = 'b'; objOutput.data[objOutput.length - 1] = 'j'; VecPush(result, objOutput); } return result; } for (size_t i = 0; i < vector.length; i++) { String currentExecutable = VecAt(vector, i); if (StrIsNull(currentExecutable)) { VecPush(result, S("")); continue; } size_t lastCharIndex = 0; for (size_t j = currentExecutable.length - 1; j > 0; j--) { char currentChar = currentExecutable.data[j]; if (currentChar == '/') { lastCharIndex = j; break; } } Assert(lastCharIndex != 0, "MateOutputTransformer: failed to transform %s, to an object file", currentExecutable.data); String output = StrSlice(state.arena, currentExecutable, lastCharIndex + 1, currentExecutable.length); output.data[output.length - 1] = 'o'; VecPush(result, output); } return result; } void ResetExecutable() { executable = (Executable){0}; VecFree(sources); } String InstallExecutable() { Assert(sources.length != 0, "InstallExecutable: Executable has zero sources, add at least one with AddFile(\"./main.c\")"); Assert(!StrIsNull(executable.output), "InstallExecutable: Before installing executable you must first CreateExecutable()"); StringBuilder builder = StringBuilderReserve(state.arena, 1024); // Compiler StringBuilderAppend(state.arena, &builder, &S("cc = ")); StringBuilderAppend(state.arena, &builder, &state.compiler); StringBuilderAppend(state.arena, &builder, &S("\n")); // Linker flags if (executable.linkerFlags.length > 0) { StringBuilderAppend(state.arena, &builder, &S("linker_flags = ")); StringBuilderAppend(state.arena, &builder, &executable.linkerFlags); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Compiler flags if (executable.flags.length > 0) { StringBuilderAppend(state.arena, &builder, &S("flags = ")); StringBuilderAppend(state.arena, &builder, &executable.flags); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Include paths if (includes.length > 0) { StringBuilderAppend(state.arena, &builder, &S("includes = ")); StringBuilderAppend(state.arena, &builder, &includes); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Libraries if (libs.length > 0) { StringBuilderAppend(state.arena, &builder, &S("libs = ")); StringBuilderAppend(state.arena, &builder, &libs); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Current working directory String cwd_path = ConvertNinjaPath(s(GetCwd())); StringBuilderAppend(state.arena, &builder, &S("cwd = ")); StringBuilderAppend(state.arena, &builder, &cwd_path); StringBuilderAppend(state.arena, &builder, &S("\n")); // Build directory String build_dir_path = ConvertNinjaPath(state.buildDirectory); StringBuilderAppend(state.arena, &builder, &S("builddir = ")); StringBuilderAppend(state.arena, &builder, &build_dir_path); StringBuilderAppend(state.arena, &builder, &S("\n")); // Target StringBuilderAppend(state.arena, &builder, &S("target = $builddir/")); StringBuilderAppend(state.arena, &builder, &executable.output); StringBuilderAppend(state.arena, &builder, &S("\n\n")); // Link command StringBuilderAppend(state.arena, &builder, &S("rule link\n command = $cc")); if (executable.flags.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $flags")); } if (executable.linkerFlags.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $linker_flags")); } if (isMSVC()) { StringBuilderAppend(state.arena, &builder, &S(" /Fe:$out $in")); } else { StringBuilderAppend(state.arena, &builder, &S(" -o $out $in")); } if (libs.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $libs")); } StringBuilderAppend(state.arena, &builder, &S("\n\n")); // Compile command StringBuilderAppend(state.arena, &builder, &S("rule compile\n command = $cc")); if (executable.flags.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $flags")); } if (includes.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $includes")); } if (isMSVC()) { StringBuilderAppend(state.arena, &builder, &S(" /c $in /Fo:$out\n\n")); } else { StringBuilderAppend(state.arena, &builder, &S(" -c $in -o $out\n\n")); } // Build individual source files StringVector outputFiles = outputTransformer(sources); StringBuilder outputBuilder = StringBuilderCreate(state.arena); for (size_t i = 0; i < sources.length; i++) { String currSource = VecAt(sources, i); if (StrIsNull(currSource)) continue; String outputFile = VecAt(outputFiles, i); String sourceFile = NormalizePathStart(state.arena, currSource); // Source build command StringBuilderAppend(state.arena, &builder, &S("build $builddir/")); StringBuilderAppend(state.arena, &builder, &outputFile); StringBuilderAppend(state.arena, &builder, &S(": compile $cwd/")); StringBuilderAppend(state.arena, &builder, &sourceFile); StringBuilderAppend(state.arena, &builder, &S("\n")); // Add to output files list if (outputBuilder.buffer.length == 0) { StringBuilderAppend(state.arena, &outputBuilder, &S("$builddir/")); StringBuilderAppend(state.arena, &outputBuilder, &outputFile); } else { StringBuilderAppend(state.arena, &outputBuilder, &S(" $builddir/")); StringBuilderAppend(state.arena, &outputBuilder, &outputFile); } } // Build target StringBuilderAppend(state.arena, &builder, &S("build $target: link ")); StringBuilderAppend(state.arena, &builder, &outputBuilder.buffer); StringBuilderAppend(state.arena, &builder, &S("\n\n")); // Default target StringBuilderAppend(state.arena, &builder, &S("default $target\n")); String ninjaBuildPath = executable.ninjaBuildPath; errno_t errWrite = FileWrite(ninjaBuildPath, builder.buffer); Assert(errWrite == SUCCESS, "InstallExecutable: failed to write build.ninja for %s, err: %d", ninjaBuildPath.data, errWrite); String buildCommand; if (state.mateCache.samuraiBuild) { String samuraiOutputPath = F(state.arena, "%s/samurai", state.buildDirectory.data); buildCommand = F(state.arena, "%s -f %s", samuraiOutputPath.data, ninjaBuildPath.data); } else { buildCommand = F(state.arena, "ninja -f %s", ninjaBuildPath.data); } i64 err = RunCommand(buildCommand); Assert(err == SUCCESS, "InstallExecutable: Ninja file compilation failed with code: %lu", err); LogSuccess("Ninja file compilation done for %s", NormalizePathEnd(state.arena, ninjaBuildPath).data); state.totalTime = TimeNow() - state.startTime; #if defined(PLATFORM_WIN) String path = F(state.arena, "%s\\%s", state.buildDirectory.data, executable.output.data); #else String path = F(state.arena, "%s/%s", state.buildDirectory.data, executable.output.data); #endif ResetExecutable(); return path; } void ResetStaticLib() { staticLib = (StaticLib){0}; VecFree(sources); } String InstallStaticLib() { Assert(sources.length != 0, "InstallStaticLib: Static Library has zero sources, add at least one with AddFile(\"./main.c\")"); Assert(!StrIsNull(staticLib.output), "InstallStaticLib: Before installing static library you must first CreateStaticLib()"); StringBuilder builder = StringBuilderReserve(state.arena, 1024); // Compiler StringBuilderAppend(state.arena, &builder, &S("cc = ")); StringBuilderAppend(state.arena, &builder, &state.compiler); StringBuilderAppend(state.arena, &builder, &S("\n")); // Archive StringBuilderAppend(state.arena, &builder, &S("ar = ar\n")); // TODO: Add different ar for MSVC // Compiler flags if (staticLib.flags.length > 0) { StringBuilderAppend(state.arena, &builder, &S("flags = ")); StringBuilderAppend(state.arena, &builder, &staticLib.flags); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Archive flags if (staticLib.arFlags.length > 0) { StringBuilderAppend(state.arena, &builder, &S("ar_flags = ")); StringBuilderAppend(state.arena, &builder, &staticLib.arFlags); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Include paths if (includes.length > 0) { StringBuilderAppend(state.arena, &builder, &S("includes = ")); StringBuilderAppend(state.arena, &builder, &includes); StringBuilderAppend(state.arena, &builder, &S("\n")); } // Current working directory String cwd_path = ConvertNinjaPath(s(GetCwd())); StringBuilderAppend(state.arena, &builder, &S("cwd = ")); StringBuilderAppend(state.arena, &builder, &cwd_path); StringBuilderAppend(state.arena, &builder, &S("\n")); // Build directory String build_dir_path = ConvertNinjaPath(state.buildDirectory); StringBuilderAppend(state.arena, &builder, &S("builddir = ")); StringBuilderAppend(state.arena, &builder, &build_dir_path); StringBuilderAppend(state.arena, &builder, &S("\n")); // Target StringBuilderAppend(state.arena, &builder, &S("target = $builddir/")); StringBuilderAppend(state.arena, &builder, &staticLib.output); StringBuilderAppend(state.arena, &builder, &S("\n\n")); // Archive command StringBuilderAppend(state.arena, &builder, &S("rule archive\n command = $ar $ar_flags $out $in\n\n")); // Compile command StringBuilderAppend(state.arena, &builder, &S("rule compile\n command = $cc")); if (staticLib.flags.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $flags")); } if (includes.length > 0) { StringBuilderAppend(state.arena, &builder, &S(" $includes")); } StringBuilderAppend(state.arena, &builder, &S(" -c $in -o $out\n\n")); // Build individual source files StringVector outputFiles = outputTransformer(sources); StringBuilder outputBuilder = StringBuilderCreate(state.arena); for (size_t i = 0; i < sources.length; i++) { String currSource = VecAt(sources, i); if (StrIsNull(currSource)) continue; String outputFile = VecAt(outputFiles, i); String sourceFile = NormalizePathStart(state.arena, currSource); // Source build command StringBuilderAppend(state.arena, &builder, &S("build $builddir/")); StringBuilderAppend(state.arena, &builder, &outputFile); StringBuilderAppend(state.arena, &builder, &S(": compile $cwd/")); StringBuilderAppend(state.arena, &builder, &sourceFile); StringBuilderAppend(state.arena, &builder, &S("\n")); // Add to output files list if (outputBuilder.buffer.length == 0) { StringBuilderAppend(state.arena, &outputBuilder, &S("$builddir/")); StringBuilderAppend(state.arena, &outputBuilder, &outputFile); } else { StringBuilderAppend(state.arena, &outputBuilder, &S(" $builddir/")); StringBuilderAppend(state.arena, &outputBuilder, &outputFile); } } // Build target StringBuilderAppend(state.arena, &builder, &S("build $target: archive ")); StringBuilderAppend(state.arena, &builder, &outputBuilder.buffer); StringBuilderAppend(state.arena, &builder, &S("\n\n")); // Default target StringBuilderAppend(state.arena, &builder, &S("default $target\n")); String ninjaBuildPath = staticLib.ninjaBuildPath; errno_t errWrite = FileWrite(ninjaBuildPath, builder.buffer); Assert(errWrite == SUCCESS, "InstallStaticLib: failed to write build-static-library.ninja for %s, err: %d", ninjaBuildPath.data, errWrite); String buildCommand; if (state.mateCache.samuraiBuild) { String samuraiOutputPath = F(state.arena, "%s/samurai", state.buildDirectory.data); buildCommand = F(state.arena, "%s -f %s", samuraiOutputPath.data, ninjaBuildPath.data); } else { buildCommand = F(state.arena, "ninja -f %s", ninjaBuildPath.data); } i64 err = RunCommand(buildCommand); Assert(err == SUCCESS, "InstallStaticLib: Ninja file compilation failed with code: %lu", err); LogSuccess("Ninja file compilation done for %s", NormalizePathEnd(state.arena, ninjaBuildPath).data); state.totalTime = TimeNow() - state.startTime; #if defined(PLATFORM_WIN) String path = F(state.arena, "%s\\%s", state.buildDirectory.data, staticLib.output.data); #else String path = F(state.arena, "%s/%s", state.buildDirectory.data, staticLib.output.data); #endif ResetStaticLib(); return path; } errno_t RunCommand(String command) { #if defined(PLATFORM_LINUX) return system(command.data) >> 8; #else return system(command.data); #endif } static void addLibraryPaths(StringVector *vector) { StringBuilder builder = StringBuilderCreate(state.arena); if (isMSVC() && libs.length == 0) { StringBuilderAppend(state.arena, &builder, &S("/link")); } if (libs.length) { StringBuilderAppend(state.arena, &builder, &libs); } if (isMSVC()) { // MSVC format: /LIBPATH:"path" for (size_t i = 0; i < vector->length; i++) { String currLib = VecAt((*vector), i); String buffer = F(state.arena, " /LIBPATH:\"%s\"", currLib.data); StringBuilderAppend(state.arena, &builder, &buffer); } } else { // GCC/Clang format: -L"path" for (size_t i = 0; i < vector->length; i++) { String currLib = VecAt((*vector), i); if (i == 0 && builder.buffer.length == 0) { String buffer = F(state.arena, "-L\"%s\"", currLib.data); StringBuilderAppend(state.arena, &builder, &buffer); continue; } String buffer = F(state.arena, " -L\"%s\"", currLib.data); StringBuilderAppend(state.arena, &builder, &buffer); } } libs = builder.buffer; } static void linkSystemLibraries(StringVector *vector) { StringBuilder builder = StringBuilderCreate(state.arena); if (isMSVC() && libs.length == 0) { StringBuilderAppend(state.arena, &builder, &S("/link")); } if (libs.length) { StringBuilderAppend(state.arena, &builder, &libs); } if (isMSVC()) { // MSVC format: library.lib for (size_t i = 0; i < vector->length; i++) { String currLib = VecAt((*vector), i); String buffer = F(state.arena, " %s.lib", currLib.data); StringBuilderAppend(state.arena, &builder, &buffer); } } else { // GCC/Clang format: -llib for (size_t i = 0; i < vector->length; i++) { String currLib = VecAt((*vector), i); if (i == 0 && builder.buffer.length == 0) { String buffer = F(state.arena, "-l%s", currLib.data); StringBuilderAppend(state.arena, &builder, &buffer); continue; } String buffer = F(state.arena, " -l%s", currLib.data); StringBuilderAppend(state.arena, &builder, &buffer); } } libs = builder.buffer; } static void addIncludePaths(StringVector *vector) { StringBuilder builder = StringBuilderCreate(state.arena); if (includes.length) { StringBuilderAppend(state.arena, &builder, &includes); StringBuilderAppend(state.arena, &builder, &S(" ")); } if (isMSVC()) { // MSVC format: /I"path" for (size_t i = 0; i < vector->length; i++) { String currInclude = VecAt((*vector), i); if (i == 0 && builder.buffer.length == 0) { String buffer = F(state.arena, "/I\"%s\"", currInclude.data); StringBuilderAppend(state.arena, &builder, &buffer); continue; } String buffer = F(state.arena, " /I\"%s\"", currInclude.data); StringBuilderAppend(state.arena, &builder, &buffer); } } else { // GCC/Clang format: -I"path" for (size_t i = 0; i < vector->length; i++) { String currInclude = VecAt((*vector), i); if (i == 0 && builder.buffer.length == 0) { String buffer = F(state.arena, "-I\"%s\"", currInclude.data); StringBuilderAppend(state.arena, &builder, &buffer); continue; } String buffer = F(state.arena, " -I\"%s\"", currInclude.data); StringBuilderAppend(state.arena, &builder, &buffer); } } includes = builder.buffer; } void EndBuild() { LogInfo("Build took: %ldms", state.totalTime); ArenaFree(state.arena); } #endif // --- MATE.H END ---