nobuild.h 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193
  1. #ifndef NOBUILD_H_
  2. #define NOBUILD_H_
  3. #ifndef _WIN32
  4. # define _POSIX_C_SOURCE 200809L
  5. # include <sys/types.h>
  6. # include <sys/wait.h>
  7. # include <sys/stat.h>
  8. # include <unistd.h>
  9. # include <dirent.h>
  10. # include <fcntl.h>
  11. # include <limits.h>
  12. # define PATH_SEP "/"
  13. typedef pid_t Pid;
  14. typedef int Fd;
  15. #else
  16. # define WIN32_MEAN_AND_LEAN
  17. # include "windows.h"
  18. # include <process.h>
  19. # define PATH_SEP "\\"
  20. typedef HANDLE Pid;
  21. typedef HANDLE Fd;
  22. // minirent.h HEADER BEGIN ////////////////////////////////////////
  23. // Copyright 2021 Alexey Kutepov <[email protected]>
  24. //
  25. // Permission is hereby granted, free of charge, to any person obtaining
  26. // a copy of this software and associated documentation files (the
  27. // "Software"), to deal in the Software without restriction, including
  28. // without limitation the rights to use, copy, modify, merge, publish,
  29. // distribute, sublicense, and/or sell copies of the Software, and to
  30. // permit persons to whom the Software is furnished to do so, subject to
  31. // the following conditions:
  32. //
  33. // The above copyright notice and this permission notice shall be
  34. // included in all copies or substantial portions of the Software.
  35. //
  36. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  37. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  38. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  39. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  40. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  41. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  42. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  43. //
  44. // ============================================================
  45. //
  46. // minirent — 0.0.1 — A subset of dirent interface for Windows.
  47. //
  48. // https://github.com/tsoding/minirent
  49. //
  50. // ============================================================
  51. //
  52. // ChangeLog (https://semver.org/ is implied)
  53. //
  54. // 0.0.1 First Official Release
  55. #ifndef MINIRENT_H_
  56. #define MINIRENT_H_
  57. #define WIN32_LEAN_AND_MEAN
  58. #include "windows.h"
  59. struct dirent {
  60. char d_name[MAX_PATH+1];
  61. };
  62. typedef struct DIR DIR;
  63. DIR *opendir(const char *dirpath);
  64. struct dirent *readdir(DIR *dirp);
  65. int closedir(DIR *dirp);
  66. #endif // MINIRENT_H_
  67. // minirent.h HEADER END ////////////////////////////////////////
  68. // TODO(#28): use GetLastErrorAsString everywhere on Windows error reporting
  69. LPSTR GetLastErrorAsString(void);
  70. #endif // _WIN32
  71. #include <assert.h>
  72. #include <stdio.h>
  73. #include <stdlib.h>
  74. #include <stdarg.h>
  75. #include <string.h>
  76. #include <errno.h>
  77. #define FOREACH_ARRAY(type, elem, array, body) \
  78. for (size_t elem_##index = 0; \
  79. elem_##index < array.count; \
  80. ++elem_##index) \
  81. { \
  82. type *elem = &array.elems[elem_##index]; \
  83. body; \
  84. }
  85. typedef const char * Cstr;
  86. int cstr_ends_with(Cstr cstr, Cstr postfix);
  87. #define ENDS_WITH(cstr, postfix) cstr_ends_with(cstr, postfix)
  88. Cstr cstr_no_ext(Cstr path);
  89. #define NOEXT(path) cstr_no_ext(path)
  90. typedef struct {
  91. Cstr *elems;
  92. size_t count;
  93. } Cstr_Array;
  94. Cstr_Array cstr_array_make(Cstr first, ...);
  95. Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr);
  96. Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs);
  97. #define JOIN(sep, ...) cstr_array_join(sep, cstr_array_make(__VA_ARGS__, NULL))
  98. #define CONCAT(...) JOIN("", __VA_ARGS__)
  99. #define PATH(...) JOIN(PATH_SEP, __VA_ARGS__)
  100. #define GETCWD() path_get_current_dir()
  101. #define SETCWD(path) path_set_current_dir(path)
  102. typedef struct {
  103. Fd read;
  104. Fd write;
  105. } Pipe;
  106. Pipe pipe_make(void);
  107. typedef struct {
  108. Cstr_Array line;
  109. } Cmd;
  110. Fd fd_open_for_read(Cstr path);
  111. Fd fd_open_for_write(Cstr path);
  112. void fd_close(Fd fd);
  113. void pid_wait(Pid pid);
  114. Cstr cmd_show(Cmd cmd);
  115. Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout);
  116. void cmd_run_sync(Cmd cmd);
  117. typedef struct {
  118. Cmd *elems;
  119. size_t count;
  120. } Cmd_Array;
  121. // TODO(#1): no way to disable echo in nobuild scripts
  122. // TODO(#2): no way to ignore fails
  123. #define CMD(...) \
  124. do { \
  125. Cmd cmd = { \
  126. .line = cstr_array_make(__VA_ARGS__, NULL) \
  127. }; \
  128. INFO("CMD: %s", cmd_show(cmd)); \
  129. cmd_run_sync(cmd); \
  130. } while (0)
  131. typedef enum {
  132. CHAIN_TOKEN_END = 0,
  133. CHAIN_TOKEN_IN,
  134. CHAIN_TOKEN_OUT,
  135. CHAIN_TOKEN_CMD
  136. } Chain_Token_Type;
  137. // A single token for the CHAIN(...) DSL syntax
  138. typedef struct {
  139. Chain_Token_Type type;
  140. Cstr_Array args;
  141. } Chain_Token;
  142. // TODO(#17): IN and OUT are already taken by WinAPI
  143. #define IN(path) \
  144. (Chain_Token) { \
  145. .type = CHAIN_TOKEN_IN, \
  146. .args = cstr_array_make(path, NULL) \
  147. }
  148. #define OUT(path) \
  149. (Chain_Token) { \
  150. .type = CHAIN_TOKEN_OUT, \
  151. .args = cstr_array_make(path, NULL) \
  152. }
  153. #define CHAIN_CMD(...) \
  154. (Chain_Token) { \
  155. .type = CHAIN_TOKEN_CMD, \
  156. .args = cstr_array_make(__VA_ARGS__, NULL) \
  157. }
  158. // TODO(#20): pipes do not allow redirecting stderr
  159. typedef struct {
  160. Cstr input_filepath;
  161. Cmd_Array cmds;
  162. Cstr output_filepath;
  163. } Chain;
  164. Chain chain_build_from_tokens(Chain_Token first, ...);
  165. void chain_run_sync(Chain chain);
  166. void chain_echo(Chain chain);
  167. // TODO(#15): PIPE does not report where exactly a syntactic error has happened
  168. #define CHAIN(...) \
  169. do { \
  170. Chain chain = chain_build_from_tokens(__VA_ARGS__, (Chain_Token) {0}); \
  171. chain_echo(chain); \
  172. chain_run_sync(chain); \
  173. } while(0)
  174. #ifndef REBUILD_URSELF
  175. # if _WIN32
  176. # if defined(__GNUC__)
  177. # define REBUILD_URSELF(binary_path, source_path) CMD("gcc", "-o", binary_path, source_path)
  178. # elif defined(__clang__)
  179. # define REBUILD_URSELF(binary_path, source_path) CMD("clang", "-o", binary_path, source_path)
  180. # elif defined(_MSC_VER)
  181. # define REBUILD_URSELF(binary_path, source_path) CMD("cl.exe", source_path)
  182. # endif
  183. # else
  184. # define REBUILD_URSELF(binary_path, source_path) CMD("cc", "-o", binary_path, source_path)
  185. # endif
  186. #endif
  187. // Go Rebuild Urself™ Technology
  188. //
  189. // How to use it:
  190. // int main(int argc, char** argv) {
  191. // GO_REBUILD_URSELF(argc, argv);
  192. // // actual work
  193. // return 0;
  194. // }
  195. //
  196. // After your added this macro every time you run ./nobuild it will detect
  197. // that you modified its original source code and will try to rebuild itself
  198. // before doing any actual work. So you only need to bootstrap your build system
  199. // once.
  200. //
  201. // The modification is detected by comparing the last modified times of the executable
  202. // and its source code. The same way the make utility usually does it.
  203. //
  204. // The rebuilding is done by using the REBUILD_URSELF macro which you can redefine
  205. // if you need a special way of bootstraping your build system. (which I personally
  206. // do not recommend since the whole idea of nobuild is to keep the process of bootstrapping
  207. // as simple as possible and doing all of the actual work inside of the nobuild)
  208. //
  209. #define GO_REBUILD_URSELF(argc, argv) \
  210. do { \
  211. const char *source_path = __FILE__; \
  212. assert(argc >= 1); \
  213. const char *binary_path = argv[0]; \
  214. \
  215. if (is_path1_modified_after_path2(source_path, binary_path)) { \
  216. RENAME(binary_path, CONCAT(binary_path, ".old")); \
  217. REBUILD_URSELF(binary_path, source_path); \
  218. Cmd cmd = { \
  219. .line = { \
  220. .elems = (Cstr*) argv, \
  221. .count = argc, \
  222. }, \
  223. }; \
  224. INFO("CMD: %s", cmd_show(cmd)); \
  225. cmd_run_sync(cmd); \
  226. exit(0); \
  227. } \
  228. } while(0)
  229. // The implementation idea is stolen from https://github.com/zhiayang/nabs
  230. void rebuild_urself(const char *binary_path, const char *source_path);
  231. int path_is_dir(Cstr path);
  232. #define IS_DIR(path) path_is_dir(path)
  233. int path_exists(Cstr path);
  234. #define PATH_EXISTS(path) path_exists(path)
  235. void path_mkdirs(Cstr_Array path);
  236. #define MKDIRS(...) \
  237. do { \
  238. Cstr_Array path = cstr_array_make(__VA_ARGS__, NULL); \
  239. INFO("MKDIRS: %s", cstr_array_join(PATH_SEP, path)); \
  240. path_mkdirs(path); \
  241. } while (0)
  242. void path_rename(Cstr old_path, Cstr new_path);
  243. #define RENAME(old_path, new_path) \
  244. do { \
  245. INFO("RENAME: %s -> %s", old_path, new_path); \
  246. path_rename(old_path, new_path); \
  247. } while (0)
  248. void path_rm(Cstr path);
  249. #define RM(path) \
  250. do { \
  251. INFO("RM: %s", path); \
  252. path_rm(path); \
  253. } while(0)
  254. #define FOREACH_FILE_IN_DIR(file, dirpath, body) \
  255. do { \
  256. struct dirent *dp = NULL; \
  257. DIR *dir = opendir(dirpath); \
  258. if (dir == NULL) { \
  259. PANIC("could not open directory %s: %s", \
  260. dirpath, strerror(errno)); \
  261. } \
  262. errno = 0; \
  263. while ((dp = readdir(dir))) { \
  264. const char *file = dp->d_name; \
  265. body; \
  266. } \
  267. \
  268. if (errno > 0) { \
  269. PANIC("could not read directory %s: %s", \
  270. dirpath, strerror(errno)); \
  271. } \
  272. \
  273. closedir(dir); \
  274. } while(0)
  275. #if defined(__GNUC__) || defined(__clang__)
  276. // https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html
  277. #define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK)))
  278. #else
  279. #define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK)
  280. #endif
  281. void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args);
  282. void INFO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2);
  283. void WARN(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2);
  284. void ERRO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2);
  285. void PANIC(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2);
  286. char *shift_args(int *argc, char ***argv);
  287. #endif // NOBUILD_H_
  288. ////////////////////////////////////////////////////////////////////////////////
  289. #ifdef NOBUILD_IMPLEMENTATION
  290. #ifdef _WIN32
  291. LPSTR GetLastErrorAsString(void)
  292. {
  293. // https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror
  294. DWORD errorMessageId = GetLastError();
  295. assert(errorMessageId != 0);
  296. LPSTR messageBuffer = NULL;
  297. DWORD size =
  298. FormatMessage(
  299. FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags,
  300. NULL, // LPCVOID lpSource,
  301. errorMessageId, // DWORD dwMessageId,
  302. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // DWORD dwLanguageId,
  303. (LPSTR) &messageBuffer, // LPTSTR lpBuffer,
  304. 0, // DWORD nSize,
  305. NULL // va_list *Arguments
  306. );
  307. return messageBuffer;
  308. }
  309. // minirent.h IMPLEMENTATION BEGIN ////////////////////////////////////////
  310. struct DIR {
  311. HANDLE hFind;
  312. WIN32_FIND_DATA data;
  313. struct dirent *dirent;
  314. };
  315. DIR *opendir(const char *dirpath)
  316. {
  317. assert(dirpath);
  318. char buffer[MAX_PATH];
  319. snprintf(buffer, MAX_PATH, "%s\\*", dirpath);
  320. DIR *dir = (DIR*)calloc(1, sizeof(DIR));
  321. dir->hFind = FindFirstFile(buffer, &dir->data);
  322. if (dir->hFind == INVALID_HANDLE_VALUE) {
  323. errno = ENOSYS;
  324. goto fail;
  325. }
  326. return dir;
  327. fail:
  328. if (dir) {
  329. free(dir);
  330. }
  331. return NULL;
  332. }
  333. struct dirent *readdir(DIR *dirp)
  334. {
  335. assert(dirp);
  336. if (dirp->dirent == NULL) {
  337. dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent));
  338. } else {
  339. if(!FindNextFile(dirp->hFind, &dirp->data)) {
  340. if (GetLastError() != ERROR_NO_MORE_FILES) {
  341. errno = ENOSYS;
  342. }
  343. return NULL;
  344. }
  345. }
  346. memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name));
  347. strncpy(
  348. dirp->dirent->d_name,
  349. dirp->data.cFileName,
  350. sizeof(dirp->dirent->d_name) - 1);
  351. return dirp->dirent;
  352. }
  353. int closedir(DIR *dirp)
  354. {
  355. assert(dirp);
  356. if(!FindClose(dirp->hFind)) {
  357. errno = ENOSYS;
  358. return -1;
  359. }
  360. if (dirp->dirent) {
  361. free(dirp->dirent);
  362. }
  363. free(dirp);
  364. return 0;
  365. }
  366. // minirent.h IMPLEMENTATION END ////////////////////////////////////////
  367. #endif // _WIN32
  368. Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr)
  369. {
  370. Cstr_Array result = {
  371. .count = cstrs.count + 1
  372. };
  373. result.elems = malloc(sizeof(result.elems[0]) * result.count);
  374. memcpy(result.elems, cstrs.elems, cstrs.count * sizeof(result.elems[0]));
  375. result.elems[cstrs.count] = cstr;
  376. return result;
  377. }
  378. int cstr_ends_with(Cstr cstr, Cstr postfix)
  379. {
  380. const size_t cstr_len = strlen(cstr);
  381. const size_t postfix_len = strlen(postfix);
  382. return postfix_len <= cstr_len
  383. && strcmp(cstr + cstr_len - postfix_len, postfix) == 0;
  384. }
  385. Cstr cstr_no_ext(Cstr path)
  386. {
  387. size_t n = strlen(path);
  388. while (n > 0 && path[n - 1] != '.') {
  389. n -= 1;
  390. }
  391. if (n > 0) {
  392. char *result = malloc(n);
  393. memcpy(result, path, n);
  394. result[n - 1] = '\0';
  395. return result;
  396. } else {
  397. return path;
  398. }
  399. }
  400. Cstr_Array cstr_array_make(Cstr first, ...)
  401. {
  402. Cstr_Array result = {0};
  403. if (first == NULL) {
  404. return result;
  405. }
  406. result.count += 1;
  407. va_list args;
  408. va_start(args, first);
  409. for (Cstr next = va_arg(args, Cstr);
  410. next != NULL;
  411. next = va_arg(args, Cstr)) {
  412. result.count += 1;
  413. }
  414. va_end(args);
  415. result.elems = malloc(sizeof(result.elems[0]) * result.count);
  416. if (result.elems == NULL) {
  417. PANIC("could not allocate memory: %s", strerror(errno));
  418. }
  419. result.count = 0;
  420. result.elems[result.count++] = first;
  421. va_start(args, first);
  422. for (Cstr next = va_arg(args, Cstr);
  423. next != NULL;
  424. next = va_arg(args, Cstr)) {
  425. result.elems[result.count++] = next;
  426. }
  427. va_end(args);
  428. return result;
  429. }
  430. Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs)
  431. {
  432. if (cstrs.count == 0) {
  433. return "";
  434. }
  435. const size_t sep_len = strlen(sep);
  436. size_t len = 0;
  437. for (size_t i = 0; i < cstrs.count; ++i) {
  438. len += strlen(cstrs.elems[i]);
  439. }
  440. const size_t result_len = (cstrs.count - 1) * sep_len + len + 1;
  441. char *result = malloc(sizeof(char) * result_len);
  442. if (result == NULL) {
  443. PANIC("could not allocate memory: %s", strerror(errno));
  444. }
  445. len = 0;
  446. for (size_t i = 0; i < cstrs.count; ++i) {
  447. if (i > 0) {
  448. memcpy(result + len, sep, sep_len);
  449. len += sep_len;
  450. }
  451. size_t elem_len = strlen(cstrs.elems[i]);
  452. memcpy(result + len, cstrs.elems[i], elem_len);
  453. len += elem_len;
  454. }
  455. result[len] = '\0';
  456. return result;
  457. }
  458. Pipe pipe_make(void)
  459. {
  460. Pipe pip = {0};
  461. #ifdef _WIN32
  462. // https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output
  463. SECURITY_ATTRIBUTES saAttr = {0};
  464. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  465. saAttr.bInheritHandle = TRUE;
  466. if (!CreatePipe(&pip.read, &pip.write, &saAttr, 0)) {
  467. PANIC("Could not create pipe: %s", GetLastErrorAsString());
  468. }
  469. #else
  470. Fd pipefd[2];
  471. if (pipe(pipefd) < 0) {
  472. PANIC("Could not create pipe: %s", strerror(errno));
  473. }
  474. pip.read = pipefd[0];
  475. pip.write = pipefd[1];
  476. #endif // _WIN32
  477. return pip;
  478. }
  479. Fd fd_open_for_read(Cstr path)
  480. {
  481. #ifndef _WIN32
  482. Fd result = open(path, O_RDONLY);
  483. if (result < 0) {
  484. PANIC("Could not open file %s: %s", path, strerror(errno));
  485. }
  486. return result;
  487. #else
  488. // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing
  489. SECURITY_ATTRIBUTES saAttr = {0};
  490. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  491. saAttr.bInheritHandle = TRUE;
  492. Fd result = CreateFile(
  493. path,
  494. GENERIC_READ,
  495. 0,
  496. &saAttr,
  497. OPEN_EXISTING,
  498. FILE_ATTRIBUTE_READONLY,
  499. NULL);
  500. if (result == INVALID_HANDLE_VALUE) {
  501. PANIC("Could not open file %s", path);
  502. }
  503. return result;
  504. #endif // _WIN32
  505. }
  506. Fd fd_open_for_write(Cstr path)
  507. {
  508. #ifndef _WIN32
  509. Fd result = open(path,
  510. O_WRONLY | O_CREAT | O_TRUNC,
  511. S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  512. if (result < 0) {
  513. PANIC("could not open file %s: %s", path, strerror(errno));
  514. }
  515. return result;
  516. #else
  517. SECURITY_ATTRIBUTES saAttr = {0};
  518. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  519. saAttr.bInheritHandle = TRUE;
  520. Fd result = CreateFile(
  521. path, // name of the write
  522. GENERIC_WRITE, // open for writing
  523. 0, // do not share
  524. &saAttr, // default security
  525. CREATE_NEW, // create new file only
  526. FILE_ATTRIBUTE_NORMAL, // normal file
  527. NULL // no attr. template
  528. );
  529. if (result == INVALID_HANDLE_VALUE) {
  530. PANIC("Could not open file %s: %s", path, GetLastErrorAsString());
  531. }
  532. return result;
  533. #endif // _WIN32
  534. }
  535. void fd_close(Fd fd)
  536. {
  537. #ifdef _WIN32
  538. CloseHandle(fd);
  539. #else
  540. close(fd);
  541. #endif // _WIN32
  542. }
  543. void pid_wait(Pid pid)
  544. {
  545. #ifdef _WIN32
  546. DWORD result = WaitForSingleObject(
  547. pid, // HANDLE hHandle,
  548. INFINITE // DWORD dwMilliseconds
  549. );
  550. if (result == WAIT_FAILED) {
  551. PANIC("could not wait on child process: %s", GetLastErrorAsString());
  552. }
  553. DWORD exit_status;
  554. if (GetExitCodeProcess(pid, &exit_status) == 0) {
  555. PANIC("could not get process exit code: %lu", GetLastError());
  556. }
  557. if (exit_status != 0) {
  558. PANIC("command exited with exit code %lu", exit_status);
  559. }
  560. CloseHandle(pid);
  561. #else
  562. for (;;) {
  563. int wstatus = 0;
  564. if (waitpid(pid, &wstatus, 0) < 0) {
  565. PANIC("could not wait on command (pid %d): %s", pid, strerror(errno));
  566. }
  567. if (WIFEXITED(wstatus)) {
  568. int exit_status = WEXITSTATUS(wstatus);
  569. if (exit_status != 0) {
  570. PANIC("command exited with exit code %d", exit_status);
  571. }
  572. break;
  573. }
  574. if (WIFSIGNALED(wstatus)) {
  575. PANIC("command process was terminated by %s", strsignal(WTERMSIG(wstatus)));
  576. }
  577. }
  578. #endif // _WIN32
  579. }
  580. Cstr cmd_show(Cmd cmd)
  581. {
  582. // TODO(#31): cmd_show does not render the command line properly
  583. // - No string literals when arguments contains space
  584. // - No escaping of special characters
  585. // - Etc.
  586. return cstr_array_join(" ", cmd.line);
  587. }
  588. Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout)
  589. {
  590. #ifdef _WIN32
  591. // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
  592. STARTUPINFO siStartInfo;
  593. ZeroMemory(&siStartInfo, sizeof(siStartInfo));
  594. siStartInfo.cb = sizeof(STARTUPINFO);
  595. // NOTE: theoretically setting NULL to std handles should not be a problem
  596. // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
  597. siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
  598. // TODO(#32): check for errors in GetStdHandle
  599. siStartInfo.hStdOutput = fdout ? *fdout : GetStdHandle(STD_OUTPUT_HANDLE);
  600. siStartInfo.hStdInput = fdin ? *fdin : GetStdHandle(STD_INPUT_HANDLE);
  601. siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
  602. PROCESS_INFORMATION piProcInfo;
  603. ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
  604. BOOL bSuccess =
  605. CreateProcess(
  606. NULL,
  607. // TODO(#33): cmd_run_async on Windows does not render command line properly
  608. // It may require wrapping some arguments with double-quotes if they contains spaces, etc.
  609. cstr_array_join(" ", cmd.line),
  610. NULL,
  611. NULL,
  612. TRUE,
  613. 0,
  614. NULL,
  615. NULL,
  616. &siStartInfo,
  617. &piProcInfo
  618. );
  619. if (!bSuccess) {
  620. PANIC("Could not create child process %s: %s\n",
  621. cmd_show(cmd), GetLastErrorAsString());
  622. }
  623. CloseHandle(piProcInfo.hThread);
  624. return piProcInfo.hProcess;
  625. #else
  626. pid_t cpid = fork();
  627. if (cpid < 0) {
  628. PANIC("Could not fork child process: %s: %s",
  629. cmd_show(cmd), strerror(errno));
  630. }
  631. if (cpid == 0) {
  632. Cstr_Array args = cstr_array_append(cmd.line, NULL);
  633. if (fdin) {
  634. if (dup2(*fdin, STDIN_FILENO) < 0) {
  635. PANIC("Could not setup stdin for child process: %s", strerror(errno));
  636. }
  637. }
  638. if (fdout) {
  639. if (dup2(*fdout, STDOUT_FILENO) < 0) {
  640. PANIC("Could not setup stdout for child process: %s", strerror(errno));
  641. }
  642. }
  643. if (execvp(args.elems[0], (char * const*) args.elems) < 0) {
  644. PANIC("Could not exec child process: %s: %s",
  645. cmd_show(cmd), strerror(errno));
  646. }
  647. }
  648. return cpid;
  649. #endif // _WIN32
  650. }
  651. void cmd_run_sync(Cmd cmd)
  652. {
  653. pid_wait(cmd_run_async(cmd, NULL, NULL));
  654. }
  655. static void chain_set_input_output_files_or_count_cmds(Chain *chain, Chain_Token token)
  656. {
  657. switch (token.type) {
  658. case CHAIN_TOKEN_CMD: {
  659. chain->cmds.count += 1;
  660. }
  661. break;
  662. case CHAIN_TOKEN_IN: {
  663. if (chain->input_filepath) {
  664. PANIC("Input file path was already set");
  665. }
  666. chain->input_filepath = token.args.elems[0];
  667. }
  668. break;
  669. case CHAIN_TOKEN_OUT: {
  670. if (chain->output_filepath) {
  671. PANIC("Output file path was already set");
  672. }
  673. chain->output_filepath = token.args.elems[0];
  674. }
  675. break;
  676. case CHAIN_TOKEN_END:
  677. default: {
  678. assert(0 && "unreachable");
  679. exit(1);
  680. }
  681. }
  682. }
  683. static void chain_push_cmd(Chain *chain, Chain_Token token)
  684. {
  685. if (token.type == CHAIN_TOKEN_CMD) {
  686. chain->cmds.elems[chain->cmds.count++] = (Cmd) {
  687. .line = token.args
  688. };
  689. }
  690. }
  691. Chain chain_build_from_tokens(Chain_Token first, ...)
  692. {
  693. Chain result = {0};
  694. chain_set_input_output_files_or_count_cmds(&result, first);
  695. va_list args;
  696. va_start(args, first);
  697. Chain_Token next = va_arg(args, Chain_Token);
  698. while (next.type != CHAIN_TOKEN_END) {
  699. chain_set_input_output_files_or_count_cmds(&result, next);
  700. next = va_arg(args, Chain_Token);
  701. }
  702. va_end(args);
  703. result.cmds.elems = malloc(sizeof(result.cmds.elems[0]) * result.cmds.count);
  704. if (result.cmds.elems == NULL) {
  705. PANIC("could not allocate memory: %s", strerror(errno));
  706. }
  707. result.cmds.count = 0;
  708. chain_push_cmd(&result, first);
  709. va_start(args, first);
  710. next = va_arg(args, Chain_Token);
  711. while (next.type != CHAIN_TOKEN_END) {
  712. chain_push_cmd(&result, next);
  713. next = va_arg(args, Chain_Token);
  714. }
  715. va_end(args);
  716. return result;
  717. }
  718. void chain_run_sync(Chain chain)
  719. {
  720. if (chain.cmds.count == 0) {
  721. return;
  722. }
  723. Pid *cpids = malloc(sizeof(Pid) * chain.cmds.count);
  724. Pipe pip = {0};
  725. Fd fdin = 0;
  726. Fd *fdprev = NULL;
  727. if (chain.input_filepath) {
  728. fdin = fd_open_for_read(chain.input_filepath);
  729. if (fdin < 0) {
  730. PANIC("could not open file %s: %s", chain.input_filepath, strerror(errno));
  731. }
  732. fdprev = &fdin;
  733. }
  734. for (size_t i = 0; i < chain.cmds.count - 1; ++i) {
  735. pip = pipe_make();
  736. cpids[i] = cmd_run_async(
  737. chain.cmds.elems[i],
  738. fdprev,
  739. &pip.write);
  740. if (fdprev) fd_close(*fdprev);
  741. fd_close(pip.write);
  742. fdprev = &fdin;
  743. fdin = pip.read;
  744. }
  745. {
  746. Fd fdout = 0;
  747. Fd *fdnext = NULL;
  748. if (chain.output_filepath) {
  749. fdout = fd_open_for_write(chain.output_filepath);
  750. if (fdout < 0) {
  751. PANIC("could not open file %s: %s",
  752. chain.output_filepath,
  753. strerror(errno));
  754. }
  755. fdnext = &fdout;
  756. }
  757. const size_t last = chain.cmds.count - 1;
  758. cpids[last] =
  759. cmd_run_async(
  760. chain.cmds.elems[last],
  761. fdprev,
  762. fdnext);
  763. if (fdprev) fd_close(*fdprev);
  764. if (fdnext) fd_close(*fdnext);
  765. }
  766. for (size_t i = 0; i < chain.cmds.count; ++i) {
  767. pid_wait(cpids[i]);
  768. }
  769. }
  770. void chain_echo(Chain chain)
  771. {
  772. printf("[INFO] CHAIN:");
  773. if (chain.input_filepath) {
  774. printf(" %s", chain.input_filepath);
  775. }
  776. FOREACH_ARRAY(Cmd, cmd, chain.cmds, {
  777. printf(" |> %s", cmd_show(*cmd));
  778. });
  779. if (chain.output_filepath) {
  780. printf(" |> %s", chain.output_filepath);
  781. }
  782. printf("\n");
  783. }
  784. Cstr path_get_current_dir()
  785. {
  786. #ifdef _WIN32
  787. DWORD nBufferLength = GetCurrentDirectory(0, NULL);
  788. if (nBufferLength == 0) {
  789. PANIC("could not get current directory: %s", GetLastErrorAsString());
  790. }
  791. char *buffer = (char*) malloc(nBufferLength);
  792. if (GetCurrentDirectory(nBufferLength, buffer) == 0) {
  793. PANIC("could not get current directory: %s", GetLastErrorAsString());
  794. }
  795. return buffer;
  796. #else
  797. char *buffer = (char*) malloc(PATH_MAX);
  798. if (getcwd(buffer, PATH_MAX) == NULL) {
  799. PANIC("could not get current directory: %s", strerror(errno));
  800. }
  801. return buffer;
  802. #endif // _WIN32
  803. }
  804. void path_set_current_dir(Cstr path)
  805. {
  806. #ifdef _WIN32
  807. if (!SetCurrentDirectory(path)) {
  808. PANIC("could not set current directory to %s: %s",
  809. path, GetLastErrorAsString());
  810. }
  811. #else
  812. if (chdir(path) < 0) {
  813. PANIC("could not set current directory to %s: %s",
  814. path, strerror(errno));
  815. }
  816. #endif // _WIN32
  817. }
  818. int path_exists(Cstr path)
  819. {
  820. #ifdef _WIN32
  821. DWORD dwAttrib = GetFileAttributes(path);
  822. return (dwAttrib != INVALID_FILE_ATTRIBUTES);
  823. #else
  824. struct stat statbuf = {0};
  825. if (stat(path, &statbuf) < 0) {
  826. if (errno == ENOENT) {
  827. errno = 0;
  828. return 0;
  829. }
  830. PANIC("could not retrieve information about file %s: %s",
  831. path, strerror(errno));
  832. }
  833. return 1;
  834. #endif
  835. }
  836. int path_is_dir(Cstr path)
  837. {
  838. #ifdef _WIN32
  839. DWORD dwAttrib = GetFileAttributes(path);
  840. return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
  841. (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
  842. #else
  843. struct stat statbuf = {0};
  844. if (stat(path, &statbuf) < 0) {
  845. if (errno == ENOENT) {
  846. errno = 0;
  847. return 0;
  848. }
  849. PANIC("could not retrieve information about file %s: %s",
  850. path, strerror(errno));
  851. }
  852. return S_ISDIR(statbuf.st_mode);
  853. #endif // _WIN32
  854. }
  855. void path_rename(const char *old_path, const char *new_path)
  856. {
  857. #ifdef _WIN32
  858. if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
  859. PANIC("could not rename %s to %s: %s", old_path, new_path,
  860. GetLastErrorAsString());
  861. }
  862. #else
  863. if (rename(old_path, new_path) < 0) {
  864. PANIC("could not rename %s to %s: %s", old_path, new_path,
  865. strerror(errno));
  866. }
  867. #endif // _WIN32
  868. }
  869. void path_mkdirs(Cstr_Array path)
  870. {
  871. if (path.count == 0) {
  872. return;
  873. }
  874. size_t len = 0;
  875. for (size_t i = 0; i < path.count; ++i) {
  876. len += strlen(path.elems[i]);
  877. }
  878. size_t seps_count = path.count - 1;
  879. const size_t sep_len = strlen(PATH_SEP);
  880. char *result = malloc(len + seps_count * sep_len + 1);
  881. len = 0;
  882. for (size_t i = 0; i < path.count; ++i) {
  883. size_t n = strlen(path.elems[i]);
  884. memcpy(result + len, path.elems[i], n);
  885. len += n;
  886. if (seps_count > 0) {
  887. memcpy(result + len, PATH_SEP, sep_len);
  888. len += sep_len;
  889. seps_count -= 1;
  890. }
  891. result[len] = '\0';
  892. if (mkdir(result, 0755) < 0) {
  893. if (errno == EEXIST) {
  894. errno = 0;
  895. WARN("directory %s already exists", result);
  896. } else {
  897. PANIC("could not create directory %s: %s", result, strerror(errno));
  898. }
  899. }
  900. }
  901. }
  902. void path_rm(Cstr path)
  903. {
  904. if (IS_DIR(path)) {
  905. FOREACH_FILE_IN_DIR(file, path, {
  906. if (strcmp(file, ".") != 0 && strcmp(file, "..") != 0)
  907. {
  908. path_rm(PATH(path, file));
  909. }
  910. });
  911. if (rmdir(path) < 0) {
  912. if (errno == ENOENT) {
  913. errno = 0;
  914. WARN("directory %s does not exist", path);
  915. } else {
  916. PANIC("could not remove directory %s: %s", path, strerror(errno));
  917. }
  918. }
  919. } else {
  920. if (unlink(path) < 0) {
  921. if (errno == ENOENT) {
  922. errno = 0;
  923. WARN("file %s does not exist", path);
  924. } else {
  925. PANIC("could not remove file %s: %s", path, strerror(errno));
  926. }
  927. }
  928. }
  929. }
  930. int is_path1_modified_after_path2(const char *path1, const char *path2)
  931. {
  932. #ifdef _WIN32
  933. FILETIME path1_time, path2_time;
  934. Fd path1_fd = fd_open_for_read(path1);
  935. if (!GetFileTime(path1_fd, NULL, NULL, &path1_time)) {
  936. PANIC("could not get time of %s: %s", path1, GetLastErrorAsString());
  937. }
  938. fd_close(path1_fd);
  939. Fd path2_fd = fd_open_for_read(path2);
  940. if (!GetFileTime(path2_fd, NULL, NULL, &path2_time)) {
  941. PANIC("could not get time of %s: %s", path2, GetLastErrorAsString());
  942. }
  943. fd_close(path2_fd);
  944. return CompareFileTime(&path1_time, &path2_time) == 1;
  945. #else
  946. struct stat statbuf = {0};
  947. if (stat(path1, &statbuf) < 0) {
  948. PANIC("could not stat %s: %s\n", path1, strerror(errno));
  949. }
  950. int path1_time = statbuf.st_mtime;
  951. if (stat(path2, &statbuf) < 0) {
  952. PANIC("could not stat %s: %s\n", path2, strerror(errno));
  953. }
  954. int path2_time = statbuf.st_mtime;
  955. return path1_time > path2_time;
  956. #endif
  957. }
  958. void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args)
  959. {
  960. fprintf(stream, "[%s] ", tag);
  961. vfprintf(stream, fmt, args);
  962. fprintf(stream, "\n");
  963. }
  964. void INFO(Cstr fmt, ...)
  965. {
  966. va_list args;
  967. va_start(args, fmt);
  968. VLOG(stderr, "INFO", fmt, args);
  969. va_end(args);
  970. }
  971. void WARN(Cstr fmt, ...)
  972. {
  973. va_list args;
  974. va_start(args, fmt);
  975. VLOG(stderr, "WARN", fmt, args);
  976. va_end(args);
  977. }
  978. void ERRO(Cstr fmt, ...)
  979. {
  980. va_list args;
  981. va_start(args, fmt);
  982. VLOG(stderr, "ERRO", fmt, args);
  983. va_end(args);
  984. }
  985. void PANIC(Cstr fmt, ...)
  986. {
  987. va_list args;
  988. va_start(args, fmt);
  989. VLOG(stderr, "ERRO", fmt, args);
  990. va_end(args);
  991. exit(1);
  992. }
  993. char *shift_args(int *argc, char ***argv)
  994. {
  995. assert(*argc > 0);
  996. char *result = **argv;
  997. *argc -= 1;
  998. *argv += 1;
  999. return result;
  1000. }
  1001. #endif // NOBUILD_IMPLEMENTATION