summaryrefslogtreecommitdiffstats
path: root/src/b.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/b.h')
-rw-r--r--src/b.h860
1 files changed, 860 insertions, 0 deletions
diff --git a/src/b.h b/src/b.h
new file mode 100644
index 0000000..cdf6772
--- /dev/null
+++ b/src/b.h
@@ -0,0 +1,860 @@
+// This is an attempt on build library for building C with C akin to nob.h from
+// tsoding which is itself based on https://github.com/tsoding/nobuild
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef B_H_
+#define B_H_
+
+#define B_ASSERT assert
+#define B_REALLOC realloc
+#define B_FREE free
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#define B_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0]))
+#define B_ARRAY_GET(array, index) \
+ (B_ASSERT(index >= 0), B_ASSERT(index < B_ARRAY_LEN(array)), array[index])
+
+typedef enum {
+ B_INFO,
+ B_WARNING,
+ B_ERROR,
+} B_Log_Level;
+
+void b_log(B_Log_Level level, const char *fmt, ...);
+
+// It is an equivalent of shift command from bash. It basically pops a command line
+// argument from the beginning.
+char *b_shift_args(int *argc, char ***argv);
+
+typedef struct {
+ const char **items;
+ size_t count;
+ size_t capacity;
+} B_File_Paths;
+
+typedef enum {
+ B_FILE_REGULAR = 0,
+ B_FILE_DIRECTORY,
+ B_FILE_SYMLINK,
+ B_FILE_OTHER,
+} B_File_Type;
+
+bool b_mkdir_if_not_exists(const char *path);
+bool b_copy_file(const char *src_path, const char *dst_path);
+bool b_copy_directory_recursively(const char *src_path, const char *dst_path);
+bool b_read_entire_dir(const char *parent, B_File_Paths *children);
+bool b_write_entire_file(const char *path, const void *data, size_t size);
+B_File_Type b_get_file_type(const char *path);
+
+#define b_return_defer(value) do { result = (value); goto defer; } while(0)
+
+// Initial capacity of a dynamic array
+#define B_DA_INIT_CAP 256
+
+// Append an item to a dynamic array
+#define b_da_append(da, item) \
+ do { \
+ if ((da)->count >= (da)->capacity) { \
+ (da)->capacity = (da)->capacity == 0 ? B_DA_INIT_CAP : (da)->capacity*2; \
+ (da)->items = B_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \
+ B_ASSERT((da)->items != NULL && "Buy more RAM lol"); \
+ } \
+ \
+ (da)->items[(da)->count++] = (item); \
+ } while (0)
+
+#define b_da_free(da) B_FREE((da).items)
+
+// Append several items to a dynamic array
+#define b_da_append_many(da, new_items, new_items_count) \
+ do { \
+ if ((da)->count + (new_items_count) > (da)->capacity) { \
+ if ((da)->capacity == 0) { \
+ (da)->capacity = B_DA_INIT_CAP; \
+ } \
+ while ((da)->count + (new_items_count) > (da)->capacity) { \
+ (da)->capacity *= 2; \
+ } \
+ (da)->items = B_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \
+ B_ASSERT((da)->items != NULL && "Buy more RAM lol"); \
+ } \
+ memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \
+ (da)->count += (new_items_count); \
+ } while (0)
+
+typedef struct {
+ char *items;
+ size_t count;
+ size_t capacity;
+} B_String_Builder;
+
+bool b_read_entire_file(const char *path, B_String_Builder *sb);
+
+// Append a sized buffer to a string builder
+#define b_sb_append_buf(sb, buf, size) b_da_append_many(sb, buf, size)
+
+// Append a NULL-terminated string to a string builder
+#define b_sb_append_cstr(sb, cstr) \
+ do { \
+ const char *s = (cstr); \
+ size_t n = strlen(s); \
+ b_da_append_many(sb, s, n); \
+ } while (0)
+
+// Append a single NULL character at the end of a string builder. So then you can
+// use it a NULL-terminated C string
+#define b_sb_append_null(sb) b_da_append_many(sb, "", 1)
+
+// Free the memory allocated by a string builder
+#define b_sb_free(sb) B_FREE((sb).items)
+
+// Process handle
+typedef int B_Proc;
+#define B_INVALID_PROC (-1)
+
+typedef struct {
+ B_Proc *items;
+ size_t count;
+ size_t capacity;
+} B_Procs;
+
+bool b_procs_wait(B_Procs procs);
+
+// Wait until the process has finished
+bool b_proc_wait(B_Proc proc);
+
+// A command - the main workhorse of B. B is all about building commands an running them
+typedef struct {
+ const char **items;
+ size_t count;
+ size_t capacity;
+} B_Cmd;
+
+// Render a string representation of a command into a string builder. Keep in mind the the
+// string builder is not NULL-terminated by default. Use b_sb_append_null if you plan to
+// use it as a C string.
+void b_cmd_render(B_Cmd cmd, B_String_Builder *render);
+
+#define b_cmd_append(cmd, ...) \
+ b_da_append_many(cmd, ((const char*[]){__VA_ARGS__}), (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*)))
+
+// Free all the memory allocated by command arguments
+#define b_cmd_free(cmd) B_FREE(cmd.items)
+
+// Run command asynchronously
+B_Proc b_cmd_run_async(B_Cmd cmd);
+
+// Run command synchronously
+bool b_cmd_run_sync(B_Cmd cmd);
+
+#ifndef B_TEMP_CAPACITY
+#define B_TEMP_CAPACITY (8*1024*1024)
+#endif // B_TEMP_CAPACITY
+char *b_temp_strdup(const char *cstr);
+void *b_temp_alloc(size_t size);
+char *b_temp_sprintf(const char *format, ...);
+void b_temp_reset(void);
+size_t b_temp_save(void);
+void b_temp_rewind(size_t checkpoint);
+
+int is_path1_modified_after_path2(const char *path1, const char *path2);
+bool b_rename(const char *old_path, const char *new_path);
+int b_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count);
+int b_needs_rebuild1(const char *output_path, const char *input_path);
+int b_file_exists(const char *file_path);
+
+// TODO: add MinGW support for Go Rebuild Urself™ Technology
+#ifndef B_REBUILD_URSELF
+# if _WIN32
+# if defined(__GNUC__)
+# define B_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path
+# elif defined(__clang__)
+# define B_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path
+# elif defined(_MSC_VER)
+# define B_REBUILD_URSELF(binary_path, source_path) "cl.exe", b_temp_sprintf("/Fe:%s", (binary_path)), source_path
+# endif
+# else
+# if defined(__clang__)
+# define B_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path
+# else
+# define B_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path
+# endif
+# endif
+#endif
+
+// Go Rebuild Urself™ Technology
+//
+// How to use it:
+// int main(int argc, char** argv) {
+// GO_REBUILD_URSELF(argc, argv);
+// // actual work
+// return 0;
+// }
+//
+// After your added this macro every time you run ./build it will detect
+// that you modified its original source code and will try to rebuild itself
+// before doing any actual work. So you only need to bootstrap your build system
+// once.
+//
+// The modification is detected by comparing the last modified times of the executable
+// and its source code. The same way the make utility usually does it.
+//
+// The rebuilding is done by using the REBUILD_URSELF macro which you can redefine
+// if you need a special way of bootstraping your build system. (which I personally
+// do not recommend since the whole idea of build is to keep the process of bootstrapping
+// as simple as possible and doing all of the actual work inside of the build)
+//
+#define B_GO_REBUILD_URSELF(argc, argv) \
+ do { \
+ const char *source_path = __FILE__; \
+ assert(argc >= 1); \
+ const char *binary_path = argv[0]; \
+ \
+ int rebuild_is_needed = b_needs_rebuild(binary_path, &source_path, 1); \
+ if (rebuild_is_needed < 0) exit(1); \
+ if (rebuild_is_needed) { \
+ B_String_Builder sb = {0}; \
+ b_sb_append_cstr(&sb, binary_path); \
+ b_sb_append_cstr(&sb, ".old"); \
+ b_sb_append_null(&sb); \
+ \
+ if (!b_rename(binary_path, sb.items)) exit(1); \
+ B_Cmd rebuild = {0}; \
+ b_cmd_append(&rebuild, B_REBUILD_URSELF(binary_path, source_path)); \
+ bool rebuild_succeeded = b_cmd_run_sync(rebuild); \
+ b_cmd_free(rebuild); \
+ if (!rebuild_succeeded) { \
+ b_rename(sb.items, binary_path); \
+ exit(1); \
+ } \
+ \
+ B_Cmd cmd = {0}; \
+ b_da_append_many(&cmd, argv, argc); \
+ if (!b_cmd_run_sync(cmd)) exit(1); \
+ exit(0); \
+ } \
+ } while(0)
+// The implementation idea is stolen from https://github.com/zhiayang/nabs
+
+typedef struct {
+ size_t count;
+ const char *data;
+} B_String_View;
+
+const char *b_temp_sv_to_cstr(B_String_View sv);
+
+B_String_View b_sv_chop_by_delim(B_String_View *sv, char delim);
+B_String_View b_sv_trim(B_String_View sv);
+bool b_sv_eq(B_String_View a, B_String_View b);
+B_String_View b_sv_from_cstr(const char *cstr);
+B_String_View b_sv_from_parts(const char *data, size_t count);
+
+// printf macros for String_View
+#ifndef SV_Fmt
+#define SV_Fmt "%.*s"
+#endif // SV_Fmt
+#ifndef SV_Arg
+#define SV_Arg(sv) (int) (sv).count, (sv).data
+#endif // SV_Arg
+// USAGE:
+// String_View name = ...;
+// printf("Name: "SV_Fmt"\n", SV_Arg(name));
+
+#endif // B_H_
+
+#ifdef B_IMPLEMENTATION
+
+static size_t b_temp_size = 0;
+static char b_temp[B_TEMP_CAPACITY] = {0};
+
+bool b_mkdir_if_not_exists(const char *path)
+{
+ int result = mkdir(path, 0755);
+ if (result < 0) {
+ if (errno == EEXIST) {
+ b_log(B_INFO, "directory `%s` already exists", path);
+ return true;
+ }
+ b_log(B_ERROR, "could not create directory `%s`: %s", path, strerror(errno));
+ return false;
+ }
+
+ b_log(B_INFO, "created directory `%s`", path);
+ return true;
+}
+
+bool b_copy_file(const char *src_path, const char *dst_path)
+{
+ b_log(B_INFO, "copying %s -> %s", src_path, dst_path);
+
+ int src_fd = -1;
+ int dst_fd = -1;
+ size_t buf_size = 32*1024;
+ char *buf = B_REALLOC(NULL, buf_size);
+ B_ASSERT(buf != NULL && "Buy more RAM lol!!");
+ bool result = true;
+
+ src_fd = open(src_path, O_RDONLY);
+ if (src_fd < 0) {
+ b_log(B_ERROR, "Could not open file %s: %s", src_path, strerror(errno));
+ b_return_defer(false);
+ }
+
+ struct stat src_stat;
+ if (fstat(src_fd, &src_stat) < 0) {
+ b_log(B_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno));
+ b_return_defer(false);
+ }
+
+ dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode);
+ if (dst_fd < 0) {
+ b_log(B_ERROR, "Could not create file %s: %s", dst_path, strerror(errno));
+ b_return_defer(false);
+ }
+
+ for (;;) {
+ ssize_t n = read(src_fd, buf, buf_size);
+ if (n == 0) break;
+ if (n < 0) {
+ b_log(B_ERROR, "Could not read from file %s: %s", src_path, strerror(errno));
+ b_return_defer(false);
+ }
+ char *buf2 = buf;
+ while (n > 0) {
+ ssize_t m = write(dst_fd, buf2, n);
+ if (m < 0) {
+ b_log(B_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno));
+ b_return_defer(false);
+ }
+ n -= m;
+ buf2 += m;
+ }
+ }
+
+defer:
+ free(buf);
+ close(src_fd);
+ close(dst_fd);
+ return result;
+}
+
+void b_cmd_render(B_Cmd cmd, B_String_Builder *render)
+{
+ for (size_t i = 0; i < cmd.count; ++i) {
+ const char *arg = cmd.items[i];
+ if (arg == NULL) break;
+ if (i > 0) b_sb_append_cstr(render, " ");
+ if (!strchr(arg, ' ')) {
+ b_sb_append_cstr(render, arg);
+ } else {
+ b_da_append(render, '\'');
+ b_sb_append_cstr(render, arg);
+ b_da_append(render, '\'');
+ }
+ }
+}
+
+B_Proc b_cmd_run_async(B_Cmd cmd)
+{
+ if (cmd.count < 1) {
+ b_log(B_ERROR, "Could not run empty command");
+ return B_INVALID_PROC;
+ }
+
+ B_String_Builder sb = {0};
+ b_cmd_render(cmd, &sb);
+ b_sb_append_null(&sb);
+ b_log(B_INFO, "CMD: %s", sb.items);
+ b_sb_free(sb);
+ memset(&sb, 0, sizeof(sb));
+
+ pid_t cpid = fork();
+ if (cpid < 0) {
+ b_log(B_ERROR, "Could not fork child process: %s", strerror(errno));
+ return B_INVALID_PROC;
+ }
+
+ if (cpid == 0) {
+ // NOTE: This leaks a bit of memory in the child process.
+ // But do we actually care? It's a one off leak anyway...
+ B_Cmd cmd_null = {0};
+ b_da_append_many(&cmd_null, cmd.items, cmd.count);
+ b_cmd_append(&cmd_null, NULL);
+
+ if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) {
+ b_log(B_ERROR, "Could not exec child process: %s", strerror(errno));
+ exit(1);
+ }
+ B_ASSERT(0 && "unreachable");
+ }
+
+ return cpid;
+}
+
+bool b_procs_wait(B_Procs procs)
+{
+ bool success = true;
+ for (size_t i = 0; i < procs.count; ++i) {
+ success = b_proc_wait(procs.items[i]) && success;
+ }
+ return success;
+}
+
+bool b_proc_wait(B_Proc proc)
+{
+ if (proc == B_INVALID_PROC) return false;
+
+ for (;;) {
+ int wstatus = 0;
+ if (waitpid(proc, &wstatus, 0) < 0) {
+ b_log(B_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno));
+ return false;
+ }
+
+ if (WIFEXITED(wstatus)) {
+ int exit_status = WEXITSTATUS(wstatus);
+ if (exit_status != 0) {
+ b_log(B_ERROR, "command exited with exit code %d", exit_status);
+ return false;
+ }
+
+ break;
+ }
+
+ if (WIFSIGNALED(wstatus)) {
+ b_log(B_ERROR, "command process was terminated by %s", strsignal(WTERMSIG(wstatus)));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool b_cmd_run_sync(B_Cmd cmd)
+{
+ B_Proc p = b_cmd_run_async(cmd);
+ if (p == B_INVALID_PROC) return false;
+ return b_proc_wait(p);
+}
+
+char *b_shift_args(int *argc, char ***argv)
+{
+ B_ASSERT(*argc > 0);
+ char *result = **argv;
+ (*argv) += 1;
+ (*argc) -= 1;
+ return result;
+}
+
+void b_log(B_Log_Level level, const char *fmt, ...)
+{
+ switch (level) {
+ case B_INFO:
+ fprintf(stderr, "[INFO] ");
+ break;
+ case B_WARNING:
+ fprintf(stderr, "[WARNING] ");
+ break;
+ case B_ERROR:
+ fprintf(stderr, "[ERROR] ");
+ break;
+ default:
+ B_ASSERT(0 && "unreachable");
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+}
+
+bool b_read_entire_dir(const char *parent, B_File_Paths *children)
+{
+ bool result = true;
+ DIR *dir = NULL;
+
+ dir = opendir(parent);
+ if (dir == NULL) {
+ b_log(B_ERROR, "Could not open directory %s: %s", parent, strerror(errno));
+ b_return_defer(false);
+ }
+
+ errno = 0;
+ struct dirent *ent = readdir(dir);
+ while (ent != NULL) {
+ b_da_append(children, b_temp_strdup(ent->d_name));
+ ent = readdir(dir);
+ }
+
+ if (errno != 0) {
+ b_log(B_ERROR, "Could not read directory %s: %s", parent, strerror(errno));
+ b_return_defer(false);
+ }
+
+defer:
+ if (dir) closedir(dir);
+ return result;
+}
+
+bool b_write_entire_file(const char *path, const void *data, size_t size)
+{
+ bool result = true;
+
+ FILE *f = fopen(path, "wb");
+ if (f == NULL) {
+ b_log(B_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno));
+ b_return_defer(false);
+ }
+
+ // len
+ // v
+ // aaaaaaaaaa
+ // ^
+ // data
+
+ const char *buf = data;
+ while (size > 0) {
+ size_t n = fwrite(buf, 1, size, f);
+ if (ferror(f)) {
+ b_log(B_ERROR, "Could not write into file %s: %s\n", path, strerror(errno));
+ b_return_defer(false);
+ }
+ size -= n;
+ buf += n;
+ }
+
+defer:
+ if (f) fclose(f);
+ return result;
+}
+
+B_File_Type b_get_file_type(const char *path)
+{
+ struct stat statbuf;
+ if (stat(path, &statbuf) < 0) {
+ b_log(B_ERROR, "Could not get stat of %s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ switch (statbuf.st_mode & S_IFMT) {
+ case S_IFDIR: return B_FILE_DIRECTORY;
+ case S_IFREG: return B_FILE_REGULAR;
+ case S_IFLNK: return B_FILE_SYMLINK;
+ default: return B_FILE_OTHER;
+ }
+}
+
+bool b_copy_directory_recursively(const char *src_path, const char *dst_path)
+{
+ bool result = true;
+ B_File_Paths children = {0};
+ B_String_Builder src_sb = {0};
+ B_String_Builder dst_sb = {0};
+ size_t temp_checkpoint = b_temp_save();
+
+ B_File_Type type = b_get_file_type(src_path);
+ if (type < 0) return false;
+
+ switch (type) {
+ case B_FILE_DIRECTORY: {
+ if (!b_mkdir_if_not_exists(dst_path)) b_return_defer(false);
+ if (!b_read_entire_dir(src_path, &children)) b_return_defer(false);
+
+ for (size_t i = 0; i < children.count; ++i) {
+ if (strcmp(children.items[i], ".") == 0) continue;
+ if (strcmp(children.items[i], "..") == 0) continue;
+
+ src_sb.count = 0;
+ b_sb_append_cstr(&src_sb, src_path);
+ b_sb_append_cstr(&src_sb, "/");
+ b_sb_append_cstr(&src_sb, children.items[i]);
+ b_sb_append_null(&src_sb);
+
+ dst_sb.count = 0;
+ b_sb_append_cstr(&dst_sb, dst_path);
+ b_sb_append_cstr(&dst_sb, "/");
+ b_sb_append_cstr(&dst_sb, children.items[i]);
+ b_sb_append_null(&dst_sb);
+
+ if (!b_copy_directory_recursively(src_sb.items, dst_sb.items)) {
+ b_return_defer(false);
+ }
+ }
+ } break;
+
+ case B_FILE_REGULAR: {
+ if (!b_copy_file(src_path, dst_path)) {
+ b_return_defer(false);
+ }
+ } break;
+
+ case B_FILE_SYMLINK: {
+ b_log(B_WARNING, "TODO: Copying symlinks is not supported yet");
+ } break;
+
+ case B_FILE_OTHER: {
+ b_log(B_ERROR, "Unsupported type of file %s", src_path);
+ b_return_defer(false);
+ } break;
+
+ default: B_ASSERT(0 && "unreachable");
+ }
+
+defer:
+ b_temp_rewind(temp_checkpoint);
+ b_da_free(src_sb);
+ b_da_free(dst_sb);
+ b_da_free(children);
+ return result;
+}
+
+char *b_temp_strdup(const char *cstr)
+{
+ size_t n = strlen(cstr);
+ char *result = b_temp_alloc(n + 1);
+ B_ASSERT(result != NULL && "Increase B_TEMP_CAPACITY");
+ memcpy(result, cstr, n);
+ result[n] = '\0';
+ return result;
+}
+
+void *b_temp_alloc(size_t size)
+{
+ if (b_temp_size + size > B_TEMP_CAPACITY) return NULL;
+ void *result = &b_temp[b_temp_size];
+ b_temp_size += size;
+ return result;
+}
+
+char *b_temp_sprintf(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ int n = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+
+ B_ASSERT(n >= 0);
+ char *result = b_temp_alloc(n + 1);
+ B_ASSERT(result != NULL && "Extend the size of the temporary allocator");
+ // TODO: use proper arenas for the temporary allocator;
+ va_start(args, format);
+ vsnprintf(result, n + 1, format, args);
+ va_end(args);
+
+ return result;
+}
+
+void b_temp_reset(void)
+{
+ b_temp_size = 0;
+}
+
+size_t b_temp_save(void)
+{
+ return b_temp_size;
+}
+
+void b_temp_rewind(size_t checkpoint)
+{
+ b_temp_size = checkpoint;
+}
+
+const char *b_temp_sv_to_cstr(B_String_View sv)
+{
+ char *result = b_temp_alloc(sv.count + 1);
+ B_ASSERT(result != NULL && "Extend the size of the temporary allocator");
+ memcpy(result, sv.data, sv.count);
+ result[sv.count] = '\0';
+ return result;
+}
+
+int b_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count)
+{
+ struct stat statbuf = {0};
+
+ if (stat(output_path, &statbuf) < 0) {
+ // NOTE: if output does not exist it 100% must be rebuilt
+ if (errno == ENOENT) return 1;
+ b_log(B_ERROR, "could not stat %s: %s", output_path, strerror(errno));
+ return -1;
+ }
+ int output_path_time = statbuf.st_mtime;
+
+ for (size_t i = 0; i < input_paths_count; ++i) {
+ const char *input_path = input_paths[i];
+ if (stat(input_path, &statbuf) < 0) {
+ // NOTE: non-existing input is an error cause it is needed for building in the first place
+ b_log(B_ERROR, "could not stat %s: %s", input_path, strerror(errno));
+ return -1;
+ }
+ int input_path_time = statbuf.st_mtime;
+ // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild
+ if (input_path_time > output_path_time) return 1;
+ }
+
+ return 0;
+}
+
+int b_needs_rebuild1(const char *output_path, const char *input_path)
+{
+ return b_needs_rebuild(output_path, &input_path, 1);
+}
+
+bool b_rename(const char *old_path, const char *new_path)
+{
+ b_log(B_INFO, "renaming %s -> %s", old_path, new_path);
+ if (rename(old_path, new_path) < 0) {
+ b_log(B_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+bool b_read_entire_file(const char *path, B_String_Builder *sb)
+{
+ bool result = true;
+
+ FILE *f = fopen(path, "rb");
+ if (f == NULL) b_return_defer(false);
+ if (fseek(f, 0, SEEK_END) < 0) b_return_defer(false);
+ long m = ftell(f);
+ if (m < 0) b_return_defer(false);
+ if (fseek(f, 0, SEEK_SET) < 0) b_return_defer(false);
+
+ size_t new_count = sb->count + m;
+ if (new_count > sb->capacity) {
+ sb->items = realloc(sb->items, new_count);
+ B_ASSERT(sb->items != NULL && "Buy more RAM lool!!");
+ sb->capacity = new_count;
+ }
+
+ fread(sb->items + sb->count, m, 1, f);
+ if (ferror(f)) {
+ // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case.
+ b_return_defer(false);
+ }
+ sb->count = new_count;
+
+defer:
+ if (!result) b_log(B_ERROR, "Could not read file %s: %s", path, strerror(errno));
+ if (f) fclose(f);
+ return result;
+}
+
+B_String_View b_sv_chop_by_delim(B_String_View *sv, char delim)
+{
+ size_t i = 0;
+ while (i < sv->count && sv->data[i] != delim) {
+ i += 1;
+ }
+
+ B_String_View result = b_sv_from_parts(sv->data, i);
+
+ if (i < sv->count) {
+ sv->count -= i + 1;
+ sv->data += i + 1;
+ } else {
+ sv->count -= i;
+ sv->data += i;
+ }
+
+ return result;
+}
+
+B_String_View b_sv_from_parts(const char *data, size_t count)
+{
+ B_String_View sv;
+ sv.count = count;
+ sv.data = data;
+ return sv;
+}
+
+B_String_View b_sv_trim_left(B_String_View sv)
+{
+ size_t i = 0;
+ while (i < sv.count && isspace(sv.data[i])) {
+ i += 1;
+ }
+
+ return b_sv_from_parts(sv.data + i, sv.count - i);
+}
+
+B_String_View b_sv_trim_right(B_String_View sv)
+{
+ size_t i = 0;
+ while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) {
+ i += 1;
+ }
+
+ return b_sv_from_parts(sv.data, sv.count - i);
+}
+
+B_String_View b_sv_trim(B_String_View sv)
+{
+ return b_sv_trim_right(b_sv_trim_left(sv));
+}
+
+B_String_View b_sv_from_cstr(const char *cstr)
+{
+ return b_sv_from_parts(cstr, strlen(cstr));
+}
+
+bool b_sv_eq(B_String_View a, B_String_View b)
+{
+ if (a.count != b.count) {
+ return false;
+ } else {
+ return memcmp(a.data, b.data, a.count) == 0;
+ }
+}
+
+// RETURNS:
+// 0 - file does not exists
+// 1 - file exists
+// -1 - error while checking if file exists. The error is logged
+int b_file_exists(const char *file_path)
+{
+ struct stat statbuf;
+ if (stat(file_path, &statbuf) < 0) {
+ if (errno == ENOENT) return 0;
+ b_log(B_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno));
+ return -1;
+ }
+ return 1;
+}
+
+#endif