diff options
| author | hachem <im@hachem.wtf> | 2026-09-07 22:48:26 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-07 22:48:26 +0200 |
| commit | 774b625c781a563e92224624f757b1e979f73d23 (patch) | |
| tree | 8a646fcb6e84983209904d9c70f92b064455ad86 | |
| parent | b8f982f6b9fc9d430c7132456706059218bc6fc2 (diff) | |
feat: makefile project generation
| -rw-r--r-- | mkproj.c | 348 |
1 files changed, 339 insertions, 9 deletions
@@ -8,9 +8,15 @@ * Version: 0.1.0 */ +#include <errno.h> +#include <fcntl.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> #define mkproj_program "mkproj" #define mkproj_version "0.1.0" @@ -56,6 +62,8 @@ struct Config enum Language language; const char *standard; enum Build_System build; + const char *bin_dir; + const char *bin_int_dir; int use_git; int use_intermediate; int use_clangd_ignore; @@ -140,7 +148,7 @@ static void print_usage(FILE *stream) "have to keep writing build files for small dumb projects.\n" "\n" "usage: %s [-l lang] [-s std] [-b system] [--no-git] [--intermediate]\n" - " [--no-clangd] <name>\n" + " [--bin dir] [--bin-int dir] [--no-clangd] <name>\n" " %s [-v | -h]\n" "\n" "options:\n" @@ -149,6 +157,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" " --no-clangd don't ignore clangd files in .gitignore\n" " -v, --version print version and bail\n" " -h, --help print this and bail\n" @@ -248,6 +258,31 @@ static int parse_args(int argc, char **argv, struct Config *config) continue; } + if (strcmp(arg, "--bin") == 0) + { + if (++i >= argc) + { + error("missing argument for", arg); + return 1; + } + + config->bin_dir = argv[i]; + continue; + } + + if (strcmp(arg, "--bin-int") == 0) + { + if (++i >= argc) + { + error("missing argument for", arg); + return 1; + } + + config->bin_int_dir = argv[i]; + config->use_intermediate = 1; + continue; + } + if (strcmp(arg, "-l") == 0 || strcmp(arg, "--lang") == 0) { if (++i >= argc) @@ -337,6 +372,305 @@ static int parse_args(int argc, char **argv, struct Config *config) return 0; } +/* run argv to completion. returns the exit code, 127 if it could not be + * executed, or -1 on a fork/wait failure. */ +static int run(char *const argv[], int quiet) +{ + pid_t pid = fork(); + + if (pid < 0) + return -1; + + if (pid == 0) + { + if (quiet) + { + int devnull = open("/dev/null", O_WRONLY); + + if (devnull >= 0) + { + (void) dup2(devnull, STDOUT_FILENO); + (void) dup2(devnull, STDERR_FILENO); + (void) close(devnull); + } + } + + (void) execvp(argv[0], argv); + _exit(127); + } + + int status = 0; + + if (waitpid(pid, &status, 0) < 0) + return -1; + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + return -1; +} + +static int git_init(const char *root) +{ + char *const argv[] = { "git", "-C", (char *) root, "init", NULL }; + + return run(argv, 1); +} + +static const char *main_source(const struct Config *config) +{ + if (config->language == language_objc) + return + "#import <Foundation/Foundation.h>\n" + "\n" + "int main(void)\n" + "{\n" + " @autoreleasepool {\n" + " (void) NSLog(@\"type shi\");\n" + " }\n" + "}\n"; + + if (config->language == language_cpp) + { + const char *std = config->standard; + + if (strcmp(std, "c++23") == 0 || strcmp(std, "c++26") == 0) + return + "#include <print>\n" + "\n" + "auto main() -> int\n" + "{\n" + " std::println(\"type shi\");\n" + "}\n"; + + if (strcmp(std, "c++98") == 0 || strcmp(std, "c++03") == 0) + return + "#include <iostream>\n" + "\n" + "int main()\n" + "{\n" + " std::cout << \"type shi\\n\";\n" + "}\n"; + + return + "#include <iostream>\n" + "\n" + "auto main() -> int\n" + "{\n" + " std::cout << \"type shi\\n\";\n" + "}\n"; + } + + return + "#include <stdio.h>\n" + "\n" + "int main(void)\n" + "{\n" + " (void) puts(\"type shi\");\n" + "}\n"; +} + +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) +{ + 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"; + flags[count++] = "-Wextra"; + flags[count++] = "-Wpedantic"; + flags[count++] = "-Wconversion"; + flags[count++] = "-Wsign-conversion"; + flags[count++] = "-Wshadow"; + + if (config->language != language_cpp) + { + flags[count++] = "-Wstrict-prototypes"; + flags[count++] = "-Wmissing-prototypes"; + } + + flags[count++] = "-g"; + flags[count++] = "-fno-omit-frame-pointer"; + + (void) fprintf(out, "CC := %s\n\nSANITIZE ?= 1\n\nCFLAGS := ", lang->compiler); + + for (size_t i = 0; i < count; i++) + (void) fprintf(out, "%s \\\n ", flags[i]); + + (void) fprintf(out, "-std=%s \\\n -Isrc\n", config->standard); + + (void) fprintf( + out, + "\n" + "ifeq ($(SANITIZE),1)\n" + "CFLAGS += -fsanitize=address,undefined\n" + "LDFLAGS += -fsanitize=address,undefined\n" + "endif\n" + "\n" + "TARGET := %s/%s\n" + "SRC := $(shell find src -name '*.%s')\n" + "OBJ := $(SRC:src/%%=%s/%%.o)\n" + "\n" + "all: $(TARGET)\n" + "\n" + "$(TARGET): $(OBJ)\n" + "\t@mkdir -p $(@D)\n" + "\t$(CC) $^ -o $@ $(LDFLAGS)\n" + "\n" + "%s/%%.%s.o: src/%%.%s\n" + "\t@mkdir -p $(@D)\n" + "\t$(CC) $(CFLAGS) -c $< -o $@\n" + "\n" + "check:\n" + "\tbear -- make clean all\n" + "\tcppcheck \\\n" + "\t\t--project=compile_commands.json \\\n" + "\t\t--enable=warning,style,performance,portability \\\n" + "\t\t--error-exitcode=1\n" + "\n" + "clean:\n" + "\t%s\n" + "\n" + ".PHONY: all check clean\n", + config->bin_dir, config->name, ext, + obj_dir, obj_dir, ext, ext, clean); +} + +static void write_gitignore(FILE *out, const struct Config *config) +{ + (void) fprintf(out, "%s/\n", config->bin_dir); + + if (config->use_intermediate) + (void) fprintf(out, "%s/\n", config->bin_int_dir); + + if (config->use_clangd_ignore) + (void) fputs( + "\n" + ".clangd\n" + "compile_flags.txt\n" + "compile_commands.json\n", + out); +} + +/* open path, hand the stream to writer, close. returns 0 on success. */ +static int emit(const char *path, const struct Config *config, + void (*writer)(FILE *, const struct Config *)) +{ + FILE *file = fopen(path, "w"); + + if (file == NULL) + { + error("cannot create file", path); + return -1; + } + + writer(file, config); + + if (fclose(file) != 0) + { + error("cannot write file", path); + return -1; + } + + return 0; +} + +static int make_dir(const char *path) +{ + if (mkdir(path, 0755) == 0) + return 0; + + if (errno == EEXIST) + error("already exists", path); + else + error("cannot create directory", path); + + return -1; +} + +static void print_next_steps(const struct Config *config) +{ + (void) printf("\ncreated %s (%s, %s, %s)\n\n", + config->name, + languages[config->language].name, + config->standard, + build_systems[config->build].name); + + (void) printf(" cd %s\n", config->name); + (void) printf(" make\n"); + (void) printf(" make check\n\n"); +} + +static int generate_project(const struct Config *config) +{ + char path[PATH_MAX]; + + if (config->build != build_make) + { + error("build system not yet implemented", + build_systems[config->build].key); + return -1; + } + + if (make_dir(config->name) != 0) + return -1; + + (void) snprintf(path, sizeof path, "%s/src", config->name); + + if (make_dir(path) != 0) + return -1; + + (void) snprintf(path, sizeof path, "%s/src/main.%s", + config->name, languages[config->language].extension); + + if (emit(path, config, write_main) != 0) + return -1; + + (void) snprintf(path, sizeof path, "%s/Makefile", config->name); + + if (emit(path, config, write_makefile) != 0) + return -1; + + (void) snprintf(path, sizeof path, "%s/.gitignore", config->name); + + if (emit(path, config, write_gitignore) != 0) + return -1; + + if (config->use_git && git_init(config->name) != 0) + (void) fprintf(stderr, "%s: warning: git is not installed\n", + mkproj_program); + + print_next_steps(config); + + { + char *const argv[] = + { "tree", "-a", "--dirsfirst", "-I", ".git", + (char *) config->name, NULL }; + + if (run(argv, 0) == 127) + (void) puts(" (tree is not installed)"); + } + + return 0; +} + int main(int argc, char **argv) { struct Config config = @@ -345,6 +679,8 @@ int main(int argc, char **argv) .language = language_c, .standard = NULL, .build = build_make, + .bin_dir = "bin", + .bin_int_dir = "bin-int", .use_git = 1, .use_intermediate = 0, .use_clangd_ignore = 1, @@ -361,14 +697,8 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - /* TODO: generation. for now, echo the resolved config. */ - (void) printf("name: %s\n", config.name); - (void) printf("language: %s\n", languages[config.language].name); - (void) printf("standard: %s\n", config.standard); - (void) printf("build: %s\n", build_systems[config.build].name); - (void) printf("git: %s\n", config.use_git ? "yes" : "no"); - (void) printf("interm.: %s\n", config.use_intermediate ? "yes" : "no"); - (void) printf("clangd: %s\n", config.use_clangd_ignore ? "yes" : "no"); + if (generate_project(&config) != 0) + return EXIT_FAILURE; return EXIT_SUCCESS; } |
