Last active
September 21, 2025 07:30
-
-
Save Canadadry/735146d379102fe9e5924a0aa20e26f1 to your computer and use it in GitHub Desktop.
build system in c for c a header only file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef BUILD_H | |
| #define BUILD_H | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdarg.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <dirent.h> | |
| #include <glob.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #define BUILD_CMD_MAX 65536 | |
| #define BUILD_PATH_MAX 4096 | |
| #define BUILD_MAX_TARGETS 32 | |
| #define BUILD_RUN_CMD(...) build_run_cmd(__VA_ARGS__, NULL) | |
| typedef enum { | |
| BUILD_TARGET_EXE, | |
| BUILD_TARGET_LIB | |
| } BuildTargetType; | |
| typedef struct { | |
| BuildTargetType type; | |
| const char *entry; | |
| const char *output; | |
| } BuildTarget; | |
| typedef struct { | |
| const char *src_dir; | |
| const char *build_dir; | |
| const char *cflags; | |
| const char *ldflags; | |
| const char *cc; | |
| const char *ld; | |
| int release; | |
| BuildTarget targets[BUILD_MAX_TARGETS]; | |
| int target_count; | |
| } BuildCtx; | |
| static inline BuildCtx build_init(void) { | |
| BuildCtx ctx = {0}; | |
| ctx.cflags = ""; | |
| ctx.ldflags = ""; | |
| ctx.cc = "cc"; | |
| ctx.ld = "cc"; | |
| ctx.release = 0; | |
| ctx.target_count = 0; | |
| return ctx; | |
| } | |
| static inline void build_set_src_dir(BuildCtx *ctx, const char *dir) { ctx->src_dir = dir; } | |
| static inline void build_set_build_dir(BuildCtx *ctx, const char *dir) { ctx->build_dir = dir; } | |
| static inline void build_set_cflags(BuildCtx *ctx, const char *flags) { ctx->cflags = flags; } | |
| static inline void build_set_ldflags(BuildCtx *ctx, const char *flags) { ctx->ldflags = flags; } | |
| static inline void build_set_release(BuildCtx *ctx, const char *opt_flags) { ctx->release = 1; ctx->cflags = opt_flags; } | |
| static inline void build_set_cc(BuildCtx *ctx, const char *compiler) { ctx->cc = compiler; } | |
| static inline void build_set_ld(BuildCtx *ctx, const char *linker) { ctx->ld = linker; } | |
| static inline void build_make_dir(const char *dir) { | |
| struct stat st = {0}; | |
| if (stat(dir, &st) == -1) mkdir(dir, 0755); | |
| } | |
| static inline int build_file_exists(const char *path) { | |
| struct stat st; | |
| return stat(path, &st) == 0; | |
| } | |
| static inline void build_run_cmd(const char *first, ...) { | |
| va_list args; | |
| va_start(args, first); | |
| char cmd[BUILD_CMD_MAX]; | |
| snprintf(cmd, sizeof(cmd), "%s", first); | |
| const char *arg; | |
| while ((arg = va_arg(args, const char*))) { | |
| strncat(cmd, " ", sizeof(cmd) - strlen(cmd) - 1); | |
| strncat(cmd, arg, sizeof(cmd) - strlen(cmd) - 1); | |
| } | |
| va_end(args); | |
| printf("RUN: %s\n", cmd); | |
| system(cmd); | |
| } | |
| static inline int build_has_arg(int argc, char **argv, int n, ...) { | |
| if (n < 1) { | |
| fprintf(stderr, "build_has_arg: need at least one alias\n"); | |
| exit(1); | |
| } | |
| va_list args; | |
| va_start(args, n); | |
| for (int i = 1; i < argc; i++) { | |
| va_list args_copy; | |
| va_copy(args_copy, args); | |
| for (int j = 0; j < n; j++) { | |
| const char *alias = va_arg(args_copy, const char*); | |
| if (strcmp(argv[i], alias) == 0) { | |
| va_end(args_copy); | |
| va_end(args); | |
| return 1; | |
| } | |
| } | |
| va_end(args_copy); | |
| } | |
| va_end(args); | |
| return 0; | |
| } | |
| static inline void build_compile(BuildCtx *ctx, const char *pattern) { | |
| char path[BUILD_PATH_MAX]; | |
| snprintf(path, sizeof(path), "%s/%s", ctx->src_dir, pattern); | |
| glob_t globbuf; | |
| if (glob(path, 0, NULL, &globbuf) != 0) return; | |
| for (size_t i = 0; i < globbuf.gl_pathc; i++) { | |
| const char *src = globbuf.gl_pathv[i]; | |
| const char *filename = strrchr(src, '/'); | |
| filename = filename ? filename + 1 : src; | |
| char obj[BUILD_PATH_MAX]; | |
| snprintf(obj, sizeof(obj), "%s/%s.o", ctx->build_dir, filename); | |
| int compile_needed = 1; | |
| struct stat src_stat, obj_stat; | |
| if (stat(src, &src_stat) == 0 && stat(obj, &obj_stat) == 0) { | |
| if (difftime(src_stat.st_mtime, obj_stat.st_mtime) <= 0) | |
| compile_needed = 0; | |
| } | |
| if (compile_needed) { | |
| BUILD_RUN_CMD(ctx->cc, "-c", src, "-o", obj, ctx->cflags); | |
| } else { | |
| printf("SKIP: %s is up to date\n", obj); | |
| } | |
| } | |
| globfree(&globbuf); | |
| } | |
| static inline void build_add_entry_point(BuildCtx *ctx, const char *entry_c, const char *output) { | |
| if (ctx->target_count < BUILD_MAX_TARGETS) { | |
| ctx->targets[ctx->target_count++] = (BuildTarget){BUILD_TARGET_EXE, entry_c, output}; | |
| } | |
| } | |
| static inline void build_add_static_lib(BuildCtx *ctx, const char *output) { | |
| if (ctx->target_count < BUILD_MAX_TARGETS) { | |
| ctx->targets[ctx->target_count++] = (BuildTarget){BUILD_TARGET_LIB, NULL, output}; | |
| } | |
| } | |
| static inline void build_link_all(BuildCtx *ctx) { | |
| for (int t = 0; t < ctx->target_count; t++) { | |
| BuildTarget *target = &ctx->targets[t]; | |
| char objs[BUILD_CMD_MAX] = {0}; | |
| DIR *d = opendir(ctx->build_dir); | |
| if (!d) continue; | |
| struct dirent *e; | |
| while ((e = readdir(d))) { | |
| if (!strstr(e->d_name, ".o")) continue; | |
| int is_other_entry = 0; | |
| for (int i = 0; i < ctx->target_count; i++) { | |
| if (ctx->targets[i].type != BUILD_TARGET_EXE) continue; | |
| if (!ctx->targets[i].entry) continue; | |
| char expected[BUILD_PATH_MAX]; | |
| snprintf(expected, sizeof(expected), "%s.o", ctx->targets[i].entry); | |
| if (strcmp(e->d_name, expected) == 0) { | |
| if (target->type == BUILD_TARGET_EXE && | |
| strcmp(ctx->targets[i].entry, target->entry) == 0) { | |
| continue; | |
| } else { | |
| is_other_entry = 1; | |
| break; | |
| } | |
| } | |
| } | |
| if (is_other_entry) continue; | |
| char obj_path[BUILD_PATH_MAX]; | |
| snprintf(obj_path, sizeof(obj_path), "%s/%s", ctx->build_dir, e->d_name); | |
| strncat(objs, obj_path, sizeof(objs) - strlen(objs) - 1); | |
| strncat(objs, " ", sizeof(objs) - strlen(objs) - 1); | |
| } | |
| closedir(d); | |
| char out_path[BUILD_PATH_MAX]; | |
| snprintf(out_path, sizeof(out_path), "%s/%s", ctx->build_dir, target->output); | |
| if (target->type == BUILD_TARGET_EXE) { | |
| BUILD_RUN_CMD(ctx->ld, objs, "-o", out_path, ctx->ldflags); | |
| } else if (target->type == BUILD_TARGET_LIB) { | |
| BUILD_RUN_CMD("ar", "rcs", out_path, objs); | |
| } | |
| } | |
| } | |
| #endif // BUILD_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment