#ifndef INCLUDEBUILD_H #define INCLUDEBUILD_H // ---- includes ---- #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #define IB_PATH_SEPARATOR '\\' // Only MSVC uses _popen/_pclose. MinGW/clang typically provide popen/pclose. #if defined(_MSC_VER) #define popen _popen #define pclose _pclose #endif #else #include #include #include #define IB_PATH_SEPARATOR '/' #endif #ifdef __cplusplus extern "C" { #endif // ---- Version information ---- #define IB_VERSION_MAJOR 1 #define IB_VERSION_MINOR 1 #define IB_VERSION_PATCH 1 // ---- Default limits (can be overridden at compile time) ---- #ifndef IB_MAX_PATH #define IB_MAX_PATH 1024 #endif #ifndef IB_MAX_CMD #define IB_MAX_CMD 8192 #endif #ifndef IB_MAX_FILES #define IB_MAX_FILES 512 #endif #ifndef IB_MAX_DEPS #define IB_MAX_DEPS 32 #endif #ifndef IB_MAX_INCLUDE_DIRS #define IB_MAX_INCLUDE_DIRS 64 #endif #ifndef IB_MAX_TARGETS #define IB_MAX_TARGETS 64 #endif #ifndef IB_MAX_LIBRARIES #define IB_MAX_LIBRARIES 128 #endif #ifndef IB_MAX_LIBRARY_PATHS #define IB_MAX_LIBRARY_PATHS 64 #endif // ---- Logging levels ---- typedef enum { IB_LOG_ERROR = 0, IB_LOG_WARN = 1, IB_LOG_INFO = 2, IB_LOG_DEBUG = 3 } ib_log_level; typedef enum { IB_MODE_DEBUG = 0, IB_MODE_RELEASE = 1, IB_MODE_CUSTOM = 2 } ib_build_mode; typedef struct ib_dep { char path[IB_MAX_PATH]; time_t mtime; } ib_dep; typedef struct ib_file { char path[IB_MAX_PATH]; // Path to source file char obj_path[IB_MAX_PATH]; // Path to output object file time_t last_modified; // Cached mtime of source bool is_cpp; // C++ source? int num_deps; // Number of dependencies (headers) ib_dep deps[IB_MAX_DEPS]; // Dependencies } ib_file; typedef struct ib_target { char name[IB_MAX_PATH]; char output_path[IB_MAX_PATH]; char main_source[IB_MAX_PATH]; bool is_library; char cflags[IB_MAX_CMD]; char cxxflags[IB_MAX_CMD]; char linker_flags[IB_MAX_CMD]; } ib_target; typedef struct ib_config { char source_dir[IB_MAX_PATH]; // Source directory root char build_dir[IB_MAX_PATH]; // Output dir for executables char obj_dir[IB_MAX_PATH]; // Object directory char compiler_c[64]; // C compiler char compiler_cpp[64]; // C++ compiler char cflags[IB_MAX_CMD]; char cxxflags[IB_MAX_CMD]; char linker_flags[IB_MAX_CMD]; ib_build_mode build_mode; char debug_cflags[IB_MAX_CMD]; char debug_cxxflags[IB_MAX_CMD]; char release_cflags[IB_MAX_CMD]; char release_cxxflags[IB_MAX_CMD]; char include_dirs[IB_MAX_INCLUDE_DIRS][IB_MAX_PATH]; int num_include_dirs; char exclude_patterns[IB_MAX_FILES][IB_MAX_PATH]; int num_exclude_patterns; char libraries[IB_MAX_LIBRARIES][64]; int num_libraries; char library_paths[IB_MAX_LIBRARY_PATHS][IB_MAX_PATH]; int num_library_paths; // Configuration bool recursive_scanning; bool verbose; bool color_output; ib_log_level log_level; } ib_config; // ---- public API ---- void ib_init(void); void ib_reset_config(void); void ib_set_verbose(bool v); void ib_allow_recursive_scanning(bool allow); void ib_set_log_level(ib_log_level level); void ib_set_cflags(const char* flags); void ib_add_cflags(const char* flags); void ib_set_cxxflags(const char* flags); void ib_add_cxxflags(const char* flags); void ib_set_linker_flags(const char* flags); void ib_add_linker_flags(const char* flags); void ib_add_include_dir(const char* dir); void ib_add_source(const char* path); void ib_exclude_file(const char* pattern); void ib_add_library_path(const char* path); void ib_add_library(const char* library); void ib_add_libraries(const char* first, ...); void ib_add_target(const char* name, const char* main_source); void ib_target_add_cflags(const char* target_name, const char* flags); void ib_target_add_cxxflags(const char* target_name, const char* flags); void ib_target_add_linker_flags(const char* target_name, const char* flags); void ib_set_build_mode(ib_build_mode mode); void ib_set_debug_flags(const char* cflags, const char* cxxflags); void ib_set_release_flags(const char* cflags, const char* cxxflags); void ib_generate_compile_commands(void); bool ib_build(void); void ib_reset_config(void); const char* ib_version(void); void ib_set_run_after_build(bool run_after_build, const char* executable_name); bool ib_run_executable(const char* executable_name); #ifdef __cplusplus } // extern "C" #endif // ============================================================================ // IMPLEMENTATION (define INCLUDEBUILD_IMPLEMENTATION in exactly ONE .c file) // ============================================================================ #ifdef INCLUDEBUILD_IMPLEMENTATION // ANSI colors #define IB_COLOR_RESET "\x1b[0m" #define IB_COLOR_RED "\x1b[31m" #define IB_COLOR_GREEN "\x1b[32m" #define IB_COLOR_YELLOW "\x1b[33m" #define IB_COLOR_CYAN "\x1b[36m" // Global state (single TU) static bool g_run_after_build = false; static char g_executable_name[IB_MAX_PATH] = {0}; static ib_config g_config; static ib_file g_files[IB_MAX_FILES]; static int g_num_files = 0; static ib_target g_targets[IB_MAX_TARGETS]; static int g_num_targets = 0; static bool g_initialized = false; // ---------- internal helpers ---------- static void ib_log_message(ib_log_level level, const char* fmt, ...); static void ib_error(const char* fmt, ...); static void ib_warning(const char* fmt, ...); static bool ib_path_exists(const char* path); static bool ib_is_dir(const char* path); static time_t ib_get_mtime(const char* path); static void ib_mkdir_p(const char* path); static void ib_join_path(char* out, const char* a, const char* b); static void ib_dirname(char* out, const char* path); static bool ib_match_pattern(const char* str, const char* pat); static bool ib_is_excluded(const char* full_path); static void ib_reset_files(void); static void ib_reset_targets(void); static void ib_find_source_files(const char* dir); void ib_add_source(const char* full_path) { if (g_num_files >= IB_MAX_FILES) { ib_error("Too many source files (max %d)", IB_MAX_FILES); return; } // Check availability for(int i=0; ipath, IB_MAX_PATH, "%s", full_path); f->last_modified = ib_get_mtime(full_path); const char* ext = strrchr(full_path, '.'); f->is_cpp = (ext && (strcmp(ext, ".cpp") == 0 || strcmp(ext, ".cc") == 0 || strcmp(ext, ".cxx") == 0)); // object path mirrors relative tree: obj_dir/.o char rel[IB_MAX_PATH] = {0}; const size_t sdlen = strlen(g_config.source_dir); if (sdlen > 0 && strncmp(full_path, g_config.source_dir, sdlen) == 0) { const char* start = full_path + sdlen; if (*start == IB_PATH_SEPARATOR) start++; snprintf(rel, IB_MAX_PATH, "%s", start); } else { const char* base = strrchr(full_path, IB_PATH_SEPARATOR); base = base ? base + 1 : full_path; snprintf(rel, IB_MAX_PATH, "%s", base); } char objrel[IB_MAX_PATH]; snprintf(objrel, IB_MAX_PATH, "%s", rel); char* dot = strrchr(objrel, '.'); if (dot) strcpy(dot, ".o"); else strncat(objrel, ".o", IB_MAX_PATH - strlen(objrel) - 1); char objpath[IB_MAX_PATH]; ib_join_path(objpath, g_config.obj_dir, objrel); // ensure parent dirs exist char parent[IB_MAX_PATH]; ib_dirname(parent, objpath); ib_mkdir_p(parent); snprintf(f->obj_path, IB_MAX_PATH, "%s", objpath); } static void ib_parse_dependencies(ib_file* f); static void ib_parse_deps_recursive(ib_file* owner, const char* path, int depth); static bool ib_needs_rebuild(const ib_file* f); static int ib_run_capture(const char* cmd); static bool ib_compile_file(ib_file* f); // returns success/failure static bool ib_link_target(const ib_target* t); static void ib_add_default_target(void); static bool ib_any_cpp_objects(void); static bool ib_is_main_of_other_target(const char* src_path, const ib_target* current); // ---------- logging ---------- static void ib_log_message(ib_log_level level, const char* fmt, ...) { // always show errors; everything else gated by log_level if (level != IB_LOG_ERROR && level > g_config.log_level) return; const char* prefix = ""; const char* color = ""; switch (level) { case IB_LOG_ERROR: prefix = "[ERROR] "; color = IB_COLOR_RED; break; case IB_LOG_WARN: prefix = "[WARN] "; color = IB_COLOR_YELLOW; break; case IB_LOG_INFO: prefix = "[INFO] "; color = IB_COLOR_GREEN; break; case IB_LOG_DEBUG: prefix = "[DEBUG] "; color = IB_COLOR_CYAN; break; } FILE* out = (level == IB_LOG_ERROR) ? stderr : stdout; if (g_config.color_output) fprintf(out, "%s%s%s", color, prefix, IB_COLOR_RESET); else fprintf(out, "%s", prefix); va_list args; va_start(args, fmt); vfprintf(out, fmt, args); va_end(args); fprintf(out, "\n"); } static void ib_error(const char* fmt, ...) { if (g_config.color_output) fprintf(stderr, "%s[ERROR] %s", IB_COLOR_RED, IB_COLOR_RESET); else fprintf(stderr, "[ERROR] "); va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } static void ib_warning(const char* fmt, ...) { if (g_config.log_level < IB_LOG_WARN) return; if (g_config.color_output) fprintf(stderr, "%s[WARN] %s", IB_COLOR_YELLOW, IB_COLOR_RESET); else fprintf(stderr, "[WARN] "); va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } // ---------- fs/path ---------- static bool ib_path_exists(const char* path) { struct stat st; return stat(path, &st) == 0; } static bool ib_is_dir(const char* path) { struct stat st; if (stat(path, &st) != 0) return false; #ifdef _WIN32 return (st.st_mode & _S_IFDIR) != 0; #else return S_ISDIR(st.st_mode); #endif } static time_t ib_get_mtime(const char* path) { struct stat st; if (stat(path, &st) != 0) return 0; return st.st_mtime; } static void ib_join_path(char* out, const char* a, const char* b) { if (!a || !a[0]) { snprintf(out, IB_MAX_PATH, "%s", b ? b : ""); return; } if (!b || !b[0]) { snprintf(out, IB_MAX_PATH, "%s", a); return; } size_t alen = strlen(a); bool a_sep = (a[alen - 1] == IB_PATH_SEPARATOR); bool b_sep = (b[0] == IB_PATH_SEPARATOR); if (a_sep && b_sep) snprintf(out, IB_MAX_PATH, "%.*s%s", (int)(alen - 1), a, b); else if (!a_sep && !b_sep) snprintf(out, IB_MAX_PATH, "%s%c%s", a, IB_PATH_SEPARATOR, b); else snprintf(out, IB_MAX_PATH, "%s%s", a, b); } static void ib_dirname(char* out, const char* path) { snprintf(out, IB_MAX_PATH, "%s", path); char* last = strrchr(out, IB_PATH_SEPARATOR); if (!last) { strcpy(out, "."); return; } // handle root like "/" -> keep "/" if (last == out) { out[1] = '\0'; return; } *last = '\0'; } static void ib_mkdir_single(const char* path) { #ifdef _WIN32 if (_mkdir(path) != 0 && errno != EEXIST) { ib_error("Failed to create dir: %s (%s)", path, strerror(errno)); exit(1); } #else if (mkdir(path, 0755) != 0 && errno != EEXIST) { ib_error("Failed to create dir: %s (%s)", path, strerror(errno)); exit(1); } #endif } static void ib_mkdir_p(const char* path) { if (!path || !path[0]) return; if (ib_path_exists(path)) return; char tmp[IB_MAX_PATH]; snprintf(tmp, IB_MAX_PATH, "%s", path); // normalize trailing separator size_t n = strlen(tmp); while (n > 1 && (tmp[n - 1] == IB_PATH_SEPARATOR)) { tmp[n - 1] = '\0'; n--; } for (char* p = tmp + 1; *p; p++) { if (*p == IB_PATH_SEPARATOR) { *p = '\0'; if (!ib_path_exists(tmp)) ib_mkdir_single(tmp); *p = IB_PATH_SEPARATOR; } } if (!ib_path_exists(tmp)) ib_mkdir_single(tmp); } // simple wildcard matcher: * and ? static bool ib_match_pattern(const char* str, const char* pat) { if (!pat || !pat[0]) return (!str || !str[0]); const char* s = str; const char* p = pat; const char* star = NULL; const char* ss = NULL; while (*s) { if (*p == '?' || *p == *s) { p++; s++; continue; } if (*p == '*') { star = p++; ss = s; continue; } if (star) { p = star + 1; s = ++ss; continue; } return false; } while (*p == '*') p++; return *p == '\0'; } static const char* ib_basename_ptr(const char* path) { if (!path) return ""; const char* a = strrchr(path, '/'); const char* b = strrchr(path, '\\'); const char* m = a > b ? a : b; return m ? (m + 1) : path; } static bool ib_is_excluded(const char* full_path) { const char* base = ib_basename_ptr(full_path); // skip common dirs/files if (strcmp(base, ".git") == 0 || strcmp(base, ".svn") == 0) return true; if (strcmp(base, "lib") == 0) return true; if (strcmp(base, "build") == 0) return true; if (strcmp(base, "cmake-build-debug") == 0) return true; if (strcmp(base, "cmake-build-release") == 0) return true; // skip our object dir root by basename (handles "build/obj" correctly) if (g_config.obj_dir[0]) { const char* objbase = ib_basename_ptr(g_config.obj_dir); if (objbase[0] && strcmp(base, objbase) == 0) return true; } // user patterns: match against full path AND basename for (int i = 0; i < g_config.num_exclude_patterns; i++) { const char* pat = g_config.exclude_patterns[i]; if (ib_match_pattern(full_path, pat) || ib_match_pattern(base, pat)) return true; // convenience: allow substring-like patterns without wildcards if (!strchr(pat, '*') && !strchr(pat, '?') && strstr(full_path, pat)) return true; } return false; } static void ib_reset_files(void) { g_num_files = 0; memset(g_files, 0, sizeof(g_files)); } static void ib_reset_targets(void) { g_num_targets = 0; memset(g_targets, 0, sizeof(g_targets)); } // ---------- public ---------- void ib_init(void) { if (g_initialized) { ib_error("IncludeBuild already initialized"); return; } memset(&g_config, 0, sizeof(g_config)); strcpy(g_config.source_dir, "."); strcpy(g_config.build_dir, "."); strcpy(g_config.obj_dir, "buildobjects"); #ifdef _WIN32 strcpy(g_config.compiler_c, "gcc"); strcpy(g_config.compiler_cpp, "g++"); #else strcpy(g_config.compiler_c, "cc"); strcpy(g_config.compiler_cpp, "c++"); #endif strcpy(g_config.cflags, ""); strcpy(g_config.cxxflags, ""); g_config.linker_flags[0] = '\0'; strcpy(g_config.debug_cflags, "-g -O0 -DDEBUG"); strcpy(g_config.debug_cxxflags, "-g -O0 -DDEBUG"); strcpy(g_config.release_cflags, "-O3 -DNDEBUG"); strcpy(g_config.release_cxxflags, "-O3 -DNDEBUG"); // Default to debug mode g_config.build_mode = IB_MODE_DEBUG; g_config.recursive_scanning = true; g_config.verbose = false; g_config.color_output = true; g_config.log_level = IB_LOG_INFO; // include current dir by default strcpy(g_config.include_dirs[0], "."); g_config.num_include_dirs = 1; g_initialized = true; ib_log_message(IB_LOG_INFO, "IncludeBuild v%d.%d.%d initialized", IB_VERSION_MAJOR, IB_VERSION_MINOR, IB_VERSION_PATCH); } void ib_reset_config(void) { memset(&g_config, 0, sizeof(g_config)); ib_reset_targets(); ib_reset_files(); g_initialized = false; } void ib_set_verbose(bool v) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } g_config.verbose = v; } void ib_allow_recursive_scanning(bool allow) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } g_config.recursive_scanning = allow; } void ib_set_log_level(ib_log_level level) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } g_config.log_level = level; } void ib_set_cflags(const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!flags) return; snprintf(g_config.cflags, IB_MAX_CMD, "%s", flags); } void ib_add_cflags(const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!flags || !flags[0]) return; strncat(g_config.cflags, " ", IB_MAX_CMD - strlen(g_config.cflags) - 1); strncat(g_config.cflags, flags, IB_MAX_CMD - strlen(g_config.cflags) - 1); } void ib_set_cxxflags(const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!flags) return; snprintf(g_config.cxxflags, IB_MAX_CMD, "%s", flags); } void ib_add_cxxflags(const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!flags || !flags[0]) return; strncat(g_config.cxxflags, " ", IB_MAX_CMD - strlen(g_config.cxxflags) - 1); strncat(g_config.cxxflags, flags, IB_MAX_CMD - strlen(g_config.cxxflags) - 1); } void ib_set_linker_flags(const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!flags) return; snprintf(g_config.linker_flags, IB_MAX_CMD, "%s", flags); } void ib_add_linker_flags(const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!flags || !flags[0]) return; strncat(g_config.linker_flags, " ", IB_MAX_CMD - strlen(g_config.linker_flags) - 1); strncat(g_config.linker_flags, flags, IB_MAX_CMD - strlen(g_config.linker_flags) - 1); } void ib_add_include_dir(const char* dir) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!dir || !dir[0]) { ib_error("Invalid include dir"); return; } if (g_config.num_include_dirs >= IB_MAX_INCLUDE_DIRS) { ib_error("Too many include dirs (max %d)", IB_MAX_INCLUDE_DIRS); return; } if (!ib_path_exists(dir)) { ib_warning("Include dir does not exist: %s", dir); } snprintf(g_config.include_dirs[g_config.num_include_dirs++], IB_MAX_PATH, "%s", dir); } void ib_exclude_file(const char* pattern) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!pattern || !pattern[0]) return; if (g_config.num_exclude_patterns >= IB_MAX_FILES) { ib_error("Too many exclude patterns"); return; } snprintf(g_config.exclude_patterns[g_config.num_exclude_patterns++], IB_MAX_PATH, "%s", pattern); } void ib_add_library_path(const char* path) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!path || !path[0]) return; if (g_config.num_library_paths >= IB_MAX_LIBRARY_PATHS) { ib_error("Too many library paths"); return; } snprintf(g_config.library_paths[g_config.num_library_paths++], IB_MAX_PATH, "%s", path); // GCC/Clang style (quotes are ok for paths with spaces) strncat(g_config.linker_flags, " -L\"", IB_MAX_CMD - strlen(g_config.linker_flags) - 1); strncat(g_config.linker_flags, path, IB_MAX_CMD - strlen(g_config.linker_flags) - 1); strncat(g_config.linker_flags, "\"", IB_MAX_CMD - strlen(g_config.linker_flags) - 1); } void ib_add_library(const char* library) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!library || !library[0]) return; if (g_config.num_libraries >= IB_MAX_LIBRARIES) { ib_error("Too many libraries"); return; } snprintf(g_config.libraries[g_config.num_libraries++], 64, "%s", library); strncat(g_config.linker_flags, " -l", IB_MAX_CMD - strlen(g_config.linker_flags) - 1); strncat(g_config.linker_flags, library, IB_MAX_CMD - strlen(g_config.linker_flags) - 1); } void ib_add_libraries(const char* first, ...) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!first || !first[0]) return; ib_add_library(first); va_list args; va_start(args, first); const char* lib = NULL; while ((lib = va_arg(args, const char*)) != NULL) { ib_add_library(lib); } va_end(args); } void ib_target_add_cflags(const char* target_name, const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!target_name || !flags) return; for (int i = 0; i < g_num_targets; i++) { if (strcmp(g_targets[i].name, target_name) == 0) { strncat(g_targets[i].cflags, " ", IB_MAX_CMD - strlen(g_targets[i].cflags) - 1); strncat(g_targets[i].cflags, flags, IB_MAX_CMD - strlen(g_targets[i].cflags) - 1); return; } } ib_error("Target not found: %s", target_name); } void ib_target_add_cxxflags(const char* target_name, const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!target_name || !flags) return; for (int i = 0; i < g_num_targets; i++) { if (strcmp(g_targets[i].name, target_name) == 0) { strncat(g_targets[i].cxxflags, " ", IB_MAX_CMD - strlen(g_targets[i].cxxflags) - 1); strncat(g_targets[i].cxxflags, flags, IB_MAX_CMD - strlen(g_targets[i].cxxflags) - 1); return; } } ib_error("Target not found: %s", target_name); } void ib_target_add_linker_flags(const char* target_name, const char* flags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!target_name || !flags) return; for (int i = 0; i < g_num_targets; i++) { if (strcmp(g_targets[i].name, target_name) == 0) { strncat(g_targets[i].linker_flags, " ", IB_MAX_CMD - strlen(g_targets[i].linker_flags) - 1); strncat(g_targets[i].linker_flags, flags, IB_MAX_CMD - strlen(g_targets[i].linker_flags) - 1); return; } } ib_error("Target not found: %s", target_name); } void ib_set_build_mode(ib_build_mode mode) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } g_config.build_mode = mode; } void ib_set_debug_flags(const char* cflags, const char* cxxflags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (cflags) snprintf(g_config.debug_cflags, IB_MAX_CMD, "%s", cflags); if (cxxflags) snprintf(g_config.debug_cxxflags, IB_MAX_CMD, "%s", cxxflags); } void ib_set_release_flags(const char* cflags, const char* cxxflags) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (cflags) snprintf(g_config.release_cflags, IB_MAX_CMD, "%s", cflags); if (cxxflags) snprintf(g_config.release_cxxflags, IB_MAX_CMD, "%s", cxxflags); } void ib_generate_compile_commands(void) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } FILE* fp = fopen("compile_commands.json", "w"); if (!fp) { ib_error("Failed to open compile_commands.json for writing"); return; } fprintf(fp, "[\n"); char abs_source_dir[IB_MAX_PATH]; #ifdef _WIN32 if (!_fullpath(abs_source_dir, g_config.source_dir, IB_MAX_PATH)) #else if (!realpath(g_config.source_dir, abs_source_dir)) #endif { strcpy(abs_source_dir, g_config.source_dir); } // We need to know which target each file belongs to to get per-target flags. // This is imperfect because one file could belong to multiple targets. // We'll just use the first target that uses it, or global flags if none specific. // Improving this requires a mapping of file -> target, or iterating targets then files. // For now, let's iterate files and make a best guess or use global flags. // Actually, accurate compile_commands needs the exact flags used. // Since ib_compile_file logic computes flags on the fly, we should duplicate that logic here. for (int i = 0; i < g_num_files; i++) { ib_file* f = &g_files[i]; // determine flags (simplified logic matching ib_compile_file) const char* cc = f->is_cpp ? g_config.compiler_cpp : g_config.compiler_c; const char* mode_flags = ""; if (g_config.build_mode == IB_MODE_DEBUG) { mode_flags = f->is_cpp ? g_config.debug_cxxflags : g_config.debug_cflags; } else if (g_config.build_mode == IB_MODE_RELEASE) { mode_flags = f->is_cpp ? g_config.release_cxxflags : g_config.release_cflags; } const char* user_flags = f->is_cpp ? g_config.cxxflags : g_config.cflags; // Include paths char inc_flags[IB_MAX_CMD] = {0}; for (int k = 0; k < g_config.num_include_dirs; k++) { char tmp[IB_MAX_PATH + 8]; snprintf(tmp, sizeof(tmp), " -I\\\"%s\\\"", g_config.include_dirs[k]); strncat(inc_flags, tmp, sizeof(inc_flags) - strlen(inc_flags) - 1); } fprintf(fp, " {\n"); fprintf(fp, " \"directory\": \"%s\",\n", abs_source_dir); fprintf(fp, " \"command\": \"%s %s %s%s -c \\\"%s\\\" -o \\\"%s\\\"\",\n", cc, mode_flags, user_flags, inc_flags, f->path, f->obj_path); fprintf(fp, " \"file\": \"%s\"\n", f->path); fprintf(fp, " }%s\n", (i < g_num_files - 1) ? "," : ""); } fprintf(fp, "]\n"); fclose(fp); ib_log_message(IB_LOG_INFO, "Generated compile_commands.json"); } void ib_add_target(const char* name, const char* main_source) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (g_num_targets >= IB_MAX_TARGETS) { ib_error("Too many targets"); return; } ib_target* t = &g_targets[g_num_targets++]; memset(t, 0, sizeof(*t)); // if caller provided a name, use it if (name && name[0]) { snprintf(t->name, IB_MAX_PATH, "%s", name); } if (main_source && main_source[0]) { snprintf(t->main_source, IB_MAX_PATH, "%s", main_source); // If name wasn't provided, derive from filename if (!t->name[0]) { const char* fn = strrchr(main_source, IB_PATH_SEPARATOR); fn = fn ? fn + 1 : main_source; snprintf(t->name, IB_MAX_PATH, "%s", fn); char* dot = strrchr(t->name, '.'); if (dot) *dot = '\0'; } } if (!t->name[0]) snprintf(t->name, IB_MAX_PATH, "app"); ib_join_path(t->output_path, g_config.build_dir, t->name); #ifdef _WIN32 strncat(t->output_path, ".exe", IB_MAX_PATH - strlen(t->output_path) - 1); #endif t->is_library = false; // Convenience: ensure main source is in file list if not recursive scanning or just missing if (main_source && main_source[0] && ib_is_dir(g_config.source_dir)) { // Construct full path to check exclusion or addition char full[IB_MAX_PATH]; ib_join_path(full, g_config.source_dir, main_source); // If we heavily rely on this, we might duplicate if main_source is already added. // But since we can't easily check duplication without iterating, let's just add it. // Duplicate files are bad though. // Let's rely on user adding it via ib_add_source OR recursive scanning. // But for convenience, if scanning is OFF, we probably WANT to add it. if (!g_config.recursive_scanning) { ib_add_source(full); } } } static bool ib_any_cpp_objects(void) { for (int i = 0; i < g_num_files; i++) { if (g_files[i].path[0] && g_files[i].is_cpp) return true; } return false; } static bool ib_is_main_of_other_target(const char* src_path, const ib_target* current) { for (int i = 0; i < g_num_targets; i++) { const ib_target* t = &g_targets[i]; if (t == current) continue; if (t->main_source[0] && strcmp(t->main_source, src_path) == 0) return true; } return false; } static int ib_run_capture(const char* cmd) { if (g_config.verbose) ib_log_message(IB_LOG_INFO, " Command: %s", cmd); FILE* proc = popen(cmd, "r"); if (!proc) { ib_error("Failed to run: %s", cmd); return -1; } char buf[1024]; while (fgets(buf, sizeof(buf), proc)) { if (g_config.verbose) fputs(buf, stdout); } int rc = pclose(proc); #ifndef _WIN32 if (rc != -1) { if (WIFEXITED(rc)) rc = WEXITSTATUS(rc); else rc = 1; } #endif return rc; } #ifdef _WIN32 static void ib_find_source_files_win32(const char* dir) { char pattern[IB_MAX_PATH]; snprintf(pattern, IB_MAX_PATH, "%s\\*", dir); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(pattern, &fd); if (h == INVALID_HANDLE_VALUE) return; do { const char* name = fd.cFileName; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; char path[IB_MAX_PATH]; ib_join_path(path, dir, name); if (ib_is_excluded(path)) continue; if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { ib_find_source_files_win32(path); } else { const char* ext = strrchr(name, '.'); if (!ext) continue; if (strcmp(name, "build.c") == 0 || strcmp(name, "build.cpp") == 0) continue; if (strcmp(ext, ".c") == 0 || strcmp(ext, ".cpp") == 0 || strcmp(ext, ".cc") == 0 || strcmp(ext, ".cxx") == 0) { if (!ib_is_excluded(path)) ib_add_source(path); } } } while (FindNextFileA(h, &fd)); FindClose(h); } #endif static void ib_find_source_files(const char* dir) { #ifdef _WIN32 ib_find_source_files_win32(dir); #else DIR* d = opendir(dir); if (!d) { ib_error("Failed to open dir: %s", dir); return; } struct dirent* ent; while ((ent = readdir(d)) != NULL) { const char* name = ent->d_name; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; char path[IB_MAX_PATH]; ib_join_path(path, dir, name); if (ib_is_excluded(path)) continue; if (ib_is_dir(path)) { ib_find_source_files(path); continue; } const char* ext = strrchr(name, '.'); if (!ext) continue; if (strcmp(name, "build.c") == 0 || strcmp(name, "build.cpp") == 0) continue; if (strcmp(ext, ".c") == 0 || strcmp(ext, ".cpp") == 0 || strcmp(ext, ".cc") == 0 || strcmp(ext, ".cxx") == 0) { if (!ib_is_excluded(path)) ib_add_source(path); } } closedir(d); #endif } static void ib_add_dep(ib_file* owner, const char* dep_path) { if (owner->num_deps >= IB_MAX_DEPS) return; for (int i = 0; i < owner->num_deps; i++) { if (strcmp(owner->deps[i].path, dep_path) == 0) return; } snprintf(owner->deps[owner->num_deps].path, IB_MAX_PATH, "%s", dep_path); owner->deps[owner->num_deps].mtime = ib_get_mtime(dep_path); owner->num_deps++; } static void ib_parse_deps_recursive(ib_file* owner, const char* path, int depth) { if (depth > 32) return; FILE* fp = fopen(path, "r"); if (!fp) return; char line[IB_MAX_PATH]; while (fgets(line, sizeof(line), fp)) { char* p = strstr(line, "#include \""); if (!p) continue; p += (int)strlen("#include \""); char* q = strchr(p, '"'); if (!q) continue; *q = '\0'; for (int i = 0; i < g_config.num_include_dirs; i++) { char cand[IB_MAX_PATH]; ib_join_path(cand, g_config.include_dirs[i], p); if (ib_path_exists(cand)) { ib_add_dep(owner, cand); ib_parse_deps_recursive(owner, cand, depth + 1); break; } } } fclose(fp); } static void ib_parse_dependencies(ib_file* f) { f->num_deps = 0; char filedir[IB_MAX_PATH]; ib_dirname(filedir, f->path); FILE* fp = fopen(f->path, "r"); if (!fp) { ib_error("Failed to open: %s", f->path); return; } char line[IB_MAX_PATH]; while (fgets(line, sizeof(line), fp)) { char* p = strstr(line, "#include \""); if (!p) continue; p += (int)strlen("#include \""); char* q = strchr(p, '"'); if (!q) continue; *q = '\0'; char relcand[IB_MAX_PATH]; ib_join_path(relcand, filedir, p); if (ib_path_exists(relcand)) { ib_add_dep(f, relcand); ib_parse_deps_recursive(f, relcand, 1); continue; } for (int i = 0; i < g_config.num_include_dirs; i++) { char cand[IB_MAX_PATH]; ib_join_path(cand, g_config.include_dirs[i], p); if (ib_path_exists(cand)) { ib_add_dep(f, cand); ib_parse_deps_recursive(f, cand, 1); break; } } } fclose(fp); } static bool ib_needs_rebuild(const ib_file* f) { if (!ib_path_exists(f->obj_path)) return true; time_t obj_m = ib_get_mtime(f->obj_path); time_t src_m = ib_get_mtime(f->path); if (src_m > obj_m) return true; for (int i = 0; i < f->num_deps; i++) { time_t dm = ib_get_mtime(f->deps[i].path); if (dm > obj_m) return true; } return false; } static bool ib_compile_file(ib_file* f) { ib_log_message(IB_LOG_INFO, "Compiling %s", f->path); char inc[IB_MAX_CMD] = {0}; for (int i = 0; i < g_config.num_include_dirs; i++) { char tmp[IB_MAX_PATH + 8]; snprintf(tmp, sizeof(tmp), " -I\"%s\"", g_config.include_dirs[i]); if (strlen(inc) + strlen(tmp) + 1 < sizeof(inc)) { strncat(inc, tmp, sizeof(inc) - strlen(inc) - 1); } else { ib_warning("Include flags too long; truncating"); break; } } const char* cc = f->is_cpp ? g_config.compiler_cpp : g_config.compiler_c; const char* user_flags = f->is_cpp ? g_config.cxxflags : g_config.cflags; const char* mode_flags = ""; if (g_config.build_mode == IB_MODE_DEBUG) { mode_flags = f->is_cpp ? g_config.debug_cxxflags : g_config.debug_cflags; } else if (g_config.build_mode == IB_MODE_RELEASE) { mode_flags = f->is_cpp ? g_config.release_cxxflags : g_config.release_cflags; } char cmd[IB_MAX_CMD]; snprintf(cmd, sizeof(cmd), "%s %s %s%s -c \"%s\" -o \"%s\"", cc, mode_flags, user_flags, inc, f->path, f->obj_path); int rc = ib_run_capture(cmd); if (rc != 0) { ib_error("Compilation failed (%d): %s", rc, f->path); return false; } return true; } static bool ib_link_target(const ib_target* t) { ib_log_message(IB_LOG_INFO, "Linking %s", t->name); const char* ld = ib_any_cpp_objects() ? g_config.compiler_cpp : g_config.compiler_c; char objs[IB_MAX_CMD] = {0}; for (int i = 0; i < g_num_files; i++) { if (!g_files[i].path[0]) continue; if (g_num_targets > 1 && ib_is_main_of_other_target(g_files[i].path, t)) { continue; } char tmp[IB_MAX_PATH + 4]; snprintf(tmp, sizeof(tmp), " \"%s\"", g_files[i].obj_path); if (strlen(objs) + strlen(tmp) + 1 < sizeof(objs)) { strncat(objs, tmp, sizeof(objs) - strlen(objs) - 1); } else { ib_error("Object list too long"); return false; } } char cmd[IB_MAX_CMD]; snprintf(cmd, sizeof(cmd), "%s -o \"%s\"%s %s %s %s", ld, t->output_path, objs, g_config.linker_flags, t->linker_flags, // Hack: include cflags/cxxflags in linker command if they contain -L/-l? // Usually linker flags are sufficient. ""); int rc = ib_run_capture(cmd); if (rc != 0) { ib_error("Link failed (%d): %s", rc, t->name); return false; } ib_log_message(IB_LOG_INFO, "Created %s", t->output_path); return true; } static void ib_add_default_target(void) { const char* candidates[] = {"main.c", "main.cpp", "Main.c", "Main.cpp", NULL}; char found[IB_MAX_PATH] = {0}; for (const char** c = candidates; *c; c++) { char p[IB_MAX_PATH]; ib_join_path(p, g_config.source_dir, *c); if (ib_path_exists(p)) { snprintf(found, IB_MAX_PATH, "%s", p); break; } } if (found[0]) { ib_add_target("app", found); return; } ib_add_target("app", ""); } bool ib_build(void) { if (!g_initialized) { ib_error("Call ib_init() first."); return false; } ib_log_message(IB_LOG_INFO, "Building project..."); // If recursive scanning is enabled, finding files will Add them. // If not, we rely on manual additions. // ISSUE: ib_reset_files() clears manual additions if called here. // Solution: Move ib_reset_files() to ib_init()? Or only call if scanning? // Proper behavior: ib_build() builds the current state. // If scanning is on, it SHOULD find files. Does it replace or append? // It appends. So we should NOT clear files if we want to support manual add BEFORE build. // But then calling ib_build twice duplicates files. // Let's rely on g_num_files. // We only reset if we intend to rescan everything. if (g_config.recursive_scanning) { ib_reset_files(); ib_find_source_files(g_config.source_dir); } if (g_num_files == 0) { ib_error("No source files found"); return false; } if (g_num_targets == 0) ib_add_default_target(); for (int i = 0; i < g_num_files; i++) ib_parse_dependencies(&g_files[i]); int compiled = 0; bool ok = true; for (int i = 0; i < g_num_files; i++) { if (ib_needs_rebuild(&g_files[i])) { if (!ib_compile_file(&g_files[i])) ok = false; else compiled++; } } if (!ok) { ib_error("Build failed: compilation errors."); return false; } for (int i = 0; i < g_num_targets; i++) { if (!ib_link_target(&g_targets[i])) return false; } ib_log_message(IB_LOG_INFO, "Build complete. Compiled %d file(s).", compiled); if (g_run_after_build) { const char* exe = g_executable_name; if ((!exe || !exe[0]) && g_num_targets > 0) exe = g_targets[0].output_path; if (exe && exe[0]) ib_run_executable(exe); } return true; } void ib_clean(void) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } if (!ib_path_exists(g_config.obj_dir) || !ib_is_dir(g_config.obj_dir)) { ib_log_message(IB_LOG_INFO, "No obj dir to clean: %s", g_config.obj_dir); return; } ib_log_message(IB_LOG_INFO, "Cleaning: %s", g_config.obj_dir); char cmd[IB_MAX_CMD]; #ifdef _WIN32 // Works on standard Windows shells snprintf(cmd, sizeof(cmd), "cmd /c rmdir /s /q \"%s\"", g_config.obj_dir); #else snprintf(cmd, sizeof(cmd), "rm -rf \"%s\"", g_config.obj_dir); #endif (void)system(cmd); ib_mkdir_p(g_config.obj_dir); } void ib_set_run_after_build(bool run_after_build, const char* executable_name) { if (!g_initialized) { ib_error("Call ib_init() first."); return; } g_run_after_build = run_after_build; if (executable_name && executable_name[0]) { snprintf(g_executable_name, IB_MAX_PATH, "%s", executable_name); } else { g_executable_name[0] = '\0'; } ib_log_message(IB_LOG_INFO, "Run after build: %s", run_after_build ? "enabled" : "disabled"); } bool ib_run_executable(const char* executable_name) { if (!g_initialized) { ib_error("Call ib_init() first."); return false; } if (!executable_name || !executable_name[0]) { ib_error("No executable name"); return false; } if (!ib_path_exists(executable_name)) { ib_error("Executable not found: %s", executable_name); return false; } ib_log_message(IB_LOG_INFO, "Running: %s", executable_name); char cmd[IB_MAX_CMD]; #ifdef _WIN32 snprintf(cmd, sizeof(cmd), "\"%s\"", executable_name); return system(cmd) == 0; #else // Linux-style; if you care about macOS, you’d also want DYLD_LIBRARY_PATH snprintf(cmd, sizeof(cmd), "LD_LIBRARY_PATH=\"$(pwd)/lib:$LD_LIBRARY_PATH\" \"%s\"", executable_name); return system(cmd) == 0; #endif } const char* ib_version(void) { static char v[32] = {0}; if (!v[0]) snprintf(v, sizeof(v), "%d.%d.%d", IB_VERSION_MAJOR, IB_VERSION_MINOR, IB_VERSION_PATCH); return v; } #endif // INCLUDEBUILD_IMPLEMENTATION #endif // INCLUDEBUILD_H