hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-07 23:35:54 +0200
committerhachem <im@hachem.wtf>2026-09-07 23:35:54 +0200
commit95a0f895d9a828a326a0c2b0f80e16028023a97b (patch)
tree4fa50164b7764e5a59618d7c037b59eb46f6666a
parent774b625c781a563e92224624f757b1e979f73d23 (diff)
feat: support other build systems
-rw-r--r--mkproj.c325
1 files changed, 291 insertions, 34 deletions
diff --git a/mkproj.c b/mkproj.c
index 66d3728..791a626 100644
--- a/mkproj.c
+++ b/mkproj.c
@@ -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,