|
@@ -0,0 +1,1151 @@
|
|
|
+#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>
|
|
|
+# 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 <[email protected]>
|
|
|
+//
|
|
|
+// 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
|
|
|
+//
|
|
|
+// ============================================================
|
|
|
+//
|
|
|
+// ChangeLog (https://semver.org/ is implied)
|
|
|
+//
|
|
|
+// 0.0.1 First Official Release
|
|
|
+
|
|
|
+#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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+Cstr_Array cstr_array_make(Cstr first, ...)
|
|
|
+{
|
|
|
+ Cstr_Array result = {0};
|
|
|
+
|
|
|
+ if (first == NULL) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ result.count += 1;
|
|
|
+
|
|
|
+ va_list args;
|
|
|
+ va_start(args, first);
|
|
|
+ for (Cstr next = va_arg(args, Cstr);
|
|
|
+ next != NULL;
|
|
|
+ next = va_arg(args, Cstr)) {
|
|
|
+ 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)) {
|
|
|
+ result.elems[result.count++] = next;
|
|
|
+ }
|
|
|
+ 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
|