// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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_CMD, B_BUILDING, B_CHANGE, 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_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_CMD: fprintf(stderr, "[CMD] "); break; case B_BUILDING: fprintf(stderr, "[BUILDING] "); break; case B_CHANGE: fprintf(stderr, "[CHANGE] "); 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) { b_log(B_BUILDING, "%s", output_path); 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) { b_log(B_CHANGE, "%s", input_path); 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