1014 lines
32 KiB
C
1014 lines
32 KiB
C
/*
|
|
This is free and unencumbered software released into the public domain.
|
|
|
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
distribute this software, either in source code form or as a compiled
|
|
binary, for any purpose, commercial or non-commercial, and by any
|
|
means.
|
|
|
|
In jurisdictions that recognize copyright laws, the author or authors
|
|
of this software dedicate any and all copyright interest in the
|
|
software to the public domain. We make this dedication for the benefit
|
|
of the public at large and to the detriment of our heirs and
|
|
successors. We intend this dedication to be an overt act of
|
|
relinquishment in perpetuity of all present and future rights to this
|
|
software under copyright law.
|
|
|
|
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 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.
|
|
|
|
For more information, please refer to <https://unlicense.org/>
|
|
*/
|
|
|
|
/* QUICKSTART
|
|
|
|
// Put into mybuild.c
|
|
// Building: cc -o mybuild mybuild.c
|
|
// Rebuilding: ./mybuild # will auto-recompile
|
|
|
|
#define GEBS_NO_PREFIX // strips the GEBS_ and gebs_ prefixes (optional)
|
|
#define GEBS_IMPLEMENTATION // include function definitions, not just declarations
|
|
#include "../gebs.h" // include gebs
|
|
|
|
int main(int argc, char ** argv)
|
|
{
|
|
// A gebs executable can recompile itself. We must supply a compiler commandline
|
|
// for recompiling. This means we can also put additional flags here.
|
|
rebuild_self(argc, argv, "cc", "-o", "self_rebuild", __FILE__);
|
|
|
|
// create a build directory
|
|
if (!exists1("build")) {
|
|
mkdir1("build");
|
|
}
|
|
|
|
// Create a dependecy:
|
|
//
|
|
// build/myapp
|
|
// (target)
|
|
// |
|
|
// / \
|
|
// / \
|
|
// / \
|
|
// myapp.c gebs.h
|
|
//
|
|
// This means that if files myapp.c or gebs.h change, build/myapp will be recompiled.
|
|
|
|
RULE("build/myapp", "myapp.c", "gebs.h") {
|
|
// commandline for building target build/myapp
|
|
// We can also check the error code of CMD(), but this is just an example.
|
|
|
|
CMD("cc", "-ggdb", "-o", "build/myapp", "myapp.c");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
*/
|
|
|
|
#ifndef GEBS_H_
|
|
#define GEBS_H_
|
|
|
|
#if !(defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)))
|
|
# error "gebs works only on posix platforms"
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Includes
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/mman.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Types
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define nil NULL
|
|
|
|
typedef unsigned char uchar;
|
|
typedef unsigned short ushort;
|
|
typedef unsigned int uint;
|
|
typedef unsigned long long vlong;
|
|
|
|
#define discard __attribute__((unused))
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Parsing commandline arguments
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define GEBS_SHIFT_CHECK(argc) \
|
|
(*(argc) <= 0 ? (GEBS_LOGE("Expected more arguments\n"), exit(EXIT_FAILURE)) : (0))
|
|
|
|
#define GEBS_SHIFT(argc, argv) \
|
|
(GEBS_SHIFT_CHECK((argc)), --(*(argc)), *(*(argv))++)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Defer macro
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define defer defer__2(__COUNTER__)
|
|
#define defer__2(X) defer__3(X)
|
|
#define defer__3(X) defer__4(defer__id##X)
|
|
#define defer__4(ID) auto void ID##func(char (*)[]); __attribute__((cleanup(ID##func))) char ID##var[0]; void ID##func(char (*ID##param)[])
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Logging
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define GEBS_LOGI(fmt, ...) fprintf(stdout, "Info: "fmt, ##__VA_ARGS__)
|
|
#define GEBS_LOGE(fmt, ...) fprintf(stdout, "Error: "fmt, ##__VA_ARGS__)
|
|
#define GEBS_LOGD(fmt, ...) fprintf(stdout, "Debug: "fmt, ##__VA_ARGS__)
|
|
#define GEBS_LOGW(fmt, ...) fprintf(stdout, "Warning: "fmt, ##__VA_ARGS__)
|
|
|
|
#ifdef GEBS_NO_FS_OPERATION_LOGGING
|
|
# define GEBS_LOGO(fmt, ...)
|
|
#else
|
|
# define GEBS_LOGO(fmt, ...) fprintf(stdout, "Info: "fmt, ##__VA_ARGS__)
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Allocators
|
|
// ----------------------------------------------------------------------------
|
|
|
|
typedef struct {
|
|
void *(*malloc)(void *self, size_t size);
|
|
void (*free)(void *self, void *memory);
|
|
void *(*realloc)(void *self, void *memory, size_t prev_size, size_t new_size);
|
|
} Gebs_Allocator;
|
|
|
|
// Wrapper macros
|
|
#define gebs_malloc(alloc, size) ((alloc)->malloc((void *)(alloc), (size)))
|
|
#define gebs_free(alloc, memory) ((alloc)->free((void *)(alloc), (memory)))
|
|
#define gebs_realloc(alloc, memory, prev_size, new_size) \
|
|
((alloc)->realloc((void *)(alloc), (memory), (prev_size), (new_size)))
|
|
|
|
extern Gebs_Allocator gebs_default_allocator;
|
|
|
|
void *gebs_default_allocator_malloc(void *self, size_t size);
|
|
void gebs_default_allocator_free(void *self, void *memory);
|
|
void *gebs_default_allocator_realloc(void *self, void *memory,
|
|
size_t prev_size, size_t new_size);
|
|
|
|
typedef struct Gebs_Arena_Chunk {
|
|
struct Gebs_Arena_Chunk *next;
|
|
size_t size;
|
|
size_t capacity;
|
|
uintptr_t memory[];
|
|
} Gebs_Arena_Chunk;
|
|
|
|
typedef struct Gebs_Arena {
|
|
void *(*malloc)(void *self, size_t size);
|
|
void (*free)(void *self, void *memory);
|
|
void *(*realloc)(void *self, void *memory, size_t prev_size, size_t new_size);
|
|
|
|
Gebs_Arena_Chunk *begin;
|
|
Gebs_Arena_Chunk *end;
|
|
} Gebs_Arena;
|
|
|
|
void *gebs_arena_malloc(void *self, size_t size);
|
|
void gebs_arena_free(void *self, void *memory);
|
|
void *gebs_arena_realloc(void *self, void *memory, size_t prev_size, size_t new_size);
|
|
|
|
void gebs_arena_reset(Gebs_Arena *a);
|
|
void gebs_arena_destroy(Gebs_Arena *a);
|
|
|
|
Gebs_Arena_Chunk *gebs_arena_create_chunk(size_t capacity);
|
|
void gebs_arena_destroy_chunk(Gebs_Arena_Chunk *chunk);
|
|
|
|
#define gebs_arena_get() \
|
|
({ \
|
|
Gebs_Arena __arena = {0}; \
|
|
__arena.malloc = &gebs_arena_malloc; \
|
|
__arena.realloc = &gebs_arena_realloc; \
|
|
__arena.free = &gebs_arena_free; \
|
|
__arena; \
|
|
})
|
|
|
|
#ifndef GEBS_ARENA_CHUNK_CAPACITY
|
|
# define GEBS_ARENA_CHUNK_CAPACITY (8*1024)
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Dynamic list
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define gebs_list_append_alloc(alloc, list, item) \
|
|
do { \
|
|
if ((list)->items == nil) { \
|
|
(list)->capacity = 1; \
|
|
(list)->items = gebs_malloc((alloc), \
|
|
sizeof(*(list)->items) * (list)->capacity); \
|
|
} else { \
|
|
if ((list)->count == (list)->capacity) { \
|
|
size_t __prev_capacity = (list)->capacity; \
|
|
(list)->capacity *= 2; \
|
|
(list)->items = gebs_realloc((alloc), (list)->items, \
|
|
sizeof(*(list)->items) * __prev_capacity, \
|
|
sizeof(*(list)->items) * (list)->capacity); \
|
|
} \
|
|
} \
|
|
(list)->items[(list)->count++] = (item); \
|
|
} while(0)
|
|
|
|
#define gebs_list_append(list, item) \
|
|
gebs_list_append_alloc(&gebs_default_allocator, (list), (item))
|
|
|
|
#define gebs_list_free_alloc(alloc, list) \
|
|
do { \
|
|
if ((list)->items != nil) { \
|
|
gebs_free((alloc), (list)->items); \
|
|
(list)->items = nil; \
|
|
} \
|
|
(list)->count = 0; \
|
|
} while(0)
|
|
|
|
#define gebs_list_free(list) gebs_list_free_alloc(&gebs_default_allocator, (list))
|
|
|
|
// List of null-terminated strings
|
|
typedef struct {
|
|
char **items;
|
|
size_t count, capacity;
|
|
} Gebs_NString_List;
|
|
|
|
#define gebs_nsl_join_alloc(alloc, nsl, sb, sep) \
|
|
do { \
|
|
for (size_t __i = 0; __i < (nsl)->count; __i++) { \
|
|
gebs_sb_append_nstr_alloc((alloc), (sb), (nsl)->items[__i]); \
|
|
if (__i != (nsl)->count - 1) { \
|
|
gebs_sb_append_nstr_alloc((alloc), (sb), (sep)); \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
#define gebs_nsl_join(nsl, sb, sep) \
|
|
gebs_nsl_join_alloc(&gebs_default_allocator, (nsl), (sb), (sep))
|
|
|
|
typedef struct {
|
|
char *items;
|
|
size_t count, capacity;
|
|
} Gebs_String_Builder;
|
|
|
|
#define gebs_sb_append_char_alloc gebs_list_append_alloc
|
|
#define gebs_sb_free_alloc gebs_list_free_alloc
|
|
#define gebs_sb_finish_alloc(alloc, sb) \
|
|
gebs_sb_append_char_alloc((alloc), (sb), '\0')
|
|
|
|
#define gebs_sb_append_nstr_alloc(alloc, sb, nstr) \
|
|
do { \
|
|
char *__p = (nstr); \
|
|
for (size_t __i = 0; __p[__i]; __i++) { \
|
|
gebs_sb_append_char_alloc((alloc), (sb), __p[__i]); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define gebs_sb_append_char(sb, chr) \
|
|
gebs_sb_append_char_alloc(&gebs_default_allocator, (sb), (chr))
|
|
#define gebs_sb_free(sb) \
|
|
gebs_sb_free_alloc(&gebs_default_allocator, (sb))
|
|
#define gebs_sb_finish(sb) \
|
|
gebs_sb_finish_alloc(&gebs_default_allocator, (sb))
|
|
#define gebs_sb_append_nstr(sb, nstr) \
|
|
gebs_sb_append_nstr_alloc(&gebs_default_allocator, (sb), (nstr))
|
|
|
|
#define gebs_sb_read_file_alloc(alloc, sb, path) \
|
|
({ \
|
|
bool __ret = false; \
|
|
FILE *__f = fopen((path), "r"); \
|
|
if (__f != nil) { \
|
|
__ret = true; \
|
|
fseek(__f, 0L, SEEK_END); \
|
|
size_t __size = ftell(__f); \
|
|
rewind(__f); \
|
|
char *__buf = gebs_malloc((alloc), __size); \
|
|
fread(__buf, __size, 1, __f); \
|
|
fclose(__f); \
|
|
for (size_t __i = 0; __i < __size; __i++) { \
|
|
gebs_sb_append_char_alloc((alloc), (sb), __buf[__i]); \
|
|
} \
|
|
gebs_free((alloc), __buf); \
|
|
} \
|
|
__ret; \
|
|
})
|
|
|
|
#define gebs_sb_read_file(sb, path) \
|
|
gebs_sb_read_file_alloc(&default_allocator, (sb), (path))
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Filesystem operations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool gebs_rename(const char *a, const char *b);
|
|
bool gebs_remove(const char *p);
|
|
bool gebs_mkdir(const char *p);
|
|
bool gebs_exists(const char *p);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Commands
|
|
// ----------------------------------------------------------------------------
|
|
|
|
typedef Gebs_NString_List Gebs_Cmd;
|
|
|
|
#define gebs_cmd_append_alloc gebs_list_append_alloc
|
|
#define gebs_cmd_free_alloc gebs_list_free_alloc
|
|
#define gebs_cmd_append gebs_list_append
|
|
#define gebs_cmd_free gebs_list_free
|
|
|
|
#define gebs_cmd_run_alloc gebs_cmd_run_sync_alloc
|
|
#define gebs_cmd_fd_run_alloc gebs_cmd_run_sync_alloc
|
|
|
|
#define gebs_cmd_run(cmd) gebs_cmd_run_alloc(&gebs_default_allocator, (cmd))
|
|
#define gebs_cmd_fd_run(fd, cmd) gebs_cmd_fd_run_alloc(&gebs_default_allocator, fd, (cmd))
|
|
|
|
int gebs_cmd_run_sync_alloc(Gebs_Allocator *alloc, Gebs_Cmd *cmd);
|
|
int gebs_cmd_fd_run_sync_alloc(Gebs_Allocator *alloc, int fd, Gebs_Cmd *cmd);
|
|
|
|
#define gebs_cmd_run_async(cmd) gebs_cmd_run_async_alloc(&gebs_default_allocator, (cmd))
|
|
|
|
pid_t gebs_cmd_run_async_alloc(Gebs_Allocator *alloc, Gebs_Cmd *cmd);
|
|
|
|
#define GEBS_CMD(...) \
|
|
({ \
|
|
char *__args[] = { __VA_ARGS__ }; \
|
|
Gebs_Cmd __cmd = {0}; \
|
|
defer { gebs_cmd_free(&__cmd); } \
|
|
for (size_t __i = 0; __i < sizeof(__args)/sizeof(__args[0]); __i++) { \
|
|
gebs_cmd_append(&__cmd, __args[__i]); \
|
|
} \
|
|
gebs_cmd_run(&__cmd); \
|
|
})
|
|
|
|
int gebs_cmd_run_sync_collect_alloc(Gebs_Allocator *alloc,
|
|
Gebs_Cmd *cmd, Gebs_String_Builder *sb_out);
|
|
|
|
int gebs_cmd_fd_run_sync_collect_alloc(Gebs_Allocator *alloc, int fd,
|
|
Gebs_Cmd *cmd, Gebs_String_Builder *sb_out);
|
|
|
|
#define gebs_cmd_run_collect_alloc gebs_cmd_run_sync_collect_alloc
|
|
#define gebs_cmd_fd_run_collect_alloc gebs_cmd_fd_run_sync_collect_alloc
|
|
|
|
#define gebs_cmd_run_collect(cmd, out) \
|
|
gebs_cmd_run_collect_alloc(&gebs_default_allocator, (cmd), (out))
|
|
|
|
#define gebs_cmd_fd_run_collect(fd, cmd, out) \
|
|
gebs_cmd_fd_run_collect_alloc(&gebs_default_allocator, (fd), (cmd), (out))
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// The build system
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool gebs_needs_rebuild(const char *out, const char *in);
|
|
void gebs_rebuild_self1_alloc(Gebs_Allocator *alloc, int argc, char ** argv,
|
|
int cmd_argc, char **cmd_argv, char *source);
|
|
|
|
#define gebs_rebuild_self(argc, argv, ...) \
|
|
do { \
|
|
const char *__argv[] = { __VA_ARGS__ }; \
|
|
gebs_rebuild_self1_alloc(&gebs_default_allocator, argc, argv, \
|
|
sizeof(__argv)/sizeof(__argv[0]), (char**)__argv, __FILE__); \
|
|
} while(0)
|
|
|
|
#define gebs_needs_rebuild_many(out, ...) \
|
|
({ \
|
|
const char *__deps[] = { __VA_ARGS__ }; \
|
|
bool __ok = false; \
|
|
if (sizeof(__deps)/sizeof(__deps[0]) == 0) { \
|
|
__ok = true; \
|
|
} \
|
|
for (size_t __i = 0; __i < sizeof(__deps)/sizeof(__deps[0]); __i++) { \
|
|
if (gebs_needs_rebuild((out), __deps[__i])) { \
|
|
__ok = true; \
|
|
break; \
|
|
} \
|
|
} \
|
|
__ok; \
|
|
})
|
|
|
|
#define gebs_make_compile_flags(...) \
|
|
do { \
|
|
if (gebs_needs_rebuild("compile_flags.txt", __FILE__)) { \
|
|
GEBS_LOGI("Creating compile_flags.txt\n"); \
|
|
const char *__flags[] = { __VA_ARGS__ }; \
|
|
FILE *__out = fopen("compile_flags.txt", "w"); \
|
|
char __nl = '\n'; \
|
|
if (__out != nil) { \
|
|
for (size_t __i = 0; __i < sizeof(__flags)/sizeof(__flags[0]); __i++) { \
|
|
const char *__flag = __flags[__i]; \
|
|
fwrite(__flag, strlen(__flag), 1, __out); \
|
|
fwrite(&__nl, 1, 1, __out); \
|
|
} \
|
|
fclose(__out); \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
#define GEBS_RULE(target, ...) if (needs_rebuild_many(target, __VA_ARGS__))
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Scratch arena
|
|
// ----------------------------------------------------------------------------
|
|
|
|
extern Gebs_Arena gebs_scratch_arena;
|
|
|
|
void gebs_scratch_arena_reset(void);
|
|
char *gebs_fmt(const char *fmt, ...);
|
|
char *gebs_cwd(void);
|
|
|
|
#ifdef GEBS_IMPLEMENTATION
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Commands
|
|
// ----------------------------------------------------------------------------
|
|
|
|
int gebs_cmd_run_sync_alloc(Gebs_Allocator *alloc, Gebs_Cmd *cmd)
|
|
{
|
|
Gebs_String_Builder sb = {0};
|
|
defer { gebs_sb_free_alloc(alloc, &sb); }
|
|
gebs_nsl_join_alloc(alloc, cmd, &sb, " ");
|
|
gebs_sb_finish_alloc(alloc, &sb);
|
|
GEBS_LOGI("cmd `%s`\n", sb.items);
|
|
|
|
// Clone the list
|
|
Gebs_Cmd new_cmd = {0};
|
|
defer { gebs_cmd_free_alloc(alloc, &new_cmd); }
|
|
for (size_t i = 0; i < cmd->count; i++) {
|
|
gebs_cmd_append_alloc(alloc, &new_cmd, cmd->items[i]);
|
|
}
|
|
gebs_cmd_append_alloc((alloc), &new_cmd, nil);
|
|
|
|
pid_t pid = vfork();
|
|
switch (pid) {
|
|
case -1:
|
|
GEBS_LOGE("Could not vfork\n");
|
|
return -1;
|
|
case 0:
|
|
execvp(new_cmd.items[0], (char * const *)new_cmd.items);
|
|
exit(EXIT_FAILURE);
|
|
default: {
|
|
int wstatus;
|
|
do {
|
|
if (waitpid(pid, &wstatus, WUNTRACED | WCONTINUED) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (WIFEXITED(wstatus)) {
|
|
return WEXITSTATUS(wstatus);
|
|
} else if (WIFSIGNALED(wstatus)) {
|
|
GEBS_LOGI("%d killed by %d\n", pid, WTERMSIG(wstatus));
|
|
return -1;
|
|
} else if (WIFSTOPPED(wstatus)) {
|
|
GEBS_LOGI("%d stopped by %d\n", pid, WSTOPSIG(wstatus));
|
|
return -1;
|
|
}
|
|
} while(!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
|
|
}
|
|
}
|
|
}
|
|
|
|
int gebs_cmd_fd_run_sync_alloc(Gebs_Allocator *alloc, int fd, Gebs_Cmd *cmd)
|
|
{
|
|
Gebs_String_Builder sb = {0};
|
|
defer { gebs_sb_free_alloc(alloc, &sb); }
|
|
gebs_nsl_join_alloc(alloc, cmd, &sb, " ");
|
|
gebs_sb_finish_alloc(alloc, &sb);
|
|
GEBS_LOGI("cmd `%s`\n", sb.items);
|
|
|
|
// Clone the list
|
|
Gebs_Cmd new_cmd = {0};
|
|
defer { gebs_cmd_free_alloc(alloc, &new_cmd); }
|
|
for (size_t i = 0; i < cmd->count; i++) {
|
|
gebs_cmd_append_alloc(alloc, &new_cmd, cmd->items[i]);
|
|
}
|
|
gebs_cmd_append_alloc((alloc), &new_cmd, nil);
|
|
|
|
pid_t pid = vfork();
|
|
switch (pid) {
|
|
case -1:
|
|
GEBS_LOGE("Could not vfork\n");
|
|
return -1;
|
|
case 0: {
|
|
const char *const envp[] = {NULL};
|
|
fexecve(fd, (char * const *)new_cmd.items, (char * const *)envp);
|
|
exit(EXIT_FAILURE);
|
|
} break;
|
|
default: {
|
|
int wstatus;
|
|
do {
|
|
if (waitpid(pid, &wstatus, WUNTRACED | WCONTINUED) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (WIFEXITED(wstatus)) {
|
|
return WEXITSTATUS(wstatus);
|
|
} else if (WIFSIGNALED(wstatus)) {
|
|
GEBS_LOGI("%d killed by %d\n", pid, WTERMSIG(wstatus));
|
|
return -1;
|
|
} else if (WIFSTOPPED(wstatus)) {
|
|
GEBS_LOGI("%d stopped by %d\n", pid, WSTOPSIG(wstatus));
|
|
return -1;
|
|
}
|
|
} while(!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
|
|
}
|
|
}
|
|
}
|
|
|
|
int gebs_cmd_run_sync_collect_alloc(Gebs_Allocator *alloc, Gebs_Cmd *cmd, Gebs_String_Builder *sb_out)
|
|
{
|
|
Gebs_String_Builder sb = {0};
|
|
defer { gebs_sb_free_alloc(alloc, &sb); }
|
|
gebs_nsl_join_alloc(alloc, cmd, &sb, " ");
|
|
gebs_sb_finish_alloc(alloc, &sb);
|
|
GEBS_LOGI("cmd `%s`\n", sb.items);
|
|
|
|
// Clone the list
|
|
Gebs_Cmd new_cmd = {0};
|
|
defer { gebs_cmd_free_alloc(alloc, &new_cmd); }
|
|
for (size_t i = 0; i < cmd->count; i++) {
|
|
gebs_cmd_append_alloc(alloc, &new_cmd, cmd->items[i]);
|
|
}
|
|
gebs_cmd_append_alloc((alloc), &new_cmd, nil);
|
|
|
|
int pipe_ends[2];
|
|
pipe(pipe_ends);
|
|
|
|
pid_t pid = vfork();
|
|
switch (pid) {
|
|
case -1:
|
|
close(pipe_ends[0]);
|
|
close(pipe_ends[1]);
|
|
GEBS_LOGE("Could not vfork\n");
|
|
return -1;
|
|
case 0:
|
|
close(pipe_ends[0]);
|
|
dup2(pipe_ends[1], 1);
|
|
close(pipe_ends[1]);
|
|
|
|
execvp(new_cmd.items[0], (char * const *)new_cmd.items);
|
|
exit(EXIT_FAILURE);
|
|
default: {
|
|
close(pipe_ends[1]);
|
|
char c;
|
|
while (read(pipe_ends[0], &c, 1) > 0) {
|
|
gebs_sb_append_char_alloc(alloc, sb_out, c);
|
|
}
|
|
gebs_sb_finish_alloc(alloc, sb_out);
|
|
|
|
int wstatus;
|
|
do {
|
|
if (waitpid(pid, &wstatus, WUNTRACED | WCONTINUED) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (WIFEXITED(wstatus)) {
|
|
return WEXITSTATUS(wstatus);
|
|
} else if (WIFSIGNALED(wstatus)) {
|
|
GEBS_LOGI("%d killed by %d\n", pid, WTERMSIG(wstatus));
|
|
return -1;
|
|
} else if (WIFSTOPPED(wstatus)) {
|
|
GEBS_LOGI("%d stopped by %d\n", pid, WSTOPSIG(wstatus));
|
|
return -1;
|
|
}
|
|
} while(!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
|
|
}
|
|
}
|
|
}
|
|
|
|
int gebs_cmd_fd_run_sync_collect_alloc(Gebs_Allocator *alloc, int fd, Gebs_Cmd *cmd, Gebs_String_Builder *sb_out)
|
|
{
|
|
Gebs_String_Builder sb = {0};
|
|
defer { gebs_sb_free_alloc(alloc, &sb); }
|
|
gebs_nsl_join_alloc(alloc, cmd, &sb, " ");
|
|
gebs_sb_finish_alloc(alloc, &sb);
|
|
GEBS_LOGI("cmd `%s`\n", sb.items);
|
|
|
|
// Clone the list
|
|
Gebs_Cmd new_cmd = {0};
|
|
defer { gebs_cmd_free_alloc(alloc, &new_cmd); }
|
|
for (size_t i = 0; i < cmd->count; i++) {
|
|
gebs_cmd_append_alloc(alloc, &new_cmd, cmd->items[i]);
|
|
}
|
|
gebs_cmd_append_alloc((alloc), &new_cmd, nil);
|
|
|
|
int pipe_ends[2];
|
|
pipe(pipe_ends);
|
|
|
|
pid_t pid = vfork();
|
|
switch (pid) {
|
|
case -1:
|
|
close(pipe_ends[0]);
|
|
close(pipe_ends[1]);
|
|
GEBS_LOGE("Could not vfork\n");
|
|
return -1;
|
|
case 0: {
|
|
close(pipe_ends[0]);
|
|
dup2(pipe_ends[1], 1);
|
|
close(pipe_ends[1]);
|
|
|
|
const char *const envp[] = {NULL};
|
|
fexecve(fd, (char *const *)new_cmd.items, (char *const *)envp);
|
|
exit(EXIT_FAILURE);
|
|
} break;
|
|
default: {
|
|
close(pipe_ends[1]);
|
|
char c;
|
|
while (read(pipe_ends[0], &c, 1) > 0) {
|
|
gebs_sb_append_char_alloc(alloc, sb_out, c);
|
|
}
|
|
gebs_sb_finish_alloc(alloc, sb_out);
|
|
|
|
int wstatus;
|
|
do {
|
|
if (waitpid(pid, &wstatus, WUNTRACED | WCONTINUED) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (WIFEXITED(wstatus)) {
|
|
return WEXITSTATUS(wstatus);
|
|
} else if (WIFSIGNALED(wstatus)) {
|
|
GEBS_LOGI("%d killed by %d\n", pid, WTERMSIG(wstatus));
|
|
return -1;
|
|
} else if (WIFSTOPPED(wstatus)) {
|
|
GEBS_LOGI("%d stopped by %d\n", pid, WSTOPSIG(wstatus));
|
|
return -1;
|
|
}
|
|
} while(!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
|
|
}
|
|
}
|
|
}
|
|
|
|
pid_t gebs_cmd_run_async_alloc(Gebs_Allocator *alloc, Gebs_Cmd *cmd)
|
|
{
|
|
Gebs_String_Builder sb = {0};
|
|
defer { gebs_sb_free_alloc(alloc, &sb); }
|
|
gebs_nsl_join_alloc(alloc, cmd, &sb, " ");
|
|
gebs_sb_finish_alloc(alloc, &sb);
|
|
GEBS_LOGI("cmd `%s`\n", sb.items);
|
|
|
|
// Clone the list
|
|
Gebs_Cmd new_cmd = {0};
|
|
defer { gebs_cmd_free_alloc(alloc, &new_cmd); }
|
|
for (size_t i = 0; i < cmd->count; i++) {
|
|
gebs_cmd_append_alloc(alloc, &new_cmd, cmd->items[i]);
|
|
}
|
|
gebs_cmd_append_alloc((alloc), &new_cmd, nil);
|
|
|
|
pid_t pid = vfork();
|
|
switch (pid) {
|
|
case -1:
|
|
GEBS_LOGE("Could not vfork\n");
|
|
return -1;
|
|
case 0:
|
|
execvp(new_cmd.items[0], (char * const *)new_cmd.items);
|
|
exit(EXIT_FAILURE);
|
|
default:
|
|
return pid;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Allocators
|
|
// ----------------------------------------------------------------------------
|
|
|
|
Gebs_Allocator gebs_default_allocator = {
|
|
.malloc = &gebs_default_allocator_malloc,
|
|
.free = &gebs_default_allocator_free,
|
|
.realloc = &gebs_default_allocator_realloc,
|
|
};
|
|
|
|
void *gebs_default_allocator_malloc(discard void *self, size_t size)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
void gebs_default_allocator_free(discard void *self, void *memory)
|
|
{
|
|
free(memory);
|
|
}
|
|
|
|
void *gebs_default_allocator_realloc(discard void *self, void *memory,
|
|
discard size_t prev_size, size_t new_size)
|
|
{
|
|
void *p = realloc(memory, new_size);
|
|
return p;
|
|
}
|
|
|
|
Gebs_Arena_Chunk *gebs_arena_create_chunk(size_t capacity)
|
|
{
|
|
size_t size = sizeof(Gebs_Arena_Chunk) + sizeof(uintptr_t)*capacity;
|
|
Gebs_Arena_Chunk *chunk = (Gebs_Arena_Chunk *)malloc(size);
|
|
chunk->next = nil;
|
|
chunk->size = 0;
|
|
chunk->capacity = capacity;
|
|
return chunk;
|
|
}
|
|
|
|
void gebs_arena_destroy_chunk(Gebs_Arena_Chunk *chunk)
|
|
{
|
|
free(chunk);
|
|
}
|
|
|
|
void *gebs_arena_malloc(void *self, size_t size)
|
|
{
|
|
Gebs_Arena *a = (Gebs_Arena *)self;
|
|
|
|
size_t sz = (size + sizeof(uintptr_t) - 1)/sizeof(uintptr_t);
|
|
|
|
if (a->end == nil) {
|
|
size_t capacity = GEBS_ARENA_CHUNK_CAPACITY;
|
|
if (capacity < sz) {
|
|
capacity = sz;
|
|
}
|
|
a->end = gebs_arena_create_chunk(capacity);
|
|
a->begin = a->end;
|
|
}
|
|
|
|
while (a->end->size + sz > a->end->capacity && a->end->next != nil) {
|
|
a->end = a->end->next;
|
|
}
|
|
|
|
if (a->end->size + sz > a->end->capacity) {
|
|
size_t capacity = GEBS_ARENA_CHUNK_CAPACITY;
|
|
if (capacity < sz) {
|
|
capacity = sz;
|
|
}
|
|
a->end->next = gebs_arena_create_chunk(capacity);
|
|
a->end = a->end->next;
|
|
}
|
|
|
|
void *result = &a->end->memory[a->end->size];
|
|
a->end->size += sz;
|
|
return result;
|
|
}
|
|
|
|
void gebs_arena_free(discard void *self, discard void *memory)
|
|
{
|
|
}
|
|
|
|
void *gebs_arena_realloc(void *self, void *memory,
|
|
size_t prev_size, size_t new_size)
|
|
{
|
|
Gebs_Arena *a = (Gebs_Arena *)self;
|
|
|
|
if (new_size <= prev_size) {
|
|
return memory;
|
|
}
|
|
|
|
void *new_memory = gebs_arena_malloc(a, new_size);
|
|
for (size_t i = 0; i < prev_size; i++) {
|
|
((char *)new_memory)[i] = ((char *)memory)[i];
|
|
}
|
|
return new_memory;
|
|
}
|
|
|
|
void gebs_arena_reset(Gebs_Arena *a)
|
|
{
|
|
for (Gebs_Arena_Chunk *chunk = a->begin; chunk != nil; chunk = chunk->next) {
|
|
chunk->size = 0;
|
|
}
|
|
a->end = a->begin;
|
|
}
|
|
|
|
void gebs_arena_destroy(Gebs_Arena *a)
|
|
{
|
|
Gebs_Arena_Chunk *chunk = a->begin;
|
|
while (chunk) {
|
|
Gebs_Arena_Chunk *chunk1 = chunk;
|
|
chunk = chunk->next;
|
|
gebs_arena_destroy_chunk(chunk1);
|
|
}
|
|
a->begin = nil;
|
|
a->end = nil;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Scratch arena
|
|
// ----------------------------------------------------------------------------
|
|
|
|
Gebs_Arena gebs_scratch_arena;
|
|
|
|
__attribute__((constructor))
|
|
void gebs_scratch_areana_init(void)
|
|
{
|
|
gebs_scratch_arena = gebs_arena_get();
|
|
}
|
|
|
|
__attribute__((destructor))
|
|
void gebs_scratch_areana_free(void)
|
|
{
|
|
gebs_arena_destroy(&gebs_scratch_arena);
|
|
}
|
|
|
|
char *gebs_fmt(const char *fmt, ...)
|
|
{
|
|
va_list list;
|
|
va_start(list, fmt);
|
|
|
|
va_list list_copy;
|
|
va_copy(list_copy, list);
|
|
size_t size = vsnprintf(nil, 0, fmt, list_copy);
|
|
va_end(list_copy);
|
|
|
|
char *buf = gebs_malloc(&gebs_scratch_arena, size+1);
|
|
vsprintf(buf, fmt, list);
|
|
va_end(list);
|
|
return buf;
|
|
}
|
|
|
|
void gebs_scratch_arena_reset(void)
|
|
{
|
|
gebs_arena_reset(&gebs_scratch_arena);
|
|
}
|
|
|
|
char *gebs_cwd(void)
|
|
{
|
|
char *cwd = gebs_malloc(&gebs_scratch_arena, PATH_MAX);
|
|
if (getcwd(cwd, PATH_MAX) == nil) {
|
|
return nil;
|
|
}
|
|
return cwd;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Filesystem operations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool gebs_mkdir(const char *p)
|
|
{
|
|
GEBS_LOGO("mkdir %s\n", p);
|
|
return mkdir(p, 0777) == 0;
|
|
}
|
|
|
|
bool gebs_exists(const char *p)
|
|
{
|
|
struct stat st;
|
|
return stat(p, &st) == 0;
|
|
}
|
|
|
|
bool gebs_rename(const char *a, const char *b)
|
|
{
|
|
GEBS_LOGO("rename %s -> %s\n", a, b);
|
|
return rename(a, b) == 0;
|
|
}
|
|
|
|
bool gebs_remove(const char *p)
|
|
{
|
|
GEBS_LOGO("remove %s\n", p);
|
|
return remove(p) == 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// The build system
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool gebs_needs_rebuild(const char *out, const char *in)
|
|
{
|
|
time_t out_mtime = 0;
|
|
time_t in_mtime = 0;
|
|
|
|
struct stat attr1 = {0};
|
|
if (stat(out, &attr1) == 0) { out_mtime = attr1.st_mtime; }
|
|
struct stat attr2 = {0};
|
|
if (stat(in, &attr2) == 0) { in_mtime = attr2.st_mtime; }
|
|
|
|
return in_mtime > out_mtime;
|
|
}
|
|
|
|
void gebs_rebuild_self1_alloc(Gebs_Allocator *alloc, int argc, char ** argv,
|
|
int cmd_argc, char **cmd_argv, char *source)
|
|
{
|
|
const char *binary_path = argv[0];
|
|
if (!gebs_needs_rebuild_many(binary_path, source, __FILE__)) {
|
|
return;
|
|
}
|
|
|
|
char old_binary_path[FILENAME_MAX];
|
|
snprintf(old_binary_path, sizeof(old_binary_path), "%s-1", binary_path);
|
|
gebs_rename(binary_path, old_binary_path);
|
|
|
|
Gebs_Cmd cmd = {0};
|
|
defer { gebs_cmd_free_alloc(alloc, &cmd); }
|
|
for (size_t i = 0; i < cmd_argc; i++) {
|
|
gebs_cmd_append_alloc(alloc, &cmd, cmd_argv[i]);
|
|
}
|
|
int code = gebs_cmd_run_alloc(alloc, &cmd);
|
|
if (code != EXIT_SUCCESS) {
|
|
gebs_rename(old_binary_path, binary_path);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
gebs_remove(old_binary_path);
|
|
|
|
cmd.count = 0;
|
|
gebs_cmd_append_alloc(alloc, &cmd, (char *)binary_path);
|
|
for (size_t i = 1; i < argc; i++) {
|
|
gebs_cmd_append_alloc(alloc, &cmd, argv[i]);
|
|
}
|
|
|
|
code = gebs_cmd_run_alloc(alloc, &cmd);
|
|
if (code != EXIT_SUCCESS) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
#endif // GEBS_IMPLEMENTATION
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Prefixes
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#ifdef GEBS_NO_PREFIX
|
|
|
|
#define SHIFT_CHECK GEBS_SHIFT_CHECK
|
|
#define SHIFT GEBS_SHIFT
|
|
|
|
#define LOGI GEBS_LOGI
|
|
#define LOGE GEBS_LOGE
|
|
#define LOGD GEBS_LOGD
|
|
#define LOGW GEBS_LOGW
|
|
#define LOGO GEBS_LOGO
|
|
|
|
#define Allocator Gebs_Allocator
|
|
#define default_allocator gebs_default_allocator
|
|
#define Arena Gebs_Arena
|
|
#define arena_reset gebs_arena_reset
|
|
#define arena_destroy gebs_arena_destroy
|
|
#define arena_get gebs_arena_get
|
|
|
|
#define list_append_alloc gebs_list_append_alloc
|
|
#define list_append gebs_list_append
|
|
#define list_free_alloc gebs_list_free_alloc
|
|
#define list_free gebs_list_free
|
|
|
|
#define NString_List Gebs_NString_List
|
|
#define nsl_join_alloc gebs_nsl_join_alloc
|
|
#define nsl_join gebs_nsl_join
|
|
|
|
#define String_Builder Gebs_String_Builder
|
|
#define sb_append_char_alloc gebs_sb_append_char_alloc
|
|
#define sb_free_alloc gebs_sb_free_alloc
|
|
#define sb_finish_alloc gebs_sb_finish_alloc
|
|
#define sb_append_nstr_alloc gebs_sb_append_nstr_alloc
|
|
#define sb_append_char gebs_sb_append_char
|
|
#define sb_free gebs_sb_free
|
|
#define sb_finish gebs_sb_finish
|
|
#define sb_append_nstr gebs_sb_append_nstr
|
|
#define sb_read_file gebs_sb_read_file
|
|
|
|
#define rename1 gebs_rename
|
|
#define remove1 gebs_remove
|
|
#define mkdir1 gebs_mkdir
|
|
#define exists1 gebs_exists
|
|
|
|
#define Cmd Gebs_Cmd
|
|
#define cmd_append_alloc gebs_cmd_append_alloc
|
|
#define cmd_free_aloc gebs_cmd_free_alloc
|
|
#define cmd_append gebs_cmd_append
|
|
#define cmd_free gebs_cmd_free
|
|
#define cmd_run_alloc gebs_cmd_run_alloc
|
|
#define cmd_run gebs_cmd_run
|
|
#define cmd_run_sync_alloc gebs_cmd_run_sync_alloc
|
|
#define CMD GEBS_CMD
|
|
#define cmd_run_sync_collect_alloc gebs_cmd_run_sync_collect_alloc
|
|
#define cmd_run_collect_alloc gebs_cmd_run_collect_alloc
|
|
#define cmd_run_collect gebs_cmd_run_collect
|
|
#define cmd_fd_run_sync_collect_alloc gebs_cmd_fd_run_sync_collect_alloc
|
|
#define cmd_fd_run_collect_alloc gebs_cmd_fd_run_collect_alloc
|
|
#define cmd_fd_run_collect gebs_cmd_fd_run_collect
|
|
#define cmd_fd_run_sync_alloc gebs_cmd_fd_run_sync_alloc
|
|
#define cmd_fd_run_alloc gebs_cmd_fd_run_alloc
|
|
#define cmd_fd_run gebs_cmd_fd_run
|
|
#define cmd_run_async_alloc gebs_cmd_run_async_alloc
|
|
#define cmd_run_async gebs_cmd_run_async
|
|
|
|
#define needs_rebuild gebs_needs_rebuild
|
|
#define needs_rebuild_many gebs_needs_rebuild_many
|
|
#define rebuild_self gebs_rebuild_self
|
|
#define make_compile_flags gebs_make_compile_flags
|
|
#define RULE GEBS_RULE
|
|
|
|
#define scratch_arena gebs_scratch_arena
|
|
#define fmt gebs_fmt
|
|
#define scratch_arena_reset gebs_scratch_arena_reset
|
|
|
|
#endif
|
|
|
|
#endif // GEBS_H_
|