diff options
| author | hachem <im@hachem.wtf> | 2026-09-18 19:33:23 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-18 19:33:26 +0200 |
| commit | 65b73b420b5eadc6f10e732f6536430ec85011a4 (patch) | |
| tree | f09c5a401ab49462db0950ddcd6ba7a26a91c85b | |
| parent | 2e9e1221538fe8148e542103552ff339891dd21b (diff) | |
feat: support asm
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | mkproj.1 | 22 | ||||
| -rw-r--r-- | mkproj.c | 170 | ||||
| -rwxr-xr-x | tests.sh | 5 |
4 files changed, 189 insertions, 10 deletions
@@ -16,7 +16,7 @@ That's a C project using `make`. `cd hello && make` and you're off. ## Usage ```text $ mkproj --help -mkproj 0.1.0 - dumb project generator +mkproj 0.3.0 - dumb project generator generates boilerplate for make, cmake, meson or premake so you can stop writing the same fucking build files every time you start a project. @@ -1,4 +1,4 @@ -.TH MKPROJ 1 "2026-09-12" "mkproj 0.2.0" "User Commands" +.TH MKPROJ 1 "2026-09-18" "mkproj 0.3.0" "User Commands" .SH NAME mkproj \- dumb project generator .SH SYNOPSIS @@ -133,6 +133,26 @@ directory. Objects still build into the .B \-\-bin directory, so the tree stays a single level deep. .TP +.B \-\-docker +Add a +.B Dockerfile +and +.B .dockerignore +that build and run the project in a container. Supported for +.B c +and +.B cpp +(with +.BR make ", " cmake " or " meson ) +and for +.BR nasm . +With +.B nasm +the container targets Linux +.RB ( elf64 ), +so you write against Linux syscalls instead of the macOS assembly +conventions the native build uses. +.TP .BR \-\-bin " " \fIdir\fR Build directory. The default depends on the build system: .B bin @@ -5,7 +5,7 @@ * stop writing the same fucking build files every time you start a project. * * Author: hachem <im@hachem.wtf> - * Version: 0.2.0 + * Version: 0.3.0 */ #include <ctype.h> @@ -21,7 +21,7 @@ #include <unistd.h> #define mkproj_program "mkproj" -#define mkproj_version "0.2.0" +#define mkproj_version "0.3.0" #define mkproj_tagline "dumb project generator" enum Language @@ -94,6 +94,7 @@ struct Config int use_intermediate; int use_clangd; int use_flat; + int use_docker; }; static const char* const c_standards[] = @@ -222,8 +223,8 @@ static void print_usage(FILE* stream) "stop writing the same fucking build files every time you start a project.\n" "\n" "usage: %s [-l lang] [-s std] [-b system] [-k kind] [--cc cc] [--license name]\n" - " [--no-git] [--intermediate] [--flat] [--bin dir] [--bin-int dir]\n" - " [--no-clangd] <name>\n" + " [--no-git] [--intermediate] [--flat] [--docker] [--bin dir]\n" + " [--bin-int dir] [--no-clangd] <name>\n" " %s [-v | -h]\n" "\n" "options:\n" @@ -236,6 +237,7 @@ static void print_usage(FILE* stream) " --no-git don't initialize a git repository\n" " --intermediate use a separate intermediate object directory\n" " --flat flat layout: sources at the root, no src/ directory\n" + " --docker add a Dockerfile to build in a container (c, cpp, nasm)\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 generate compile_flags.txt for clangd\n" @@ -428,6 +430,12 @@ static int parse_args(int argc, char** argv, struct Config* config) continue; } + if (strcmp(arg, "--docker") == 0) + { + config->use_docker = 1; + continue; + } + int matched = 0; matched = opt_value(arg, "-l", "--lang", argc, argv, &i, &lang_arg); @@ -596,6 +604,24 @@ static int parse_args(int argc, char** argv, struct Config* config) return 1; } + if (config->use_docker + && config->language != language_c + && config->language != language_cpp + && config->language != language_nasm) + { + error("--docker supports only c, cpp and nasm", languages[config->language].key); + return 1; + } + + if (config->use_docker + && config->build != build_make + && config->build != build_cmake + && config->build != build_meson) + { + error("--docker supports only make, cmake and meson", 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; @@ -650,6 +676,27 @@ static int git_init(const char* root) static const char* main_source(const struct Config* config) { if (config->language == language_nasm) + { + if (config->use_docker) + return + " global _start\n" + "\n" + " section .rodata\n" + "msg: db \"type shi\", 10\n" + "msg.len equ $ - msg\n" + "\n" + " section .text\n" + "_start:\n" + " mov rax, 1 ; write\n" + " mov rdi, 1 ; stdout\n" + " lea rsi, [rel msg]\n" + " mov rdx, msg.len\n" + " syscall\n" + "\n" + " mov rax, 60 ; exit\n" + " xor edi, edi\n" + " syscall\n"; + return " default rel\n" "\n" @@ -669,6 +716,7 @@ static const char* main_source(const struct Config* config) "\n" " section .data\n" "msg: db \"type shi\", 10, 0\n"; + } if (config->language == language_objc) return @@ -826,22 +874,26 @@ static void write_makefile_nasm(FILE* out, const struct Config* config) else (void) snprintf(clean, sizeof clean, "rm -rf %s", config->bin_dir); + const char* format = config->use_docker ? "elf64" : "macho64"; + const char* ld = config->use_docker ? "ld" : "cc"; + const char* ldflags = config->use_docker ? "" : "-arch x86_64"; + (void) fprintf( out, "AS := nasm\n" - "LD := cc\n" + "LD := %s\n" "\n" "DEBUG ?= 1\n" "\n" - "ASFLAGS := -f macho64\n" - "LDFLAGS := -arch x86_64\n" + "ASFLAGS := -f %s\n" + "LDFLAGS := %s\n" "\n" "ifeq ($(DEBUG),1)\n" "ASFLAGS += -g -F dwarf\n" "endif\n" "\n" "TARGET := %s/%s\n", - config->bin_dir, config->name); + ld, format, ldflags, config->bin_dir, config->name); if (config->use_flat) (void) fprintf(out, "SRC := $(wildcard *.asm)\nOBJ := $(SRC:%%=%s/%%.o)\n", obj_dir); @@ -1378,6 +1430,86 @@ static void write_license(FILE* out, const struct Config* config) } } +/* a single-stage Dockerfile that installs the toolchain, builds a release + * (no sanitizers), and runs the binary. c/cpp with make, cmake or meson. */ +static void write_dockerfile(FILE* out, const struct Config* config) +{ + if (config->language == language_nasm) + { + (void) fprintf( + out, + "FROM --platform=linux/amd64 debian:stable-slim\n" + "\n" + "RUN apt-get update \\\n" + " && apt-get install -y --no-install-recommends nasm binutils make \\\n" + " && rm -rf /var/lib/apt/lists/*\n" + "\n" + "WORKDIR /app\n" + "COPY . .\n" + "\n" + "RUN make\n" + "\n" + "CMD [\"./%s/%s\"]\n", + config->bin_dir, config->name); + return; + } + + const char* tools; + char build[2 * PATH_MAX]; + + switch (config->build) + { + case build_cmake: + tools = "make cmake"; + (void) snprintf(build, sizeof build, + "cmake -B %s -DSANITIZE=OFF && cmake --build %s", + config->bin_dir, config->bin_dir); + break; + + case build_meson: + tools = "meson ninja-build"; + (void) snprintf(build, sizeof build, + "meson setup %s -Db_sanitize=none && meson compile -C %s", + config->bin_dir, config->bin_dir); + break; + + default: + tools = "make"; + (void) snprintf(build, sizeof build, "make DEBUG=0 SANITIZE=0"); + break; + } + + char packages[128]; + (void) snprintf(packages, sizeof packages, "clang %s%s", + tools, config->language == language_cpp ? " libstdc++-dev" : ""); + + (void) fprintf( + out, + "FROM debian:stable-slim\n" + "\n" + "RUN apt-get update \\\n" + " && apt-get install -y --no-install-recommends %s \\\n" + " && rm -rf /var/lib/apt/lists/*\n" + "\n" + "ENV CC=clang CXX=clang++\n" + "\n" + "WORKDIR /app\n" + "COPY . .\n" + "\n" + "RUN %s\n" + "\n" + "CMD [\"./%s/%s\"]\n", + packages, build, config->bin_dir, config->name); +} + +static void write_dockerignore(FILE* out, const struct Config* config) +{ + (void) fprintf(out, ".git\n%s/\n", config->bin_dir); + + if (config->use_intermediate && strcmp(config->bin_int_dir, config->bin_dir) != 0) + (void) fprintf(out, "%s/\n", config->bin_int_dir); +} + static void write_gitignore(FILE* out, const struct Config* config) { (void) fprintf(out, "%s/\n", config->bin_dir); @@ -1452,6 +1584,14 @@ static void print_next_steps(const struct Config* config) (void) printf(" cd %s\n", config->name); + if (config->use_docker) + { + (void) printf(" docker build -t %s .\n", config->name); + (void) printf(" docker run --rm %s\n", config->name); + (void) printf("\n"); + return; + } + switch (config->build) { case build_make: @@ -1568,6 +1708,19 @@ static int generate_project(const struct Config* config) return -1; } + if (config->use_docker) + { + (void) snprintf(path, sizeof path, "%s/Dockerfile", config->name); + + if (emit(path, config, write_dockerfile) != 0) + return -1; + + (void) snprintf(path, sizeof path, "%s/.dockerignore", config->name); + + if (emit(path, config, write_dockerignore) != 0) + return -1; + } + if (config->use_git && git_init(config->name) != 0) (void) fprintf(stderr, "%s: warning: git is not installed\n", mkproj_program); @@ -1600,6 +1753,7 @@ int main(int argc, char** argv) .use_intermediate = 0, .use_clangd = 1, .use_flat = 0, + .use_docker = 0, }; int status = parse_args(argc, argv, &config); @@ -139,6 +139,11 @@ echo "generated files:" grep -q 'MIT License' "$work/lic_mit/LICENSE" 2>/dev/null && ok "--license mit" || bad "--license mit" ( cd "$work" && MKPROJ_BUILD=cmake "$mkproj" --no-git envdef ) >/dev/null 2>&1 [ -f "$work/envdef/CMakeLists.txt" ] && ok "MKPROJ_BUILD env default" || bad "MKPROJ_BUILD env default" +( cd "$work" && "$mkproj" --no-git --docker dok ) >/dev/null 2>&1 +[ -f "$work/dok/Dockerfile" ] && [ -f "$work/dok/.dockerignore" ] && ok "--docker files" || bad "--docker files" +( cd "$work" && "$mkproj" --no-git --docker -l nasm dok_nasm ) >/dev/null 2>&1 +[ -f "$work/dok_nasm/Dockerfile" ] && grep -q 'elf64' "$work/dok_nasm/Makefile" && ok "--docker nasm targets elf64" || bad "--docker nasm targets elf64" +( cd "$work" && "$mkproj" --no-git --docker -l objc dok_objc ) >/dev/null 2>&1 && bad "--docker rejects objc" || ok "--docker rejects objc" echo printf '%d passed, %d failed\n' "$pass" "$fail" |
