1- /* nob - v3.8.3 - Public Domain - https://github.com/tsoding/nob.h
1+ /* nob - v3.9.0 - Public Domain - https://github.com/tsoding/nob.h
22
33 This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea.
44
8080 but if you want to know what is discouraged you may want to enable this flag.
8181 - NOB_EXPERIMENTAL_DELETE_OLD - Experimental feature that automatically removes `nob.old` files. It's unclear how well
8282 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.
8385 - NOB_UNSTRIP_PREFIX - do not strip the `nob_` prefixes from non-redefinable names.
8486 - NOB_NO_ECHO - do not echo the actions various nob functions are doing (like nob_cmd_run(), nob_mkdir_if_not_exists(), etc).
8587
201203# define NOB_PRINTF_FORMAT (STRING_INDEX, FIRST_TO_CHECK )
202204#endif
203205
206+ NOBDEF void nob__panicf (const char *file, int line, const char *label, const char *format, ...);
207+
204208#define NOB_UNUSED (value ) (void )(value)
205209#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__)
206211#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__)
207213
208214#define NOB_ARRAY_LEN (array ) (sizeof (array)/sizeof (array[0 ]))
209215#define NOB_ARRAY_GET (array, index ) \
@@ -540,6 +546,7 @@ typedef struct {
540546
541547// Run the command with options.
542548NOBDEF bool nob_cmd_run_opt (Nob_Cmd *cmd, Nob_Cmd_Opt opt);
549+ NOBDEF bool nob__cmd_run_opt_with_location (Nob_Cmd *cmd, const char *file, int line, Nob_Cmd_Opt opt);
543550
544551// Command Chains (in Shell Scripting they are know as Pipes)
545552//
@@ -616,7 +623,7 @@ NOBDEF uint64_t nob_nanos_since_unspecified_epoch(void);
616623
617624// Same as nob_cmd_run_opt but using cool variadic macro to set the default options.
618625// See https://x.com/vkrajacic/status/1749816169736073295 for more info on how to use such macros.
619- #define nob_cmd_run (cmd, ...) nob_cmd_run_opt ((cmd), NOB_CLIT (Nob_Cmd_Opt){__VA_ARGS__})
626+ #define nob_cmd_run (cmd, ...) nob__cmd_run_opt_with_location ((cmd), __FILE__, __LINE__, NOB_CLIT (Nob_Cmd_Opt){ __VA_ARGS__ })
620627
621628// DEPRECATED:
622629//
@@ -902,7 +909,10 @@ NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_pat
902909
903910typedef struct {
904911 size_t count;
905- const char *data;
912+ union {
913+ const char *data;
914+ const char *items;
915+ };
906916} Nob_String_View;
907917
908918NOBDEF const char *nob_temp_sv_to_cstr (Nob_String_View sv);
@@ -937,6 +947,8 @@ NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count);
937947// nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View
938948#define nob_sb_to_sv (sb ) nob_sv_from_parts((sb).items, (sb).count)
939949
950+ #define NOB_SVLIT (lit ) nob_sv_from_parts((lit), sizeof (lit)-1 )
951+
940952// printf macros for String_View
941953#ifndef SV_Fmt
942954#define SV_Fmt " %.*s"
@@ -948,6 +960,29 @@ NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count);
948960// String_View name = ...;
949961// printf("Name: "SV_Fmt"\n", SV_Arg(name));
950962
963+ // Stolen from Jai's Unicode module
964+ // Example:
965+ // ```c
966+ // String_View sv = SVLIT("Привет, Мир!");
967+ // while (sv.count > 0) {
968+ // size_t n = nob_bytes_for_utf8[(uint8_t)*sv.data];
969+ // String_View c = sv_chop_left(&sv, n);
970+ // printf(SV_Fmt" => %zu\n", SV_Arg(c), n);
971+ // }
972+ // ```
973+ static const uint8_t nob_bytes_for_utf8[] = {
974+ 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 ,
975+ 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 ,
976+ 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 ,
977+ 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 ,
978+ 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 ,
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+ 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 ,
981+ 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 ,
982+ };
983+
984+ NOBDEF size_t nob_sv_utf8_len (Nob_String_View sv, size_t *bytes_overrun);
985+
951986#ifdef _WIN32
952987
953988NOBDEF char *nob_win32_error_message (DWORD err);
@@ -976,6 +1011,22 @@ NOBDEF void nob__cmd_append(Nob_Cmd *cmd, size_t n, const char **args)
9761011 }
9771012}
9781013
1014+ NOBDEF size_t nob_sv_utf8_len (Nob_String_View sv, size_t *bytes_overrun)
1015+ {
1016+ size_t i = 0 ;
1017+ size_t n = 0 ;
1018+ while (true ) {
1019+ if (i >= sv.count ) {
1020+ if (bytes_overrun) *bytes_overrun = i - sv.count ;
1021+ return n;
1022+ }
1023+ i += nob_bytes_for_utf8[(uint8_t )sv.data [i]];
1024+ n += 1 ;
1025+ }
1026+ NOB_UNREACHABLE (" sv_utf8_len" );
1027+ return 0 ;
1028+ }
1029+
9791030#ifdef _WIN32
9801031
9811032// Base on https://stackoverflow.com/a/75644008
@@ -1016,6 +1067,17 @@ NOBDEF char *nob_win32_error_message(DWORD err) {
10161067
10171068#endif // _WIN32
10181069
1070+ NOBDEF void nob__panicf (const char *file, int line, const char *label, const char *format, ...)
1071+ {
1072+ fprintf (stderr, " %s:%d: %s: " , file, line, label);
1073+ va_list args;
1074+ va_start (args, format);
1075+ vfprintf (stderr, format, args);
1076+ va_end (args);
1077+ fprintf (stderr, " \n " );
1078+ abort ();
1079+ }
1080+
10191081// The implementation idea is stolen from https://github.com/zhiayang/nabs
10201082NOBDEF void nob__go_rebuild_urself (int argc, char **argv, const char *source_path, ...)
10211083{
@@ -1231,6 +1293,20 @@ NOBDEF int nob_nprocs(void)
12311293#endif
12321294}
12331295
1296+ NOBDEF bool nob__cmd_run_opt_with_location (Nob_Cmd *cmd, const char *file, int line, Nob_Cmd_Opt opt)
1297+ {
1298+ bool ok = nob_cmd_run_opt (cmd, opt);
1299+ #ifdef NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL
1300+ if (!ok) {
1301+ nob_log (NOB_ERROR , " %s:%d: ERROR: cmd_run failed" , file, line);
1302+ }
1303+ #else
1304+ NOB_UNUSED (file);
1305+ NOB_UNUSED (line);
1306+ #endif // NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL
1307+ return ok;
1308+ }
1309+
12341310NOBDEF bool nob_cmd_run_opt (Nob_Cmd *cmd, Nob_Cmd_Opt opt)
12351311{
12361312 bool result = true ;
@@ -1899,23 +1975,26 @@ NOBDEF void nob_default_log_handler(Nob_Log_Level level, const char *fmt, va_lis
18991975{
19001976 if (level < nob_minimal_log_level) return ;
19011977
1978+ const char *prefix = NULL ;
19021979 switch (level) {
19031980 case NOB_INFO :
1904- fprintf (stderr, " [INFO] " ) ;
1981+ prefix = " [INFO] " ;
19051982 break ;
19061983 case NOB_WARNING :
1907- fprintf (stderr, " [WARNING] " ) ;
1984+ prefix = " [WARNING] " ;
19081985 break ;
19091986 case NOB_ERROR :
1910- fprintf (stderr, " [ERROR] " ) ;
1987+ prefix = " [ERROR] " ;
19111988 break ;
19121989 case NOB_NO_LOGS : return ;
19131990 default :
19141991 NOB_UNREACHABLE (" Nob_Log_Level" );
19151992 }
19161993
1917- vfprintf (stderr, fmt, args);
1918- fprintf (stderr, " \n " );
1994+ size_t mark = nob_temp_save ();
1995+ const char *msg = nob_temp_vsprintf (fmt, args);
1996+ fprintf (stderr, " %s%s\n " , prefix, msg);
1997+ nob_temp_rewind (mark);
19191998}
19201999
19212000NOBDEF void nob_null_log_handler (Nob_Log_Level level, const char *fmt, va_list args)
@@ -1927,23 +2006,28 @@ NOBDEF void nob_null_log_handler(Nob_Log_Level level, const char *fmt, va_list a
19272006
19282007NOBDEF void nob_cancer_log_handler (Nob_Log_Level level, const char *fmt, va_list args)
19292008{
2009+ if (level < nob_minimal_log_level) return ;
2010+
2011+ const char *prefix = NULL ;
19302012 switch (level) {
19312013 case NOB_INFO :
1932- fprintf (stderr, " ℹ️ \x1b [36m[INFO]\x1b [0m " ) ;
2014+ prefix = " ℹ️ \x1b [36m[INFO]\x1b [0m " ;
19332015 break ;
19342016 case NOB_WARNING :
1935- fprintf (stderr, " ⚠️ \x1b [33m[WARNING]\x1b [0m " ) ;
2017+ prefix = " ⚠️ \x1b [33m[WARNING]\x1b [0m " ;
19362018 break ;
19372019 case NOB_ERROR :
1938- fprintf (stderr, " 🚨 \x1b [31m[ERROR]\x1b [0m " ) ;
2020+ prefix = " 🚨 \x1b [31m[ERROR]\x1b [0m " ;
19392021 break ;
19402022 case NOB_NO_LOGS : return ;
19412023 default :
19422024 NOB_UNREACHABLE (" Nob_Log_Level" );
19432025 }
19442026
1945- vfprintf (stderr, fmt, args);
1946- fprintf (stderr, " \n " );
2027+ size_t mark = nob_temp_save ();
2028+ const char *msg = nob_temp_vsprintf (fmt, args);
2029+ fprintf (stderr, " %s%s\n " , prefix, msg);
2030+ nob_temp_rewind (mark);
19472031}
19482032
19492033NOBDEF void nob_log (Nob_Log_Level level, const char *fmt, ...)
@@ -2868,7 +2952,10 @@ NOBDEF char *nob_temp_running_executable_path(void)
28682952 // end of the file after the NOB_IMPLEMENTATION.
28692953 #ifndef NOB_UNSTRIP_PREFIX
28702954 #define TODO NOB_TODO
2955+ #define TODOF NOB_TODOF
28712956 #define UNREACHABLE NOB_UNREACHABLE
2957+ #define UNREACHABLEF NOB_UNREACHABLEF
2958+ #define SVLIT NOB_SVLIT
28722959 #define UNUSED NOB_UNUSED
28732960 #define ARRAY_LEN NOB_ARRAY_LEN
28742961 #define ARRAY_GET NOB_ARRAY_GET
@@ -2930,7 +3017,10 @@ NOBDEF char *nob_temp_running_executable_path(void)
29303017 #define da_remove_unordered nob_da_remove_unordered
29313018 #define da_foreach nob_da_foreach
29323019 #define fa_append nob_fa_append
2933- #define swap nob_swap
3020+ // C++ has it's own std::swap which may collide with ours
3021+ #ifndef __cplusplus
3022+ #define swap nob_swap
3023+ #endif // __cplusplus
29343024 #define String_Builder Nob_String_Builder
29353025 #define read_entire_file nob_read_entire_file
29363026 #define sb_appendf nob_sb_appendf
@@ -3022,6 +3112,8 @@ NOBDEF char *nob_temp_running_executable_path(void)
30223112 #define sv_ends_with_cstr nob_sv_ends_with_cstr
30233113 #define sv_from_cstr nob_sv_from_cstr
30243114 #define sv_from_parts nob_sv_from_parts
3115+ #define sv_utf8_len nob_sv_utf8_len
3116+ #define bytes_for_utf8 nob_bytes_for_utf8
30253117 #define sb_to_sv nob_sb_to_sv
30263118 #define win32_error_message nob_win32_error_message
30273119 #define nprocs nob_nprocs
@@ -3033,6 +3125,13 @@ NOBDEF char *nob_temp_running_executable_path(void)
30333125/*
30343126 Revision history:
30353127
3128+ 3.9.0 (2026-07-15) Add NOB_TODOF() and NOB_UNREACHABLEF()
3129+ Add NOB_SVLIT()
3130+ Add nob_bytes_for_utf8[] and nob_sv_utf8_len()
3131+ Add String_View.items alias to String_View.data
3132+ Make nob_cmd_run log the location of its failure
3133+ Make nob_default_log_handler and nob_cancer_log_handler work better in multi-threaded or multi-process environments (by @ChenXinyu-CHD)
3134+ Do not strip the prefix for nob_swap on C++ (by @LoganjdM)
30363135 3.8.3 (2026-07-14) Fix "applying zero offset to null pointer" error by clang's UndefinedBehaviorSanitizer
30373136 3.8.2 (2026-04-01) Fix the broken type safety of nob_cmd_append() (by @aalmkainzi)
30383137 3.8.1 (2026-04-01) Fix annoying clang warning
0 commit comments