nobuild.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. // Copyright 2021 Alexey Kutepov <[email protected]>
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining
  4. // a copy of this software and associated documentation files (the
  5. // "Software"), to deal in the Software without restriction, including
  6. // without limitation the rights to use, copy, modify, merge, publish,
  7. // distribute, sublicense, and/or sell copies of the Software, and to
  8. // permit persons to whom the Software is furnished to do so, subject to
  9. // the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be
  12. // included in all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. //
  22. // ============================================================
  23. //
  24. // nobuild — 0.0.1 — Header only library for writing build recipes in C.
  25. //
  26. // https://github.com/tsoding/nobuild
  27. //
  28. // ============================================================
  29. //
  30. // ChangeLog (https://semver.org/ is implied)
  31. //
  32. // 0.0.1 First Official Release
  33. #ifndef NOBUILD_H_
  34. #define NOBUILD_H_
  35. #include <assert.h>
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <stdarg.h>
  39. #include <string.h>
  40. #include <errno.h>
  41. #ifdef _WIN32
  42. # define WIN32_LEAN_AND_MEAN
  43. # include "windows.h"
  44. # include <process.h>
  45. // minirent.h HEADER BEGIN ////////////////////////////////////////
  46. // Copyright 2021 Alexey Kutepov <[email protected]>
  47. //
  48. // Permission is hereby granted, free of charge, to any person obtaining
  49. // a copy of this software and associated documentation files (the
  50. // "Software"), to deal in the Software without restriction, including
  51. // without limitation the rights to use, copy, modify, merge, publish,
  52. // distribute, sublicense, and/or sell copies of the Software, and to
  53. // permit persons to whom the Software is furnished to do so, subject to
  54. // the following conditions:
  55. //
  56. // The above copyright notice and this permission notice shall be
  57. // included in all copies or substantial portions of the Software.
  58. //
  59. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  60. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  61. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  62. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  63. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  64. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  65. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  66. //
  67. // ============================================================
  68. //
  69. // minirent — 0.0.1 — A subset of dirent interface for Windows.
  70. //
  71. // https://github.com/tsoding/minirent
  72. //
  73. // ============================================================
  74. //
  75. // ChangeLog (https://semver.org/ is implied)
  76. //
  77. // 0.0.1 First Official Release
  78. #ifndef MINIRENT_H_
  79. #define MINIRENT_H_
  80. #define WIN32_LEAN_AND_MEAN
  81. #include "windows.h"
  82. struct dirent
  83. {
  84. char d_name[MAX_PATH+1];
  85. };
  86. typedef struct DIR DIR;
  87. DIR *opendir(const char *dirpath);
  88. struct dirent *readdir(DIR *dirp);
  89. int closedir(DIR *dirp);
  90. #endif // MINIRENT_H_
  91. // minirent.h HEADER END ////////////////////////////////////////
  92. #else
  93. # include <sys/stat.h>
  94. # include <sys/types.h>
  95. # include <sys/wait.h>
  96. # include <unistd.h>
  97. # include <dirent.h>
  98. #endif // _WIN32
  99. // TODO(#1): no way to disable echo in nobuild scripts
  100. // TODO(#2): no way to ignore fails
  101. #ifdef _WIN32
  102. # define PATH_SEP "\\"
  103. #else
  104. # define PATH_SEP "/"
  105. #endif // _WIN32
  106. #define PATH_SEP_LEN (sizeof(PATH_SEP) - 1)
  107. #define FOREACH_VARGS(param, arg, args, body) \
  108. do { \
  109. va_start(args, param); \
  110. for (const char *arg = va_arg(args, const char *); \
  111. arg != NULL; \
  112. arg = va_arg(args, const char *)) \
  113. { \
  114. body; \
  115. } \
  116. va_end(args); \
  117. } while(0)
  118. #define FOREACH_ARRAY(type, item, items, body) \
  119. do { \
  120. for (size_t i = 0; \
  121. i < sizeof(items) / sizeof((items)[0]); \
  122. ++i) \
  123. { \
  124. type item = items[i]; \
  125. body; \
  126. } \
  127. } while(0)
  128. #define FOREACH_FILE_IN_DIR(file, dirpath, body) \
  129. do { \
  130. struct dirent *dp = NULL; \
  131. DIR *dir = opendir(dirpath); \
  132. while ((dp = readdir(dir))) { \
  133. const char *file = dp->d_name; \
  134. body; \
  135. } \
  136. closedir(dir); \
  137. } while(0)
  138. // TODO(#5): there is no way to redirect the output of CMD to a file
  139. #define CMD(...) \
  140. do { \
  141. printf("[INFO] %s\n", CONCAT_SEP(" ", __VA_ARGS__)); \
  142. cmd_impl(69, __VA_ARGS__, NULL); \
  143. } while(0)
  144. const char *concat_impl(int ignore, ...);
  145. const char *concat_sep_impl(const char *sep, ...);
  146. void mkdirs_impl(int ignore, ...);
  147. void cmd_impl(int ignore, ...);
  148. void nobuild_exec(const char **argv);
  149. const char *remove_ext(const char *path);
  150. char *shift(int *argc, char ***argv);
  151. #define CONCAT(...) concat_impl(69, __VA_ARGS__, NULL)
  152. #define CONCAT_SEP(sep, ...) concat_sep_impl(sep, __VA_ARGS__, NULL)
  153. #define PATH(...) CONCAT_SEP(PATH_SEP, __VA_ARGS__)
  154. #define MKDIRS(...) mkdirs_impl(69, __VA_ARGS__, NULL)
  155. #endif // NOBUILD_H_
  156. #ifdef NOBUILD_IMPLEMENTATION
  157. #ifdef _WIN32
  158. // minirent.h IMPLEMENTATION BEGIN ////////////////////////////////////////
  159. struct DIR
  160. {
  161. HANDLE hFind;
  162. WIN32_FIND_DATA data;
  163. struct dirent *dirent;
  164. };
  165. DIR *opendir(const char *dirpath)
  166. {
  167. assert(dirpath);
  168. char buffer[MAX_PATH];
  169. snprintf(buffer, MAX_PATH, "%s\\*", dirpath);
  170. DIR *dir = (DIR*)calloc(1, sizeof(DIR));
  171. dir->hFind = FindFirstFile(buffer, &dir->data);
  172. if (dir->hFind == INVALID_HANDLE_VALUE) {
  173. errno = ENOSYS;
  174. goto fail;
  175. }
  176. return dir;
  177. fail:
  178. if (dir) {
  179. free(dir);
  180. }
  181. return NULL;
  182. }
  183. struct dirent *readdir(DIR *dirp)
  184. {
  185. assert(dirp);
  186. if (dirp->dirent == NULL) {
  187. dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent));
  188. } else {
  189. if(!FindNextFile(dirp->hFind, &dirp->data)) {
  190. if (GetLastError() != ERROR_NO_MORE_FILES) {
  191. errno = ENOSYS;
  192. }
  193. return NULL;
  194. }
  195. }
  196. memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name));
  197. strncpy(
  198. dirp->dirent->d_name,
  199. dirp->data.cFileName,
  200. sizeof(dirp->dirent->d_name) - 1);
  201. return dirp->dirent;
  202. }
  203. int closedir(DIR *dirp)
  204. {
  205. assert(dirp);
  206. if(!FindClose(dirp->hFind)) {
  207. errno = ENOSYS;
  208. return -1;
  209. }
  210. if (dirp->dirent) {
  211. free(dirp->dirent);
  212. }
  213. free(dirp);
  214. return 0;
  215. }
  216. // minirent.h IMPLEMENTATION END ////////////////////////////////////////
  217. #endif // _WIN32
  218. const char *concat_sep_impl(const char *sep, ...)
  219. {
  220. const size_t sep_len = strlen(sep);
  221. size_t length = 0;
  222. size_t seps_count = 0;
  223. va_list args;
  224. FOREACH_VARGS(sep, arg, args, {
  225. length += strlen(arg);
  226. seps_count += 1;
  227. });
  228. assert(length > 0);
  229. seps_count -= 1;
  230. char *result = malloc(length + seps_count * sep_len + 1);
  231. length = 0;
  232. FOREACH_VARGS(sep, arg, args, {
  233. size_t n = strlen(arg);
  234. memcpy(result + length, arg, n);
  235. length += n;
  236. if (seps_count > 0) {
  237. memcpy(result + length, sep, sep_len);
  238. length += sep_len;
  239. seps_count -= 1;
  240. }
  241. });
  242. result[length] = '\0';
  243. return result;
  244. }
  245. void mkdirs_impl(int ignore, ...)
  246. {
  247. size_t length = 0;
  248. size_t seps_count = 0;
  249. va_list args;
  250. FOREACH_VARGS(ignore, arg, args, {
  251. length += strlen(arg);
  252. seps_count += 1;
  253. });
  254. assert(length > 0);
  255. seps_count -= 1;
  256. char *result = malloc(length + seps_count * PATH_SEP_LEN + 1);
  257. length = 0;
  258. FOREACH_VARGS(ignore, arg, args, {
  259. size_t n = strlen(arg);
  260. memcpy(result + length, arg, n);
  261. length += n;
  262. if (seps_count > 0) {
  263. memcpy(result + length, PATH_SEP, PATH_SEP_LEN);
  264. length += PATH_SEP_LEN;
  265. seps_count -= 1;
  266. }
  267. result[length] = '\0';
  268. printf("[INFO] mkdir %s\n", result);
  269. if (mkdir(result, 0755) < 0) {
  270. if (errno == EEXIST) {
  271. fprintf(stderr, "[WARN] directory %s already exists\n",
  272. result);
  273. } else {
  274. fprintf(stderr, "[ERROR] could not create directory %s: %s\n",
  275. result, strerror(errno));
  276. exit(1);
  277. }
  278. }
  279. });
  280. }
  281. // TODO(#3): there is no way to remove a folder
  282. // TODO(#4): there is no way to remove a file
  283. const char *concat_impl(int ignore, ...)
  284. {
  285. size_t length = 0;
  286. va_list args;
  287. FOREACH_VARGS(ignore, arg, args, {
  288. length += strlen(arg);
  289. });
  290. char *result = malloc(length + 1);
  291. length = 0;
  292. FOREACH_VARGS(ignore, arg, args, {
  293. size_t n = strlen(arg);
  294. memcpy(result + length, arg, n);
  295. length += n;
  296. });
  297. result[length] = '\0';
  298. return result;
  299. }
  300. void nobuild_exec(const char **argv)
  301. {
  302. #ifdef _WIN32
  303. intptr_t status = _spawnvp(_P_WAIT, argv[0], (char * const*) argv);
  304. if (status < 0) {
  305. fprintf(stderr, "[ERROR] could not start child process: %s\n",
  306. strerror(errno));
  307. exit(1);
  308. }
  309. if (status > 0) {
  310. fprintf(stderr, "[ERROR] command exited with exit code %d\n",
  311. status);
  312. exit(1);
  313. }
  314. #else
  315. pid_t cpid = fork();
  316. if (cpid == -1) {
  317. fprintf(stderr, "[ERROR] could not fork a child process: %s\n",
  318. strerror(errno));
  319. exit(1);
  320. }
  321. if (cpid == 0) {
  322. if (execvp(argv[0], (char * const*) argv) < 0) {
  323. fprintf(stderr, "[ERROR] could not execute child process: %s\n",
  324. strerror(errno));
  325. exit(1);
  326. }
  327. } else {
  328. for (;;) {
  329. int wstatus = 0;
  330. wait(&wstatus);
  331. if (WIFEXITED(wstatus)) {
  332. int exit_status = WEXITSTATUS(wstatus);
  333. if (exit_status != 0) {
  334. fprintf(stderr, "[ERROR] command exited with exit code %d\n", exit_status);
  335. exit(-1);
  336. }
  337. break;
  338. }
  339. if (WIFSIGNALED(wstatus)) {
  340. fprintf(stderr, "[ERROR] command process was terminated by signal %d\n",
  341. WTERMSIG(wstatus));
  342. exit(-1);
  343. }
  344. }
  345. }
  346. #endif // _WIN32
  347. }
  348. void cmd_impl(int ignore, ...)
  349. {
  350. size_t argc = 0;
  351. va_list args;
  352. FOREACH_VARGS(ignore, arg, args, {
  353. argc += 1;
  354. });
  355. const char **argv = malloc(sizeof(const char*) * (argc + 1));
  356. argc = 0;
  357. FOREACH_VARGS(ignore, arg, args, {
  358. argv[argc++] = arg;
  359. });
  360. argv[argc] = NULL;
  361. assert(argc >= 1);
  362. nobuild_exec(argv);
  363. }
  364. const char *remove_ext(const char *path)
  365. {
  366. size_t n = strlen(path);
  367. while (n > 0 && path[n - 1] != '.') {
  368. n -= 1;
  369. }
  370. if (n > 0) {
  371. char *result = malloc(n);
  372. memcpy(result, path, n);
  373. result[n - 1] = '\0';
  374. return result;
  375. } else {
  376. return path;
  377. }
  378. }
  379. char *shift(int *argc, char ***argv)
  380. {
  381. assert(*argc > 0);
  382. char *result = **argv;
  383. *argv += 1;
  384. *argc -= 1;
  385. return result;
  386. }
  387. #endif // NOBUILD_IMPLEMENTATION