diff --git a/ChangeLog.md b/ChangeLog.md index 1e676fd..4938c43 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,12 @@ *Not Released Yet* +- Introduced `PIPE(...)` macro. + +## Deprecations + +- `FOREACH_VARGS(...)` is deprecated. Use `FOREACH_VARGS_TYPE(...)` instead. + # 0.2.0 — Removing Everything *2021-01-30* diff --git a/README.md b/README.md index 08b44a8..23abea4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The idea is that you should not need anything but a C compiler to build a C proj Try it out right here: -``` +```console $ cc ./nobuild.c -o nobuild $ ./nobuild ``` diff --git a/examples/.gitignore b/examples/.gitignore index 29538b7..9c39486 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,4 +1,5 @@ string foreach logging -file \ No newline at end of file +file +pipe \ No newline at end of file diff --git a/examples/file.c b/examples/file.c index 6718db3..ba221f4 100644 --- a/examples/file.c +++ b/examples/file.c @@ -18,7 +18,7 @@ void print_file_recursively(const char *path) } } -int main(int argc, char *argv[]) +int main(void) { DEMO(IS_DIR("./nobuild.c")); DEMO(IS_DIR("./examples")); diff --git a/examples/foreach.c b/examples/foreach.c index c378575..ea9dab8 100644 --- a/examples/foreach.c +++ b/examples/foreach.c @@ -5,7 +5,7 @@ void foreach_vargs(int ignore, ...) { va_list args; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { INFO(" %s", arg); }); } @@ -34,7 +34,7 @@ void foreach_file_in_dir(const char *dir_path) expr; \ } while(0) -int main(int argc, char *argv[]) +int main(void) { DEMO(foreach_vargs(69, "foo", "bar", "baz", NULL)); DEMO(foreach_array()); diff --git a/examples/logging.c b/examples/logging.c index 27c08d1..e512e60 100644 --- a/examples/logging.c +++ b/examples/logging.c @@ -1,7 +1,7 @@ #define NOBUILD_IMPLEMENTATION #include "../nobuild.h" -int main(int argc, char *argv[]) +int main(void) { INFO(" Informational Message"); WARN(" Warning Message"); diff --git a/examples/pipe.c b/examples/pipe.c new file mode 100644 index 0000000..b19b3ab --- /dev/null +++ b/examples/pipe.c @@ -0,0 +1,13 @@ +#define NOBUILD_IMPLEMENTATION +#include "../nobuild.h" + +int main(void) +{ +#if _WIN32 + WARN("Piping is not implemented on Windows yet"); +#else + PIPE(IN("./nobuild.c"), CHAIN("cat")); +#endif + + return 0; +} diff --git a/examples/string.c b/examples/string.c index 4393d86..7bbf97b 100644 --- a/examples/string.c +++ b/examples/string.c @@ -7,7 +7,7 @@ #define DEMO_D(expr) \ INFO(" " #expr " == %d", expr) -int main(int argc, char *argv[]) +int main(void) { DEMO_S(CONCAT("foo", "bar", "baz")); DEMO_S(PATH("foo", "bar", "baz")); diff --git a/nobuild.c b/nobuild.c index 7221959..7edc852 100644 --- a/nobuild.c +++ b/nobuild.c @@ -1,6 +1,8 @@ #define NOBUILD_IMPLEMENTATION #include "./nobuild.h" +#define CFLAGS "-Wall", "-Wextra", "-std=c11", "-pedantic", "-ggdb" + void check_example(const char *example) { const char *example_path = PATH("examples", NOEXT(example)); @@ -11,7 +13,10 @@ void check_example(const char *example) CMD("cl.exe", "/Fe.\\examples\\", PATH("examples", example)); CMD(CONCAT(example_path, ".exe")); #else - CMD("cc", "-o", example_path, PATH("examples", example)); + const char *cc = getenv("CC"); + if (cc == NULL) cc = "cc"; + + CMD(cc, CFLAGS, "-o", example_path, PATH("examples", example)); CMD(example_path); #endif // _WIN32 } diff --git a/nobuild.h b/nobuild.h index c5165f9..57dd273 100644 --- a/nobuild.h +++ b/nobuild.h @@ -30,6 +30,10 @@ #ifndef NOBUILD_H_ #define NOBUILD_H_ +#ifndef _WIN32 +#define _POSIX_C_SOURCE 200809L +#endif // _WIN32 + #include #include #include @@ -100,8 +104,10 @@ # include # include # include +# include #endif // _WIN32 + // TODO(#1): no way to disable echo in nobuild scripts // TODO(#2): no way to ignore fails @@ -113,8 +119,21 @@ #define PATH_SEP_LEN (sizeof(PATH_SEP) - 1) +#define FOREACH_VARGS_TYPE(param, type, arg, args, body) \ + do { \ + va_start(args, param); \ + for (type arg = va_arg(args, type); \ + arg != NULL; \ + arg = va_arg(args, type)) \ + { \ + body; \ + } \ + va_end(args); \ + } while(0) + #define FOREACH_VARGS(param, arg, args, body) \ do { \ + WARN("DEPRECATED: Please don't use FOREACH_VARGS(...). Please use FOREACH_VARGS_TYPE(...) instead. FOREACH_VARGS(...) will be removed on the next major release."); \ va_start(args, param); \ for (const char *arg = va_arg(args, const char *); \ arg != NULL; \ @@ -166,6 +185,33 @@ const char *remove_ext(const char *path); char *shift(int *argc, char ***argv); void nobuild__rm(const char *path); +#ifndef _WIN32 +void nobuild__posix_wait_for_pid(pid_t pid); +#endif + +typedef enum { + PIPE_ARG_END, + PIPE_ARG_IN, + PIPE_ARG_OUT, + PIPE_ARG_CHAIN, +} Pipe_Arg_Type; + +const char *nobuild__pipe_arg_as_cstr(Pipe_Arg_Type type); + +typedef struct { + Pipe_Arg_Type type; + const char** args; +} Pipe_Arg; + +void nobuild__pipe(int ignore, ...); +Pipe_Arg nobuild__make_pipe_arg(Pipe_Arg_Type type, ...); + +#define PIPE(...) nobuild__pipe(69, __VA_ARGS__, nobuild__make_pipe_arg(PIPE_ARG_END, NULL)) +// TODO(#17): IN and OUT are already taken by WinAPI +#define IN(path) nobuild__make_pipe_arg(PIPE_ARG_IN, path, NULL) +#define OUT(path) nobuild__make_pipe_arg(PIPE_ARG_OUT, path, NULL) +#define CHAIN(...) nobuild__make_pipe_arg(PIPE_ARG_CHAIN, __VA_ARGS__, NULL) + #define CONCAT(...) concat_impl(69, __VA_ARGS__, NULL) #define CONCAT_SEP(sep, ...) build__deprecated_concat_sep(sep, __VA_ARGS__, NULL) #define JOIN(sep, ...) build__join(sep, __VA_ARGS__, NULL) @@ -277,7 +323,7 @@ const char *build__join(const char *sep, ...) va_list args; - FOREACH_VARGS(sep, arg, args, { + FOREACH_VARGS_TYPE(sep, const char*, arg, args, { length += strlen(arg); seps_count += 1; }); @@ -288,7 +334,7 @@ const char *build__join(const char *sep, ...) char *result = malloc(length + seps_count * sep_len + 1); length = 0; - FOREACH_VARGS(sep, arg, args, { + FOREACH_VARGS_TYPE(sep, const char*, arg, args, { size_t n = strlen(arg); memcpy(result + length, arg, n); length += n; @@ -315,7 +361,7 @@ const char *build__deprecated_concat_sep(const char *sep, ...) va_list args; - FOREACH_VARGS(sep, arg, args, { + FOREACH_VARGS_TYPE(sep, const char*, arg, args, { length += strlen(arg); seps_count += 1; }); @@ -326,7 +372,7 @@ const char *build__deprecated_concat_sep(const char *sep, ...) char *result = malloc(length + seps_count * sep_len + 1); length = 0; - FOREACH_VARGS(sep, arg, args, { + FOREACH_VARGS_TYPE(sep, const char*, arg, args, { size_t n = strlen(arg); memcpy(result + length, arg, n); length += n; @@ -353,7 +399,7 @@ const char *concat_sep_impl(const char *sep, ...) va_list args; - FOREACH_VARGS(sep, arg, args, { + FOREACH_VARGS_TYPE(sep, const char*, arg, args, { length += strlen(arg); seps_count += 1; }); @@ -364,7 +410,7 @@ const char *concat_sep_impl(const char *sep, ...) char *result = malloc(length + seps_count * sep_len + 1); length = 0; - FOREACH_VARGS(sep, arg, args, { + FOREACH_VARGS_TYPE(sep, const char*, arg, args, { size_t n = strlen(arg); memcpy(result + length, arg, n); length += n; @@ -388,7 +434,7 @@ void mkdirs_impl(int ignore, ...) va_list args; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { length += strlen(arg); seps_count += 1; }); @@ -399,7 +445,7 @@ void mkdirs_impl(int ignore, ...) char *result = malloc(length + seps_count * PATH_SEP_LEN + 1); length = 0; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { size_t n = strlen(arg); memcpy(result + length, arg, n); length += n; @@ -430,14 +476,14 @@ const char *concat_impl(int ignore, ...) { size_t length = 0; va_list args; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { length += strlen(arg); }); char *result = malloc(length + 1); length = 0; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { size_t n = strlen(arg); memcpy(result + length, arg, n); length += n; @@ -473,25 +519,7 @@ void nobuild_exec(const char **argv) exit(1); } } else { - for (;;) { - int wstatus = 0; - wait(&wstatus); - - if (WIFEXITED(wstatus)) { - int exit_status = WEXITSTATUS(wstatus); - if (exit_status != 0) { - ERRO("command exited with exit code %d", exit_status); - exit(1); - } - - break; - } - - if (WIFSIGNALED(wstatus)) { - ERRO("command process was terminated by signal %d", WTERMSIG(wstatus)); - exit(1); - } - } + nobuild__posix_wait_for_pid(cpid); } #endif // _WIN32 } @@ -501,14 +529,14 @@ void cmd_impl(int ignore, ...) size_t argc = 0; va_list args; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { argc += 1; }); const char **argv = malloc(sizeof(const char*) * (argc + 1)); argc = 0; - FOREACH_VARGS(ignore, arg, args, { + FOREACH_VARGS_TYPE(ignore, const char*, arg, args, { argv[argc++] = arg; }); argv[argc] = NULL; @@ -540,6 +568,8 @@ const char *remove_ext(const char *path) { WARN("DEPRECATED: Please use `NOEXT(path)` instead of `remove_ext(path)`. `remove_ext(path)` will be removed in the next major release."); nobuild__remove_ext(path); + + return NULL; } char *shift(int *argc, char ***argv) @@ -616,7 +646,7 @@ int nobuild__is_dir(const char *path) exit(1); } - return (statbuf.st_mode & S_IFMT) == S_IFDIR; + return S_ISDIR(statbuf.st_mode); #endif // _WIN32 } @@ -630,7 +660,7 @@ void nobuild__rm(const char *path) }); if (rmdir(path) < 0) { - if (errno = ENOENT) { + if (errno == ENOENT) { WARN("directory %s does not exist"); } else { ERRO("could not remove directory %s: %s", path, strerror(errno)); @@ -639,7 +669,7 @@ void nobuild__rm(const char *path) } } else { if (unlink(path) < 0) { - if (errno = ENOENT) { + if (errno == ENOENT) { WARN("file %s does not exist"); } else { ERRO("could not remove file %s: %s", path, strerror(errno)); @@ -649,4 +679,204 @@ void nobuild__rm(const char *path) } } +#ifdef _WIN32 +void nobuild__pipe(int ignore, ...) +{ + ERRO("TODO(#14): piping is not implemented on Windows at all"); + exit(1); +} +#else +void nobuild__pipe(int ignore, ...) +{ + const char *input_filepath = NULL; + const char *output_filepath = NULL; + size_t cmds_count = 0; + + va_list args; + va_start(args, ignore); + { + Pipe_Arg arg = va_arg(args, Pipe_Arg); + while (arg.type != PIPE_ARG_END) { + switch (arg.type) { + case PIPE_ARG_IN: { + if (input_filepath == NULL) { + input_filepath = arg.args[0]; + } else { + // TODO(#15): PIPE does not report where exactly a syntactic error has happened + ERRO("input file was already set for the pipe"); + exit(1); + } + } break; + + case PIPE_ARG_OUT: { + if (output_filepath == NULL) { + output_filepath = arg.args[0]; + } else { + ERRO("output file was already set for the pipe"); + exit(1); + } + } break; + + case PIPE_ARG_CHAIN: { + cmds_count += 1; + } break; + + case PIPE_ARG_END: + default: { + assert(0 && "unreachable"); + } + } + + arg = va_arg(args, Pipe_Arg); + } + } + va_end(args); + + if (cmds_count == 0) { + ERRO("no chains provided for the pipe"); + exit(1); + } + + Pipe_Arg *cmds = malloc(sizeof(Pipe_Arg) * cmds_count); + pid_t *cpids = malloc(sizeof(pid_t) * cmds_count); + + cmds_count = 0; + va_start(args, ignore); + { + Pipe_Arg arg = va_arg(args, Pipe_Arg); + while (arg.type != PIPE_ARG_END) { + if (arg.type == PIPE_ARG_CHAIN) { + cmds[cmds_count++] = arg; + } + arg = va_arg(args, Pipe_Arg); + } + } + va_end(args); + + // TODO(#16): input/output piping is not implemented for Linux + for (size_t i = 0; i < cmds_count; ++i) { + cpids[i] = fork(); + if (cpids[i] < 0) { + ERRO("could not fork a child: %s", strerror(errno)); + exit(1); + } + + if (cpids[i] == 0) { + // Chain is first and there is input file + if (i == 0) { + if (input_filepath != NULL) { + int fdin = open(input_filepath, O_RDONLY); + if (fdin < 0) { + ERRO("could not open file %s: %s", + input_filepath, strerror(errno)); + exit(1); + } + + if (dup2(fdin, STDIN_FILENO) < 0) { + ERRO("could not duplication file descriptor for %s: %s", + input_filepath, strerror(errno)); + exit(1); + } + } + } else { + assert(0 && "input piping is not implemented"); + } + + // Chain is last and there is output file + if (i == cmds_count - 1) { + if (output_filepath != NULL) { + int fdout = open(output_filepath, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fdout < 0) { + ERRO("could not open file %s: %s", + output_filepath, strerror(errno)); + exit(1); + } + + if (dup2(fdout, STDOUT_FILENO) < 0) { + ERRO("could not duplication file descriptor for %s: %s", + output_filepath, strerror(errno)); + exit(1); + } + } + } else { + assert(0 && "output piping is not implemented"); + } + + if (execvp(cmds[i].args[0], (char * const*) cmds[i].args) < 0) { + ERRO("could not execute command: %s", strerror(errno)); + exit(1); + } + } + } + + for (size_t i = 0; i < cmds_count; ++i) { + nobuild__posix_wait_for_pid(cpids[i]); + } +} +#endif // _WIN32 + +#ifndef _WIN32 +void nobuild__posix_wait_for_pid(pid_t pid) +{ + for (;;) { + int wstatus = 0; + waitpid(pid, &wstatus, 0); + + if (WIFEXITED(wstatus)) { + int exit_status = WEXITSTATUS(wstatus); + if (exit_status != 0) { + ERRO("command exited with exit code %d", exit_status); + exit(1); + } + + break; + } + + if (WIFSIGNALED(wstatus)) { + ERRO("command process was terminated by %s", strsignal(WTERMSIG(wstatus))); + exit(1); + } + } +} +#endif + +Pipe_Arg nobuild__make_pipe_arg(Pipe_Arg_Type type, ...) +{ + Pipe_Arg result = { + .type = type + }; + + va_list args; + + size_t count = 0; + FOREACH_VARGS_TYPE(type, const char*, arg, args, { + count += 1; + }); + + result.args = malloc(sizeof(const char*) * (count + 1)); + + count = 0; + FOREACH_VARGS_TYPE(type, const char*, arg, args, { + result.args[count++] = arg; + }); + result.args[count] = NULL; + + return result; +} + +const char *nobuild__pipe_arg_as_cstr(Pipe_Arg_Type type) +{ + switch (type) { + case PIPE_ARG_IN: return "PIPE_ARG_IN"; + case PIPE_ARG_OUT: return "PIPE_ARG_OUT"; + case PIPE_ARG_CHAIN: return "PIPE_ARG_CHAIN"; + default: { + assert(0 && "nobuild__pipe_arg_as_cstr: unreachable"); + return NULL; + } + } +} + #endif // NOBUILD_IMPLEMENTATION