hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-18 19:33:23 +0200
committerhachem <im@hachem.wtf>2026-09-18 19:33:26 +0200
commit65b73b420b5eadc6f10e732f6536430ec85011a4 (patch)
treef09c5a401ab49462db0950ddcd6ba7a26a91c85b
parent2e9e1221538fe8148e542103552ff339891dd21b (diff)
feat: support asm
-rw-r--r--README.md2
-rw-r--r--mkproj.122
-rw-r--r--mkproj.c170
-rwxr-xr-xtests.sh5
4 files changed, 189 insertions, 10 deletions
diff --git a/README.md b/README.md
index ab3d0ae..4832174 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/mkproj.1 b/mkproj.1
index b416bcc..b06b583 100644
--- a/mkproj.1
+++ b/mkproj.1
@@ -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
diff --git a/mkproj.c b/mkproj.c
index 7fa6a54..96d7119 100644
--- a/mkproj.c
+++ b/mkproj.c
@@ -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);
diff --git a/tests.sh b/tests.sh
index 98b30fb..adafa50 100755
--- a/tests.sh
+++ b/tests.sh
@@ -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"