diff --git a/mate.c b/mate.c new file mode 100644 index 000000000..bea29a7db --- /dev/null +++ b/mate.c @@ -0,0 +1,33 @@ +#define MATE_IMPLEMENTATION +#include "mate.h" + +i32 main() { + StartBuild(); + { + // Asume it will be GLFW and X11, can later get options from `String arg = Argument("target")` + char *cflags = "-fno-sanitize=undefined -D_GNU_SOURCE -DGL_SILENCE_DEPRECATION=199309L -DPLATFORM_DESKTOP -DPLATFORM_DESKTOP_GLFW -D_GLFW_X11"; + CreateStaticLib((StaticLibOptions){.output = "libraylib", .std = FLAG_STD_C99, .warnings = FLAG_WARNINGS, .flags = cflags}); + + // Add the core files + AddFile("./src/rcore.c"); + AddFile("./src/utils.c"); + AddFile("./src/rglfw.c"); + + // Add optional modules (these can be made configurable with command line args) + AddFile("./src/rshapes.c"); + AddFile("./src/rtextures.c"); + AddFile("./src/rtext.c"); + AddFile("./src/rmodels.c"); + AddFile("./src/raudio.c"); + + // Add include directories + AddIncludePaths("./src/platforms"); + AddIncludePaths("./src/external/glfw/include"); + + // Linux-specific libraries + LinkSystemLibraries("GL", "rt", "dl", "m", "X11", "Xcursor", "Xext", "Xfixes", "Xi", "Xinerama", "Xrandr", "Xrender"); + + InstallStaticLib(); + } + EndBuild(); +} diff --git a/mate.h b/mate.h new file mode 100644 index 000000000..a1fa554aa --- /dev/null +++ b/mate.h @@ -0,0 +1,6460 @@ +/* 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 ---