|
|
@@ -0,0 +1,3399 @@ |
|
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, "ed); |
|
1573 |
+ nob_sb_append_null("ed); |
|
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 |
+*/ |