The plan
I'm thinking about making a reusable C++ build system implemented in both Bash and Batch, so that one does not have to install Python nor CMake, just to compile a project. In order to maintain the same behavior across different platforms even if someone makes modifications to the build system itself for all projects, it would be nice to have both versions generated from a single portable scripting language before distributing the source code with pre-generated Bash and Batch build scripts. Compiling a utility binary for checksums and other helper functions can probably be done as an automatic part of the build system, if the program is not large enough to depend on itself for building.
The long rant about C++ build systems
If you have ever tried to compile CMake itself on an ARM-based mini-computer, overheating for eight hours before crashing, you might understand the need for a lighter cross-platform build system. Using handwritten Make files is a major source of linker errors and crashes. Python keeps breaking backward compatibility, so you have to install a specific version. Whenever a build system has no official binary distribution for your system, you have to either take your chances with a download site full of malware or build it from source code using another build system, which is built using yet another build system, until you have a cyclic loop of dependencies just for calling the compiler with a few arguments.
The question
Is there a clean scripting language capable of transpiling to almost any system's pre-installed scripting language?
Fallback solutions
If it's not possible due to unwanted side-effects or name collisions, I can just translate my existing build system from Bash to Batch and read project dependencies and settings from portable project files listing source folders and linked libraries, or automatically compile a build system from a single source file before the project is built using it.
Looked at https://batsh.org/ and multiple Bash to Batch converters, but none so far was powerful enough for a build system.
nobuild (https://github.com/tsoding/nobuild) might be an option for what you are looking for.
That bootstrapping from C concept should be easy to extend with a project file parser, so that you only automatically compile the build system when trying to call it for the first time. Just have to prevent feature bloat from taking hours to compile the build system.
A first prototype of my build system now runs on Linux. Will use a combination of Bash and Batch for compiling the build system and calling it with the project description and platform arguments.
Once I started making a build system using C++, I realized how easy it was to make more advanced features such as automatic detection of dependencies. So now I have the build system taking main.cpp as the argument, finding included headers recursively, jumping from *.h to *.c/cpp implementations and continuing to find their headers...
Then I made a system for including a backend description into the project using the library, which decides which backends and system libraries should be used on each platform.
The hardest part now is asking the user where the compiler is located in a user friendly way, before it has been used to compile the build system itself. Maybe just place a file in the build system's folder, use cat to read the compiler's path on Posix systems and something similar for Batch on MS-Windows. Or require global aliases to be created for both compiler and build system for easy use of both. Ideal would probably be a text file listing potential paths for compilers on each platform, so that it does not require anything to be set up in advance.
I was inspired by this Our Machinery (RIP) post to look into using C to build C. I ended up this polyglot C/Bash file that gives me a perverse satisfaction every time I run it:
build.c:
#if 0 # Self-building c file. Invoke like: `./build.c` outdir=out input=$(basename "$0") output="$outdir"/$(basename "$0" .c) if [ "$input" -nt "$output" ]; then mkdir --parents "$outdir" || exit echo "Building build file." || exit clang -std=c11 -Wall -Wextra -Werror -pedantic -Wno-unused-function "$input" -o "$output" || exit fi ./"$output" "$@" exit #endif #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "am_base.h" #define EXE_NAME "out/extruder" // TODO: wrap all stdlib functions in versions that fail on error int main(int argc, char **argv) { bool flag_debug = false; bool flag_run = false; // TODO: make a sweet ass arg parser for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "-d") == 0) { flag_debug = true; } else if (strcmp(argv[i], "-r") == 0) { flag_run = true; } } #define SHARED_FLAGS \ " -std=c11" \ " -g" \ " -O0" \ " -Wall" \ " -Wextra" \ " -Werror" \ " -Wconversion" \ " -Wmissing-noreturn" \ " -Wmissing-prototypes" \ " -Wshadow" \ " -Wno-unused-function" \ " -DAM_ENABLE_ASSERT=1" char *compile_command = flag_debug ? "gcc" SHARED_FLAGS " -Wno-missing-field-initializers" " main.c" " -o " EXE_NAME : "clang" SHARED_FLAGS " -pedantic" " -Wmissing-variable-declarations" " -Wreserved-identifier" " main.c" " -o " EXE_NAME; printf("%s\n", compile_command); int err = system(compile_command); char *run_command = NULL; if (!err) { if (flag_run && flag_debug) { run_command = "gf2 ./" EXE_NAME " &"; } else if (flag_run) { run_command = "./" EXE_NAME; } } if (run_command) { printf("%s\n", run_command); err = system(run_command); } return err; }
I'm pretty sure you could torture the polyglot header into something runnable in both bash and batch. Then I think you're set to do whatever build logic you want in C with no additional manual build steps and minimal system dependencies.