Created
August 27, 2021 10:26
-
-
Save aelobdog/14a5cfb76760b83d6488fcb3c7117ded to your computer and use it in GitHub Desktop.
My modifications of https://github.com/tsoding/nobuild. Added support for providing arguments to CMD marcro which are strings formed of space separated sub-arguments; which I think is not a supported feature by default (I may be wrong though)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef NOBUILD_H_ | |
#define NOBUILD_H_ | |
#ifndef _WIN32 | |
# define _POSIX_C_SOURCE 200809L | |
# include <sys/types.h> | |
# include <sys/wait.h> | |
# include <sys/stat.h> | |
# include <unistd.h> | |
# include <dirent.h> | |
# include <fcntl.h> | |
# include <string.h> | |
# define PATH_SEP "/" | |
typedef pid_t Pid; | |
typedef int Fd; | |
#else | |
# define WIN32_MEAN_AND_LEAN | |
# include "windows.h" | |
# include <process.h> | |
# define PATH_SEP "\\" | |
typedef HANDLE Pid; | |
typedef HANDLE Fd; | |
// minirent.h HEADER BEGIN //////////////////////////////////////// | |
// Copyright 2021 Alexey Kutepov <reximkut@gmail.com> | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining | |
// a copy of this software and associated documentation files (the | |
// "Software"), to deal in the Software without restriction, including | |
// without limitation the rights to use, copy, modify, merge, publish, | |
// distribute, sublicense, and/or sell copies of the Software, and to | |
// permit persons to whom the Software is furnished to do so, subject to | |
// the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be | |
// included in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
// | |
// ============================================================ | |
// | |
// minirent — 0.0.1 — A subset of dirent interface for Windows. | |
// | |
// https://github.com/tsoding/minirent | |
// | |
// ============================================================ | |
// | |
// Changes made to the file by @aelobdog: | |
// | |
// added | |
// ----- | |
// countSpaces : counts the number of spaces in a | |
// string | |
// strSplit : splits a string at the given delimiter | |
// and returns an array containing the | |
// split strings. | |
// | |
// modified | |
// -------- | |
// cstr_array_make : to support string args that contain | |
// space separated arguments. This is so | |
// that I can have something like | |
// `src/a.c src/b.c` as an argument to | |
// CMD and it will now detect that the | |
// string is actually 2 arguments and | |
// add the 2 arguments separately. | |
// (which doesn't seem to work by default) | |
// | |
// ============================================================ | |
#ifndef MINIRENT_H_ | |
#define MINIRENT_H_ | |
#define WIN32_LEAN_AND_MEAN | |
#include "windows.h" | |
struct dirent { | |
char d_name[MAX_PATH+1]; | |
}; | |
typedef struct DIR DIR; | |
DIR *opendir(const char *dirpath); | |
struct dirent *readdir(DIR *dirp); | |
int closedir(DIR *dirp); | |
#endif // MINIRENT_H_ | |
// minirent.h HEADER END //////////////////////////////////////// | |
// TODO(#28): use GetLastErrorAsString everywhere on Windows error reporting | |
LPSTR GetLastErrorAsString(void); | |
#endif // _WIN32 | |
#include <assert.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdarg.h> | |
#include <string.h> | |
#include <errno.h> | |
#define FOREACH_ARRAY(type, elem, array, body) \ | |
for (size_t elem_##index = 0; \ | |
elem_##index < array.count; \ | |
++elem_##index) \ | |
{ \ | |
type *elem = &array.elems[elem_##index]; \ | |
body; \ | |
} | |
typedef const char * Cstr; | |
int cstr_ends_with(Cstr cstr, Cstr postfix); | |
#define ENDS_WITH(cstr, postfix) cstr_ends_with(cstr, postfix) | |
Cstr cstr_no_ext(Cstr path); | |
#define NOEXT(path) cstr_no_ext(path) | |
typedef struct { | |
Cstr *elems; | |
size_t count; | |
} Cstr_Array; | |
Cstr_Array cstr_array_make(Cstr first, ...); | |
Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr); | |
Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs); | |
#define JOIN(sep, ...) cstr_array_join(sep, cstr_array_make(__VA_ARGS__, NULL)) | |
#define CONCAT(...) JOIN("", __VA_ARGS__) | |
#define PATH(...) JOIN(PATH_SEP, __VA_ARGS__) | |
typedef struct { | |
Fd read; | |
Fd write; | |
} Pipe; | |
Pipe pipe_make(void); | |
typedef struct { | |
Cstr_Array line; | |
} Cmd; | |
Fd fd_open_for_read(Cstr path); | |
Fd fd_open_for_write(Cstr path); | |
void fd_close(Fd fd); | |
void pid_wait(Pid pid); | |
Cstr cmd_show(Cmd cmd); | |
Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout); | |
void cmd_run_sync(Cmd cmd); | |
typedef struct { | |
Cmd *elems; | |
size_t count; | |
} Cmd_Array; | |
// TODO(#1): no way to disable echo in nobuild scripts | |
// TODO(#2): no way to ignore fails | |
#define CMD(...) \ | |
do { \ | |
Cmd cmd = { \ | |
.line = cstr_array_make(__VA_ARGS__, NULL) \ | |
}; \ | |
INFO("CMD: %s", cmd_show(cmd)); \ | |
cmd_run_sync(cmd); \ | |
} while (0) | |
typedef enum { | |
CHAIN_TOKEN_END = 0, | |
CHAIN_TOKEN_IN, | |
CHAIN_TOKEN_OUT, | |
CHAIN_TOKEN_CMD | |
} Chain_Token_Type; | |
// A single token for the CHAIN(...) DSL syntax | |
typedef struct { | |
Chain_Token_Type type; | |
Cstr_Array args; | |
} Chain_Token; | |
// TODO(#17): IN and OUT are already taken by WinAPI | |
#define IN(path) \ | |
(Chain_Token) { \ | |
.type = CHAIN_TOKEN_IN, \ | |
.args = cstr_array_make(path, NULL) \ | |
} | |
#define OUT(path) \ | |
(Chain_Token) { \ | |
.type = CHAIN_TOKEN_OUT, \ | |
.args = cstr_array_make(path, NULL) \ | |
} | |
#define CHAIN_CMD(...) \ | |
(Chain_Token) { \ | |
.type = CHAIN_TOKEN_CMD, \ | |
.args = cstr_array_make(__VA_ARGS__, NULL) \ | |
} | |
// TODO(#20): pipes do not allow redirecting stderr | |
typedef struct { | |
Cstr input_filepath; | |
Cmd_Array cmds; | |
Cstr output_filepath; | |
} Chain; | |
Chain chain_build_from_tokens(Chain_Token first, ...); | |
void chain_run_sync(Chain chain); | |
void chain_echo(Chain chain); | |
// TODO(#15): PIPE does not report where exactly a syntactic error has happened | |
#define CHAIN(...) \ | |
do { \ | |
Chain chain = chain_build_from_tokens(__VA_ARGS__, (Chain_Token) {0}); \ | |
chain_echo(chain); \ | |
chain_run_sync(chain); \ | |
} while(0) | |
#ifndef REBUILD_URSELF | |
# if _WIN32 | |
# if defined(__GNUC__) | |
# define REBUILD_URSELF(binary_path, source_path) CMD("gcc", "-o", binary_path, source_path) | |
# elif defined(__clang__) | |
# define REBUILD_URSELF(binary_path, source_path) CMD("clang", "-o", binary_path, source_path) | |
# elif defined(_MSC_VER) | |
# define REBUILD_URSELF(binary_path, source_path) CMD("cl.exe", source_path) | |
# endif | |
# else | |
# define REBUILD_URSELF(binary_path, source_path) CMD("cc", "-o", binary_path, source_path) | |
# endif | |
#endif | |
// Go Rebuild Urself™ Technology | |
// | |
// How to use it: | |
// int main(int argc, char** argv) { | |
// GO_REBUILD_URSELF(argc, argv); | |
// // actual work | |
// return 0; | |
// } | |
// | |
// After your added this macro every time you run ./nobuild it will detect | |
// that you modified its original source code and will try to rebuild itself | |
// before doing any actual work. So you only need to bootstrap your build system | |
// once. | |
// | |
// The modification is detected by comparing the last modified times of the executable | |
// and its source code. The same way the make utility usually does it. | |
// | |
// The rebuilding is done by using the REBUILD_URSELF macro which you can redefine | |
// if you need a special way of bootstraping your build system. (which I personally | |
// do not recommend since the whole idea of nobuild is to keep the process of bootstrapping | |
// as simple as possible and doing all of the actual work inside of the nobuild) | |
// | |
#define GO_REBUILD_URSELF(argc, argv) \ | |
do { \ | |
const char *source_path = __FILE__; \ | |
assert(argc >= 1); \ | |
const char *binary_path = argv[0]; \ | |
\ | |
if (is_path1_modified_after_path2(source_path, binary_path)) { \ | |
RENAME(binary_path, CONCAT(binary_path, ".old")); \ | |
REBUILD_URSELF(binary_path, source_path); \ | |
Cmd cmd = { \ | |
.line = { \ | |
.elems = (Cstr*) argv, \ | |
.count = argc, \ | |
}, \ | |
}; \ | |
INFO("CMD: %s", cmd_show(cmd)); \ | |
cmd_run_sync(cmd); \ | |
exit(0); \ | |
} \ | |
} while(0) | |
// The implementation idea is stolen from https://github.com/zhiayang/nabs | |
void rebuild_urself(const char *binary_path, const char *source_path); | |
int path_is_dir(Cstr path); | |
#define IS_DIR(path) path_is_dir(path) | |
int path_exists(Cstr path); | |
#define PATH_EXISTS(path) path_exists(path) | |
void path_mkdirs(Cstr_Array path); | |
#define MKDIRS(...) \ | |
do { \ | |
Cstr_Array path = cstr_array_make(__VA_ARGS__, NULL); \ | |
INFO("MKDIRS: %s", cstr_array_join(PATH_SEP, path)); \ | |
path_mkdirs(path); \ | |
} while (0) | |
void path_rename(Cstr old_path, Cstr new_path); | |
#define RENAME(old_path, new_path) \ | |
do { \ | |
INFO("RENAME: %s -> %s", old_path, new_path); \ | |
path_rename(old_path, new_path); \ | |
} while (0) | |
void path_rm(Cstr path); | |
#define RM(path) \ | |
do { \ | |
INFO("RM: %s", path); \ | |
path_rm(path); \ | |
} while(0) | |
#define FOREACH_FILE_IN_DIR(file, dirpath, body) \ | |
do { \ | |
struct dirent *dp = NULL; \ | |
DIR *dir = opendir(dirpath); \ | |
if (dir == NULL) { \ | |
PANIC("could not open directory %s: %s", \ | |
dirpath, strerror(errno)); \ | |
} \ | |
errno = 0; \ | |
while ((dp = readdir(dir))) { \ | |
const char *file = dp->d_name; \ | |
body; \ | |
} \ | |
\ | |
if (errno > 0) { \ | |
PANIC("could not read directory %s: %s", \ | |
dirpath, strerror(errno)); \ | |
} \ | |
\ | |
closedir(dir); \ | |
} while(0) | |
#if defined(__GNUC__) || defined(__clang__) | |
// https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html | |
#define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK))) | |
#else | |
#define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) | |
#endif | |
void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args); | |
void INFO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); | |
void WARN(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); | |
void ERRO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); | |
void PANIC(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); | |
char *shift_args(int *argc, char ***argv); | |
#endif // NOBUILD_H_ | |
//////////////////////////////////////////////////////////////////////////////// | |
#ifdef NOBUILD_IMPLEMENTATION | |
#ifdef _WIN32 | |
LPSTR GetLastErrorAsString(void) | |
{ | |
// https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror | |
DWORD errorMessageId = GetLastError(); | |
assert(errorMessageId != 0); | |
LPSTR messageBuffer = NULL; | |
DWORD size = | |
FormatMessage( | |
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags, | |
NULL, // LPCVOID lpSource, | |
errorMessageId, // DWORD dwMessageId, | |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // DWORD dwLanguageId, | |
(LPSTR) &messageBuffer, // LPTSTR lpBuffer, | |
0, // DWORD nSize, | |
NULL // va_list *Arguments | |
); | |
return messageBuffer; | |
} | |
// minirent.h IMPLEMENTATION BEGIN //////////////////////////////////////// | |
struct DIR { | |
HANDLE hFind; | |
WIN32_FIND_DATA data; | |
struct dirent *dirent; | |
}; | |
DIR *opendir(const char *dirpath) | |
{ | |
assert(dirpath); | |
char buffer[MAX_PATH]; | |
snprintf(buffer, MAX_PATH, "%s\\*", dirpath); | |
DIR *dir = (DIR*)calloc(1, sizeof(DIR)); | |
dir->hFind = FindFirstFile(buffer, &dir->data); | |
if (dir->hFind == INVALID_HANDLE_VALUE) { | |
errno = ENOSYS; | |
goto fail; | |
} | |
return dir; | |
fail: | |
if (dir) { | |
free(dir); | |
} | |
return NULL; | |
} | |
struct dirent *readdir(DIR *dirp) | |
{ | |
assert(dirp); | |
if (dirp->dirent == NULL) { | |
dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); | |
} else { | |
if(!FindNextFile(dirp->hFind, &dirp->data)) { | |
if (GetLastError() != ERROR_NO_MORE_FILES) { | |
errno = ENOSYS; | |
} | |
return NULL; | |
} | |
} | |
memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); | |
strncpy( | |
dirp->dirent->d_name, | |
dirp->data.cFileName, | |
sizeof(dirp->dirent->d_name) - 1); | |
return dirp->dirent; | |
} | |
int closedir(DIR *dirp) | |
{ | |
assert(dirp); | |
if(!FindClose(dirp->hFind)) { | |
errno = ENOSYS; | |
return -1; | |
} | |
if (dirp->dirent) { | |
free(dirp->dirent); | |
} | |
free(dirp); | |
return 0; | |
} | |
// minirent.h IMPLEMENTATION END //////////////////////////////////////// | |
#endif // _WIN32 | |
Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr) | |
{ | |
Cstr_Array result = { | |
.count = cstrs.count + 1 | |
}; | |
result.elems = malloc(sizeof(result.elems[0]) * result.count); | |
memcpy(result.elems, cstrs.elems, cstrs.count * sizeof(result.elems[0])); | |
result.elems[cstrs.count] = cstr; | |
return result; | |
} | |
int cstr_ends_with(Cstr cstr, Cstr postfix) | |
{ | |
const size_t cstr_len = strlen(cstr); | |
const size_t postfix_len = strlen(postfix); | |
return postfix_len <= cstr_len | |
&& strcmp(cstr + cstr_len - postfix_len, postfix) == 0; | |
} | |
Cstr cstr_no_ext(Cstr path) | |
{ | |
size_t n = strlen(path); | |
while (n > 0 && path[n - 1] != '.') { | |
n -= 1; | |
} | |
if (n > 0) { | |
char *result = malloc(n); | |
memcpy(result, path, n); | |
result[n - 1] = '\0'; | |
return result; | |
} else { | |
return path; | |
} | |
} | |
int countChar(char *string, char delim) { | |
int count = 0; | |
for(int i = 0; i < strlen(string); i++) { | |
if(string[i] == delim) count++; | |
} | |
return count; | |
} | |
char** strSplit(const char* string, char delim, int numdelim) { | |
char **arrays = (char**)malloc(sizeof(char*) * (numdelim + 1)); | |
int start = 0; | |
int j; | |
for (int i=start; i<=numdelim; i++) { | |
arrays[i] = (char*)malloc(strlen(string)); | |
for (j=0; string[start+j] != delim && (start+j)<strlen(string); j++) { | |
arrays[i][j] = string[start+j]; | |
} | |
start = start+j+1; | |
} | |
return arrays; | |
} | |
Cstr_Array cstr_array_make(Cstr first, ...) | |
{ | |
Cstr_Array result = {0}; | |
if (first == NULL) { | |
return result; | |
} | |
result.count += 1; | |
// added by : @aelobdog ---------------------- | |
int spaces; | |
// ------------------------------------------- | |
va_list args; | |
va_start(args, first); | |
for (Cstr next = va_arg(args, Cstr); next != NULL; next = va_arg(args, Cstr)) { | |
// added by : @aelobdog ---------------------- | |
spaces = countChar((char*)next, ' '); | |
if (spaces > 0) { | |
result.count += spaces; | |
} | |
// ------------------------------------------- | |
result.count += 1; | |
} | |
va_end(args); | |
result.elems = malloc(sizeof(result.elems[0]) * result.count); | |
if (result.elems == NULL) { | |
PANIC("could not allocate memory: %s", strerror(errno)); | |
} | |
result.count = 0; | |
result.elems[result.count++] = first; | |
va_start(args, first); | |
for (Cstr next = va_arg(args, Cstr); next != NULL; next = va_arg(args, Cstr)) { | |
// added by : @aelobdog ---------------------- | |
spaces = countChar((char*)next, ' '); | |
if (spaces > 0) { | |
char** subargs = strSplit(next, ' ', spaces); | |
for(int i=0; i <= spaces; i++) { | |
result.elems[result.count++] = subargs[i]; | |
} | |
free(subargs); | |
// ------------------------------------------- | |
} else { | |
result.elems[result.count++] = next; | |
} | |
// char *token; | |
// printf("splitting : %s\n", next); | |
// token = strtok((char*)next, " "); | |
// printf("first token : %s", token); | |
// while(token != NULL) { | |
// result.elems[result.count++] = token; | |
// printf("%s", token); | |
// token = strtok(NULL, " "); | |
} | |
va_end(args); | |
return result; | |
} | |
Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs) | |
{ | |
if (cstrs.count == 0) { | |
return ""; | |
} | |
const size_t sep_len = strlen(sep); | |
size_t len = 0; | |
for (size_t i = 0; i < cstrs.count; ++i) { | |
len += strlen(cstrs.elems[i]); | |
} | |
const size_t result_len = (cstrs.count - 1) * sep_len + len + 1; | |
char *result = malloc(sizeof(char) * result_len); | |
if (result == NULL) { | |
PANIC("could not allocate memory: %s", strerror(errno)); | |
} | |
len = 0; | |
for (size_t i = 0; i < cstrs.count; ++i) { | |
if (i > 0) { | |
memcpy(result + len, sep, sep_len); | |
len += sep_len; | |
} | |
size_t elem_len = strlen(cstrs.elems[i]); | |
memcpy(result + len, cstrs.elems[i], elem_len); | |
len += elem_len; | |
} | |
result[len] = '\0'; | |
return result; | |
} | |
Pipe pipe_make(void) | |
{ | |
Pipe pip = {0}; | |
#ifdef _WIN32 | |
// https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output | |
SECURITY_ATTRIBUTES saAttr = {0}; | |
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); | |
saAttr.bInheritHandle = TRUE; | |
if (!CreatePipe(&pip.read, &pip.write, &saAttr, 0)) { | |
PANIC("Could not create pipe: %s", GetLastErrorAsString()); | |
} | |
#else | |
Fd pipefd[2]; | |
if (pipe(pipefd) < 0) { | |
PANIC("Could not create pipe: %s", strerror(errno)); | |
} | |
pip.read = pipefd[0]; | |
pip.write = pipefd[1]; | |
#endif // _WIN32 | |
return pip; | |
} | |
Fd fd_open_for_read(Cstr path) | |
{ | |
#ifndef _WIN32 | |
Fd result = open(path, O_RDONLY); | |
if (result < 0) { | |
PANIC("Could not open file %s: %s", path, strerror(errno)); | |
} | |
return result; | |
#else | |
// https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing | |
SECURITY_ATTRIBUTES saAttr = {0}; | |
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); | |
saAttr.bInheritHandle = TRUE; | |
Fd result = CreateFile( | |
path, | |
GENERIC_READ, | |
0, | |
&saAttr, | |
OPEN_EXISTING, | |
FILE_ATTRIBUTE_READONLY, | |
NULL); | |
if (result == INVALID_HANDLE_VALUE) { | |
PANIC("Could not open file %s", path); | |
} | |
return result; | |
#endif // _WIN32 | |
} | |
Fd fd_open_for_write(Cstr path) | |
{ | |
#ifndef _WIN32 | |
Fd result = open(path, | |
O_WRONLY | O_CREAT | O_TRUNC, | |
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); | |
if (result < 0) { | |
PANIC("could not open file %s: %s", path, strerror(errno)); | |
} | |
return result; | |
#else | |
SECURITY_ATTRIBUTES saAttr = {0}; | |
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); | |
saAttr.bInheritHandle = TRUE; | |
Fd result = CreateFile( | |
path, // name of the write | |
GENERIC_WRITE, // open for writing | |
0, // do not share | |
&saAttr, // default security | |
CREATE_NEW, // create new file only | |
FILE_ATTRIBUTE_NORMAL, // normal file | |
NULL // no attr. template | |
); | |
if (result == INVALID_HANDLE_VALUE) { | |
PANIC("Could not open file %s: %s", path, GetLastErrorAsString()); | |
} | |
return result; | |
#endif // _WIN32 | |
} | |
void fd_close(Fd fd) | |
{ | |
#ifdef _WIN32 | |
CloseHandle(fd); | |
#else | |
close(fd); | |
#endif // _WIN32 | |
} | |
void pid_wait(Pid pid) | |
{ | |
#ifdef _WIN32 | |
DWORD result = WaitForSingleObject( | |
pid, // HANDLE hHandle, | |
INFINITE // DWORD dwMilliseconds | |
); | |
if (result == WAIT_FAILED) { | |
PANIC("could not wait on child process: %s", GetLastErrorAsString()); | |
} | |
DWORD exit_status; | |
if (GetExitCodeProcess(pid, &exit_status) == 0) { | |
PANIC("could not get process exit code: %lu", GetLastError()); | |
} | |
if (exit_status != 0) { | |
PANIC("command exited with exit code %lu", exit_status); | |
} | |
CloseHandle(pid); | |
#else | |
for (;;) { | |
int wstatus = 0; | |
if (waitpid(pid, &wstatus, 0) < 0) { | |
PANIC("could not wait on command (pid %d): %s", pid, strerror(errno)); | |
} | |
if (WIFEXITED(wstatus)) { | |
int exit_status = WEXITSTATUS(wstatus); | |
if (exit_status != 0) { | |
PANIC("command exited with exit code %d", exit_status); | |
} | |
break; | |
} | |
if (WIFSIGNALED(wstatus)) { | |
PANIC("command process was terminated by %s", strsignal(WTERMSIG(wstatus))); | |
} | |
} | |
#endif // _WIN32 | |
} | |
Cstr cmd_show(Cmd cmd) | |
{ | |
// TODO(#31): cmd_show does not render the command line properly | |
// - No string literals when arguments contains space | |
// - No escaping of special characters | |
// - Etc. | |
return cstr_array_join(" ", cmd.line); | |
} | |
Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout) | |
{ | |
#ifdef _WIN32 | |
// https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output | |
STARTUPINFO siStartInfo; | |
ZeroMemory(&siStartInfo, sizeof(siStartInfo)); | |
siStartInfo.cb = sizeof(STARTUPINFO); | |
// NOTE: theoretically setting NULL to std handles should not be a problem | |
// https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior | |
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); | |
// TODO(#32): check for errors in GetStdHandle | |
siStartInfo.hStdOutput = fdout ? *fdout : GetStdHandle(STD_OUTPUT_HANDLE); | |
siStartInfo.hStdInput = fdin ? *fdin : GetStdHandle(STD_INPUT_HANDLE); | |
siStartInfo.dwFlags |= STARTF_USESTDHANDLES; | |
PROCESS_INFORMATION piProcInfo; | |
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); | |
BOOL bSuccess = | |
CreateProcess( | |
NULL, | |
// TODO(#33): cmd_run_async on Windows does not render command line properly | |
// It may require wrapping some arguments with double-quotes if they contains spaces, etc. | |
cstr_array_join(" ", cmd.line), | |
NULL, | |
NULL, | |
TRUE, | |
0, | |
NULL, | |
NULL, | |
&siStartInfo, | |
&piProcInfo | |
); | |
if (!bSuccess) { | |
PANIC("Could not create child process %s: %s\n", | |
cmd_show(cmd), GetLastErrorAsString()); | |
} | |
CloseHandle(piProcInfo.hThread); | |
return piProcInfo.hProcess; | |
#else | |
pid_t cpid = fork(); | |
if (cpid < 0) { | |
PANIC("Could not fork child process: %s: %s", | |
cmd_show(cmd), strerror(errno)); | |
} | |
if (cpid == 0) { | |
Cstr_Array args = cstr_array_append(cmd.line, NULL); | |
if (fdin) { | |
if (dup2(*fdin, STDIN_FILENO) < 0) { | |
PANIC("Could not setup stdin for child process: %s", strerror(errno)); | |
} | |
} | |
if (fdout) { | |
if (dup2(*fdout, STDOUT_FILENO) < 0) { | |
PANIC("Could not setup stdout for child process: %s", strerror(errno)); | |
} | |
} | |
if (execvp(args.elems[0], (char * const*) args.elems) < 0) { | |
PANIC("Could not exec child process: %s: %s", | |
cmd_show(cmd), strerror(errno)); | |
} | |
} | |
return cpid; | |
#endif // _WIN32 | |
} | |
void cmd_run_sync(Cmd cmd) | |
{ | |
pid_wait(cmd_run_async(cmd, NULL, NULL)); | |
} | |
static void chain_set_input_output_files_or_count_cmds(Chain *chain, Chain_Token token) | |
{ | |
switch (token.type) { | |
case CHAIN_TOKEN_CMD: { | |
chain->cmds.count += 1; | |
} | |
break; | |
case CHAIN_TOKEN_IN: { | |
if (chain->input_filepath) { | |
PANIC("Input file path was already set"); | |
} | |
chain->input_filepath = token.args.elems[0]; | |
} | |
break; | |
case CHAIN_TOKEN_OUT: { | |
if (chain->output_filepath) { | |
PANIC("Output file path was already set"); | |
} | |
chain->output_filepath = token.args.elems[0]; | |
} | |
break; | |
case CHAIN_TOKEN_END: | |
default: { | |
assert(0 && "unreachable"); | |
exit(1); | |
} | |
} | |
} | |
static void chain_push_cmd(Chain *chain, Chain_Token token) | |
{ | |
if (token.type == CHAIN_TOKEN_CMD) { | |
chain->cmds.elems[chain->cmds.count++] = (Cmd) { | |
.line = token.args | |
}; | |
} | |
} | |
Chain chain_build_from_tokens(Chain_Token first, ...) | |
{ | |
Chain result = {0}; | |
chain_set_input_output_files_or_count_cmds(&result, first); | |
va_list args; | |
va_start(args, first); | |
Chain_Token next = va_arg(args, Chain_Token); | |
while (next.type != CHAIN_TOKEN_END) { | |
chain_set_input_output_files_or_count_cmds(&result, next); | |
next = va_arg(args, Chain_Token); | |
} | |
va_end(args); | |
result.cmds.elems = malloc(sizeof(result.cmds.elems[0]) * result.cmds.count); | |
if (result.cmds.elems == NULL) { | |
PANIC("could not allocate memory: %s", strerror(errno)); | |
} | |
result.cmds.count = 0; | |
chain_push_cmd(&result, first); | |
va_start(args, first); | |
next = va_arg(args, Chain_Token); | |
while (next.type != CHAIN_TOKEN_END) { | |
chain_push_cmd(&result, next); | |
next = va_arg(args, Chain_Token); | |
} | |
va_end(args); | |
return result; | |
} | |
void chain_run_sync(Chain chain) | |
{ | |
if (chain.cmds.count == 0) { | |
return; | |
} | |
Pid *cpids = malloc(sizeof(Pid) * chain.cmds.count); | |
Pipe pip = {0}; | |
Fd fdin = 0; | |
Fd *fdprev = NULL; | |
if (chain.input_filepath) { | |
fdin = fd_open_for_read(chain.input_filepath); | |
if (fdin < 0) { | |
PANIC("could not open file %s: %s", chain.input_filepath, strerror(errno)); | |
} | |
fdprev = &fdin; | |
} | |
for (size_t i = 0; i < chain.cmds.count - 1; ++i) { | |
pip = pipe_make(); | |
cpids[i] = cmd_run_async( | |
chain.cmds.elems[i], | |
fdprev, | |
&pip.write); | |
if (fdprev) fd_close(*fdprev); | |
fd_close(pip.write); | |
fdprev = &fdin; | |
fdin = pip.read; | |
} | |
{ | |
Fd fdout = 0; | |
Fd *fdnext = NULL; | |
if (chain.output_filepath) { | |
fdout = fd_open_for_write(chain.output_filepath); | |
if (fdout < 0) { | |
PANIC("could not open file %s: %s", | |
chain.output_filepath, | |
strerror(errno)); | |
} | |
fdnext = &fdout; | |
} | |
const size_t last = chain.cmds.count - 1; | |
cpids[last] = | |
cmd_run_async( | |
chain.cmds.elems[last], | |
fdprev, | |
fdnext); | |
if (fdprev) fd_close(*fdprev); | |
if (fdnext) fd_close(*fdnext); | |
} | |
for (size_t i = 0; i < chain.cmds.count; ++i) { | |
pid_wait(cpids[i]); | |
} | |
} | |
void chain_echo(Chain chain) | |
{ | |
printf("[INFO] CHAIN:"); | |
if (chain.input_filepath) { | |
printf(" %s", chain.input_filepath); | |
} | |
FOREACH_ARRAY(Cmd, cmd, chain.cmds, { | |
printf(" |> %s", cmd_show(*cmd)); | |
}); | |
if (chain.output_filepath) { | |
printf(" |> %s", chain.output_filepath); | |
} | |
printf("\n"); | |
} | |
int path_exists(Cstr path) | |
{ | |
#ifdef _WIN32 | |
DWORD dwAttrib = GetFileAttributes(path); | |
return (dwAttrib != INVALID_FILE_ATTRIBUTES); | |
#else | |
struct stat statbuf = {0}; | |
if (stat(path, &statbuf) < 0) { | |
if (errno == ENOENT) { | |
errno = 0; | |
return 0; | |
} | |
PANIC("could not retrieve information about file %s: %s", | |
path, strerror(errno)); | |
} | |
return 1; | |
#endif | |
} | |
int path_is_dir(Cstr path) | |
{ | |
#ifdef _WIN32 | |
DWORD dwAttrib = GetFileAttributes(path); | |
return (dwAttrib != INVALID_FILE_ATTRIBUTES && | |
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); | |
#else | |
struct stat statbuf = {0}; | |
if (stat(path, &statbuf) < 0) { | |
if (errno == ENOENT) { | |
errno = 0; | |
return 0; | |
} | |
PANIC("could not retrieve information about file %s: %s", | |
path, strerror(errno)); | |
} | |
return S_ISDIR(statbuf.st_mode); | |
#endif // _WIN32 | |
} | |
void path_rename(const char *old_path, const char *new_path) | |
{ | |
#ifdef _WIN32 | |
if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { | |
PANIC("could not rename %s to %s: %s", old_path, new_path, | |
GetLastErrorAsString()); | |
} | |
#else | |
if (rename(old_path, new_path) < 0) { | |
PANIC("could not rename %s to %s: %s", old_path, new_path, | |
strerror(errno)); | |
} | |
#endif // _WIN32 | |
} | |
void path_mkdirs(Cstr_Array path) | |
{ | |
if (path.count == 0) { | |
return; | |
} | |
size_t len = 0; | |
for (size_t i = 0; i < path.count; ++i) { | |
len += strlen(path.elems[i]); | |
} | |
size_t seps_count = path.count - 1; | |
const size_t sep_len = strlen(PATH_SEP); | |
char *result = malloc(len + seps_count * sep_len + 1); | |
len = 0; | |
for (size_t i = 0; i < path.count; ++i) { | |
size_t n = strlen(path.elems[i]); | |
memcpy(result + len, path.elems[i], n); | |
len += n; | |
if (seps_count > 0) { | |
memcpy(result + len, PATH_SEP, sep_len); | |
len += sep_len; | |
seps_count -= 1; | |
} | |
result[len] = '\0'; | |
if (mkdir(result, 0755) < 0) { | |
if (errno == EEXIST) { | |
errno = 0; | |
WARN("directory %s already exists", result); | |
} else { | |
PANIC("could not create directory %s: %s", result, strerror(errno)); | |
} | |
} | |
} | |
} | |
void path_rm(Cstr path) | |
{ | |
if (IS_DIR(path)) { | |
FOREACH_FILE_IN_DIR(file, path, { | |
if (strcmp(file, ".") != 0 && strcmp(file, "..") != 0) | |
{ | |
path_rm(PATH(path, file)); | |
} | |
}); | |
if (rmdir(path) < 0) { | |
if (errno == ENOENT) { | |
errno = 0; | |
WARN("directory %s does not exist", path); | |
} else { | |
PANIC("could not remove directory %s: %s", path, strerror(errno)); | |
} | |
} | |
} else { | |
if (unlink(path) < 0) { | |
if (errno == ENOENT) { | |
errno = 0; | |
WARN("file %s does not exist", path); | |
} else { | |
PANIC("could not remove file %s: %s", path, strerror(errno)); | |
} | |
} | |
} | |
} | |
int is_path1_modified_after_path2(const char *path1, const char *path2) | |
{ | |
#ifdef _WIN32 | |
FILETIME path1_time, path2_time; | |
Fd path1_fd = fd_open_for_read(path1); | |
if (!GetFileTime(path1_fd, NULL, NULL, &path1_time)) { | |
PANIC("could not get time of %s: %s", path1, GetLastErrorAsString()); | |
} | |
fd_close(path1_fd); | |
Fd path2_fd = fd_open_for_read(path2); | |
if (!GetFileTime(path2_fd, NULL, NULL, &path2_time)) { | |
PANIC("could not get time of %s: %s", path2, GetLastErrorAsString()); | |
} | |
fd_close(path2_fd); | |
return CompareFileTime(&path1_time, &path2_time) == 1; | |
#else | |
struct stat statbuf = {0}; | |
if (stat(path1, &statbuf) < 0) { | |
PANIC("could not stat %s: %s\n", path1, strerror(errno)); | |
} | |
int path1_time = statbuf.st_mtime; | |
if (stat(path2, &statbuf) < 0) { | |
PANIC("could not stat %s: %s\n", path2, strerror(errno)); | |
} | |
int path2_time = statbuf.st_mtime; | |
return path1_time > path2_time; | |
#endif | |
} | |
void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args) | |
{ | |
fprintf(stream, "[%s] ", tag); | |
vfprintf(stream, fmt, args); | |
fprintf(stream, "\n"); | |
} | |
void INFO(Cstr fmt, ...) | |
{ | |
va_list args; | |
va_start(args, fmt); | |
VLOG(stderr, "INFO", fmt, args); | |
va_end(args); | |
} | |
void WARN(Cstr fmt, ...) | |
{ | |
va_list args; | |
va_start(args, fmt); | |
VLOG(stderr, "WARN", fmt, args); | |
va_end(args); | |
} | |
void ERRO(Cstr fmt, ...) | |
{ | |
va_list args; | |
va_start(args, fmt); | |
VLOG(stderr, "ERRO", fmt, args); | |
va_end(args); | |
} | |
void PANIC(Cstr fmt, ...) | |
{ | |
va_list args; | |
va_start(args, fmt); | |
VLOG(stderr, "ERRO", fmt, args); | |
va_end(args); | |
exit(1); | |
} | |
char *shift_args(int *argc, char ***argv) | |
{ | |
assert(*argc > 0); | |
char *result = **argv; | |
*argc -= 1; | |
*argv += 1; | |
return result; | |
} | |
#endif // NOBUILD_IMPLEMENTATION |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment