diff options
| author | hachem <im@hachem.wtf> | 2026-09-07 23:35:54 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-07 23:35:54 +0200 |
| commit | 95a0f895d9a828a326a0c2b0f80e16028023a97b (patch) | |
| tree | 4fa50164b7764e5a59618d7c037b59eb46f6666a | |
| parent | 774b625c781a563e92224624f757b1e979f73d23 (diff) | |
feat: support other build systems
| -rw-r--r-- | mkproj.c | 325 |
1 files changed, 291 insertions, 34 deletions
@@ -54,6 +54,8 @@ struct Build_Info { const char *key; const char *name; + const char *default_bin; + const char *default_bin_int; }; struct Config @@ -127,10 +129,26 @@ static const struct Language_Info languages[language_count] = static const struct Build_Info build_systems[build_system_count] = { - [build_make] = { .key = "make", .name = "Make" }, - [build_cmake] = { .key = "cmake", .name = "CMake" }, - [build_meson] = { .key = "meson", .name = "Meson" }, - [build_premake] = { .key = "premake", .name = "premake5" }, + [build_make] = + { + .key = "make", .name = "Make", + .default_bin = "bin", .default_bin_int = "bin-int", + }, + [build_cmake] = + { + .key = "cmake", .name = "CMake", + .default_bin = "build", .default_bin_int = "build", + }, + [build_meson] = + { + .key = "meson", .name = "Meson", + .default_bin = "builddir", .default_bin_int = "builddir", + }, + [build_premake] = + { + .key = "premake", .name = "premake5", + .default_bin = "bin", .default_bin_int = "bin-int", + }, }; static void print_version(void) @@ -157,8 +175,8 @@ static void print_usage(FILE *stream) " -b, --build SYSTEM build system: make, cmake, meson, premake (default make)\n" " --no-git don't initialize a git repository\n" " --intermediate use a separate intermediate object directory\n" - " --bin DIR output directory (default bin)\n" - " --bin-int DIR object dir; implies --intermediate (default bin-int)\n" + " --bin DIR build directory (default per build system)\n" + " --bin-int DIR separate object dir for make/premake; implies --intermediate\n" " --no-clangd don't ignore clangd files in .gitignore\n" " -v, --version print version and bail\n" " -h, --help print this and bail\n" @@ -222,6 +240,8 @@ static int parse_args(int argc, char **argv, struct Config *config) const char *lang_arg = NULL; const char *std_arg = NULL; const char *build_arg = NULL; + const char *bin_arg = NULL; + const char *bin_int_arg = NULL; const char *name = NULL; for (int i = 1; i < argc; i++) @@ -266,7 +286,7 @@ static int parse_args(int argc, char **argv, struct Config *config) return 1; } - config->bin_dir = argv[i]; + bin_arg = argv[i]; continue; } @@ -278,7 +298,7 @@ static int parse_args(int argc, char **argv, struct Config *config) return 1; } - config->bin_int_dir = argv[i]; + bin_int_arg = argv[i]; config->use_intermediate = 1; continue; } @@ -369,6 +389,21 @@ static int parse_args(int argc, char **argv, struct Config *config) config->standard = languages[config->language].default_standard; } + if (config->use_intermediate + && config->build != build_make + && config->build != build_premake) + { + error("--intermediate/--bin-int only apply to make and premake", + build_systems[config->build].key); + return 1; + } + + config->bin_dir = + bin_arg != NULL ? bin_arg : build_systems[config->build].default_bin; + config->bin_int_dir = + bin_int_arg != NULL ? bin_int_arg + : build_systems[config->build].default_bin_int; + return 0; } @@ -475,22 +510,10 @@ static void write_main(FILE *out, const struct Config *config) (void) fputs(main_source(config), out); } -static void write_makefile(FILE *out, const struct Config *config) +/* fill flags with the shared warning + debug flags and return the count. + * every generator reuses these so all four build systems compile alike. */ +static size_t base_flags(const struct Config *config, const char *flags[]) { - const struct Language_Info *lang = &languages[config->language]; - const char *ext = lang->extension; - const char *obj_dir = - config->use_intermediate ? config->bin_int_dir : config->bin_dir; - - char clean[2 * PATH_MAX + 16]; - - if (config->use_intermediate) - (void) snprintf(clean, sizeof clean, "rm -rf %s %s", - config->bin_dir, config->bin_int_dir); - else - (void) snprintf(clean, sizeof clean, "rm -rf %s", config->bin_dir); - - const char *flags[10]; size_t count = 0; flags[count++] = "-Wall"; @@ -509,6 +532,38 @@ static void write_makefile(FILE *out, const struct Config *config) flags[count++] = "-g"; flags[count++] = "-fno-omit-frame-pointer"; + return count; +} + +static const char *cmake_language(enum Language language) +{ + if (language == language_cpp) + return "CXX"; + + if (language == language_objc) + return "OBJC"; + + return "C"; +} + +static void write_makefile(FILE *out, const struct Config *config) +{ + const struct Language_Info *lang = &languages[config->language]; + const char *ext = lang->extension; + const char *obj_dir = + config->use_intermediate ? config->bin_int_dir : config->bin_dir; + + char clean[2 * PATH_MAX + 16]; + + if (config->use_intermediate) + (void) snprintf(clean, sizeof clean, "rm -rf %s %s", + config->bin_dir, config->bin_int_dir); + else + (void) snprintf(clean, sizeof clean, "rm -rf %s", config->bin_dir); + + const char *flags[10]; + size_t count = base_flags(config, flags); + (void) fprintf(out, "CC := %s\n\nSANITIZE ?= 1\n\nCFLAGS := ", lang->compiler); for (size_t i = 0; i < count; i++) @@ -553,13 +608,185 @@ static void write_makefile(FILE *out, const struct Config *config) obj_dir, obj_dir, ext, ext, clean); } +static void write_cmake(FILE *out, const struct Config *config) +{ + const char *name = config->name; + const char *ext = languages[config->language].extension; + const char *flags[10]; + size_t count = base_flags(config, flags); + + (void) fprintf( + out, + "cmake_minimum_required(VERSION 3.16)\n" + "\n" + "project(%s %s)\n" + "\n" + "option(SANITIZE \"build with asan + ubsan\" ON)\n" + "\n" + "file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*.%s)\n" + "\n" + "add_executable(%s ${SOURCES})\n" + "\n" + "target_include_directories(%s PRIVATE src)\n" + "\n" + "target_compile_options(%s PRIVATE\n", + name, cmake_language(config->language), + ext, name, name, name); + + for (size_t i = 0; i < count; i++) + (void) fprintf(out, " %s\n", flags[i]); + + (void) fprintf(out, " -std=%s)\n", config->standard); + + if (config->language == language_objc) + (void) fprintf( + out, + "\n" + "target_link_libraries(%s PRIVATE \"-framework Foundation\")\n", + name); + + (void) fprintf( + out, + "\n" + "if(SANITIZE)\n" + " target_compile_options(%s PRIVATE -fsanitize=address,undefined)\n" + " target_link_options(%s PRIVATE -fsanitize=address,undefined)\n" + "endif()\n", + name, name); +} + +static const char *meson_language(enum Language language) +{ + if (language == language_cpp) + return "cpp"; + + if (language == language_objc) + return "objc"; + + return "c"; +} + +static void write_meson(FILE *out, const struct Config *config) +{ + const char *name = config->name; + const char *ext = languages[config->language].extension; + const char *lang = meson_language(config->language); + const char *flags[10]; + size_t count = base_flags(config, flags); + + (void) fprintf( + out, + "project('%s', '%s',\n" + " default_options: [\n" + " 'b_sanitize=address,undefined',\n" + " 'b_lundef=false',\n" + " ])\n" + "\n" + "sources = run_command(\n" + " 'find', 'src', '-name', '*.%s',\n" + " check: true).stdout().strip().split('\\n')\n" + "\n" + "add_project_arguments(\n", + name, lang, ext); + + for (size_t i = 0; i < count; i++) + (void) fprintf(out, " '%s',\n", flags[i]); + + (void) fprintf( + out, + " '-std=%s',\n" + " language: '%s')\n" + "\n" + "inc = include_directories('src')\n" + "\n", + config->standard, lang); + + if (config->language == language_objc) + (void) fprintf( + out, + "foundation = dependency('appleframeworks', modules: ['Foundation'])\n" + "\n" + "executable('%s', sources,\n" + " include_directories: inc,\n" + " dependencies: foundation)\n", + name); + else + (void) fprintf( + out, + "executable('%s', sources,\n" + " include_directories: inc)\n", + name); +} + +static const char *premake_language(enum Language language) +{ + if (language == language_cpp) + return "C++"; + + return "C"; +} + +static void write_premake(FILE *out, const struct Config *config) +{ + const char *name = config->name; + const char *ext = languages[config->language].extension; + const char *obj_dir = + config->use_intermediate ? config->bin_int_dir : config->bin_dir; + const char *flags[10]; + size_t count = base_flags(config, flags); + + (void) fprintf( + out, + "workspace \"%s\"\n" + " configurations { \"debug\" }\n" + "\n" + "project \"%s\"\n" + " kind \"ConsoleApp\"\n" + " language \"%s\"\n" + " toolset \"clang\"\n" + "\n" + " targetdir \"%s\"\n" + " targetname \"%s\"\n" + " objdir \"%s\"\n" + "\n" + " files { \"src/**.%s\" }\n" + " includedirs { \"src\" }\n" + "\n" + " buildoptions {\n", + name, name, premake_language(config->language), + config->bin_dir, name, obj_dir, ext); + + for (size_t i = 0; i < count; i++) + (void) fprintf(out, " \"%s\",\n", flags[i]); + + (void) fprintf( + out, + " \"-std=%s\",\n" + " \"-fsanitize=address,undefined\",\n" + " }\n" + "\n" + " linkoptions { \"-fsanitize=address,undefined\" }\n", + config->standard); + + if (config->language == language_objc) + (void) fputs( + "\n" + " links { \"Foundation.framework\" }\n", + out); +} + static void write_gitignore(FILE *out, const struct Config *config) { (void) fprintf(out, "%s/\n", config->bin_dir); - if (config->use_intermediate) + if (config->use_intermediate + && (config->build == build_make || config->build == build_premake) + && strcmp(config->bin_int_dir, config->bin_dir) != 0) (void) fprintf(out, "%s/\n", config->bin_int_dir); + if (config->build == build_premake) + (void) fputs("Makefile\n*.make\n", out); + if (config->use_clangd_ignore) (void) fputs( "\n" @@ -614,19 +841,49 @@ static void print_next_steps(const struct Config *config) build_systems[config->build].name); (void) printf(" cd %s\n", config->name); - (void) printf(" make\n"); - (void) printf(" make check\n\n"); + + switch (config->build) + { + case build_make: + (void) printf(" make\n"); + (void) printf(" make check\n"); + break; + + case build_cmake: + (void) printf(" cmake -B %s\n", config->bin_dir); + (void) printf(" cmake --build %s\n", config->bin_dir); + break; + + case build_meson: + (void) printf(" meson setup %s\n", config->bin_dir); + (void) printf(" meson compile -C %s\n", config->bin_dir); + break; + + case build_premake: + (void) printf(" premake5 gmake2\n"); + (void) printf(" make\n"); + break; + + default: + break; + } + + (void) printf("\n"); } static int generate_project(const struct Config *config) { char path[PATH_MAX]; + const char *build_file; + void (*write_build)(FILE *, const struct Config *); - if (config->build != build_make) + switch (config->build) { - error("build system not yet implemented", - build_systems[config->build].key); - return -1; + case build_make: build_file = "Makefile"; write_build = write_makefile; break; + case build_cmake: build_file = "CMakeLists.txt"; write_build = write_cmake; break; + case build_meson: build_file = "meson.build"; write_build = write_meson; break; + case build_premake: build_file = "premake5.lua"; write_build = write_premake; break; + default: error("unknown build system", NULL); return -1; } if (make_dir(config->name) != 0) @@ -643,9 +900,9 @@ static int generate_project(const struct Config *config) if (emit(path, config, write_main) != 0) return -1; - (void) snprintf(path, sizeof path, "%s/Makefile", config->name); + (void) snprintf(path, sizeof path, "%s/%s", config->name, build_file); - if (emit(path, config, write_makefile) != 0) + if (emit(path, config, write_build) != 0) return -1; (void) snprintf(path, sizeof path, "%s/.gitignore", config->name); @@ -679,8 +936,8 @@ int main(int argc, char **argv) .language = language_c, .standard = NULL, .build = build_make, - .bin_dir = "bin", - .bin_int_dir = "bin-int", + .bin_dir = NULL, + .bin_int_dir = NULL, .use_git = 1, .use_intermediate = 0, .use_clangd_ignore = 1, |
