fordon_greeman / pkger

Branch: 6649f5c
Switch branches/tags
main
theidioticdev feat: use tsoding's nob.h, split repos.h into separate recipes 02f8442 12d ago
3399 lines · 123.4 KB Raw Blame History
1 /* nob - v3.10.0 - Public Domain - https://github.com/tsoding/nob.h
2
3 This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea.
4
5 # Quick Example
6
7 ```c
8 // nob.c
9 #define NOB_IMPLEMENTATION
10 #include "nob.h"
11
12 int main(int argc, char **argv)
13 {
14 NOB_GO_REBUILD_URSELF(argc, argv);
15 Nob_Cmd cmd = {0};
16 nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
17 if (!nob_cmd_run(&cmd)) return 1;
18 return 0;
19 }
20 ```
21
22 ```console
23 $ cc -o nob nob.c
24 $ ./nob
25 ```
26
27 The `nob` automatically rebuilds itself if `nob.c` is modified thanks to
28 the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below)
29
30 # Stripping off `nob_` Prefixes
31
32 Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any
33 potential conflicts with any other names in your code. But sometimes it is very annoying and makes
34 the code noisy. Because of that you can drop the `nob_` prefix.
35
36 ```c
37 // nob.c
38 #define NOB_IMPLEMENTATION
39 #include "nob.h"
40
41 int main(int argc, char **argv)
42 {
43 GO_REBUILD_URSELF(argc, argv);
44 Cmd cmd = {0};
45 cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
46 if (!cmd_run(&cmd)) return 1;
47 return 0;
48 }
49 ```
50
51 If the lack of prefixes causes any problems you can disable the prefix stripping by defining
52 `NOB_UNSTRIP_PREFIX` feature macro before including "nob.h".
53
54 Not all the names have strippable prefixes. All the redefinable names like `NOB_REBUILD_URSELF`
55 for instance will retain their prefix always. Notable exception is the nob_log() function. Stripping
56 away the prefix results in log() which was historically always referring to the natural logarithmic
57 function that is already defined in math.h. So there is no reason to strip off the prefix for nob_log().
58 Another exception is nob_rename() which collides with the widely known POSIX function rename(2) if you
59 strip the prefix off.
60
61 The prefixes are stripped off only on the level of the preprocessor. The names of the functions in the
62 compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h
63 from other languages (for whatever reason).
64
65 If only few specific names create conflicts for you, you can just #undef those names after the
66 `#include <nob.h>` without enabling `NOB_UNSTRIP_PREFIX` since they are macros anyway.
67
68 # Macro Interface
69
70 All these macros are `#define`d by the user before including nob.h
71
72 ## Flags
73
74 Enable or disable certain aspects of nob.h
75
76 - NOB_IMPLEMENTATION - Enable definitions of the functions. By default only declarations are included.
77 See https://github.com/nothings/stb/blob/f58f558c120e9b32c217290b80bad1a0729fbb2c/docs/stb_howto.txt
78 for more info.
79 - NOB_WARN_DEPRECATED - Warn about the usage of deprecated function. We rarely actually remove deprecated functions,
80 but if you want to know what is discouraged you may want to enable this flag.
81 - NOB_EXPERIMENTAL_DELETE_OLD - Experimental feature that automatically removes `nob.old` files. It's unclear how well
82 it works on Windows, so it's experimental for now.
83 - NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL - Log the locations of fail nob_cmd_run()-s. It's behind an experimental flag
84 because I'm not sure how much it alters the UX of the library for other people, but this is something I personally need.
85 - NOB_UNSTRIP_PREFIX - do not strip the `nob_` prefixes from non-redefinable names.
86 - NOB_NO_ECHO - do not echo the actions various nob functions are doing (like nob_cmd_run(), nob_mkdir_if_not_exists(), etc).
87
88 ## Redefinable Macros
89
90 Redefine default behaviors of nob.h.
91
92 - NOBDEF - Appends additional things to function declarations. You can do something like `#define NOBDEF static inline`.
93 - NOB_ASSERT(condition) - Redefine which assert() nob.h shall use.
94 - NOB_REALLOC(oldptr, size) - Redefine which realloc() nob.h shall use.
95 - NOB_FREE(ptr) - Redefine which free() nob.h shall use.
96 - NOB_DEPRECATED(message) - Redefine how nob.h shall mark functions as deprecated.
97 - NOB_DA_INIT_CAP - Redefine initial capacity of Dynamic Arrays.
98 - NOB_TEMP_CAPACITY - Redefine the capacity of the temporary storage.
99 - NOB_REBUILD_URSELF(binary_path, source_path) - redefine how nob.h shall rebuild itself.
100 - NOB_WIN32_ERR_MSG_SIZE - Redefine the capacity of the buffer for error message on Windows.
101 */
102
103 #ifndef NOB_H_
104 #define NOB_H_
105 #ifdef _WIN32
106 # ifndef _CRT_SECURE_NO_WARNINGS
107 # define _CRT_SECURE_NO_WARNINGS (1)
108 # endif // _CRT_SECURE_NO_WARNINGS
109 #endif // _WIN32
110
111 #ifndef NOBDEF
112 /*
113 Goes before declarations and definitions of the nob functions. Useful to `#define NOBDEF static inline`
114 if your source code is a single file and you want the compiler to remove unused functions.
115 */
116 #define NOBDEF
117 #endif /* NOBDEF */
118
119 #ifndef NOB_ASSERT
120 #include <assert.h>
121 #define NOB_ASSERT assert
122 #endif /* NOB_ASSERT */
123
124 #ifndef NOB_REALLOC
125 #include <stdlib.h>
126 #define NOB_REALLOC realloc
127 #endif /* NOB_REALLOC */
128
129 #ifndef NOB_FREE
130 #include <stdlib.h>
131 #define NOB_FREE free
132 #endif /* NOB_FREE */
133
134 #ifdef NOB_WARN_DEPRECATED
135 # ifndef NOB_DEPRECATED
136 # if defined(__GNUC__) || defined(__clang__)
137 # define NOB_DEPRECATED(message) __attribute__((deprecated(message)))
138 # elif defined(_MSC_VER)
139 # define NOB_DEPRECATED(message) __declspec(deprecated(message))
140 # else
141 # define NOB_DEPRECATED(...)
142 # endif
143 # endif /* NOB_DEPRECATED */
144 #else
145 # define NOB_DEPRECATED(...)
146 #endif /* NOB_WARN_DEPRECATED */
147
148 #include <stdbool.h>
149 #include <stdint.h>
150 #include <stdlib.h>
151 #include <stdio.h>
152 #include <stdarg.h>
153 #include <string.h>
154 #include <errno.h>
155 #include <ctype.h>
156 #include <limits.h>
157 #include <time.h>
158
159 #ifdef _WIN32
160 # define WIN32_LEAN_AND_MEAN
161 # define _WINUSER_
162 # define _WINGDI_
163 # define _IMM_
164 # define _WINCON_
165 # include <windows.h>
166 # include <direct.h>
167 # include <io.h>
168 # include <shellapi.h>
169 #else
170 # ifdef __APPLE__
171 # include <mach-o/dyld.h>
172 # endif
173 # ifdef __FreeBSD__
174 # include <sys/sysctl.h>
175 # endif
176 # include <sys/types.h>
177 # include <sys/wait.h>
178 # include <sys/stat.h>
179 # include <unistd.h>
180 # include <fcntl.h>
181 # include <dirent.h>
182 #endif
183
184 #ifdef __HAIKU__
185 # include <image.h>
186 #endif
187
188 #ifdef _WIN32
189 # define NOB_LINE_END "\r\n"
190 #else
191 # define NOB_LINE_END "\n"
192 #endif
193
194 #if defined(__GNUC__) || defined(__clang__)
195 // https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html
196 # ifdef __MINGW_PRINTF_FORMAT
197 # define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (__MINGW_PRINTF_FORMAT, STRING_INDEX, FIRST_TO_CHECK)))
198 # else
199 # define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK)))
200 # endif // __MINGW_PRINTF_FORMAT
201 #else
202 // TODO: implement NOB_PRINTF_FORMAT for MSVC
203 # define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK)
204 #endif
205
206 NOBDEF void nob__panicf(const char *file, int line, const char *label, const char *format, ...);
207
208 #define NOB_UNUSED(value) (void)(value)
209 #define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0)
210 #define NOB_TODOF(...) nob__panicf(__FILE__, __LINE__, "TODO", __VA_ARGS__)
211 #define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0)
212 #define NOB_UNREACHABLEF(...) nob__panicf(__FILE__, __LINE__, "UNREACHABLE", __VA_ARGS__)
213
214 #define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0]))
215 #define NOB_ARRAY_GET(array, index) \
216 (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index])
217
218 typedef enum {
219 NOB_INFO,
220 NOB_WARNING,
221 NOB_ERROR,
222 NOB_NO_LOGS,
223 } Nob_Log_Level;
224
225 // Any messages with the level below nob_minimal_log_level are going to be suppressed by the nob_default_log_handler.
226 extern Nob_Log_Level nob_minimal_log_level;
227
228 typedef void (Nob_Log_Handler)(Nob_Log_Level level, const char *fmt, va_list args);
229 NOB_DEPRECATED("Uncapitalized nob_log_handler type is deprecated. Use Nob_Log_Handler instead. It's just when we were releasing the log handler feature we forgot that we had a convention that all the types must be capitalized like that. Sorry about it!")
230 typedef Nob_Log_Handler nob_log_handler;
231
232 NOBDEF void nob_set_log_handler(Nob_Log_Handler *handler);
233 NOBDEF Nob_Log_Handler *nob_get_log_handler(void);
234
235 NOBDEF Nob_Log_Handler nob_default_log_handler;
236 NOBDEF Nob_Log_Handler nob_cancer_log_handler;
237 NOBDEF Nob_Log_Handler nob_null_log_handler;
238
239 NOBDEF void nob_log(Nob_Log_Level level, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3);
240
241 // It is an equivalent of shift command from bash (do `help shift` in bash). It basically
242 // pops an element from the beginning of a sized array.
243 #define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++)
244 // NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with
245 // the command line arguments passed to the main() function. nob_shift() is more generic.
246 // So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently
247 // remove it. This alias does not hurt anybody.
248 #define nob_shift_args(argc, argv) nob_shift(*argv, *argc)
249
250 typedef struct {
251 const char **items;
252 size_t count;
253 size_t capacity;
254 } Nob_File_Paths;
255
256 typedef enum {
257 NOB_FILE_REGULAR = 0,
258 NOB_FILE_DIRECTORY,
259 NOB_FILE_SYMLINK,
260 NOB_FILE_OTHER,
261 } Nob_File_Type;
262
263 NOBDEF bool nob_mkdir_if_not_exists(const char *path);
264 NOBDEF bool nob_copy_file
265 (const char *src_path, const char *dst_path);
266 NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path);
267 NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children);
268 NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size);
269 NOBDEF Nob_File_Type nob_get_file_type(const char *path);
270 NOBDEF bool nob_delete_file(const char *path);
271
272 typedef enum {
273 // If the current file is a directory go inside of it.
274 NOB_WALK_CONT,
275 // If the current file is a directory do not go inside of it.
276 NOB_WALK_SKIP,
277 // Stop the recursive traversal process entirely.
278 NOB_WALK_STOP,
279 } Nob_Walk_Action;
280
281 typedef struct {
282 // The path to the visited file. The lifetime of the path string is very short.
283 // As soon as the execution exits the Nob_Walk_Func it's dead. Dup it somewhere
284 // if you want to preserve it for longer periods of time.
285 const char *path;
286 // The type of the visited file.
287 Nob_File_Type type;
288 // How nested we currently are in the directory tree.
289 size_t level;
290 // User data supplied in Nob_Walk_Dir_Opt.data.
291 void *data;
292 // The action nob_walk_dir_opt() must perform after the Nob_Walk_Func has returned.
293 // Default is NOB_WALK_CONT.
294 Nob_Walk_Action *action;
295 } Nob_Walk_Entry;
296
297 // A function that is called by nob_walk_dir_opt() on each visited file.
298 // Nob_Walk_Entry provides the details about the visited file and also
299 // expects you to modify the `action` in case you want to alter the
300 // usual behavior of the recursive walking algorithm.
301 //
302 // If the function returns `false`, an error is assumed which causes the entire
303 // recursive walking process to exit and nob_walk_dir_opt() return `false`.
304 typedef bool (*Nob_Walk_Func)(Nob_Walk_Entry entry);
305
306 typedef struct {
307 // User data passed to Nob_Walk_Entry.data
308 void *data;
309 // Walk the directory in post-order visiting the leaf files first.
310 bool post_order;
311 } Nob_Walk_Dir_Opt;
312
313 NOBDEF bool nob_walk_dir_opt(const char *root, Nob_Walk_Func func, Nob_Walk_Dir_Opt);
314
315 #define nob_walk_dir(root, func, ...) nob_walk_dir_opt((root), (func), NOB_CLIT(Nob_Walk_Dir_Opt){__VA_ARGS__})
316
317 typedef struct {
318 char *name;
319 bool error;
320
321 struct {
322 #ifdef _WIN32
323 WIN32_FIND_DATA win32_data;
324 HANDLE win32_hFind;
325 bool win32_init;
326 #else
327 DIR *posix_dir;
328 struct dirent *posix_ent;
329 #endif // _WIN32
330 } nob__private; // TODO: we don't have solid conventions regarding private struct fields
331 } Nob_Dir_Entry;
332
333 // nob_dir_entry_open() - open the directory entry for iteration.
334 // RETURN:
335 // true - Sucess.
336 // false - Error. I will be logged automatically with nob_log/().
337 NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir);
338 // nob_dir_entry_next() - acquire the next file in the directory.
339 // RETURN:
340 // true - Successfully acquired the next file.
341 // false - Either failure or no more files to iterate. In case of failure dir->error is set to true.
342 NOBDEF bool nob_dir_entry_next(Nob_Dir_Entry *dir);
343 NOBDEF void nob_dir_entry_close(Nob_Dir_Entry dir);
344
345 #define nob_return_defer(value) do { result = (value); goto defer; } while(0)
346
347 // Initial capacity of a dynamic array
348 #ifndef NOB_DA_INIT_CAP
349 #define NOB_DA_INIT_CAP 256
350 #endif
351
352 #ifdef __cplusplus
353 #define NOB_DECLTYPE_CAST(T) (decltype(T))
354 #else
355 #define NOB_DECLTYPE_CAST(T)
356 #endif // __cplusplus
357
358 #define nob_da_reserve(da, expected_capacity) \
359 do { \
360 if ((expected_capacity) > (da)->capacity) { \
361 if ((da)->capacity == 0) { \
362 (da)->capacity = NOB_DA_INIT_CAP; \
363 } \
364 while ((expected_capacity) > (da)->capacity) { \
365 (da)->capacity *= 2; \
366 } \
367 (da)->items = NOB_DECLTYPE_CAST((da)->items)NOB_REALLOC((da)->items, (da)->capacity * sizeof(*(da)->items)); \
368 NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \
369 } \
370 } while (0)
371
372 // Append an item to a dynamic array
373 #define nob_da_append(da, item) \
374 do { \
375 nob_da_reserve((da), (da)->count + 1); \
376 (da)->items[(da)->count++] = (item); \
377 } while (0)
378
379 #define nob_da_free(da) NOB_FREE((da).items)
380
381 // Append several items to a dynamic array
382 #define nob_da_append_many(da, new_items, new_items_count) \
383 do { \
384 nob_da_reserve((da), (da)->count + (new_items_count)); \
385 memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \
386 (da)->count += (new_items_count); \
387 } while (0)
388
389 #define nob_da_resize(da, new_size) \
390 do { \
391 nob_da_reserve((da), new_size); \
392 (da)->count = (new_size); \
393 } while (0)
394
395 #define nob_da_pop(da) (da)->items[(NOB_ASSERT((da)->count > 0), --(da)->count)]
396 #define nob_da_first(da) (da)->items[(NOB_ASSERT((da)->count > 0), 0)]
397 #define nob_da_last(da) (da)->items[(NOB_ASSERT((da)->count > 0), (da)->count-1)]
398 #define nob_da_remove_unordered(da, i) \
399 do { \
400 size_t j = (i); \
401 NOB_ASSERT(j < (da)->count); \
402 (da)->items[j] = (da)->items[--(da)->count]; \
403 } while(0)
404
405 // Foreach over Dynamic Arrays. Example:
406 // ```c
407 // typedef struct {
408 // int *items;
409 // size_t count;
410 // size_t capacity;
411 // } Numbers;
412 //
413 // Numbers xs = {0};
414 //
415 // nob_da_append(&xs, 69);
416 // nob_da_append(&xs, 420);
417 // nob_da_append(&xs, 1337);
418 //
419 // nob_da_foreach(int, x, &xs) {
420 // // `x` here is a pointer to the current element. You can get its index by taking a difference
421 // // between `x` and the start of the array which is `x.items`.
422 // size_t index = x - xs.items;
423 // nob_log(INFO, "%zu: %d", index, *x);
424 // }
425 // ```
426 #define nob_da_foreach(Type, it, da) for (Type *it = (da)->items; it < (da)->items + (da)->count; ++it)
427
428 // The Fixed Array append. `items` fields must be a fixed size array. Its size determines the capacity.
429 #define nob_fa_append(fa, item) \
430 (NOB_ASSERT((fa)->count < NOB_ARRAY_LEN((fa)->items)), \
431 (fa)->items[(fa)->count++] = (item))
432
433 typedef struct {
434 char *items;
435 size_t count;
436 size_t capacity;
437 } Nob_String_Builder;
438
439 #define nob_swap(T, a, b) do { T t = a; a = b; b = t; } while (0)
440
441 NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb);
442 NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3);
443 // Pads the String_Builder (sb) to the desired word size boundary with 0s.
444 // Imagine we have sb that contains 5 `a`-s:
445 //
446 // aaaa|a
447 //
448 // If we pad align it by size 4 it will look like this:
449 //
450 // aaaa|a000| <- padded with 0s to the next size 4 boundary
451 //
452 // Useful when you are building some sort of binary format using String_Builder.
453 NOBDEF void nob_sb_pad_align(Nob_String_Builder *sb, size_t size);
454
455 // Append a sized buffer to a string builder
456 #define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size)
457
458 // Append a string view to a string builder
459 #define nob_sb_append_sv(sb, sv) nob_sb_append_buf((sb), (sv).data, (sv).count)
460
461 // Append a NULL-terminated string to a string builder
462 #define nob_sb_append_cstr(sb, cstr) \
463 do { \
464 const char *s = (cstr); \
465 size_t n = strlen(s); \
466 nob_da_append_many(sb, s, n); \
467 } while (0)
468
469 // Append a single NULL character at the end of a string builder. So then you can
470 // use it a NULL-terminated C string
471 #define nob_sb_append_null(sb) nob_da_append_many(sb, "", 1)
472
473 #define nob_sb_append nob_da_append
474
475 // Free the memory allocated by a string builder
476 #define nob_sb_free(sb) NOB_FREE((sb).items)
477
478 // Process handle
479 #ifdef _WIN32
480 typedef HANDLE Nob_Proc;
481 #define NOB_INVALID_PROC INVALID_HANDLE_VALUE
482 typedef HANDLE Nob_Fd;
483 #define NOB_INVALID_FD INVALID_HANDLE_VALUE
484 #else
485 typedef int Nob_Proc;
486 #define NOB_INVALID_PROC (-1)
487 typedef int Nob_Fd;
488 #define NOB_INVALID_FD (-1)
489 #endif // _WIN32
490
491 NOBDEF Nob_Fd nob_fd_open_for_read(const char *path);
492 NOBDEF Nob_Fd nob_fd_open_for_write(const char *path);
493 NOBDEF void nob_fd_close(Nob_Fd fd);
494
495 typedef struct {
496 Nob_Fd read;
497 Nob_Fd write;
498 } Nob_Pipe;
499
500 NOBDEF bool nob_pipe_create(Nob_Pipe *pp);
501
502 typedef struct {
503 Nob_Proc *items;
504 size_t count;
505 size_t capacity;
506 } Nob_Procs;
507
508 // Wait until the process has finished
509 NOBDEF bool nob_proc_wait(Nob_Proc proc);
510
511 // Wait until all the processes have finished
512 NOBDEF bool nob_procs_wait(Nob_Procs procs);
513
514 // Wait until all the processes have finished and empty the procs array.
515 NOBDEF bool nob_procs_flush(Nob_Procs *procs);
516
517 // Alias to nob_procs_flush
518 NOB_DEPRECATED("Use `nob_procs_flush(&procs)` instead.")
519 NOBDEF bool nob_procs_wait_and_reset(Nob_Procs *procs);
520
521 // Append a new process to procs array and if procs.count reaches max_procs_count call nob_procs_wait_and_reset() on it
522 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .async = &procs, .max_procs = <integer>)` instead")
523 NOBDEF bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count);
524
525 // A command - the main workhorse of Nob. Nob is all about building commands and running them
526 typedef struct {
527 const char **items;
528 size_t count;
529 size_t capacity;
530 } Nob_Cmd;
531
532 // Options for nob_cmd_run_opt() function.
533 typedef struct {
534 // Run the command asynchronously appending its Nob_Proc to the provided Nob_Procs array
535 Nob_Procs *async;
536 // Maximum processes allowed in the .async list. Zero implies nob_nprocs().
537 size_t max_procs;
538 // Do not reset the command after execution.
539 bool dont_reset;
540 // Redirect stdin to file
541 const char *stdin_path;
542 // Redirect stdout to file
543 const char *stdout_path;
544 // Redirect stderr to file
545 const char *stderr_path;
546 } Nob_Cmd_Opt;
547
548 // Run the command with options.
549 NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt);
550 NOBDEF bool nob__cmd_run_opt_with_location(Nob_Cmd *cmd, const char *file, int line, Nob_Cmd_Opt opt);
551
552 // Command Chains (in Shell Scripting they are know as Pipes)
553 //
554 // Usage:
555 // ```c
556 // Nob_Cmd cmd = {0};
557 // Nob_Chain chain = {0};
558 // if (!nob_chain_begin(&chain)) return 1;
559 // {
560 // nob_cmd_append(&cmd, "echo", "Hello, World");
561 // if (!nob_chain_cmd(&chain, &cmd)) return 1;
562 //
563 // nob_cmd_append(&cmd, "rev");
564 // if (!nob_chain_cmd(&chain, &cmd)) return 1;
565 //
566 // nob_cmd_append(&cmd, "xxd");
567 // if (!nob_chain_cmd(&chain, &cmd)) return 1;
568 // }
569 // if (!nob_chain_end(&chain)) return 1;
570 // ```
571 //
572 // The above is equivalent to a shell command:
573 //
574 // ```sh
575 // echo "Hello, World" | rev | xxd
576 // ```
577 //
578 // After nob_chain_end() the Nob_Chain struct can be reused again.
579 //
580 // The fields of the Nob_Chain struct contain the intermediate state of the Command
581 // Chain that is being built with the nob_chain_cmd() calls and generally have no
582 // particular use for the user.
583 //
584 // The only memory dynamically allocated within Nob_Chain belongs to the .cmd field.
585 // So if you want to clean it all up you can just do free(chain.cmd.items).
586 typedef struct {
587 // The file descriptor of the output of the previous command. Will be used as the input for the next command.
588 Nob_Fd fdin;
589 // The command from the last nob_chain_cmd() call.
590 Nob_Cmd cmd;
591 // The value of the optional .err2out parameter from the last nob_chain_cmd() call.
592 bool err2out;
593 } Nob_Chain;
594
595 typedef struct {
596 const char *stdin_path;
597 } Nob_Chain_Begin_Opt;
598 #define nob_chain_begin(chain, ...) nob_chain_begin_opt((chain), NOB_CLIT(Nob_Chain_Begin_Opt) { __VA_ARGS__ })
599 NOBDEF bool nob_chain_begin_opt(Nob_Chain *chain, Nob_Chain_Begin_Opt opt);
600
601 typedef struct {
602 bool err2out;
603 bool dont_reset;
604 } Nob_Chain_Cmd_Opt;
605 #define nob_chain_cmd(chain, cmd, ...) nob_chain_cmd_opt((chain), (cmd), NOB_CLIT(Nob_Chain_Cmd_Opt) { __VA_ARGS__ })
606 NOBDEF bool nob_chain_cmd_opt(Nob_Chain *chain, Nob_Cmd *cmd, Nob_Chain_Cmd_Opt opt);
607
608 typedef struct {
609 Nob_Procs *async;
610 size_t max_procs;
611 const char *stdout_path;
612 const char *stderr_path;
613 } Nob_Chain_End_Opt;
614 #define nob_chain_end(chain, ...) nob_chain_end_opt((chain), NOB_CLIT(Nob_Chain_End_Opt) { __VA_ARGS__ })
615 NOBDEF bool nob_chain_end_opt(Nob_Chain *chain, Nob_Chain_End_Opt opt);
616
617 // Get amount of processors on the machine.
618 NOBDEF int nob_nprocs(void);
619
620 #define NOB_NANOS_PER_SEC (1000*1000*1000)
621
622 // The maximum time span representable is 584 years.
623 NOBDEF uint64_t nob_nanos_since_unspecified_epoch(void);
624
625 // Same as nob_cmd_run_opt but using cool variadic macro to set the default options.
626 // See https://x.com/vkrajacic/status/1749816169736073295 for more info on how to use such macros.
627 #define nob_cmd_run(cmd, ...) nob__cmd_run_opt_with_location((cmd), __FILE__, __LINE__, NOB_CLIT(Nob_Cmd_Opt){ __VA_ARGS__ })
628
629 // DEPRECATED:
630 //
631 // You were suppose to use this structure like this:
632 //
633 // ```c
634 // Nob_Fd fdin = nob_fd_open_for_read("input.txt");
635 // if (fdin == NOB_INVALID_FD) fail();
636 // Nob_Fd fdout = nob_fd_open_for_write("output.txt");
637 // if (fdout == NOB_INVALID_FD) fail();
638 // Nob_Cmd cmd = {0};
639 // nob_cmd_append(&cmd, "cat");
640 // if (!nob_cmd_run_sync_redirect_and_reset(&cmd, (Nob_Cmd_Redirect) {
641 // .fdin = &fdin,
642 // .fdout = &fdout
643 // })) fail();
644 // ```
645 //
646 // But these days you should do:
647 //
648 // ```c
649 // Nob_Cmd cmd = {0};
650 // nob_cmd_append(&cmd, "cat");
651 // if (!nob_cmd_run(&cmd, .stdin_path = "input.txt", .stdout_path = "output.txt")) fail();
652 // ```
653 typedef struct {
654 Nob_Fd *fdin;
655 Nob_Fd *fdout;
656 Nob_Fd *fderr;
657 } Nob_Cmd_Redirect;
658
659 // Render a string representation of a command into a string builder. Keep in mind the the
660 // string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to
661 // use it as a C string.
662 NOBDEF void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render);
663
664 // Compound Literal
665 #if defined(__cplusplus)
666 #define NOB_CLIT(type) type
667 #else
668 #define NOB_CLIT(type) (type)
669 #endif
670
671 NOBDEF void nob__cmd_append(Nob_Cmd *cmd, size_t n, const char **args);
672 #if defined(__cplusplus)
673 template <typename... Args>
674 static inline void nob__cpp_cmd_append_wrapper(Nob_Cmd *cmd, Args... strs)
675 {
676 const char* args[] = { strs... };
677 nob__cmd_append(cmd, sizeof(args)/sizeof(args[0]), args);
678 }
679 #define nob_cmd_append(cmd, ...) nob__cpp_cmd_append_wrapper(cmd, __VA_ARGS__)
680 #else
681 #define nob_cmd_append(cmd, ...) \
682 nob__cmd_append(cmd, sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*), (const char*[]){__VA_ARGS__})
683 #endif // __cplusplus
684
685 // TODO: nob_cmd_extend() evaluates other_cmd twice
686 // It can be fixed by turning nob_cmd_extend() call into a statement.
687 // But that may break backward compatibility of the API.
688 #define nob_cmd_extend(cmd, other_cmd) \
689 nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count)
690
691 // Free all the memory allocated by command arguments
692 #define nob_cmd_free(cmd) NOB_FREE(cmd.items)
693
694 // Run command asynchronously
695 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .async = &procs, .dont_reset = true)`.")
696 NOBDEF Nob_Proc nob_cmd_run_async(Nob_Cmd cmd);
697
698 // nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0
699 // so the Nob_Cmd instance can be seamlessly used several times in a row
700 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .async = &procs)` intead.")
701 NOBDEF Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd);
702
703 // Run redirected command asynchronously
704 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, "
705 ".async = &procs, "
706 ".stdin_path = \"path/to/stdin\", "
707 ".stdout_path = \"path/to/stdout\", "
708 ".stderr_path = \"path/to/stderr\", "
709 ".dont_reset = true"
710 ")` instead.")
711 NOBDEF Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect);
712
713 // Run redirected command asynchronously and set cmd.count to 0 and close all the opened files
714 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, "
715 ".async = &procs, "
716 ".stdin_path = \"path/to/stdin\", "
717 ".stdout_path = \"path/to/stdout\", "
718 ".stderr_path = \"path/to/stderr\")` instead.")
719 NOBDEF Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect);
720
721 // Run command synchronously
722 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .dont_reset = true)` instead.")
723 NOBDEF bool nob_cmd_run_sync(Nob_Cmd cmd);
724
725 // NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0
726 // so the Nob_Cmd instance can be seamlessly used several times in a row
727 NOB_DEPRECATED("Use `nob_cmd_run(&cmd)` instead.")
728 NOBDEF bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd);
729
730 // Run redirected command synchronously
731 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, "
732 ".stdin_path = \"path/to/stdin\", "
733 ".stdout_path = \"path/to/stdout\", "
734 ".stderr_path = \"path/to/stderr\", "
735 ".dont_reset = true"
736 ")` instead.")
737 NOBDEF bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect);
738
739 // Run redirected command synchronously and set cmd.count to 0 and close all the opened files
740 NOB_DEPRECATED("Use `nob_cmd_run(&cmd, "
741 ".stdin_path = \"path/to/stdin\", "
742 ".stdout_path = \"path/to/stdout\", "
743 ".stderr_path = \"path/to/stderr\")` instead.")
744 NOBDEF bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect);
745
746 #ifndef NOB_TEMP_CAPACITY
747 #define NOB_TEMP_CAPACITY (8*1024*1024)
748 #endif // NOB_TEMP_CAPACITY
749 NOBDEF char *nob_temp_strdup(const char *cstr);
750 NOBDEF char *nob_temp_strndup(const char *cstr, size_t size);
751 NOBDEF void *nob_temp_alloc(size_t size);
752 NOBDEF char *nob_temp_sprintf(const char *format, ...) NOB_PRINTF_FORMAT(1, 2);
753 NOBDEF char *nob_temp_vsprintf(const char *format, va_list ap);
754 // nob_temp_reset() - Resets the entire temporary storage to 0.
755 //
756 // It is generally not recommended to call this function ever. What you usually want to do is let's say you have a loop,
757 // that allocates some temporary objects and cleans them up at the end of each iteration. You should use
758 // nob_temp_save() and nob_temp_rewind() to organize such loop like this:
759 //
760 // ```c
761 // char *message = nob_temp_sprintf("This message is still valid after the loop below");
762 // while (!quit) {
763 // size_t mark = nob_temp_save();
764 // nob_temp_alloc(69);
765 // nob_temp_alloc(420);
766 // nob_temp_alloc(1337);
767 // nob_temp_rewind(mark);
768 // }
769 // printf("%s\n", message);
770 // ```
771 //
772 // That way all the temporary allocations created before the loop are still valid even after the loop.
773 // Such save/rewind blocks define lifetime boundaries of the temporary objects which also could be nested.
774 // This turns the temporary storage into kind of a second stack with a more manual management.
775 NOBDEF void nob_temp_reset(void);
776 NOBDEF size_t nob_temp_save(void);
777 NOBDEF void nob_temp_rewind(size_t checkpoint);
778
779 // Given any path returns the last part of that path.
780 // "/path/to/a/file.c" -> "file.c"; "/path/to/a/directory" -> "directory"
781 NOBDEF const char *nob_path_name(const char *path);
782 NOBDEF bool nob_rename(const char *old_path, const char *new_path);
783 NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count);
784 NOBDEF int nob_needs_rebuild1(const char *output_path, const char *input_path);
785 NOBDEF int nob_file_exists(const char *file_path);
786 NOBDEF const char *nob_get_current_dir_temp(void);
787 NOBDEF bool nob_set_current_dir(const char *path);
788 // Returns you the directory part of the path allocated on the temporary storage.
789 NOBDEF char *nob_temp_dir_name(const char *path);
790 NOBDEF char *nob_temp_file_name(const char *path);
791 NOBDEF char *nob_temp_file_ext(const char *path);
792 NOBDEF char *nob_temp_running_executable_path(void);
793
794 // TODO: we should probably document somewhere all the compilers we support
795
796 // The nob_cc_* macros try to abstract away the specific compiler.
797 // They are very basic and not particularly flexible, but you can redefine them if you need to
798 // or not use them at all and create your own abstraction on top of Nob_Cmd.
799
800 #ifndef nob_cc
801 # if _WIN32
802 # if defined(__GNUC__)
803 # define nob_cc(cmd) nob_cmd_append(cmd, "cc")
804 # elif defined(__clang__)
805 # define nob_cc(cmd) nob_cmd_append(cmd, "clang")
806 # elif defined(_MSC_VER)
807 # define nob_cc(cmd) nob_cmd_append(cmd, "cl.exe")
808 # elif defined(__TINYC__)
809 # define nob_cc(cmd) nob_cmd_append(cmd, "tcc")
810 # endif
811 # else
812 # define nob_cc(cmd) nob_cmd_append(cmd, "cc")
813 # endif
814 #endif // nob_cc
815
816 #ifndef nob_cc_flags
817 # if defined(_MSC_VER) && !defined(__clang__)
818 # define nob_cc_flags(cmd) nob_cmd_append(cmd, "/W4", "/nologo", "/D_CRT_SECURE_NO_WARNINGS")
819 # else
820 # define nob_cc_flags(cmd) nob_cmd_append(cmd, "-Wall", "-Wextra")
821 # endif
822 #endif // nob_cc_flags
823
824 #ifndef nob_cc_output
825 # if defined(_MSC_VER) && !defined(__clang__)
826 # define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, nob_temp_sprintf("/Fe:%s", (output_path)), nob_temp_sprintf("/Fo:%s", (output_path)))
827 # else
828 # define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, "-o", (output_path))
829 # endif
830 #endif // nob_cc_output
831
832 #ifndef nob_cc_inputs
833 # define nob_cc_inputs(cmd, ...) nob_cmd_append(cmd, __VA_ARGS__)
834 #endif // nob_cc_inputs
835
836 // TODO: add MinGW support for Go Rebuild Urself™ Technology and all the nob_cc_* macros above
837 // Musializer contributors came up with a pretty interesting idea of an optional prefix macro which could be useful for
838 // MinGW support:
839 // https://github.com/tsoding/musializer/blob/b7578cc76b9ecb573d239acc9ccf5a04d3aba2c9/src_build/nob_win64_mingw.c#L3-L9
840 // TODO: Maybe instead NOB_REBUILD_URSELF macro, the Go Rebuild Urself™ Technology should use the
841 // user defined nob_cc_* macros instead?
842 #ifndef NOB_REBUILD_URSELF
843 # if defined(_WIN32)
844 # if defined(__clang__)
845 # if defined(__cplusplus)
846 # define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-x", "c++", "-o", binary_path, source_path
847 # else
848 # define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-x", "c", "-o", binary_path, source_path
849 # endif
850 # elif defined(__GNUC__)
851 # if defined(__cplusplus)
852 # define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-x", "c++", "-o", binary_path, source_path
853 # else
854 # define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-x", "c", "-o", binary_path, source_path
855 # endif
856 # elif defined(_MSC_VER)
857 # define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", nob_temp_sprintf("/Fe:%s", (binary_path)), source_path
858 # elif defined(__TINYC__)
859 # define NOB_REBUILD_URSELF(binary_path, source_path) "tcc", "-o", binary_path, source_path
860 # endif
861 # else
862 # if defined(__cplusplus)
863 # define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-x", "c++", "-o", binary_path, source_path
864 # else
865 # define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-x", "c", "-o", binary_path, source_path
866 # endif
867 # endif
868 #endif
869
870 // Go Rebuild Urself™ Technology
871 //
872 // How to use it:
873 // int main(int argc, char** argv) {
874 // NOB_GO_REBUILD_URSELF(argc, argv);
875 // // actual work
876 // return 0;
877 // }
878 //
879 // After you added this macro every time you run ./nob it will detect
880 // that you modified its original source code and will try to rebuild itself
881 // before doing any actual work. So you only need to bootstrap your build system
882 // once.
883 //
884 // The modification is detected by comparing the last modified times of the executable
885 // and its source code. The same way the make utility usually does it.
886 //
887 // The rebuilding is done by using the NOB_REBUILD_URSELF macro which you can redefine
888 // if you need a special way of bootstraping your build system. (which I personally
889 // do not recommend since the whole idea of NoBuild is to keep the process of bootstrapping
890 // as simple as possible and doing all of the actual work inside of ./nob)
891 //
892 NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...);
893 #define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(argc, argv, __FILE__, NULL)
894 // Sometimes your nob.c includes additional files, so you want the Go Rebuild Urself™ Technology to check
895 // if they also were modified and rebuild nob.c accordingly. For that we have NOB_GO_REBUILD_URSELF_PLUS():
896 // ```c
897 // #define NOB_IMPLEMENTATION
898 // #include "nob.h"
899 //
900 // #include "foo.c"
901 // #include "bar.c"
902 //
903 // int main(int argc, char **argv)
904 // {
905 // NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "foo.c", "bar.c");
906 // // ...
907 // return 0;
908 // }
909 #define NOB_GO_REBUILD_URSELF_PLUS(argc, argv, ...) nob__go_rebuild_urself(argc, argv, __FILE__, __VA_ARGS__, NULL);
910
911 typedef struct {
912 size_t count;
913 union {
914 const char *data;
915 const char *items;
916 };
917 } Nob_String_View;
918
919 NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv);
920
921 NOBDEF Nob_String_View nob_sv_chop_while(Nob_String_View *sv, int (*p)(int x));
922 NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim);
923 NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n);
924 NOBDEF Nob_String_View nob_sv_chop_right(Nob_String_View *sv, size_t n);
925 // If `sv` starts with `prefix` chops off the prefix and returns true.
926 // Otherwise, leaves `sv` unmodified and returns false.
927 NOBDEF bool nob_sv_chop_prefix(Nob_String_View *sv, Nob_String_View prefix);
928 // If `sv` ends with `suffix` chops off the suffix and returns true.
929 // Otherwise, leaves `sv` unmodified and returns false.
930 NOBDEF bool nob_sv_chop_suffix(Nob_String_View *sv, Nob_String_View suffix);
931 NOBDEF Nob_String_View nob_sv_trim(Nob_String_View sv);
932 NOBDEF Nob_String_View nob_sv_trim_left(Nob_String_View sv);
933 NOBDEF Nob_String_View nob_sv_trim_right(Nob_String_View sv);
934 NOBDEF bool nob_sv_eq(Nob_String_View a, Nob_String_View b);
935 NOB_DEPRECATED("Use nob_sv_ends_with_cstr(sv, suffix) instead. "
936 "Pay attention to the `s` at the end of the `end`. "
937 "The reason this function was deprecated is because "
938 "of the typo in the name, of course, but also "
939 "because the second argument was a NULL-terminated string "
940 "while nob_sv_starts_with() accepted Nob_String_View as the "
941 "prefix which created an inconsistency in the API.")
942 NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr);
943 NOBDEF bool nob_sv_ends_with_cstr(Nob_String_View sv, const char *cstr);
944 NOBDEF bool nob_sv_ends_with(Nob_String_View sv, Nob_String_View suffix);
945 NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View prefix);
946 NOBDEF Nob_String_View nob_sv_from_cstr(const char *cstr);
947 NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count);
948 // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View
949 #define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count)
950
951 #define NOB_SVLIT(lit) (NOB_CLIT(Nob_String_View){.count = sizeof(lit)-1, .data = (lit)})
952 // Generally majority of the C/C++ compilers will allow you to use NOB_SVLIT to construct global variables.
953 // But there are some (specifically MSVC with /TC flag enabled) that will refuse.
954 // For such compilers use NOB_SVLIT_STATIC instead.
955 #define NOB_SVLIT_STATIC(lit) {.count = sizeof(lit)-1, .data = (lit)}
956
957 // printf macros for String_View
958 #ifndef SV_Fmt
959 #define SV_Fmt "%.*s"
960 #endif // SV_Fmt
961 #ifndef SV_Arg
962 #define SV_Arg(sv) (int) (sv).count, (sv).data
963 #endif // SV_Arg
964 // USAGE:
965 // String_View name = ...;
966 // printf("Name: "SV_Fmt"\n", SV_Arg(name));
967
968 // Stolen from Jai's Unicode module
969 // Example:
970 // ```c
971 // String_View sv = SVLIT("Привет, Мир!");
972 // while (sv.count > 0) {
973 // size_t n = nob_bytes_for_utf8[(uint8_t)*sv.data];
974 // String_View c = sv_chop_left(&sv, n);
975 // printf(SV_Fmt" => %zu\n", SV_Arg(c), n);
976 // }
977 // ```
978 static const uint8_t nob_bytes_for_utf8[] = {
979 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
980 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
981 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
982 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
983 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
984 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
985 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
986 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,5,5,5,5,6,6,6,6,
987 };
988
989 NOBDEF size_t nob_sv_utf8_len(Nob_String_View sv, size_t *bytes_overrun);
990
991 #ifdef _WIN32
992
993 NOBDEF char *nob_win32_error_message(DWORD err);
994
995 #endif // _WIN32
996
997 #endif // NOB_H_
998
999 #ifdef NOB_IMPLEMENTATION
1000
1001 // This is like nob_proc_wait() but waits asynchronously. Depending on the platform ms means different thing.
1002 // On Windows it means timeout. On POSIX it means for how long to sleep after checking if the process exited,
1003 // so to not peg the core too much. Since this API is kinda of weird, the function is private for now.
1004 static int nob__proc_wait_async(Nob_Proc proc, int ms);
1005
1006 // Starts the process for the command. Its main purpose is to be the base for nob_cmd_run() and nob_cmd_run_opt().
1007 static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, Nob_Fd *fderr);
1008
1009 // Any messages with the level below nob_minimal_log_level are going to be suppressed.
1010 Nob_Log_Level nob_minimal_log_level = NOB_INFO;
1011
1012 NOBDEF void nob__cmd_append(Nob_Cmd *cmd, size_t n, const char **args)
1013 {
1014 for (size_t i = 0; i < n; ++i) {
1015 nob_da_append(cmd, args[i]);
1016 }
1017 }
1018
1019 NOBDEF size_t nob_sv_utf8_len(Nob_String_View sv, size_t *bytes_overrun)
1020 {
1021 size_t i = 0;
1022 size_t n = 0;
1023 while (true) {
1024 if (i >= sv.count) {
1025 if (bytes_overrun) *bytes_overrun = i - sv.count;
1026 return n;
1027 }
1028 i += nob_bytes_for_utf8[(uint8_t)sv.data[i]];
1029 n += 1;
1030 }
1031 NOB_UNREACHABLE("sv_utf8_len");
1032 return 0;
1033 }
1034
1035 #ifdef _WIN32
1036
1037 // Base on https://stackoverflow.com/a/75644008
1038 // > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it.
1039 // >
1040 // > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265
1041 #ifndef NOB_WIN32_ERR_MSG_SIZE
1042 #define NOB_WIN32_ERR_MSG_SIZE (4 * 1024)
1043 #endif // NOB_WIN32_ERR_MSG_SIZE
1044
1045 NOBDEF char *nob_win32_error_message(DWORD err) {
1046 static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0};
1047 DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg,
1048 NOB_WIN32_ERR_MSG_SIZE, NULL);
1049
1050 if (errMsgSize == 0) {
1051 if (GetLastError() != ERROR_MR_MID_NOT_FOUND) {
1052 if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) {
1053 return (char *)&win32ErrMsg;
1054 } else {
1055 return NULL;
1056 }
1057 } else {
1058 if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) {
1059 return (char *)&win32ErrMsg;
1060 } else {
1061 return NULL;
1062 }
1063 }
1064 }
1065
1066 while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) {
1067 win32ErrMsg[--errMsgSize] = '\0';
1068 }
1069
1070 return win32ErrMsg;
1071 }
1072
1073 #endif // _WIN32
1074
1075 NOBDEF void nob__panicf(const char *file, int line, const char *label, const char *format, ...)
1076 {
1077 fprintf(stderr, "%s:%d: %s: ", file, line, label);
1078 va_list args;
1079 va_start(args, format);
1080 vfprintf(stderr, format, args);
1081 va_end(args);
1082 fprintf(stderr, "\n");
1083 abort();
1084 }
1085
1086 // The implementation idea is stolen from https://github.com/zhiayang/nabs
1087 NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...)
1088 {
1089 const char *binary_path = nob_shift(argv, argc);
1090 #ifdef _WIN32
1091 // On Windows executables almost always invoked without extension, so
1092 // it's ./nob, not ./nob.exe. For renaming the extension is a must.
1093 if (!nob_sv_ends_with_cstr(nob_sv_from_cstr(binary_path), ".exe")) {
1094 binary_path = nob_temp_sprintf("%s.exe", binary_path);
1095 }
1096 #endif
1097
1098 Nob_File_Paths source_paths = {0};
1099 nob_da_append(&source_paths, source_path);
1100 va_list args;
1101 va_start(args, source_path);
1102 for (;;) {
1103 const char *path = va_arg(args, const char*);
1104 if (path == NULL) break;
1105 nob_da_append(&source_paths, path);
1106 }
1107 va_end(args);
1108
1109 int rebuild_is_needed = nob_needs_rebuild(binary_path, source_paths.items, source_paths.count);
1110 if (rebuild_is_needed < 0) exit(1); // error
1111 if (!rebuild_is_needed) { // no rebuild is needed
1112 NOB_FREE(source_paths.items);
1113 return;
1114 }
1115
1116 Nob_Cmd cmd = {0};
1117
1118 const char *old_binary_path = nob_temp_sprintf("%s.old", binary_path);
1119
1120 if (!nob_rename(binary_path, old_binary_path)) exit(1);
1121 nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path));
1122 Nob_Cmd_Opt opt = {0};
1123 if (!nob_cmd_run_opt(&cmd, opt)) {
1124 nob_rename(old_binary_path, binary_path);
1125 exit(1);
1126 }
1127 #ifdef NOB_EXPERIMENTAL_DELETE_OLD
1128 // TODO: this is an experimental behavior behind a compilation flag.
1129 // Once it is confirmed that it does not cause much problems on both POSIX and Windows
1130 // we may turn it on by default.
1131 nob_delete_file(old_binary_path);
1132 #endif // NOB_EXPERIMENTAL_DELETE_OLD
1133
1134 nob_cmd_append(&cmd, binary_path);
1135 nob_da_append_many(&cmd, argv, argc);
1136 if (!nob_cmd_run_opt(&cmd, opt)) exit(1);
1137 exit(0);
1138 }
1139
1140 static size_t nob_temp_size = 0;
1141 static char nob_temp[NOB_TEMP_CAPACITY] = {0};
1142
1143 NOBDEF bool nob_mkdir_if_not_exists(const char *path)
1144 {
1145 #ifdef _WIN32
1146 int result = _mkdir(path);
1147 #else
1148 int result = mkdir(path, 0755);
1149 #endif
1150 if (result < 0) {
1151 if (errno == EEXIST) {
1152 #ifndef NOB_NO_ECHO
1153 nob_log(NOB_INFO, "directory `%s` already exists", path);
1154 #endif // NOB_NO_ECHO
1155 return true;
1156 }
1157 nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno));
1158 return false;
1159 }
1160
1161 #ifndef NOB_NO_ECHO
1162 nob_log(NOB_INFO, "created directory `%s`", path);
1163 #endif // NOB_NO_ECHO
1164 return true;
1165 }
1166
1167 NOBDEF bool nob_copy_file(const char *src_path, const char *dst_path)
1168 {
1169 #ifndef NOB_NO_ECHO
1170 nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path);
1171 #endif // NOB_NO_ECHO
1172 #ifdef _WIN32
1173 if (!CopyFile(src_path, dst_path, FALSE)) {
1174 nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError()));
1175 return false;
1176 }
1177 return true;
1178 #else
1179 int src_fd = -1;
1180 int dst_fd = -1;
1181 size_t buf_size = 32*1024;
1182 char *buf = (char*)NOB_REALLOC(NULL, buf_size);
1183 NOB_ASSERT(buf != NULL && "Buy more RAM lol!!");
1184 bool result = true;
1185
1186 src_fd = open(src_path, O_RDONLY);
1187 if (src_fd < 0) {
1188 nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno));
1189 nob_return_defer(false);
1190 }
1191
1192 struct stat src_stat;
1193 if (fstat(src_fd, &src_stat) < 0) {
1194 nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno));
1195 nob_return_defer(false);
1196 }
1197
1198 dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode);
1199 if (dst_fd < 0) {
1200 nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno));
1201 nob_return_defer(false);
1202 }
1203
1204 for (;;) {
1205 ssize_t n = read(src_fd, buf, buf_size);
1206 if (n == 0) break;
1207 if (n < 0) {
1208 nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno));
1209 nob_return_defer(false);
1210 }
1211 char *buf2 = buf;
1212 while (n > 0) {
1213 ssize_t m = write(dst_fd, buf2, n);
1214 if (m < 0) {
1215 nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno));
1216 nob_return_defer(false);
1217 }
1218 n -= m;
1219 buf2 += m;
1220 }
1221 }
1222
1223 defer:
1224 NOB_FREE(buf);
1225 close(src_fd);
1226 close(dst_fd);
1227 return result;
1228 #endif
1229 }
1230
1231 NOBDEF void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render)
1232 {
1233 for (size_t i = 0; i < cmd.count; ++i) {
1234 const char *arg = cmd.items[i];
1235 if (arg == NULL) break;
1236 if (i > 0) nob_sb_append_cstr(render, " ");
1237 if (!strchr(arg, ' ')) {
1238 nob_sb_append_cstr(render, arg);
1239 } else {
1240 nob_da_append(render, '\'');
1241 nob_sb_append_cstr(render, arg);
1242 nob_da_append(render, '\'');
1243 }
1244 }
1245 }
1246
1247 #ifdef _WIN32
1248 // https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
1249 static void nob__win32_cmd_quote(Nob_Cmd cmd, Nob_String_Builder *quoted)
1250 {
1251 for (size_t i = 0; i < cmd.count; ++i) {
1252 const char *arg = cmd.items[i];
1253 if (arg == NULL) break;
1254 size_t len = strlen(arg);
1255 if (i > 0) nob_da_append(quoted, ' ');
1256 if (len != 0 && NULL == strpbrk(arg, " \t\n\v\"")) {
1257 // no need to quote
1258 nob_da_append_many(quoted, arg, len);
1259 } else {
1260 // we need to escape:
1261 // 1. double quotes in the original arg
1262 // 2. consequent backslashes before a double quote
1263 size_t backslashes = 0;
1264 nob_da_append(quoted, '\"');
1265 for (size_t j = 0; j < len; ++j) {
1266 char x = arg[j];
1267 if (x == '\\') {
1268 backslashes += 1;
1269 } else {
1270 if (x == '\"') {
1271 // escape backslashes (if any) and the double quote
1272 for (size_t k = 0; k < 1+backslashes; ++k) {
1273 nob_da_append(quoted, '\\');
1274 }
1275 }
1276 backslashes = 0;
1277 }
1278 nob_da_append(quoted, x);
1279 }
1280 // escape backslashes (if any)
1281 for (size_t k = 0; k < backslashes; ++k) {
1282 nob_da_append(quoted, '\\');
1283 }
1284 nob_da_append(quoted, '\"');
1285 }
1286 }
1287 }
1288 #endif
1289
1290 NOBDEF int nob_nprocs(void)
1291 {
1292 #ifdef _WIN32
1293 SYSTEM_INFO siSysInfo;
1294 GetSystemInfo(&siSysInfo);
1295 return siSysInfo.dwNumberOfProcessors;
1296 #else
1297 return sysconf(_SC_NPROCESSORS_ONLN);
1298 #endif
1299 }
1300
1301 NOBDEF bool nob__cmd_run_opt_with_location(Nob_Cmd *cmd, const char *file, int line, Nob_Cmd_Opt opt)
1302 {
1303 bool ok = nob_cmd_run_opt(cmd, opt);
1304 #ifdef NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL
1305 if (!ok) {
1306 nob_log(NOB_ERROR, "%s:%d: ERROR: cmd_run failed", file, line);
1307 }
1308 #else
1309 NOB_UNUSED(file);
1310 NOB_UNUSED(line);
1311 #endif // NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL
1312 return ok;
1313 }
1314
1315 NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt)
1316 {
1317 bool result = true;
1318 Nob_Fd fdin = NOB_INVALID_FD;
1319 Nob_Fd fdout = NOB_INVALID_FD;
1320 Nob_Fd fderr = NOB_INVALID_FD;
1321 Nob_Fd *opt_fdin = NULL;
1322 Nob_Fd *opt_fdout = NULL;
1323 Nob_Fd *opt_fderr = NULL;
1324 Nob_Proc proc = NOB_INVALID_PROC;
1325
1326 size_t max_procs = opt.max_procs > 0 ? opt.max_procs : (size_t) nob_nprocs() + 1;
1327
1328 if (opt.async && max_procs > 0) {
1329 while (opt.async->count >= max_procs) {
1330 for (size_t i = 0; i < opt.async->count; ++i) {
1331 int ret = nob__proc_wait_async(opt.async->items[i], 1);
1332 if (ret < 0) nob_return_defer(false);
1333 if (ret) {
1334 nob_da_remove_unordered(opt.async, i);
1335 break;
1336 }
1337 }
1338 }
1339 }
1340
1341 if (opt.stdin_path) {
1342 fdin = nob_fd_open_for_read(opt.stdin_path);
1343 if (fdin == NOB_INVALID_FD) nob_return_defer(false);
1344 opt_fdin = &fdin;
1345 }
1346 if (opt.stdout_path) {
1347 fdout = nob_fd_open_for_write(opt.stdout_path);
1348 if (fdout == NOB_INVALID_FD) nob_return_defer(false);
1349 opt_fdout = &fdout;
1350 }
1351 if (opt.stderr_path) {
1352 fderr = nob_fd_open_for_write(opt.stderr_path);
1353 if (fderr == NOB_INVALID_FD) nob_return_defer(false);
1354 opt_fderr = &fderr;
1355 }
1356 proc = nob__cmd_start_process(*cmd, opt_fdin, opt_fdout, opt_fderr);
1357
1358 if (opt.async) {
1359 if (proc == NOB_INVALID_PROC) nob_return_defer(false);
1360 nob_da_append(opt.async, proc);
1361 } else {
1362 if (!nob_proc_wait(proc)) nob_return_defer(false);
1363 }
1364
1365 defer:
1366 if (opt_fdin) nob_fd_close(*opt_fdin);
1367 if (opt_fdout) nob_fd_close(*opt_fdout);
1368 if (opt_fderr) nob_fd_close(*opt_fderr);
1369 if (!opt.dont_reset) cmd->count = 0;
1370 return result;
1371 }
1372
1373 NOBDEF bool nob_chain_begin_opt(Nob_Chain *chain, Nob_Chain_Begin_Opt opt)
1374 {
1375 chain->cmd.count = 0;
1376 chain->err2out = false;
1377 chain->fdin = NOB_INVALID_FD;
1378 if (opt.stdin_path) {
1379 chain->fdin = nob_fd_open_for_read(opt.stdin_path);
1380 if (chain->fdin == NOB_INVALID_FD) return false;
1381 }
1382 return true;
1383 }
1384
1385 NOBDEF bool nob_chain_cmd_opt(Nob_Chain *chain, Nob_Cmd *cmd, Nob_Chain_Cmd_Opt opt)
1386 {
1387 bool result = true;
1388 Nob_Pipe pp = {0};
1389 struct {
1390 size_t count;
1391 Nob_Fd items[5]; // should be no more than 3, but we allocate 5 just in case
1392 } fds = {0};
1393
1394 NOB_ASSERT(cmd->count > 0);
1395
1396 if (chain->cmd.count != 0) { // not first cmd in the chain
1397 Nob_Fd *pfdin = NULL;
1398 if (chain->fdin != NOB_INVALID_FD) {
1399 nob_fa_append(&fds, chain->fdin);
1400 pfdin = &chain->fdin;
1401 }
1402 if (!nob_pipe_create(&pp)) nob_return_defer(false);
1403 nob_fa_append(&fds, pp.write);
1404 Nob_Fd *pfdout = &pp.write;
1405 Nob_Fd *pfderr = chain->err2out ? pfdout : NULL;
1406
1407 Nob_Proc proc = nob__cmd_start_process(chain->cmd, pfdin, pfdout, pfderr);
1408 chain->cmd.count = 0;
1409 if (proc == NOB_INVALID_PROC) {
1410 nob_fa_append(&fds, pp.read);
1411 nob_return_defer(false);
1412 }
1413 chain->fdin = pp.read;
1414 }
1415
1416 nob_da_append_many(&chain->cmd, cmd->items, cmd->count);
1417 chain->err2out = opt.err2out;
1418
1419 defer:
1420 for (size_t i = 0; i < fds.count; ++i) {
1421 nob_fd_close(fds.items[i]);
1422 }
1423 if (!opt.dont_reset) cmd->count = 0;
1424 return result;
1425 }
1426
1427 static Nob_Fd nob__fd_stdout(void)
1428 {
1429 #ifdef _WIN32
1430 return GetStdHandle(STD_OUTPUT_HANDLE);
1431 #else
1432 return STDOUT_FILENO;
1433 #endif // _WIN32
1434 }
1435
1436 NOBDEF bool nob_chain_end_opt(Nob_Chain *chain, Nob_Chain_End_Opt opt)
1437 {
1438 bool result = true;
1439
1440 Nob_Fd *pfdin = NULL;
1441 struct {
1442 size_t count;
1443 Nob_Fd items[5]; // should be no more than 3, but we allocate 5 just in case
1444 } fds = {0};
1445
1446 if (chain->fdin != NOB_INVALID_FD) {
1447 nob_fa_append(&fds, chain->fdin);
1448 pfdin = &chain->fdin;
1449 }
1450
1451 if (chain->cmd.count != 0) { // Non-empty chain case
1452 size_t max_procs = opt.max_procs > 0 ? opt.max_procs : (size_t) nob_nprocs() + 1;
1453
1454 if (opt.async && max_procs > 0) {
1455 while (opt.async->count >= max_procs) {
1456 for (size_t i = 0; i < opt.async->count; ++i) {
1457 int ret = nob__proc_wait_async(opt.async->items[i], 1);
1458 if (ret < 0) nob_return_defer(false);
1459 if (ret) {
1460 nob_da_remove_unordered(opt.async, i);
1461 break;
1462 }
1463 }
1464 }
1465 }
1466
1467 Nob_Fd fdout = nob__fd_stdout();
1468 if (opt.stdout_path) {
1469 fdout = nob_fd_open_for_write(opt.stdout_path);
1470 if (fdout == NOB_INVALID_FD) nob_return_defer(false);
1471 nob_fa_append(&fds, fdout);
1472 }
1473
1474 Nob_Fd fderr = 0;
1475 Nob_Fd *pfderr = NULL;
1476 if (chain->err2out) pfderr = &fdout;
1477 if (opt.stderr_path) {
1478 if (pfderr == NULL) {
1479 fderr = nob_fd_open_for_write(opt.stderr_path);
1480 if (fderr == NOB_INVALID_FD) nob_return_defer(false);
1481 nob_fa_append(&fds, fderr);
1482 pfderr = &fderr;
1483 } else {
1484 // There was err2out set for the last command.
1485 // All the stderr will go to stdout.
1486 // So the stderr file is going to be empty.
1487 NOB_ASSERT(chain->err2out);
1488 if (!nob_write_entire_file(opt.stderr_path, NULL, 0)) nob_return_defer(false);
1489 }
1490 }
1491
1492 Nob_Proc proc = nob__cmd_start_process(chain->cmd, pfdin, &fdout, pfderr);
1493 chain->cmd.count = 0;
1494
1495 if (opt.async) {
1496 if (proc == NOB_INVALID_PROC) nob_return_defer(false);
1497 nob_da_append(opt.async, proc);
1498 } else {
1499 if (!nob_proc_wait(proc)) nob_return_defer(false);
1500 }
1501 }
1502
1503 defer:
1504 for (size_t i = 0; i < fds.count; ++i) {
1505 nob_fd_close(fds.items[i]);
1506 }
1507 return result;
1508 }
1509
1510 // The maximum time span representable is 584 years.
1511 NOBDEF uint64_t nob_nanos_since_unspecified_epoch(void)
1512 {
1513 #ifdef _WIN32
1514 LARGE_INTEGER Time;
1515 QueryPerformanceCounter(&Time);
1516
1517 static LARGE_INTEGER Frequency = {0};
1518 if (Frequency.QuadPart == 0) {
1519 QueryPerformanceFrequency(&Frequency);
1520 }
1521
1522 uint64_t Secs = Time.QuadPart / Frequency.QuadPart;
1523 uint64_t Nanos = Time.QuadPart % Frequency.QuadPart * NOB_NANOS_PER_SEC / Frequency.QuadPart;
1524 return NOB_NANOS_PER_SEC * Secs + Nanos;
1525 #else
1526 struct timespec ts;
1527 clock_gettime(CLOCK_MONOTONIC, &ts);
1528
1529 return NOB_NANOS_PER_SEC * ts.tv_sec + ts.tv_nsec;
1530 #endif // _WIN32
1531 }
1532
1533 NOBDEF Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)
1534 {
1535 return nob__cmd_start_process(cmd, redirect.fdin, redirect.fdout, redirect.fderr);
1536 }
1537
1538 static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, Nob_Fd *fderr)
1539 {
1540 if (cmd.count < 1) {
1541 nob_log(NOB_ERROR, "Could not run empty command");
1542 return NOB_INVALID_PROC;
1543 }
1544
1545 #ifndef NOB_NO_ECHO
1546 Nob_String_Builder sb = {0};
1547 nob_cmd_render(cmd, &sb);
1548 nob_sb_append_null(&sb);
1549 nob_log(NOB_INFO, "CMD: %s", sb.items);
1550 nob_sb_free(sb);
1551 memset(&sb, 0, sizeof(sb));
1552 #endif // NOB_NO_ECHO
1553
1554 #ifdef _WIN32
1555 // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
1556
1557 STARTUPINFO siStartInfo;
1558 ZeroMemory(&siStartInfo, sizeof(siStartInfo));
1559 siStartInfo.cb = sizeof(STARTUPINFO);
1560 // NOTE: theoretically setting NULL to std handles should not be a problem
1561 // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
1562 // TODO: check for errors in GetStdHandle
1563 siStartInfo.hStdError = fderr ? *fderr : GetStdHandle(STD_ERROR_HANDLE);
1564 siStartInfo.hStdOutput = fdout ? *fdout : GetStdHandle(STD_OUTPUT_HANDLE);
1565 siStartInfo.hStdInput = fdin ? *fdin : GetStdHandle(STD_INPUT_HANDLE);
1566 siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
1567
1568 PROCESS_INFORMATION piProcInfo;
1569 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
1570
1571 Nob_String_Builder quoted = {0};
1572 nob__win32_cmd_quote(cmd, &quoted);
1573 nob_sb_append_null(&quoted);
1574 BOOL bSuccess = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);
1575 nob_sb_free(quoted);
1576
1577 if (!bSuccess) {
1578 nob_log(NOB_ERROR, "Could not create child process for %s: %s", cmd.items[0], nob_win32_error_message(GetLastError()));
1579 return NOB_INVALID_PROC;
1580 }
1581
1582 CloseHandle(piProcInfo.hThread);
1583
1584 return piProcInfo.hProcess;
1585 #else
1586 pid_t cpid = fork();
1587 if (cpid < 0) {
1588 nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno));
1589 return NOB_INVALID_PROC;
1590 }
1591
1592 if (cpid == 0) {
1593 if (fdin) {
1594 if (dup2(*fdin, STDIN_FILENO) < 0) {
1595 nob_log(NOB_ERROR, "Could not setup stdin for child process: %s", strerror(errno));
1596 exit(1);
1597 }
1598 }
1599
1600 if (fdout) {
1601 if (dup2(*fdout, STDOUT_FILENO) < 0) {
1602 nob_log(NOB_ERROR, "Could not setup stdout for child process: %s", strerror(errno));
1603 exit(1);
1604 }
1605 }
1606
1607 if (fderr) {
1608 if (dup2(*fderr, STDERR_FILENO) < 0) {
1609 nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno));
1610 exit(1);
1611 }
1612 }
1613
1614 // NOTE: This leaks a bit of memory in the child process.
1615 // But do we actually care? It's a one off leak anyway...
1616 Nob_Cmd cmd_null = {0};
1617 nob_da_append_many(&cmd_null, cmd.items, cmd.count);
1618 nob_cmd_append(&cmd_null, (const char*)NULL);
1619
1620 if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) {
1621 nob_log(NOB_ERROR, "Could not exec child process for %s: %s", cmd.items[0], strerror(errno));
1622 exit(1);
1623 }
1624 NOB_UNREACHABLE("nob_cmd_run_async_redirect");
1625 }
1626
1627 return cpid;
1628 #endif
1629 }
1630
1631 NOBDEF Nob_Proc nob_cmd_run_async(Nob_Cmd cmd)
1632 {
1633 return nob__cmd_start_process(cmd, NULL, NULL, NULL);
1634 }
1635
1636 NOBDEF Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd)
1637 {
1638 Nob_Proc proc = nob__cmd_start_process(*cmd, NULL, NULL, NULL);
1639 cmd->count = 0;
1640 return proc;
1641 }
1642
1643 NOBDEF Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect)
1644 {
1645 Nob_Proc proc = nob__cmd_start_process(*cmd, redirect.fdin, redirect.fdout, redirect.fderr);
1646 cmd->count = 0;
1647 if (redirect.fdin) {
1648 nob_fd_close(*redirect.fdin);
1649 *redirect.fdin = NOB_INVALID_FD;
1650 }
1651 if (redirect.fdout) {
1652 nob_fd_close(*redirect.fdout);
1653 *redirect.fdout = NOB_INVALID_FD;
1654 }
1655 if (redirect.fderr) {
1656 nob_fd_close(*redirect.fderr);
1657 *redirect.fderr = NOB_INVALID_FD;
1658 }
1659 return proc;
1660 }
1661
1662 NOBDEF Nob_Fd nob_fd_open_for_read(const char *path)
1663 {
1664 #ifndef _WIN32
1665 Nob_Fd result = open(path, O_RDONLY);
1666 if (result < 0) {
1667 nob_log(NOB_ERROR, "Could not open file %s: %s", path, strerror(errno));
1668 return NOB_INVALID_FD;
1669 }
1670 return result;
1671 #else
1672 // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing
1673 SECURITY_ATTRIBUTES saAttr = {0};
1674 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
1675 saAttr.bInheritHandle = TRUE;
1676
1677 Nob_Fd result = CreateFile(
1678 path,
1679 GENERIC_READ,
1680 0,
1681 &saAttr,
1682 OPEN_EXISTING,
1683 FILE_ATTRIBUTE_READONLY,
1684 NULL);
1685
1686 if (result == INVALID_HANDLE_VALUE) {
1687 nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError()));
1688 return NOB_INVALID_FD;
1689 }
1690
1691 return result;
1692 #endif // _WIN32
1693 }
1694
1695 NOBDEF Nob_Fd nob_fd_open_for_write(const char *path)
1696 {
1697 #ifndef _WIN32
1698 Nob_Fd result = open(path,
1699 O_WRONLY | O_CREAT | O_TRUNC,
1700 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
1701 if (result < 0) {
1702 nob_log(NOB_ERROR, "could not open file %s: %s", path, strerror(errno));
1703 return NOB_INVALID_FD;
1704 }
1705 return result;
1706 #else
1707 SECURITY_ATTRIBUTES saAttr = {0};
1708 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
1709 saAttr.bInheritHandle = TRUE;
1710
1711 Nob_Fd result = CreateFile(
1712 path, // name of the write
1713 GENERIC_WRITE, // open for writing
1714 0, // do not share
1715 &saAttr, // default security
1716 CREATE_ALWAYS, // create always
1717 FILE_ATTRIBUTE_NORMAL, // normal file
1718 NULL // no attr. template
1719 );
1720
1721 if (result == INVALID_HANDLE_VALUE) {
1722 nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError()));
1723 return NOB_INVALID_FD;
1724 }
1725
1726 return result;
1727 #endif // _WIN32
1728 }
1729
1730 NOBDEF void nob_fd_close(Nob_Fd fd)
1731 {
1732 #ifdef _WIN32
1733 CloseHandle(fd);
1734 #else
1735 close(fd);
1736 #endif // _WIN32
1737 }
1738
1739 NOBDEF bool nob_pipe_create(Nob_Pipe *pp)
1740 {
1741 #ifdef _WIN32
1742 // https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output
1743
1744 SECURITY_ATTRIBUTES saAttr = {0};
1745 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
1746 saAttr.bInheritHandle = TRUE;
1747
1748 if (!CreatePipe(&pp->read, &pp->write, &saAttr, 0)) {
1749 nob_log(NOB_ERROR, "Could not create pipe: %s", nob_win32_error_message(GetLastError()));
1750 return false;
1751 }
1752
1753 return true;
1754 #else
1755 int pipefd[2];
1756 if (pipe(pipefd) < 0) {
1757 nob_log(NOB_ERROR, "Could not create pipe: %s\n", strerror(errno));
1758 return false;
1759 }
1760
1761 pp->read = pipefd[0];
1762 pp->write = pipefd[1];
1763
1764 return true;
1765 #endif // _WIN32
1766 }
1767
1768 NOBDEF bool nob_procs_wait(Nob_Procs procs)
1769 {
1770 bool success = true;
1771 for (size_t i = 0; i < procs.count; ++i) {
1772 success = nob_proc_wait(procs.items[i]) && success;
1773 }
1774 return success;
1775 }
1776
1777 NOBDEF bool nob_procs_flush(Nob_Procs *procs)
1778 {
1779 bool success = nob_procs_wait(*procs);
1780 procs->count = 0;
1781 return success;
1782 }
1783
1784 NOBDEF bool nob_procs_wait_and_reset(Nob_Procs *procs)
1785 {
1786 return nob_procs_flush(procs);
1787 }
1788
1789 NOBDEF bool nob_proc_wait(Nob_Proc proc)
1790 {
1791 if (proc == NOB_INVALID_PROC) return false;
1792
1793 #ifdef _WIN32
1794 DWORD result = WaitForSingleObject(
1795 proc, // HANDLE hHandle,
1796 INFINITE // DWORD dwMilliseconds
1797 );
1798
1799 if (result == WAIT_FAILED) {
1800 nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError()));
1801 return false;
1802 }
1803
1804 DWORD exit_status;
1805 if (!GetExitCodeProcess(proc, &exit_status)) {
1806 nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError()));
1807 return false;
1808 }
1809
1810 if (exit_status != 0) {
1811 nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status);
1812 return false;
1813 }
1814
1815 CloseHandle(proc);
1816
1817 return true;
1818 #else
1819 for (;;) {
1820 int wstatus = 0;
1821 if (waitpid(proc, &wstatus, 0) < 0) {
1822 nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno));
1823 return false;
1824 }
1825
1826 if (WIFEXITED(wstatus)) {
1827 int exit_status = WEXITSTATUS(wstatus);
1828 if (exit_status != 0) {
1829 nob_log(NOB_ERROR, "command exited with exit code %d", exit_status);
1830 return false;
1831 }
1832
1833 break;
1834 }
1835
1836 if (WIFSIGNALED(wstatus)) {
1837 nob_log(NOB_ERROR, "command process was terminated by signal %d", WTERMSIG(wstatus));
1838 return false;
1839 }
1840 }
1841
1842 return true;
1843 #endif
1844 }
1845
1846 static int nob__proc_wait_async(Nob_Proc proc, int ms)
1847 {
1848 if (proc == NOB_INVALID_PROC) return false;
1849
1850 #ifdef _WIN32
1851 DWORD result = WaitForSingleObject(
1852 proc, // HANDLE hHandle,
1853 ms // DWORD dwMilliseconds
1854 );
1855
1856 if (result == WAIT_TIMEOUT) {
1857 return 0;
1858 }
1859
1860 if (result == WAIT_FAILED) {
1861 nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError()));
1862 return -1;
1863 }
1864
1865 DWORD exit_status;
1866 if (!GetExitCodeProcess(proc, &exit_status)) {
1867 nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError()));
1868 return -1;
1869 }
1870
1871 if (exit_status != 0) {
1872 nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status);
1873 return -1;
1874 }
1875
1876 CloseHandle(proc);
1877
1878 return 1;
1879 #else
1880 long ns = ms*1000*1000;
1881 struct timespec duration = {
1882 .tv_sec = ns/(1000*1000*1000),
1883 .tv_nsec = ns%(1000*1000*1000),
1884 };
1885
1886 int wstatus = 0;
1887 pid_t pid = waitpid(proc, &wstatus, WNOHANG);
1888 if (pid < 0) {
1889 nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno));
1890 return -1;
1891 }
1892
1893 if (pid == 0) {
1894 nanosleep(&duration, NULL);
1895 return 0;
1896 }
1897
1898 if (WIFEXITED(wstatus)) {
1899 int exit_status = WEXITSTATUS(wstatus);
1900 if (exit_status != 0) {
1901 nob_log(NOB_ERROR, "command exited with exit code %d", exit_status);
1902 return -1;
1903 }
1904
1905 return 1;
1906 }
1907
1908 if (WIFSIGNALED(wstatus)) {
1909 nob_log(NOB_ERROR, "command process was terminated by signal %d", WTERMSIG(wstatus));
1910 return -1;
1911 }
1912
1913 nanosleep(&duration, NULL);
1914 return 0;
1915 #endif
1916 }
1917
1918 NOBDEF bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count)
1919 {
1920 nob_da_append(procs, proc);
1921
1922 if (procs->count >= max_procs_count) {
1923 if (!nob_procs_flush(procs)) return false;
1924 }
1925
1926 return true;
1927 }
1928
1929 NOBDEF bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)
1930 {
1931 Nob_Proc p = nob__cmd_start_process(cmd, redirect.fdin, redirect.fdout, redirect.fderr);
1932 return nob_proc_wait(p);
1933 }
1934
1935 NOBDEF bool nob_cmd_run_sync(Nob_Cmd cmd)
1936 {
1937 Nob_Proc p = nob__cmd_start_process(cmd, NULL, NULL, NULL);
1938 return nob_proc_wait(p);
1939 }
1940
1941 NOBDEF bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd)
1942 {
1943 Nob_Proc p = nob__cmd_start_process(*cmd, NULL, NULL, NULL);
1944 cmd->count = 0;
1945 return nob_proc_wait(p);
1946 }
1947
1948 NOBDEF bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect)
1949 {
1950 Nob_Proc p = nob__cmd_start_process(*cmd, redirect.fdin, redirect.fdout, redirect.fderr);
1951 cmd->count = 0;
1952 if (redirect.fdin) {
1953 nob_fd_close(*redirect.fdin);
1954 *redirect.fdin = NOB_INVALID_FD;
1955 }
1956 if (redirect.fdout) {
1957 nob_fd_close(*redirect.fdout);
1958 *redirect.fdout = NOB_INVALID_FD;
1959 }
1960 if (redirect.fderr) {
1961 nob_fd_close(*redirect.fderr);
1962 *redirect.fderr = NOB_INVALID_FD;
1963 }
1964 return nob_proc_wait(p);
1965 }
1966
1967 static Nob_Log_Handler *nob__log_handler = &nob_default_log_handler;
1968
1969 NOBDEF void nob_set_log_handler(Nob_Log_Handler *handler)
1970 {
1971 nob__log_handler = handler;
1972 }
1973
1974 NOBDEF Nob_Log_Handler *nob_get_log_handler(void)
1975 {
1976 return nob__log_handler;
1977 }
1978
1979 NOBDEF void nob_default_log_handler(Nob_Log_Level level, const char *fmt, va_list args)
1980 {
1981 if (level < nob_minimal_log_level) return;
1982
1983 const char *prefix = NULL;
1984 switch (level) {
1985 case NOB_INFO:
1986 prefix = "[INFO] ";
1987 break;
1988 case NOB_WARNING:
1989 prefix = "[WARNING] ";
1990 break;
1991 case NOB_ERROR:
1992 prefix = "[ERROR] ";
1993 break;
1994 case NOB_NO_LOGS: return;
1995 default:
1996 NOB_UNREACHABLE("Nob_Log_Level");
1997 }
1998
1999 size_t mark = nob_temp_save();
2000 const char *msg = nob_temp_vsprintf(fmt, args);
2001 fprintf(stderr, "%s%s\n", prefix, msg);
2002 nob_temp_rewind(mark);
2003 }
2004
2005 NOBDEF void nob_null_log_handler(Nob_Log_Level level, const char *fmt, va_list args)
2006 {
2007 NOB_UNUSED(level);
2008 NOB_UNUSED(fmt);
2009 NOB_UNUSED(args);
2010 }
2011
2012 NOBDEF void nob_cancer_log_handler(Nob_Log_Level level, const char *fmt, va_list args)
2013 {
2014 if (level < nob_minimal_log_level) return;
2015
2016 const char *prefix = NULL;
2017 switch (level) {
2018 case NOB_INFO:
2019 prefix = "ℹ️ \x1b[36m[INFO]\x1b[0m ";
2020 break;
2021 case NOB_WARNING:
2022 prefix = "⚠️ \x1b[33m[WARNING]\x1b[0m ";
2023 break;
2024 case NOB_ERROR:
2025 prefix = "🚨 \x1b[31m[ERROR]\x1b[0m ";
2026 break;
2027 case NOB_NO_LOGS: return;
2028 default:
2029 NOB_UNREACHABLE("Nob_Log_Level");
2030 }
2031
2032 size_t mark = nob_temp_save();
2033 const char *msg = nob_temp_vsprintf(fmt, args);
2034 fprintf(stderr, "%s%s\n", prefix, msg);
2035 nob_temp_rewind(mark);
2036 }
2037
2038 NOBDEF void nob_log(Nob_Log_Level level, const char *fmt, ...)
2039 {
2040 va_list args;
2041 va_start(args, fmt);
2042 nob__log_handler(level, fmt, args);
2043 va_end(args);
2044 }
2045
2046 NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir)
2047 {
2048 memset(dir, 0, sizeof(*dir));
2049 #ifdef _WIN32
2050 size_t temp_mark = nob_temp_save();
2051 char *buffer = nob_temp_sprintf("%s\\*", dir_path);
2052 dir->nob__private.win32_hFind = FindFirstFile(buffer, &dir->nob__private.win32_data);
2053 nob_temp_rewind(temp_mark);
2054
2055 if (dir->nob__private.win32_hFind == INVALID_HANDLE_VALUE) {
2056 nob_log(NOB_ERROR, "Could not open directory %s: %s", dir_path, nob_win32_error_message(GetLastError()));
2057 dir->error = true;
2058 return false;
2059 }
2060 #else
2061 dir->nob__private.posix_dir = opendir(dir_path);
2062 if (dir->nob__private.posix_dir == NULL) {
2063 nob_log(NOB_ERROR, "Could not open directory %s: %s", dir_path, strerror(errno));
2064 dir->error = true;
2065 return false;
2066 }
2067 #endif // _WIN32
2068 return true;
2069 }
2070
2071 NOBDEF bool nob_dir_entry_next(Nob_Dir_Entry *dir)
2072 {
2073 #ifdef _WIN32
2074 if (!dir->nob__private.win32_init) {
2075 dir->nob__private.win32_init = true;
2076 dir->name = dir->nob__private.win32_data.cFileName;
2077 return true;
2078 }
2079
2080 if (!FindNextFile(dir->nob__private.win32_hFind, &dir->nob__private.win32_data)) {
2081 if (GetLastError() == ERROR_NO_MORE_FILES) return false;
2082 nob_log(NOB_ERROR, "Could not read next directory entry: %s", nob_win32_error_message(GetLastError()));
2083 dir->error = true;
2084 return false;
2085 }
2086 dir->name = dir->nob__private.win32_data.cFileName;
2087 #else
2088 errno = 0;
2089 dir->nob__private.posix_ent = readdir(dir->nob__private.posix_dir);
2090 if (dir->nob__private.posix_ent == NULL) {
2091 if (errno == 0) return false;
2092 nob_log(NOB_ERROR, "Could not read next directory entry: %s", strerror(errno));
2093 dir->error = true;
2094 return false;
2095 }
2096 dir->name = dir->nob__private.posix_ent->d_name;
2097 #endif // _WIN32
2098 return true;
2099 }
2100
2101 NOBDEF void nob_dir_entry_close(Nob_Dir_Entry dir)
2102 {
2103 #ifdef _WIN32
2104 FindClose(dir.nob__private.win32_hFind);
2105 #else
2106 if (dir.nob__private.posix_dir) closedir(dir.nob__private.posix_dir);
2107 #endif
2108 }
2109
2110 // On the moment of entering `nob__walk_dir_opt_impl()`, the `file_path` Nob_String_Builder is expected to be NULL-terminated.
2111 // So you can freely pass `file_path->items` to functions that expect NULL-terminated file path.
2112 // On existing `nob__walk_dir_opt_impl()` is expected to restore the original content of `file_path`
2113 bool nob__walk_dir_opt_impl(Nob_String_Builder *file_path, Nob_Walk_Func func, size_t level, bool *stop, Nob_Walk_Dir_Opt opt)
2114 {
2115 NOB_ASSERT(file_path->count > 0 && "file_path was probably not properly NULL-terminated");
2116 bool result = true;
2117
2118 Nob_Dir_Entry dir = {0};
2119 size_t saved_file_path_count = file_path->count;
2120 Nob_Walk_Action action = NOB_WALK_CONT;
2121
2122 Nob_File_Type file_type = nob_get_file_type(file_path->items);
2123 if (file_type < 0) nob_return_defer(false);
2124
2125 // Pre-order walking
2126 if (!opt.post_order) {
2127 if (!func(NOB_CLIT(Nob_Walk_Entry) {
2128 .path = file_path->items,
2129 .type = file_type,
2130 .level = level,
2131 .data = opt.data,
2132 .action = &action,
2133 })) nob_return_defer(false);
2134 switch (action) {
2135 case NOB_WALK_CONT: break;
2136 case NOB_WALK_STOP: *stop = true; // fallthrough
2137 case NOB_WALK_SKIP: nob_return_defer(true);
2138 default: NOB_UNREACHABLE("Nob_Walk_Action");
2139 }
2140 }
2141
2142 if (file_type == NOB_FILE_DIRECTORY) {
2143 if (!nob_dir_entry_open(file_path->items, &dir)) nob_return_defer(false);
2144 for (;;) {
2145 // Next entry
2146 if (!nob_dir_entry_next(&dir)) {
2147 if (!dir.error) break;
2148 nob_return_defer(false);
2149 }
2150
2151 // Ignore . and ..
2152 if (strcmp(dir.name, ".") == 0) continue;
2153 if (strcmp(dir.name, "..") == 0) continue;
2154
2155 // Prepare the new file_path
2156 file_path->count = saved_file_path_count - 1;
2157 #ifdef _WIN32
2158 nob_sb_appendf(file_path, "\\%s", dir.name);
2159 #else
2160 nob_sb_appendf(file_path, "/%s", dir.name);
2161 #endif // _WIN32
2162 nob_sb_append_null(file_path);
2163
2164 // Recurse
2165 if (!nob__walk_dir_opt_impl(file_path, func, level+1, stop, opt)) nob_return_defer(false);
2166 if (*stop) nob_return_defer(true);
2167 }
2168 file_path->count = saved_file_path_count;
2169 nob_da_last(file_path) = '\0';
2170 }
2171
2172 // Post-order walking
2173 if (opt.post_order) {
2174 if (!func(NOB_CLIT(Nob_Walk_Entry) {
2175 .path = file_path->items,
2176 .type = file_type,
2177 .level = level,
2178 .data = opt.data,
2179 .action = &action,
2180 })) nob_return_defer(false);
2181 switch (action) {
2182 case NOB_WALK_CONT: break;
2183 case NOB_WALK_STOP: *stop = true; // fallthrough
2184 case NOB_WALK_SKIP: nob_return_defer(true);
2185 default: NOB_UNREACHABLE("Nob_Walk_Action");
2186 }
2187 }
2188
2189 defer:
2190 // Always reset the file_path back to what it was
2191 file_path->count = saved_file_path_count;
2192 nob_da_last(file_path) = '\0';
2193
2194 nob_dir_entry_close(dir);
2195 return result;
2196 }
2197
2198 NOBDEF bool nob_walk_dir_opt(const char *root, Nob_Walk_Func func, Nob_Walk_Dir_Opt opt)
2199 {
2200 Nob_String_Builder file_path = {0};
2201
2202 nob_sb_appendf(&file_path, "%s", root);
2203 nob_sb_append_null(&file_path);
2204
2205 bool stop = false;
2206 bool ok = nob__walk_dir_opt_impl(&file_path, func, 0, &stop, opt);
2207 free(file_path.items);
2208 return ok;
2209 }
2210
2211 NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)
2212 {
2213 if (strlen(parent) == 0) {
2214 nob_log(NOB_ERROR, "Cannot read empty path");
2215 return false;
2216 }
2217 bool result = true;
2218 Nob_Dir_Entry dir = {0};
2219 if (!nob_dir_entry_open(parent, &dir)) nob_return_defer(false);
2220 while (nob_dir_entry_next(&dir)) nob_da_append(children, nob_temp_strdup(dir.name));
2221 if (dir.error) nob_return_defer(false);
2222 defer:
2223 nob_dir_entry_close(dir);
2224 return result;
2225 }
2226
2227 NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size)
2228 {
2229 bool result = true;
2230
2231 const char *buf = NULL;
2232 FILE *f = fopen(path, "wb");
2233 if (f == NULL) {
2234 nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno));
2235 nob_return_defer(false);
2236 }
2237
2238 // len
2239 // v
2240 // aaaaaaaaaa
2241 // ^
2242 // data
2243
2244 buf = (const char*)data;
2245 while (size > 0) {
2246 size_t n = fwrite(buf, 1, size, f);
2247 if (ferror(f)) {
2248 nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno));
2249 nob_return_defer(false);
2250 }
2251 size -= n;
2252 buf += n;
2253 }
2254
2255 defer:
2256 if (f) fclose(f);
2257 return result;
2258 }
2259
2260 NOBDEF Nob_File_Type nob_get_file_type(const char *path)
2261 {
2262 #ifdef _WIN32
2263 DWORD attr = GetFileAttributesA(path);
2264 if (attr == INVALID_FILE_ATTRIBUTES) {
2265 nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError()));
2266 return (Nob_File_Type)-1;
2267 }
2268
2269 if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY;
2270 // TODO: detect symlinks on Windows (whatever that means on Windows anyway)
2271 return NOB_FILE_REGULAR;
2272 #else // _WIN32
2273 struct stat statbuf;
2274 if (lstat(path, &statbuf) < 0) {
2275 nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno));
2276 return (Nob_File_Type)(-1);
2277 }
2278
2279 if (S_ISREG(statbuf.st_mode)) return NOB_FILE_REGULAR;
2280 if (S_ISDIR(statbuf.st_mode)) return NOB_FILE_DIRECTORY;
2281 if (S_ISLNK(statbuf.st_mode)) return NOB_FILE_SYMLINK;
2282 return NOB_FILE_OTHER;
2283 #endif // _WIN32
2284 }
2285
2286 NOBDEF bool nob_delete_file(const char *path)
2287 {
2288 #ifndef NOB_NO_ECHO
2289 nob_log(NOB_INFO, "deleting %s", path);
2290 #endif // NOB_NO_ECHO
2291 #ifdef _WIN32
2292 Nob_File_Type type = nob_get_file_type(path);
2293 switch (type) {
2294 case NOB_FILE_DIRECTORY:
2295 if (!RemoveDirectoryA(path)) {
2296 nob_log(NOB_ERROR, "Could not delete directory %s: %s", path, nob_win32_error_message(GetLastError()));
2297 return false;
2298 }
2299 break;
2300 case NOB_FILE_REGULAR:
2301 case NOB_FILE_SYMLINK:
2302 case NOB_FILE_OTHER:
2303 if (!DeleteFileA(path)) {
2304 nob_log(NOB_ERROR, "Could not delete file %s: %s", path, nob_win32_error_message(GetLastError()));
2305 return false;
2306 }
2307 break;
2308 default: NOB_UNREACHABLE("Nob_File_Type");
2309 }
2310 return true;
2311 #else
2312 if (remove(path) < 0) {
2313 nob_log(NOB_ERROR, "Could not delete file %s: %s", path, strerror(errno));
2314 return false;
2315 }
2316 return true;
2317 #endif // _WIN32
2318 }
2319
2320 NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path)
2321 {
2322 bool result = true;
2323 Nob_File_Paths children = {0};
2324 Nob_String_Builder src_sb = {0};
2325 Nob_String_Builder dst_sb = {0};
2326 size_t temp_checkpoint = nob_temp_save();
2327
2328 Nob_File_Type type = nob_get_file_type(src_path);
2329 if (type < 0) return false;
2330
2331 switch (type) {
2332 case NOB_FILE_DIRECTORY: {
2333 if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false);
2334 if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false);
2335
2336 for (size_t i = 0; i < children.count; ++i) {
2337 if (strcmp(children.items[i], ".") == 0) continue;
2338 if (strcmp(children.items[i], "..") == 0) continue;
2339
2340 src_sb.count = 0;
2341 nob_sb_append_cstr(&src_sb, src_path);
2342 nob_sb_append_cstr(&src_sb, "/");
2343 nob_sb_append_cstr(&src_sb, children.items[i]);
2344 nob_sb_append_null(&src_sb);
2345
2346 dst_sb.count = 0;
2347 nob_sb_append_cstr(&dst_sb, dst_path);
2348 nob_sb_append_cstr(&dst_sb, "/");
2349 nob_sb_append_cstr(&dst_sb, children.items[i]);
2350 nob_sb_append_null(&dst_sb);
2351
2352 if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) {
2353 nob_return_defer(false);
2354 }
2355 }
2356 } break;
2357
2358 case NOB_FILE_REGULAR: {
2359 if (!nob_copy_file(src_path, dst_path)) {
2360 nob_return_defer(false);
2361 }
2362 } break;
2363
2364 case NOB_FILE_SYMLINK: {
2365 nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet");
2366 } break;
2367
2368 case NOB_FILE_OTHER: {
2369 nob_log(NOB_ERROR, "Unsupported type of file %s", src_path);
2370 nob_return_defer(false);
2371 } break;
2372
2373 default: NOB_UNREACHABLE("nob_copy_directory_recursively");
2374 }
2375
2376 defer:
2377 nob_temp_rewind(temp_checkpoint);
2378 nob_da_free(src_sb);
2379 nob_da_free(dst_sb);
2380 nob_da_free(children);
2381 return result;
2382 }
2383
2384 NOBDEF char *nob_temp_strdup(const char *cstr)
2385 {
2386 size_t n = strlen(cstr);
2387 char *result = (char*)nob_temp_alloc(n + 1);
2388 NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY");
2389 memcpy(result, cstr, n);
2390 result[n] = '\0';
2391 return result;
2392 }
2393
2394 NOBDEF char *nob_temp_strndup(const char *s, size_t n)
2395 {
2396 char *r = (char*)nob_temp_alloc(n + 1);
2397 NOB_ASSERT(r != NULL && "Extend the size of the temporary allocator");
2398 memcpy(r, s, n);
2399 r[n] = '\0';
2400 return r;
2401 }
2402
2403 NOBDEF void *nob_temp_alloc(size_t requested_size)
2404 {
2405 size_t word_size = sizeof(uintptr_t);
2406 size_t size = (requested_size + word_size - 1)/word_size*word_size;
2407 if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL;
2408 void *result = &nob_temp[nob_temp_size];
2409 nob_temp_size += size;
2410 return result;
2411 }
2412
2413 NOBDEF char *nob_temp_vsprintf(const char *format, va_list ap)
2414 {
2415 va_list args;
2416 va_copy(args, ap);
2417 int n = vsnprintf(NULL, 0, format, args);
2418 va_end(args);
2419
2420 NOB_ASSERT(n >= 0);
2421 char *result = (char*)nob_temp_alloc(n + 1);
2422 NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator");
2423 // TODO: use proper arenas for the temporary allocator;
2424 va_copy(args, ap);
2425 vsnprintf(result, n + 1, format, args);
2426 va_end(args);
2427
2428 return result;
2429 }
2430
2431 NOBDEF char *nob_temp_sprintf(const char *format, ...)
2432 {
2433 va_list args;
2434 va_start(args, format);
2435 char *result = nob_temp_vsprintf(format, args);
2436 va_end(args);
2437 return result;
2438 }
2439
2440 NOBDEF void nob_temp_reset(void)
2441 {
2442 nob_temp_size = 0;
2443 }
2444
2445 NOBDEF size_t nob_temp_save(void)
2446 {
2447 return nob_temp_size;
2448 }
2449
2450 NOBDEF void nob_temp_rewind(size_t checkpoint)
2451 {
2452 nob_temp_size = checkpoint;
2453 }
2454
2455 NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv)
2456 {
2457 return nob_temp_strndup(sv.data, sv.count);
2458 }
2459
2460 NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count)
2461 {
2462 #ifdef _WIN32
2463 BOOL bSuccess;
2464
2465 HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
2466 if (output_path_fd == INVALID_HANDLE_VALUE) {
2467 // NOTE: if output does not exist it 100% must be rebuilt
2468 if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
2469 nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError()));
2470 return -1;
2471 }
2472 FILETIME output_path_time;
2473 bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time);
2474 CloseHandle(output_path_fd);
2475 if (!bSuccess) {
2476 nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError()));
2477 return -1;
2478 }
2479
2480 for (size_t i = 0; i < input_paths_count; ++i) {
2481 const char *input_path = input_paths[i];
2482 HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
2483 if (input_path_fd == INVALID_HANDLE_VALUE) {
2484 // NOTE: non-existing input is an error cause it is needed for building in the first place
2485 nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError()));
2486 return -1;
2487 }
2488 FILETIME input_path_time;
2489 bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time);
2490 CloseHandle(input_path_fd);
2491 if (!bSuccess) {
2492 nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError()));
2493 return -1;
2494 }
2495
2496 // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild
2497 if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1;
2498 }
2499
2500 return 0;
2501 #else
2502 struct stat statbuf = {0};
2503
2504 if (stat(output_path, &statbuf) < 0) {
2505 // NOTE: if output does not exist it 100% must be rebuilt
2506 if (errno == ENOENT) return 1;
2507 nob_log(NOB_ERROR, "could not stat %s: %s", output_path, strerror(errno));
2508 return -1;
2509 }
2510 time_t output_path_time = statbuf.st_mtime;
2511
2512 for (size_t i = 0; i < input_paths_count; ++i) {
2513 const char *input_path = input_paths[i];
2514 if (stat(input_path, &statbuf) < 0) {
2515 // NOTE: non-existing input is an error cause it is needed for building in the first place
2516 nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno));
2517 return -1;
2518 }
2519 time_t input_path_time = statbuf.st_mtime;
2520 // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild
2521 if (input_path_time > output_path_time) return 1;
2522 }
2523
2524 return 0;
2525 #endif
2526 }
2527
2528 NOBDEF int nob_needs_rebuild1(const char *output_path, const char *input_path)
2529 {
2530 return nob_needs_rebuild(output_path, &input_path, 1);
2531 }
2532
2533 NOBDEF const char *nob_path_name(const char *path)
2534 {
2535 #ifdef _WIN32
2536 const char *p1 = strrchr(path, '/');
2537 const char *p2 = strrchr(path, '\\');
2538 const char *p = (p1 > p2)? p1 : p2; // NULL is ignored if the other search is successful
2539 return p ? p + 1 : path;
2540 #else
2541 const char *p = strrchr(path, '/');
2542 return p ? p + 1 : path;
2543 #endif // _WIN32
2544 }
2545
2546 NOBDEF bool nob_rename(const char *old_path, const char *new_path)
2547 {
2548 #ifndef NOB_NO_ECHO
2549 nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path);
2550 #endif // NOB_NO_ECHO
2551 #ifdef _WIN32
2552 if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
2553 nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError()));
2554 return false;
2555 }
2556 #else
2557 if (rename(old_path, new_path) < 0) {
2558 nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno));
2559 return false;
2560 }
2561 #endif // _WIN32
2562 return true;
2563 }
2564
2565 NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb)
2566 {
2567 bool result = true;
2568
2569 FILE *f = fopen(path, "rb");
2570 size_t new_count = 0;
2571 long long m = 0;
2572 if (f == NULL) nob_return_defer(false);
2573 if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false);
2574 #ifndef _WIN32
2575 m = ftell(f);
2576 #else
2577 m = _telli64(_fileno(f));
2578 #endif
2579 if (m < 0) nob_return_defer(false);
2580 if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false);
2581
2582 new_count = sb->count + m;
2583 if (new_count > sb->capacity) {
2584 sb->items = NOB_DECLTYPE_CAST(sb->items)NOB_REALLOC(sb->items, new_count);
2585 NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!");
2586 sb->capacity = new_count;
2587 }
2588
2589 fread(sb->items + sb->count, m, 1, f);
2590 if (ferror(f)) {
2591 // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case.
2592 nob_return_defer(false);
2593 }
2594 sb->count = new_count;
2595
2596 defer:
2597 if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno));
2598 if (f) fclose(f);
2599 return result;
2600 }
2601
2602 NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...)
2603 {
2604 va_list args;
2605
2606 va_start(args, fmt);
2607 int n = vsnprintf(NULL, 0, fmt, args);
2608 va_end(args);
2609
2610 // NOTE: the new_capacity needs to be +1 because of the null terminator.
2611 // However, further below we increase sb->count by n, not n + 1.
2612 // This is because we don't want the sb to include the null terminator. The user can always sb_append_null() if they want it
2613 nob_da_reserve(sb, sb->count + n + 1);
2614 char *dest = sb->items + sb->count;
2615 va_start(args, fmt);
2616 vsnprintf(dest, n+1, fmt, args);
2617 va_end(args);
2618
2619 sb->count += n;
2620
2621 return n;
2622 }
2623
2624 NOBDEF void nob_sb_pad_align(Nob_String_Builder *sb, size_t size)
2625 {
2626 size_t rem = sb->count%size;
2627 if (rem == 0) return;
2628 for (size_t i = 0; i < size - rem; ++i) {
2629 nob_da_append(sb, 0);
2630 }
2631 }
2632
2633 NOBDEF Nob_String_View nob_sv_chop_while(Nob_String_View *sv, int (*p)(int x))
2634 {
2635 size_t i = 0;
2636 while (i < sv->count && p(sv->data[i])) {
2637 i += 1;
2638 }
2639
2640 Nob_String_View result = nob_sv_from_parts(sv->data, i);
2641 sv->count -= i;
2642 sv->data += i;
2643
2644 return result;
2645 }
2646
2647 NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim)
2648 {
2649 size_t i = 0;
2650 while (i < sv->count && sv->data[i] != delim) {
2651 i += 1;
2652 }
2653
2654 Nob_String_View result = nob_sv_from_parts(sv->data, i);
2655
2656 if (i < sv->count) {
2657 sv->count -= i + 1;
2658 sv->data += i + 1;
2659 } else {
2660 sv->count -= i;
2661 sv->data += i;
2662 }
2663
2664 return result;
2665 }
2666
2667 NOBDEF bool nob_sv_chop_prefix(Nob_String_View *sv, Nob_String_View prefix)
2668 {
2669 if (nob_sv_starts_with(*sv, prefix)) {
2670 nob_sv_chop_left(sv, prefix.count);
2671 return true;
2672 }
2673 return false;
2674 }
2675
2676 NOBDEF bool nob_sv_chop_suffix(Nob_String_View *sv, Nob_String_View suffix)
2677 {
2678 if (nob_sv_ends_with(*sv, suffix)) {
2679 nob_sv_chop_right(sv, suffix.count);
2680 return true;
2681 }
2682 return false;
2683 }
2684
2685 NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n)
2686 {
2687 if (n > sv->count) {
2688 n = sv->count;
2689 }
2690
2691 Nob_String_View result = nob_sv_from_parts(sv->data, n);
2692
2693 sv->data += n;
2694 sv->count -= n;
2695
2696 return result;
2697 }
2698
2699 NOBDEF Nob_String_View nob_sv_chop_right(Nob_String_View *sv, size_t n)
2700 {
2701 if (n > sv->count) {
2702 n = sv->count;
2703 }
2704
2705 Nob_String_View result = nob_sv_from_parts(sv->data + sv->count - n, n);
2706
2707 sv->count -= n;
2708
2709 return result;
2710 }
2711
2712 NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count)
2713 {
2714 Nob_String_View sv;
2715 sv.count = count;
2716 sv.data = data;
2717 return sv;
2718 }
2719
2720 NOBDEF Nob_String_View nob_sv_trim_left(Nob_String_View sv)
2721 {
2722 size_t i = 0;
2723 while (i < sv.count && isspace(sv.data[i])) {
2724 i += 1;
2725 }
2726
2727 // Apparently if sv.data == 0 and i == 0 adding them
2728 // together is an undefined behavior. According to
2729 // clang's UndefinedBehaviorSanitizer at least.
2730 if (i == 0) return sv;
2731
2732 return nob_sv_from_parts(sv.data + i, sv.count - i);
2733 }
2734
2735 NOBDEF Nob_String_View nob_sv_trim_right(Nob_String_View sv)
2736 {
2737 size_t i = 0;
2738 while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) {
2739 i += 1;
2740 }
2741
2742 return nob_sv_from_parts(sv.data, sv.count - i);
2743 }
2744
2745 NOBDEF Nob_String_View nob_sv_trim(Nob_String_View sv)
2746 {
2747 return nob_sv_trim_right(nob_sv_trim_left(sv));
2748 }
2749
2750 NOBDEF Nob_String_View nob_sv_from_cstr(const char *cstr)
2751 {
2752 return nob_sv_from_parts(cstr, strlen(cstr));
2753 }
2754
2755 NOBDEF bool nob_sv_eq(Nob_String_View a, Nob_String_View b)
2756 {
2757 if (a.count != b.count) {
2758 return false;
2759 } else {
2760 return memcmp(a.data, b.data, a.count) == 0;
2761 }
2762 }
2763
2764 NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr)
2765 {
2766 return nob_sv_ends_with_cstr(sv, cstr);
2767 }
2768
2769 NOBDEF bool nob_sv_ends_with_cstr(Nob_String_View sv, const char *cstr)
2770 {
2771 return nob_sv_ends_with(sv, nob_sv_from_cstr(cstr));
2772 }
2773
2774 NOBDEF bool nob_sv_ends_with(Nob_String_View sv, Nob_String_View suffix)
2775 {
2776 if (sv.count >= suffix.count) {
2777 Nob_String_View sv_tail = {
2778 .count = suffix.count,
2779 .data = sv.data + sv.count - suffix.count,
2780 };
2781 return nob_sv_eq(sv_tail, suffix);
2782 }
2783 return false;
2784 }
2785
2786 NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix)
2787 {
2788 if (expected_prefix.count <= sv.count) {
2789 Nob_String_View actual_prefix = nob_sv_from_parts(sv.data, expected_prefix.count);
2790 return nob_sv_eq(expected_prefix, actual_prefix);
2791 }
2792
2793 return false;
2794 }
2795
2796 // RETURNS:
2797 // 0 - file does not exists
2798 // 1 - file exists
2799 NOBDEF int nob_file_exists(const char *file_path)
2800 {
2801 #if _WIN32
2802 return GetFileAttributesA(file_path) != INVALID_FILE_ATTRIBUTES;
2803 #else
2804 return access(file_path, F_OK) == 0;
2805 #endif
2806 }
2807
2808 NOBDEF const char *nob_get_current_dir_temp(void)
2809 {
2810 #ifdef _WIN32
2811 DWORD nBufferLength = GetCurrentDirectory(0, NULL);
2812 if (nBufferLength == 0) {
2813 nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError()));
2814 return NULL;
2815 }
2816
2817 char *buffer = (char*) nob_temp_alloc(nBufferLength);
2818 if (GetCurrentDirectory(nBufferLength, buffer) == 0) {
2819 nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError()));
2820 return NULL;
2821 }
2822
2823 return buffer;
2824 #else
2825 char *buffer = (char*) nob_temp_alloc(PATH_MAX);
2826 if (getcwd(buffer, PATH_MAX) == NULL) {
2827 nob_log(NOB_ERROR, "could not get current directory: %s", strerror(errno));
2828 return NULL;
2829 }
2830
2831 return buffer;
2832 #endif // _WIN32
2833 }
2834
2835 NOBDEF bool nob_set_current_dir(const char *path)
2836 {
2837 #ifdef _WIN32
2838 if (!SetCurrentDirectory(path)) {
2839 nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError()));
2840 return false;
2841 }
2842 return true;
2843 #else
2844 if (chdir(path) < 0) {
2845 nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno));
2846 return false;
2847 }
2848 return true;
2849 #endif // _WIN32
2850 }
2851
2852 NOBDEF char *nob_temp_dir_name(const char *path)
2853 {
2854 #ifndef _WIN32
2855 // Stolen from the musl's implementation of dirname.
2856 // We are implementing our own one because libc vendors cannot agree on whether dirname(3)
2857 // modifies the path or not.
2858 if (!path || !*path) return nob_temp_strdup(".");
2859 size_t i = strlen(path) - 1;
2860 for (; path[i] == '/'; i--) if (!i) return nob_temp_strdup("/");
2861 for (; path[i] != '/'; i--) if (!i) return nob_temp_strdup(".");
2862 for (; path[i] == '/'; i--) if (!i) return nob_temp_strdup("/");
2863 return nob_temp_strndup(path, i + 1);
2864 #else
2865 if (!path) path = ""; // Treating NULL as empty.
2866 char *drive = (char*) nob_temp_alloc(_MAX_DRIVE);
2867 char *dir = (char*) nob_temp_alloc(_MAX_DIR);
2868 // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/8e46eyt7(v=vs.100)
2869 errno_t ret = _splitpath_s(path, drive, _MAX_DRIVE, dir, _MAX_DIR, NULL, 0, NULL, 0);
2870 NOB_ASSERT(ret == 0);
2871 return nob_temp_sprintf("%s%s", drive, dir);
2872 #endif // _WIN32
2873 }
2874
2875 NOBDEF char *nob_temp_file_name(const char *path)
2876 {
2877 #ifndef _WIN32
2878 // Stolen from the musl's implementation of dirname.
2879 // We are implementing our own one because libc vendors cannot agree on whether basename(3)
2880 // modifies the path or not.
2881 if (!path || !*path) return nob_temp_strdup(".");
2882 char *s = nob_temp_strdup(path);
2883 size_t i = strlen(s)-1;
2884 for (; i&&s[i]=='/'; i--) s[i] = 0;
2885 for (; i&&s[i-1]!='/'; i--);
2886 return s+i;
2887 #else
2888 if (!path) path = ""; // Treating NULL as empty.
2889 char *fname = (char*)nob_temp_alloc(_MAX_FNAME);
2890 char *ext = (char*)nob_temp_alloc(_MAX_EXT);
2891 // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/8e46eyt7(v=vs.100)
2892 errno_t ret = _splitpath_s(path, NULL, 0, NULL, 0, fname, _MAX_FNAME, ext, _MAX_EXT);
2893 NOB_ASSERT(ret == 0);
2894 return nob_temp_sprintf("%s%s", fname, ext);
2895 #endif // _WIN32
2896 }
2897
2898 NOBDEF char *nob_temp_file_ext(const char *path)
2899 {
2900 #ifndef _WIN32
2901 return strrchr(nob_temp_file_name(path), '.');
2902 #else
2903 if (!path) path = ""; // Treating NULL as empty.
2904 char *ext = (char*)nob_temp_alloc(_MAX_EXT);
2905 // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/8e46eyt7(v=vs.100)
2906 errno_t ret = _splitpath_s(path, NULL, 0, NULL, 0, NULL, 0, ext, _MAX_EXT);
2907 NOB_ASSERT(ret == 0);
2908 return ext;
2909 #endif // _WIN32
2910 }
2911
2912 NOBDEF char *nob_temp_running_executable_path(void)
2913 {
2914 #if defined(__linux__)
2915 char buf[4096];
2916 int length = readlink("/proc/self/exe", buf, NOB_ARRAY_LEN(buf));
2917 if (length < 0) return nob_temp_strdup("");
2918 return nob_temp_strndup(buf, length);
2919 #elif defined(_WIN32)
2920 char buf[MAX_PATH];
2921 int length = GetModuleFileNameA(NULL, buf, MAX_PATH);
2922 return nob_temp_strndup(buf, length);
2923 #elif defined(__APPLE__)
2924 char buf[4096];
2925 uint32_t size = NOB_ARRAY_LEN(buf);
2926 if (_NSGetExecutablePath(buf, &size) != 0) return nob_temp_strdup("");
2927 int length = strlen(buf);
2928 return nob_temp_strndup(buf, length);
2929 #elif defined(__FreeBSD__)
2930 char buf[4096];
2931 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
2932 size_t length = sizeof(buf);
2933 if (sysctl(mib, 4, buf, &length, NULL, 0) < 0) return nob_temp_strdup("");
2934 return nob_temp_strndup(buf, length);
2935 #elif defined(__HAIKU__)
2936 int cookie = 0;
2937 image_info info;
2938 while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK)
2939 if (info.type == B_APP_IMAGE)
2940 break;
2941 return nob_temp_strndup(info.name, strlen(info.name));
2942 #else
2943 fprintf(stderr, "%s:%d: TODO: nob_temp_running_executable_path is not implemented for this platform\n", __FILE__, __LINE__);
2944 return nob_temp_strdup("");
2945 #endif
2946 }
2947
2948 #endif // NOB_IMPLEMENTATION
2949
2950 #ifndef NOB_STRIP_PREFIX_GUARD_
2951 #define NOB_STRIP_PREFIX_GUARD_
2952 // NOTE: The name stripping should be part of the header so it's not accidentally included
2953 // several times. At the same time, it should be at the end of the file so to not create any
2954 // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end
2955 // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the
2956 // solution is to split the header into two parts where the name stripping part is at the
2957 // end of the file after the NOB_IMPLEMENTATION.
2958 #ifndef NOB_UNSTRIP_PREFIX
2959 #define TODO NOB_TODO
2960 #define TODOF NOB_TODOF
2961 #define UNREACHABLE NOB_UNREACHABLE
2962 #define UNREACHABLEF NOB_UNREACHABLEF
2963 #define SVLIT NOB_SVLIT
2964 #define SVLIT_STATIC NOB_SVLIT_STATIC
2965 #define UNUSED NOB_UNUSED
2966 #define ARRAY_LEN NOB_ARRAY_LEN
2967 #define ARRAY_GET NOB_ARRAY_GET
2968 #define INFO NOB_INFO
2969 #define WARNING NOB_WARNING
2970 #define ERROR NOB_ERROR
2971 #define NO_LOGS NOB_NO_LOGS
2972 #define Log_Level Nob_Log_Level
2973 #define minimal_log_level nob_minimal_log_level
2974 #define log_handler nob_log_handler
2975 #define Log_Handler Nob_Log_Handler
2976 #define set_log_handler nob_set_log_handler
2977 #define get_log_handler nob_get_log_handler
2978 #define null_log_handler nob_null_log_handler
2979 #define default_log_handler nob_default_log_handler
2980 #define cancer_log_handler nob_cancer_log_handler
2981 // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function.
2982 // So there should be no reason to strip the `nob_` prefix in this specific case.
2983 // #define log nob_log
2984 #define shift nob_shift
2985 #define shift_args nob_shift_args
2986 #define GO_REBUILD_URSELF NOB_GO_REBUILD_URSELF
2987 #define GO_REBUILD_URSELF_PLUS NOB_GO_REBUILD_URSELF_PLUS
2988 #define File_Paths Nob_File_Paths
2989 #define FILE_REGULAR NOB_FILE_REGULAR
2990 #define FILE_DIRECTORY NOB_FILE_DIRECTORY
2991 #define FILE_SYMLINK NOB_FILE_SYMLINK
2992 #define FILE_OTHER NOB_FILE_OTHER
2993 #define File_Type Nob_File_Type
2994 #define mkdir_if_not_exists nob_mkdir_if_not_exists
2995 #define copy_file nob_copy_file
2996 #define copy_directory_recursively nob_copy_directory_recursively
2997 #define read_entire_dir nob_read_entire_dir
2998 #define WALK_CONT NOB_WALK_CONT
2999 #define WALK_SKIP NOB_WALK_SKIP
3000 #define WALK_STOP NOB_WALK_STOP
3001 #define Walk_Action Nob_Walk_Action
3002 #define Walk_Entry Nob_Walk_Entry
3003 #define Walk_Func Nob_Walk_Func
3004 #define Walk_Dir_Opt Nob_Walk_Dir_Opt
3005 #define walk_dir nob_walk_dir
3006 #define walk_dir_opt nob_walk_dir_opt
3007 #define write_entire_file nob_write_entire_file
3008 #define get_file_type nob_get_file_type
3009 #define delete_file nob_delete_file
3010 #define Dir_Entry Nob_Dir_Entry
3011 #define dir_entry_open nob_dir_entry_open
3012 #define dir_entry_next nob_dir_entry_next
3013 #define dir_entry_close nob_dir_entry_close
3014 #define return_defer nob_return_defer
3015 #define da_append nob_da_append
3016 #define da_free nob_da_free
3017 #define da_append_many nob_da_append_many
3018 #define da_resize nob_da_resize
3019 #define da_reserve nob_da_reserve
3020 #define da_last nob_da_last
3021 #define da_first nob_da_first
3022 #define da_pop nob_da_pop
3023 #define da_remove_unordered nob_da_remove_unordered
3024 #define da_foreach nob_da_foreach
3025 #define fa_append nob_fa_append
3026 // C++ has it's own std::swap which may collide with ours
3027 #ifndef __cplusplus
3028 #define swap nob_swap
3029 #endif // __cplusplus
3030 #define String_Builder Nob_String_Builder
3031 #define read_entire_file nob_read_entire_file
3032 #define sb_appendf nob_sb_appendf
3033 #define sb_append_buf nob_sb_append_buf
3034 #define sb_append_sv nob_sb_append_sv
3035 #define sb_append_cstr nob_sb_append_cstr
3036 #define sb_append_null nob_sb_append_null
3037 #define sb_append nob_sb_append
3038 #define sb_pad_align nob_sb_pad_align
3039 #define sb_free nob_sb_free
3040 #define Proc Nob_Proc
3041 #define INVALID_PROC NOB_INVALID_PROC
3042 #define Fd Nob_Fd
3043 #define Pipe Nob_Pipe
3044 #define pipe_create nob_pipe_create
3045 #define Chain Nob_Chain
3046 #define Chain_Begin_Opt Nob_Chain_Begin_Opt
3047 #define chain_begin nob_chain_begin
3048 #define chain_begin_opt nob_chain_begin_opt
3049 #define Chain_Cmd_Opt Nob_Chain_Cmd_Opt
3050 #define chain_cmd nob_chain_cmd
3051 #define chain_cmd_opt nob_chain_cmd_opt
3052 #define Chain_End_Opt Nob_Chain_End_Opt
3053 #define chain_end nob_chain_end
3054 #define chain_end_opt nob_chain_end_opt
3055 #define INVALID_FD NOB_INVALID_FD
3056 #define fd_open_for_read nob_fd_open_for_read
3057 #define fd_open_for_write nob_fd_open_for_write
3058 #define fd_close nob_fd_close
3059 #define Procs Nob_Procs
3060 #define proc_wait nob_proc_wait
3061 #define procs_wait nob_procs_wait
3062 #define procs_wait_and_reset nob_procs_wait_and_reset
3063 #define procs_append_with_flush nob_procs_append_with_flush
3064 #define procs_flush nob_procs_flush
3065 #define CLIT NOB_CLIT
3066 #define Cmd Nob_Cmd
3067 #define Cmd_Redirect Nob_Cmd_Redirect
3068 #define Cmd_Opt Nob_Cmd_Opt
3069 #define cmd_run_opt nob_cmd_run_opt
3070 #define cmd_run nob_cmd_run
3071 #define cmd_render nob_cmd_render
3072 #define cmd_append nob_cmd_append
3073 #define cmd_extend nob_cmd_extend
3074 #define cmd_free nob_cmd_free
3075 #define cmd_run_async nob_cmd_run_async
3076 #define cmd_run_async_and_reset nob_cmd_run_async_and_reset
3077 #define cmd_run_async_redirect nob_cmd_run_async_redirect
3078 #define cmd_run_async_redirect_and_reset nob_cmd_run_async_redirect_and_reset
3079 #define cmd_run_sync nob_cmd_run_sync
3080 #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset
3081 #define cmd_run_sync_redirect nob_cmd_run_sync_redirect
3082 #define cmd_run_sync_redirect_and_reset nob_cmd_run_sync_redirect_and_reset
3083 #define temp_strdup nob_temp_strdup
3084 #define temp_strndup nob_temp_strndup
3085 #define temp_alloc nob_temp_alloc
3086 #define temp_sprintf nob_temp_sprintf
3087 #define temp_vsprintf nob_temp_vsprintf
3088 #define temp_reset nob_temp_reset
3089 #define temp_save nob_temp_save
3090 #define temp_rewind nob_temp_rewind
3091 #define path_name nob_path_name
3092 // NOTE: rename(2) is widely known POSIX function. We never wanna collide with it.
3093 // #define rename nob_rename
3094 #define needs_rebuild nob_needs_rebuild
3095 #define needs_rebuild1 nob_needs_rebuild1
3096 #define file_exists nob_file_exists
3097 #define get_current_dir_temp nob_get_current_dir_temp
3098 #define set_current_dir nob_set_current_dir
3099 #define temp_dir_name nob_temp_dir_name
3100 #define temp_file_name nob_temp_file_name
3101 #define temp_file_ext nob_temp_file_ext
3102 #define temp_running_executable_path nob_temp_running_executable_path
3103 #define String_View Nob_String_View
3104 #define temp_sv_to_cstr nob_temp_sv_to_cstr
3105 #define sv_chop_by_delim nob_sv_chop_by_delim
3106 #define sv_chop_while nob_sv_chop_while
3107 #define sv_chop_prefix nob_sv_chop_prefix
3108 #define sv_chop_suffix nob_sv_chop_suffix
3109 #define sv_chop_left nob_sv_chop_left
3110 #define sv_chop_right nob_sv_chop_right
3111 #define sv_trim nob_sv_trim
3112 #define sv_trim_left nob_sv_trim_left
3113 #define sv_trim_right nob_sv_trim_right
3114 #define sv_eq nob_sv_eq
3115 #define sv_starts_with nob_sv_starts_with
3116 #define sv_end_with nob_sv_end_with
3117 #define sv_ends_with nob_sv_ends_with
3118 #define sv_ends_with_cstr nob_sv_ends_with_cstr
3119 #define sv_from_cstr nob_sv_from_cstr
3120 #define sv_from_parts nob_sv_from_parts
3121 #define sv_utf8_len nob_sv_utf8_len
3122 #define bytes_for_utf8 nob_bytes_for_utf8
3123 #define sb_to_sv nob_sb_to_sv
3124 #define win32_error_message nob_win32_error_message
3125 #define nprocs nob_nprocs
3126 #define nanos_since_unspecified_epoch nob_nanos_since_unspecified_epoch
3127 #define NANOS_PER_SEC NOB_NANOS_PER_SEC
3128 #endif // NOB_STRIP_PREFIX
3129 #endif // NOB_STRIP_PREFIX_GUARD_
3130
3131 /*
3132 Revision history:
3133
3134 3.10.0 (2026-07-17) Make NOB_SVLIT a bit more usable at compile-time (by @Arhcout)
3135 Add NOB_SVLIT_STATIC for when a compiler simply refuses to accept NOB_SVLIT() at compile-time (looking at you `cl.exe /TC`) (by @rexim)
3136 3.9.0 (2026-07-15) Add NOB_TODOF() and NOB_UNREACHABLEF()
3137 Add NOB_SVLIT()
3138 Add nob_bytes_for_utf8[] and nob_sv_utf8_len()
3139 Add String_View.items alias to String_View.data
3140 Make nob_cmd_run log the location of its failure
3141 Make nob_default_log_handler and nob_cancer_log_handler work better in multi-threaded or multi-process environments (by @ChenXinyu-CHD)
3142 Do not strip the prefix for nob_swap on C++ (by @LoganjdM)
3143 3.8.3 (2026-07-14) Fix "applying zero offset to null pointer" error by clang's UndefinedBehaviorSanitizer
3144 3.8.2 (2026-04-01) Fix the broken type safety of nob_cmd_append() (by @aalmkainzi)
3145 3.8.1 (2026-04-01) Fix annoying clang warning
3146 3.8.0 (2026-03-24) Add NOB_CLIT()
3147 Fix compliation on MSVC with /TP
3148 3.7.0 (2026-03-20) Add nob_sb_append_sv()
3149 3.6.0 (2026-03-16) Add nob_sv_chop_suffix()
3150 Deprecate nob_sv_end_with()
3151 Add nob_sv_ends_with_cstr() instead of nob_sv_end_with()
3152 Add nob_sv_ends_with()
3153 Add nob_sv_chop_right()
3154 3.5.0 (2026-03-13) Add nob_null_log_handler() (by @rexim)
3155 Rename nob_log_handler to Nob_Log_Handler (by @rexim)
3156 3.4.0 (2026-03-12) Add nob_da_first() (by @rexim)
3157 Add nob_da_pop() (by @rexim)
3158 Add nob_sv_chop_while() (by @rexim)
3159 3.3.0 (2026-03-07) Add nob_sv_chop_prefix() (by @rexim)
3160 3.2.2 (2026-02-06) Fix read_entire_dir crash on empty path (by @ysoftware)
3161 3.2.1 (2026-01-29) Fix the implicit declaration error when nob is included as a header (by @ysoftware)
3162 3.2.0 (2026-01-28) Introduce Chain API
3163 - Nob_Chain
3164 - Nob_Chain_Begin_Opt
3165 - nob_chain_begin()
3166 - nob_chain_begin_opt()
3167 - Nob_Chain_Cmd_Opt
3168 - nob_chain_cmd()
3169 - nob_chain_cmd_opt()
3170 - Nob_Chain_End_Opt
3171 - nob_chain_end()
3172 - nob_chain_end_opt()
3173 Introduce some auxiliary things that were used in Chain API implementation, but might be useful outside of it:
3174 - Nob_Pipe
3175 - nob_pipe_create()
3176 - nob_fa_append()
3177 3.1.0 (2026-01-22) Make nob_delete_file() be able to delete empty dir on Windows (by @rexim)
3178 Introduce Directory Entry API - similar to POSIX dirent but with names that don't collide
3179 - Nob_Dir_Entry
3180 - nob_dir_entry_open()
3181 - nob_dir_entry_next()
3182 - nob_dir_entry_close()
3183 Rewrite Directory Walking API using Directory Entry API
3184 Introduce .post_order parameter to Nob_Walk_Dir_Opt which walks the directories in post order starting from leaf files
3185 Rewrite nob_read_entire_dir() using Directory Entry API
3186 3.0.0 (2026-01-13) Improve C++ support (by @rexim)
3187 - Fix various C++ compilers warnings and complains throughout the code.
3188 - Reimplement nob_cmd_append() without taking a pointer to temporary array (some C++ compilers don't like that)
3189 - Make default NOB_REBUILD_URSELF() try to recompile with C++ if __cplusplus macro is defined
3190 Strip prefixes by default (by @rexim)
3191 - Ignore NOB_STRIP_PREFIX macro
3192 - Introduce NOB_UNSTRIP_PREFIX macro
3193 BACKWARD INCOMPATIBLE CHANGE!!! If you had code that intentionally didn't enable NOB_STRIP_PREFIX
3194 because all the names from nob.h were causing too many collisions for you, upgrading to 3.0.0 may break it.
3195 In that case you should go and explicitly enable NOB_UNSTRIP_PREFIX where needed after upgrading.
3196 Add nob_sb_append alias to nob_da_append (by @rexim)
3197 2.0.1 (2026-01-07) Fix Walk_Entry naming (by @Sinha-Ujjawal)
3198 Using single String Builder in nob__walk_dir_opt_impl (by @Sinha-Ujjawal)
3199 Add tcc to nob_cc_*() and NOB_REBUILD_URSELF() macros (by @vylsaz)
3200 Fix building nob_read_entire_file() with tcc on windows (by @vylsaz)
3201 Fix Y2038 in nob_needs_rebuild() (by @lnvitesace)
3202 2.0.0 (2026-01-06) Remove minirent.h (by @rexim)
3203 BACKWARD INCOMPATIBLE CHANGE!!! If you were using minirent.h from this library
3204 just use it directly from https://github.com/tsoding/minirent
3205 or consider using the New Directory Walking API.
3206 Introduce New Directory Walking API (by @rexim)
3207 - NOB_WALK_CONT
3208 - NOB_WALK_SKIP
3209 - NOB_WALK_STOP
3210 - Nob_Walk_Action
3211 - Nob_Walk_Entry
3212 - Nob_Walk_Func
3213 - Nob_Walk_Dir_Opt
3214 - nob_walk_dir()
3215 - nob_walk_dir_opt()
3216 Add support for Haiku to nob_temp_running_executable_path() (By @Cephon)
3217 Make nob_file_exists() unfailable (By @rexim)
3218 1.27.0 (2025-12-30) Add .dont_reset option to cmd_run (by @Israel77)
3219 Fix support for FreeBSD (by @cqundefine)
3220 Strip prefixes from NOB_GO_REBUILD_URSELF and NOB_GO_REBUILD_URSELF_PLUS (by @huwwa)
3221 Add /Fo flag to MSVC version of nob_cc_output() (by @ratchetfreak)
3222 1.26.0 (2025-12-28) Introduce customizable log handlers (by @rexim)
3223 - Add nob_log_handler
3224 - Add nob_set_log_handler
3225 - Add nob_get_log_handler
3226 - Add nob_default_log_handler
3227 - Add nob_cancer_log_handler
3228 Introduce nob_temp_vsprintf (by @rexim)
3229 Fix compilation error on Windows when NOB_NO_ECHO is enabled (by @mlorenc227)
3230 Do not redefine _CRT_SECURE_NO_WARNINGS if it's already defined (by @vylsaz)
3231 1.25.1 (2025-11-06) Fix forward declaration of _NSGetExecutablePath on MacOS (by @agss0)
3232 1.25.0 (2025-10-25) - Add nob_sb_pad_align()
3233 - Add nob_swap()
3234 - Add nob_temp_strndup()
3235 - Add nob_temp_dir_name()
3236 - Add nob_temp_file_name()
3237 - Add nob_temp_file_ext()
3238 - Add nob_temp_running_executable_path()
3239 1.24.0 (2025-10-23) Introduce NOB_NO_ECHO macro flag (@rexim)
3240 1.23.0 (2025-08-22) Introduce new API for running commands (by @rexim, @programmerlexi, @0x152a)
3241 - Add nob_cmd_run()
3242 - Add nob_cmd_run_opt()
3243 - Add struct Nob_Cmd_Opt
3244 - Add nob_procs_flush()
3245 - Add nob_nprocs()
3246 Deprecate old API for running commands. (by @rexim)
3247 We do not plan to delete this API any time, but we believe that the new one is more convenient.
3248 - Deprecate struct Nob_Cmd_Redirect{} (it's not explicitly marked with NOB_DEPRECATED, but functions that use it are)
3249 - Turn nob_cmd_run_async() into a function (otherwise it's not deprecatable with NOB_DEPRECATED)
3250 - Deprecate nob_cmd_run_async()
3251 - Deprecate nob_cmd_run_async_and_reset()
3252 - Deprecate nob_cmd_run_async_redirect()
3253 - Deprecate nob_cmd_run_async_redirect_and_reset()
3254 - Deprecate nob_cmd_run_sync()
3255 - Deprecate nob_cmd_run_sync_and_reset()
3256 - Deprecate nob_cmd_run_sync_redirect()
3257 - Deprecate nob_cmd_run_sync_redirect_and_reset()
3258 - Deprecate nob_procs_append_with_flush()
3259 - Deprecate nob_procs_wait_and_reset()
3260 Introduce deprecation mechanism (by @yuI4140, @rexim)
3261 By default, deprecation warnings are not reported. You have to #define NOB_WARN_DEPRECATED to enable them.
3262 - Add NOB_DEPRECATED()
3263 - Add NOB_WARN_DEPRECATED
3264 Add NOB_DECLTYPE_CAST() for C++-compatible casting of allocation results (by @rexim)
3265 Introduce basic performance measuring mechanism (By @mikmart)
3266 - Add nob_nanos_since_unspecified_epoch()
3267 - Add NOB_NANOS_PER_SEC
3268 1.22.0 (2025-08-12) Add NOBDEF macro to the beginning of function declarations (by @minefreak19)
3269 Add more flags to MSVC nob_cc_flags() (by @PieVieRo)
3270 1.21.0 (2025-08-11) Add NOB_NO_MINIRENT guard for "minirent.h" (by @fietec)
3271 1.20.9 (2025-08-11) Fix warnings on Windows: Define _CRT_SECURE_NO_WARNINGS, Rename mkdir to _mkdir (by @OetkenPurveyorOfCode)
3272 1.20.8 (2025-08-11) Fix the bug with nob_get_file_type() not identifying symlinks correctly on POSIX (By @samuellieberman)
3273 1.20.7 (2025-07-29) Align nob_temp_alloc() allocations by the word size (By @rexim)
3274 1.20.6 (2025-05-16) Never strip nob_* suffix from nob_rename (By @rexim)
3275 1.20.5 (2025-05-16) NOB_PRINTF_FORMAT() support for MinGW (By @KillerxDBr)
3276 1.20.4 (2025-05-16) More reliable rendering of the Windows command (By @vylsaz)
3277 1.20.3 (2025-05-16) Add check for __clang__ along with _MSC_VER checks (By @nashiora)
3278 1.20.2 (2025-04-24) Report the program name that failed to start up in nob_cmd_run_async_redirect() (By @rexim)
3279 1.20.1 (2025-04-16) Use vsnprintf() in nob_sb_appendf() instead of vsprintf() (By @LainLayer)
3280 1.20.0 (2025-04-16) Introduce nob_cc(), nob_cc_flags(), nob_cc_inputs(), nob_cc_output() macros (By @rexim)
3281 1.19.0 (2025-03-25) Add nob_procs_append_with_flush() (By @rexim and @anion155)
3282 1.18.0 (2025-03-24) Add nob_da_foreach() (By @rexim)
3283 Allow file sizes greater than 2GB to be read on windows (By @satchelfrost and @KillerxDBr)
3284 Fix nob_fd_open_for_write behaviour on windows so it truncates the opened files (By @twixuss)
3285 1.17.0 (2025-03-16) Factor out nob_da_reserve() (By @rexim)
3286 Add nob_sb_appendf() (By @angelcaru)
3287 1.16.1 (2025-03-16) Make nob_da_resize() exponentially grow capacity similar to no_da_append_many()
3288 1.16.0 (2025-03-16) Introduce NOB_PRINTF_FORMAT
3289 1.15.1 (2025-03-16) Make nob.h compilable in gcc/clang with -std=c99 on POSIX. This includes:
3290 not using strsignal()
3291 using S_IS* stat macros instead of S_IF* flags
3292 1.15.0 (2025-03-03) Add nob_sv_chop_left()
3293 1.14.1 (2025-03-02) Add NOB_EXPERIMENTAL_DELETE_OLD flag that enables deletion of nob.old in Go Rebuild Urself™ Technology
3294 1.14.0 (2025-02-17) Add nob_da_last()
3295 Add nob_da_remove_unordered()
3296 1.13.1 (2025-02-17) Fix segfault in nob_delete_file() (By @SileNce5k)
3297 1.13.0 (2025-02-11) Add nob_da_resize() (By @satchelfrost)
3298 1.12.0 (2025-02-04) Add nob_delete_file()
3299 Add nob_sv_start_with()
3300 1.11.0 (2025-02-04) Add NOB_GO_REBUILD_URSELF_PLUS() (By @rexim)
3301 1.10.0 (2025-02-04) Make NOB_ASSERT, NOB_REALLOC, and NOB_FREE redefinable (By @OleksiiBulba)
3302 1.9.1 (2025-02-04) Fix signature of nob_get_current_dir_temp() (By @julianstoerig)
3303 1.9.0 (2024-11-06) Add Nob_Cmd_Redirect mechanism (By @rexim)
3304 Add nob_path_name() (By @0dminnimda)
3305 1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda)
3306 1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr)
3307 1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset()
3308 Add nob_sb_to_sv()
3309 Add nob_procs_wait_and_reset()
3310 1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin)
3311 1.5.0 (2024-10-23) Add nob_get_current_dir_temp()
3312 Add nob_set_current_dir()
3313 1.4.0 (2024-10-21) Fix UX issues with NOB_GO_REBUILD_URSELF on Windows when you call nob without the .exe extension (By @pgalkin)
3314 Add nob_sv_end_with (By @pgalkin)
3315 1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS
3316 1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr)
3317 1.3.0 (2024-10-17) Add NOB_UNREACHABLE
3318 1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr)
3319 1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX.
3320 1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable
3321 Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names
3322 Add NOB_UNUSED macro
3323 Add NOB_TODO macro
3324 Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part
3325 1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2
3326 1.1.0 (2024-10-15) nob_minimal_log_level
3327 nob_cmd_run_sync_and_reset
3328 1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h
3329 */
3330
3331 /*
3332 Version Conventions:
3333
3334 We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH:
3335 - Modifying comments does not update the version.
3336 - PATCH is incremented in case of a bug fix or refactoring without touching the API.
3337 - MINOR is incremented when new functions and/or types are added in a way that does
3338 not break any existing user code. We want to do this in the majority of the situation.
3339 If we want to delete a certain function or type in favor of another one we should
3340 just add the new function/type and deprecate the old one in a backward compatible way
3341 and let them co-exist for a while.
3342 - MAJOR update should be just a periodic cleanup of the DEPRECATED functions and types
3343 without really modifying any existing functionality.
3344 - Breaking backward compatibility in a MINOR release should be considered a bug and
3345 should be promptly fixed in the next PATCH release.
3346
3347 API conventions:
3348
3349 - All the user facing names should be prefixed with `nob_`, `NOB_`, or `Nob_` depending on the case.
3350 - The prefixes of non-redefinable names should be stripped in NOB_STRIP_PREFIX_GUARD_ section,
3351 unless explicitly stated otherwise like in case of nob_log() or nob_rename().
3352 - Internal (private) names should be prefixed with `nob__` (double underscore). The user code is discouraged
3353 from using such names since they are allowed to be broken in a backward incompatible way even in PATCH
3354 releases. (This is why they are internal)
3355 - If a public macro uses an private function internally such function must be forward declared in the NOB_H_
3356 section.
3357 */
3358
3359 /*
3360 ------------------------------------------------------------------------------
3361 This software is available under 2 licenses -- choose whichever you prefer.
3362 ------------------------------------------------------------------------------
3363 ALTERNATIVE A - MIT License
3364 Copyright (c) 2024 Alexey Kutepov
3365 Permission is hereby granted, free of charge, to any person obtaining a copy of
3366 this software and associated documentation files (the "Software"), to deal in
3367 the Software without restriction, including without limitation the rights to
3368 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
3369 of the Software, and to permit persons to whom the Software is furnished to do
3370 so, subject to the following conditions:
3371 The above copyright notice and this permission notice shall be included in all
3372 copies or substantial portions of the Software.
3373 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3374 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3375 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3376 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3377 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3378 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3379 SOFTWARE.
3380 ------------------------------------------------------------------------------
3381 ALTERNATIVE B - Public Domain (www.unlicense.org)
3382 This is free and unencumbered software released into the public domain.
3383 Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
3384 software, either in source code form or as a compiled binary, for any purpose,
3385 commercial or non-commercial, and by any means.
3386 In jurisdictions that recognize copyright laws, the author or authors of this
3387 software dedicate any and all copyright interest in the software to the public
3388 domain. We make this dedication for the benefit of the public at large and to
3389 the detriment of our heirs and successors. We intend this dedication to be an
3390 overt act of relinquishment in perpetuity of all present and future rights to
3391 this software under copyright law.
3392 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3393 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3394 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3395 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
3396 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
3397 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3398 ------------------------------------------------------------------------------
3399 */