raylib/mate.h
TomasBorquez 92ca03d246 Add example
2025-05-16 02:52:17 -03:00

6460 lines
223 KiB
C

/* 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 <windows.h>
#else
# define _POSIX_C_SOURCE 200809L
# define _GNU_SOURCE
# include <dirent.h>
# include <errno.h>
# include <fcntl.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
#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 <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* --- 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 <errno.h>\n"\
"#include <fcntl.h>\n"\
"#include <inttypes.h>\n"\
"#include <stdbool.h>\n"\
"#include <stdint.h>\n"\
"#include <ctype.h>\n"\
"#include <stdio.h>\n"\
"#include <stdlib.h>\n"\
"#include <assert.h>\n"\
"#include <stdarg.h>\n"\
"#include <string.h>\n"\
"#include <sys/stat.h>\n"\
"#include <poll.h>\n"\
"#include <signal.h>\n"\
"#include <spawn.h>\n"\
"#include <sys/wait.h>\n"\
"#include <time.h>\n"\
"#include <unistd.h>\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(&copy.data[2], &copy.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 ---