commit 6e85efd733da510dcbac2d7f44724c2afabd75f7
parent a3a7be4f1dcf5e5dec804abb2e54eeb05aa7ddf8
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Tue, 3 Sep 2024 01:21:10 +0530
Implement build chains
Implement custom chains
Fix Makefile to work on both gmake and nmake
Add build type specifier for both msvc and gcc/clang in Makefile
Add usage in README
Add section for "Build Chains" in README
Bump version to 0.2.3
Diffstat:
M | Makefile | | | 13 | +++++++++---- |
M | README | | | 79 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
M | config.h | | | 77 | ++++++++++++++++++++++++++++++++++++++++------------------------------------- |
M | config.mk | | | 22 | ++++++++++++++++++---- |
M | fmake.c | | | 250 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- |
5 files changed, 310 insertions(+), 131 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,16 +1,21 @@
include config.mk
+# not nmake \
+!ifdef 0
+.DEFAULT_GOAL:=fmake
+# \
+!endif
+
SOURCE = fmake.c config.h
fmake.exe: $(SOURCE)
- cl /nologo $(cl_CFLAGS) $(CFLAGS) fmake.c
+ cl /nologo $(CFLAGS) fmake.c
fmake: $(SOURCE)
- $(CC) -o $@ $(_CFLAGS) $(CFLAGS) $(LDFLAGS) fmake.c
+ $(CC) -o $@ $(CFLAGS) $(LDFLAGS) fmake.c
clean:
- -rm fmake
- -del fmake.exe
+ -$(RM) fmake fmake.exe fmake.i fmake.pdb fmake.ilk fmake.obj
install: fmake
mkdir -p $(DESTDIR)$(PREFIX)/bin
diff --git a/README b/README
@@ -7,43 +7,72 @@ fmake
| ++ gradle buildDebug |
!______________________________!
-A tool for the repo nomad.
+fmake is a tool that brings `make`s interface to almost any build system.
-fmake is a tool that brings `make`s interface to
-almost any build system.
+fmake offers a functionality similar to vim's makeprg, but does so in a
+way that it can be used in various other programs and applications.
-fmake "intelligently" knows what targets to build which can be
+fmake "intelligently" knows what targets to build and can be
configured in the config.h file.
-Opinionates build directory as 'out' in case of no clear build out
-directory standard. Avoids in-source builds.
-
Usage
-----
-Just type `fmake`, in your project directory to quickly run
-fmake commands.
+fmake [options] [target] ...
+Options:
+ -? Prints fmake usage
+ -1|2|3 Force fmake to start from the level specified
+ -C path Change to directory before calling the programs
+ -D Print various types of debugging information.
+ -N Don't actually run any build commands; just print them.
+ -V Print the version number of make and exit.
-When none is found, it just defaults to running `make`.
+Build Chains
+------------
+fmake automatically understands the following(and many more) sequences,
+and tries to run it one after the other
-Supported build files
----------------------
-* *make
-* bazel
-* cmake
-* configure
-* gradle
-* ninja
-* nobuild
+ $ cmake -Bout .
+ $ cd out
+ $ make
+
+--------(vs)----------
+
+ $ fmake ------------------=> [ You only have to type fmake! ]
+ ++ cmake -Bout
+ ...
+ ++ cmake --build out
+
+fmake can be forced to re-run the generator using `fmake -2`
-and much more! see the config.h for the complete list
-Find the complete list in config.h
+Supported build files
+---------------------
+fmake queries filenames from `maker_config_t makers[]` defined in config.h.
Building fmake
--------------
-For *NIX,
- make
+*NIX,
+ $ make
+ $ make install
+
+MSVC,
+ * Open Developer Command Prompt *
+ C:\fmake> nmake
+
+Additionally 'type=release' can be passed to build as release.
+Default is debug.
+
+FAQ
+----
+> Why ?
+fmake was born out of my frustration with having to remember specific
+commands for every project I worked on.
+
+> Why default to 'out/' ?
+'out/' is preferred, as more and more projects are using the 'build'
+directory for other purposes like setting up CI/CD.
-For MSVC,
- nmake
+See Also
+--------
+* errorformat by reviewdog
diff --git a/config.h b/config.h
@@ -1,43 +1,46 @@
-/* level filetype lookup name cmd cmd args */
-#define BUILD_SYSTEMS \
- X( 1, FMAKE_FIL, "Makefile" , "make") \
- X( 1, FMAKE_FIL, "makefile" , "make") \
- X( 1, FMAKE_FIL, "GNUMakefile" , "gmake") \
- X( 1, FMAKE_FIL, "BSDMakefile" , "bmake") \
- X( 1, FMAKE_FIL, "Justfile" , "just") \
- X( 1, FMAKE_EXT, "*.pro" , "qmake") \
- X( 1, FMAKE_EXT, "*.sln" , "msbuild") \
- X( 1, FMAKE_EXT, "*.vcxproj" , "msbuild") \
- X( 1, FMAKE_FIL, "make" , "sh" , "make") \
- X( 1, FMAKE_FIL, "Gruntfile" , "grunt") \
- X( 1, FMAKE_FIL, "build.sh" , "sh" , "build.sh") \
- X( 1, FMAKE_FIL, "build.ninja" , "ninja") \
- X( 1, FMAKE_FIL, "OMakefile" , "omake") \
- X( 2, FMAKE_FIL, "configure" , "sh" , "configure") \
- X( 1, FMAKE_FIL, "configure.ac" , "autoreconf" , "-fiv") \
- X( 2, FMAKE_FIL, "CMakeLists.txt" , "cmake" , "-B" , "out/") \
- X( 2, FMAKE_FIL, "BUILD.gn" , "gn" , "gen" , "out/") \
- X( 1, FMAKE_FIL, "BUILD" , "bazel" , "build") \
- X( 1, FMAKE_FIL, "nob" , "./nob") \
- X( 1, FMAKE_FIL, "nob.c" , "cc" , "./nob.c" , "-o" , "nob") \
- X( 1, FMAKE_FIL, "nobuild" , "./nobuild" , ) \
- X( 1, FMAKE_FIL, "nobuild.c" , "cc" , "./nobuild.c" , "-o" , "nobuild") \
- X( 1, FMAKE_FIL, "packages.json" , "npm" , "install") \
- X( 1, FMAKE_DIR, "node_modules" , "npm" , "run") \
- X( 1, FMAKE_FIL, "Cargo.toml" , "cargo" , "build") \
- X( 1, FMAKE_FIL, "setup.py" , "pip" , "install" , ".") \
- X( 1, FMAKE_FIL, "gradlew.bat" , "./gradlew.bat") \
- X( 1, FMAKE_FIL, "gradlew" , "sh" , "gradlew") \
- X( 3, FMAKE_FIL, "APKBUILD" , "abuild" , "-r") \
- X( 3, FMAKE_FIL, "PKGBUILD" , "makepkg" , "-i")
+#define X(BLEVEL, FTYPE, OTYPE, LOOKFOR, CMD, ...) \
+ BLEVEL, FTYPE, OTYPE, LOOKFOR, CMD, { CMD, __VA_ARGS__}, \
+ (sizeof((char*[]){"" __VA_ARGS__}) / sizeof(char**)) + 1
+
+const maker_config_t cmake_chain = { X(1, FM_FIL, 0, "CMakeCache.txt", "cmake", "--build", ".") };
+/*X(level type operation lookup name cmd cmd args) opt chain */
+#define BUILD_SYSTEMS \
+{ X(1, FM_FIL, 0 , "Makefile" , "make") },\
+{ X(1, FM_FIL, 0 , "makefile" , "make") },\
+{ X(1, FM_FIL, 0 , "GNUMakefile" , "gmake") },\
+{ X(1, FM_FIL, 0 , "BSDMakefile" , "bmake") },\
+{ X(1, FM_FIL, 0 , "Justfile" , "just") },\
+{ X(1, FM_FIL, 0 , "OMakefile" , "omake") },\
+{ X(1, FM_FIL, 0 , "make" , "sh" , "make") },\
+{ X(1, FM_FIL, 0 , "build.sh" , "sh" , "build.sh") },\
+{ X(1, FM_FIL, 0 , "Gruntfile" , "grunt") },\
+{ X(1, FM_FIL, 0 , "build.ninja" , "ninja") },\
+{ X(1, FM_FIL, 0 , "WORKSPACE" , "bazel" , "build") },\
+{ X(1, FM_FIL, 0 , "nob" , "./nob") },\
+{ X(1, FM_FIL, 0 , "nob.c" , "cc" , "./nob.c" , "-o" , "nob") },\
+{ X(1, FM_FIL, 0 , "nobuild" , "./nobuild" , ) },\
+{ X(1, FM_FIL, 0 , "nobuild.c" , "cc" , "./nobuild.c" , "-o" , "nobuild") },\
+{ X(1, FM_FIL, 0 , "packages.json" , "npm" , "install") },\
+{ X(1, FM_DIR, 0 , "node_modules" , "npm" , "run") },\
+{ X(1, FM_FIL, 0 , "Cargo.toml" , "cargo" , "build") },\
+{ X(1, FM_FIL, 0 , "setup.py" , "pip" , "install" , ".") },\
+{ X(1, FM_FIL, 0 , "gradlew.bat" , "cmd" , "/c", "gradlew.bat") },\
+{ X(1, FM_FIL, 0 , "gradlew" , "sh" , "gradlew") },\
+{ X(1, FM_EXT, 0 , "*.pro" , "qmake") },\
+{ X(1, FM_EXT, 0 , "*.sln" , "msbuild") },\
+{ X(1, FM_EXT, 0 , "*.vcxproj" , "msbuild") },\
+{ X(2, FM_FIL, FM_MKD | FM_CBB , "configure" , "sh" , "../configure") },\
+{ X(2, FM_FIL, FM_CAB , "CMakeLists.txt" , "cmake" , "-Bout"), &cmake_chain },\
+{ X(2, FM_FIL, FM_CAB , "BUILD.gn" , "gn" , "gen" , "out/") },\
+{ X(3, FM_FIL, 0 , "configure.ac" , "autoreconf" , "-fiv") },\
+{ X(3, FM_FIL, 0 , "CMakePresets.json", "cmake" , "--preset", "default") },\
+{ X(3, FM_FIL, 0 , "APKBUILD" , "abuild" , "-r") },\
+{ X(3, FM_FIL, 0 , "PKGBUILD" , "makepkg" , "-i")}
static const maker_config_t makers[] = {
-#define X(MLEVEL, ISEXT, LOOKFOR, CMD, ...) {MLEVEL, ISEXT, LOOKFOR, \
- CMD, { CMD, __VA_ARGS__}, (sizeof((char*[]){"" __VA_ARGS__}) / sizeof(char**)) },
BUILD_SYSTEMS
-#undef X
-#undef NUMARGS
};
-static const size_t makers_len = sizeof(makers) / sizeof(maker_config_t);
+#undef BUILD_SYSTEMS
+#undef X
diff --git a/config.mk b/config.mk
@@ -1,6 +1,20 @@
-VERSION = 0.1.6
-
-$(CC)_CFLAGS = -DFMAKE_VERSION=\"$(VERSION)\" -Wall -Wextra -g
-cl_CFLAGS = /DFMAKE_VERSION=\"$(VERSION)\" /Zi
+VERSION = 0.2.3
+# nmake \
+!ifndef 0 # \
+CFLAGS = /DFMAKE_VERSION=\"$(VERSION)\" # \
+!if "$(type)" == "release" # \
+CFLAGS = $(CFLAGS) /ZI /DNDEBUG /O2 # \
+!else # \
+CFLAGS = $(CFLAGS) /Zi # \
+!endif # \
+RM=del # \
+!else
+type?=debug
+debugCFLAGS = -g
+releaseCFLAGS = -O2
+CFLAGS ?= -DFMAKE_VERSION=\"$(VERSION)\" -Wall -Wextra $($(type)CFLAGS)
PREFIX = /usr/local
+# \
+!endif
+
diff --git a/fmake.c b/fmake.c
@@ -10,71 +10,66 @@
#include <dirent.h>
#endif
+#ifdef _WIN32
+static HANDLE dir;
+static WIN32_FIND_DATA entry;
+#else
+static DIR *dir;
+static struct dirent *entry;
+#endif
+
typedef enum {
- FMAKE_FIL = 0,
- FMAKE_DIR = 1,
- FMAKE_EXT = 2
+ FM_FIL = 1,
+ FM_DIR = 2,
+ FM_EXT = 4,
} filetype_t;
-typedef struct {
+typedef enum {
+ FM_NOP = 0, /* no-op */
+ FM_CBB = 1, /* chdir before building */
+ FM_CAB = 2, /* chdir after building */
+ FM_MKD = 4, /* mkdir */
+} operation_t;
+
+typedef struct maker_config {
short level;
filetype_t filetype;
- const char* filename;
- const char* cmd;
- const char* args[256];
+ operation_t operation;
+ const char *filename;
+ const char *cmd;
+ const char *args[32];
size_t args_len;
+ const struct maker_config *chain;
} maker_config_t;
#include "config.h"
+static const size_t makers_len = sizeof(makers) / sizeof(maker_config_t);
static maker_config_t maker;
-static short should_execute_commands = 1;
-static short is_accepting_cmd_args = 0;
-static short is_verbose = 0;
+static short
+ should_execute_commands = 1, /* if 0 commands will be printed to stdout */
+ level_requested = 1, /* -1 indicates no level requested */
+ is_verbose = 0;
+ bs_i = 0;
-#ifdef _WIN32
-static HANDLE dir;
-static WIN32_FIND_DATA entry;
-#else
-static DIR *dir;
-static struct dirent *entry;
-#endif
+char *errormsg;
+size_t errormsg_len;
#define info(...) \
- if (should_execute_commands) { \
- fprintf(stderr, __VA_ARGS__); \
+ if (is_verbose) { \
+ fprintf(stderr, "fmake: " __VA_ARGS__); \
}
-int
-process_build(int argc, char* argv[]) {
- int status = -1;
- info("++");
- for(size_t bs_i = 0; bs_i < maker.args_len && maker.args[bs_i] != NULL; bs_i++) {
- fprintf(should_execute_commands? stderr : stdout, " %s", maker.args[bs_i]);
- }
- info("\n");
- if (should_execute_commands) {
- fflush(stderr);
- if (*maker.args[0] == '\0') {
- status = execlp(maker.cmd, maker.cmd, '\0', (void*)0);
- }
- else {
- status = execvp(maker.cmd, (char* const*)maker.args);
- }
- if (status == -1) {
- perror(maker.cmd);
- }
- }
- return status;
-}
+#define errprint(...) \
+ fprintf(stderr, "fmake: " __VA_ARGS__);
inline int
is_file_present(short filetype, const char* name, size_t name_len) {
switch(filetype) {
#ifdef _WIN32
- case FMAKE_FIL:
- case FMAKE_EXT:
+ case FM_FIL:
+ case FM_EXT:
dir = FindFirstFile(name, &entry);
if (dir == INVALID_HANDLE_VALUE) {
return -1;
@@ -91,7 +86,7 @@ is_file_present(short filetype, const char* name, size_t name_len) {
}
}
break;
- case FMAKE_EXT:
+ case FM_EXT:
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG || entry->d_type == DT_LNK) {
char* dot = strrchr(entry->d_name, '.');
@@ -102,7 +97,7 @@ is_file_present(short filetype, const char* name, size_t name_len) {
}
break;
#endif
- case FMAKE_DIR:
+ case FM_DIR:
if (access(name, 0) == 0) {
goto FMAKE_FOUND_MATCH;
}
@@ -116,15 +111,135 @@ FMAKE_FOUND_MATCH:
return 0;
}
+int
+check_files(int maker_i) {
+ do {
+ maker = makers[maker_i];
+ if (is_file_present(maker.filetype,
+ maker.filename, strlen(maker.filename)) == 0)
+ break;
+
+ info("%s\n", maker.filename);
+ } while(++maker_i < makers_len || makers[maker_i].level <= level_requested);
+
+ if (maker_i == makers_len) maker_i = 0;
+ return maker_i;
+}
+
+int
+process_build(int argc, char* argv[]) {
+ int status = -1;
+ FILE* fd = should_execute_commands? stderr : stdout;
+ STARTUPINFO si = {0};
+ PROCESS_INFORMATION pi = {0};
+ int tmpint;
+ size_t cmdargs_len = 0;
+
+ if (should_execute_commands && (maker.operation & FM_MKD)) {
+ errprint("Running mkdir for 'out'\n");
+ if (CreateDirectory("out", NULL) == 0) {
+ switch(GetLastError()) {
+ case ERROR_ALREADY_EXISTS:
+ break;
+ case ERROR_PATH_NOT_FOUND:
+ errprint("Cannot create 'out' directory\n");
+ default:
+ return -1;
+ }
+ }
+ }
+
+ if (maker.operation & FM_CBB) {
+ errprint("Entering directory 'out'\n");
+ if (chdir("out") != 0) {
+ perror("fmake");
+ return -1;
+ }
+ }
+
+ if (should_execute_commands) fprintf(fd, "++ ");
+ for(size_t args_i = 0; args_i < maker.args_len && maker.args[args_i] != NULL; args_i++) {
+ tmpint = strlen(maker.args[args_i]);
+ /* +1 for space */
+ cmdargs_len += tmpint + 1;
+ fprintf(fd, "%.*s ", tmpint, maker.args[args_i]);
+ }
+ if (should_execute_commands) fprintf(fd, "\n");
+ fflush(fd);
+
+ if (!should_execute_commands) return 0;
+
+#ifdef _WIN32
+ char *cmdargs = (char *)malloc(cmdargs_len);
+ tmpint = 0;
+
+ /* Windows can't accept argv[] style args. This converts it to a string */
+ for(int i = 0; i < maker.args_len && maker.args[i] != NULL; i++) {
+ size_t cmdargs_pointer = (size_t) maker.args[i];
+ strcpy(&cmdargs[tmpint], maker.args[i]);
+ tmpint += strlen(maker.args[i]);
+ cmdargs[tmpint++] = ' ';
+ }
+ cmdargs[cmdargs_len] = '\0';
+
+ if (!CreateProcess( NULL, cmdargs, NULL, NULL, FALSE, 0, NULL, NULL,
+ &si, &pi)) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ errprint("%s: not found\n", maker.cmd);
+ }
+ return -1;
+ }
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ if (GetExitCodeProcess(pi.hProcess, &status) == 0) {
+ errprint("fatal: cannot get process status for %d", pi.dwProcessId);
+ return -1;
+ }
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+#else
+ if (*maker.args[0] == '\0') {
+ status = execlp(maker.cmd, maker.cmd, '\0', (void*)0);
+ }
+ else {
+ status = execvp(maker.cmd, maker.args);
+ }
+#endif
+
+ if (status != 0) {
+ info("'%s' exited with non-zero exit code\n", maker.cmd);
+ return status;
+ }
+
+ if (maker.operation & FM_CAB) {
+ errprint("Entering directory 'out'\n");
+ if (chdir("out") != 0) {
+ perror("fmake");
+ return -1;
+ }
+ }
+
+ if (maker.level > 1) {
+ maker = maker.chain? *maker.chain :
+ makers[check_files(maker.level - 1)];
+ return process_build(argc, argv);
+ }
+ return 0;
+}
+
void
fmake_usage(int status) {
printf("Usage: fmake [options] [target] ...\n");
printf("Options:\n");
char* opt_array[] = {
- "-?", "Prints fmake usage",
- "-D", "Print various types of debugging information.",
- "-N", "Don't actually run any build commands; just print them.",
- "-V", "Print the version number of make and exit."
+ "-?", "Prints fmake usage",
+ "-C path", "Change to directory before calling the programs",
+ "-D", "Print various types of debugging information.",
+ "-N", "Don't actually run any build commands; just print them.",
+ "-V", "Print the version number of make and exit."
};
for(int i = 0; i < sizeof(opt_array) / sizeof(char**); i += 2) {
printf(" %-27s %s\n", opt_array[i], opt_array[i + 1]);
@@ -134,15 +249,13 @@ fmake_usage(int status) {
int
main(int argc, char* argv[]) {
- int bs_i = 0;
- int makers_len = sizeof(makers) / sizeof(maker_config_t);
+ char working_dir[256];
for(bs_i = 1; bs_i < argc; bs_i++) {
if (!(argv[bs_i][0] == '-' && argv[bs_i][1] != '\0')) {
exit(-1);
}
switch(argv[bs_i][1]) {
case '-':
- is_accepting_cmd_args = 1;
goto FMAKE_AFTER_ARG_CHECK;
break;
case 'l':
@@ -155,6 +268,21 @@ main(int argc, char* argv[]) {
printf("\n");
}
break;
+ case 'C':
+ if (++bs_i == argc) {
+ fprintf(stderr, "fmake.exe: Option requires an argument\n");
+ fmake_usage(-1);
+ }
+ if (chdir(argv[bs_i]) == -1){
+ errprint("fmake: *** %s: %s. Stop.\n", argv[bs_i], strerror(errno));
+ return -1;
+ }
+ break;
+ case '1':
+ case '2':
+ case '3':
+ level_requested = atoi(&argv[bs_i][1]);
+ break;
case 'N':
should_execute_commands = 0;
break;
@@ -170,9 +298,12 @@ main(int argc, char* argv[]) {
break;
}
}
+
FMAKE_AFTER_ARG_CHECK:
argc = argc - bs_i;
argv = argv + bs_i;
+ bs_i = -1;
+
#ifndef _WIN32
dir = opendir("./");
if (dir == NULL) {
@@ -180,14 +311,11 @@ FMAKE_AFTER_ARG_CHECK:
return -1;
}
#endif
- for (bs_i = 0; bs_i < makers_len; bs_i++) {
- const char *str = makers[bs_i].filename;
- if (is_file_present(makers[bs_i].filetype, str, sizeof(makers[bs_i].filename)) == 0) {
- break;
- } else if (is_verbose) {
- fprintf(stderr, "fmake: %s (%d)\n", makers[bs_i].filename, makers[bs_i].filetype);
- }
- }
- maker = makers[bs_i == makers_len? 0 : bs_i];
+
+ /* foward through levels if requested */
+ while(makers[++bs_i].level < level_requested);
+
+ maker = makers[check_files(bs_i)];
+
return process_build(argc, argv);
}