From b25a85c623a5dc8b0999b8eaae2d55ce847a8f35 Mon Sep 17 00:00:00 2001 From: rexim Date: Wed, 15 Jul 2026 00:34:24 +0700 Subject: [PATCH] Release v3.9.0 Co-authored-by: Chen Xinyu Co-authored-by: LoganjdM --- .github/workflows/nob.yaml | 6 +- nob.c | 2 + nob.h | 127 +++++++++++++++++++++++--- tests/bytes_for_utf8.c | 17 ++++ tests/bytes_for_utf8.stdout.txt | 13 +++ tests/bytes_for_utf8.win32.stdout.txt | 13 +++ tests/sv_foreach.c | 13 +++ tests/sv_foreach.stdout.txt | 12 +++ tests/sv_foreach.win32.stdout.txt | 12 +++ 9 files changed, 199 insertions(+), 16 deletions(-) create mode 100644 tests/bytes_for_utf8.c create mode 100644 tests/bytes_for_utf8.stdout.txt create mode 100644 tests/bytes_for_utf8.win32.stdout.txt create mode 100644 tests/sv_foreach.c create mode 100644 tests/sv_foreach.stdout.txt create mode 100644 tests/sv_foreach.win32.stdout.txt diff --git a/.github/workflows/nob.yaml b/.github/workflows/nob.yaml index 6d22798..65f6004 100644 --- a/.github/workflows/nob.yaml +++ b/.github/workflows/nob.yaml @@ -45,6 +45,8 @@ jobs: steps: - name: Clone GIT repo uses: actions/checkout@v4 + - name: vswhere + run: vswhere - name: Build tests shell: cmd # cl.exe isn't available as-is in Github images because they don't want @@ -52,13 +54,13 @@ jobs: # https://github.com/actions/runner-images/issues/6205#issuecomment-1241573412 # https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#create-your-own-command-prompt-shortcut run: | - call "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat" + call "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat" cl.exe ${{ matrix.mode }} /Fe:nob nob.c nob.exe - name: Build how_to-s shell: cmd run: | - call "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat" + call "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat" cd how_to/ cl.exe ${{ matrix.mode }} /Fe:nob nob.c nob.exe diff --git a/nob.c b/nob.c index 58c0500..1ae982d 100644 --- a/nob.c +++ b/nob.c @@ -18,6 +18,8 @@ const char *test_names[] = { "cmd_run_dont_reset", "chain", "private_functions_inside_public_macros", + "bytes_for_utf8", + "sv_foreach", }; #define test_names_count ARRAY_LEN(test_names) diff --git a/nob.h b/nob.h index cd842b1..055a1df 100644 --- a/nob.h +++ b/nob.h @@ -1,4 +1,4 @@ -/* nob - v3.8.3 - Public Domain - https://github.com/tsoding/nob.h +/* nob - v3.9.0 - Public Domain - https://github.com/tsoding/nob.h This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea. @@ -80,6 +80,8 @@ but if you want to know what is discouraged you may want to enable this flag. - NOB_EXPERIMENTAL_DELETE_OLD - Experimental feature that automatically removes `nob.old` files. It's unclear how well it works on Windows, so it's experimental for now. + - NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL - Log the locations of fail nob_cmd_run()-s. It's behind an experimental flag + because I'm not sure how much it alters the UX of the library for other people, but this is something I personally need. - NOB_UNSTRIP_PREFIX - do not strip the `nob_` prefixes from non-redefinable names. - NOB_NO_ECHO - do not echo the actions various nob functions are doing (like nob_cmd_run(), nob_mkdir_if_not_exists(), etc). @@ -201,9 +203,13 @@ # define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) #endif +NOBDEF void nob__panicf(const char *file, int line, const char *label, const char *format, ...); + #define NOB_UNUSED(value) (void)(value) #define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) +#define NOB_TODOF(...) nob__panicf(__FILE__, __LINE__, "TODO", __VA_ARGS__) #define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) +#define NOB_UNREACHABLEF(...) nob__panicf(__FILE__, __LINE__, "UNREACHABLE", __VA_ARGS__) #define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) #define NOB_ARRAY_GET(array, index) \ @@ -540,6 +546,7 @@ typedef struct { // Run the command with options. NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt); +NOBDEF bool nob__cmd_run_opt_with_location(Nob_Cmd *cmd, const char *file, int line, Nob_Cmd_Opt opt); // Command Chains (in Shell Scripting they are know as Pipes) // @@ -616,7 +623,7 @@ NOBDEF uint64_t nob_nanos_since_unspecified_epoch(void); // Same as nob_cmd_run_opt but using cool variadic macro to set the default options. // See https://x.com/vkrajacic/status/1749816169736073295 for more info on how to use such macros. -#define nob_cmd_run(cmd, ...) nob_cmd_run_opt((cmd), NOB_CLIT(Nob_Cmd_Opt){__VA_ARGS__}) +#define nob_cmd_run(cmd, ...) nob__cmd_run_opt_with_location((cmd), __FILE__, __LINE__, NOB_CLIT(Nob_Cmd_Opt){ __VA_ARGS__ }) // DEPRECATED: // @@ -902,7 +909,10 @@ NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_pat typedef struct { size_t count; - const char *data; + union { + const char *data; + const char *items; + }; } Nob_String_View; NOBDEF 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); // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View #define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count) +#define NOB_SVLIT(lit) nob_sv_from_parts((lit), sizeof(lit)-1) + // printf macros for String_View #ifndef SV_Fmt #define SV_Fmt "%.*s" @@ -948,6 +960,29 @@ NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count); // String_View name = ...; // printf("Name: "SV_Fmt"\n", SV_Arg(name)); +// Stolen from Jai's Unicode module +// Example: +// ```c +// String_View sv = SVLIT("Привет, Мир!"); +// while (sv.count > 0) { +// size_t n = nob_bytes_for_utf8[(uint8_t)*sv.data]; +// String_View c = sv_chop_left(&sv, n); +// printf(SV_Fmt" => %zu\n", SV_Arg(c), n); +// } +// ``` +static const uint8_t nob_bytes_for_utf8[] = { + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, +}; + +NOBDEF size_t nob_sv_utf8_len(Nob_String_View sv, size_t *bytes_overrun); + #ifdef _WIN32 NOBDEF 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) } } +NOBDEF size_t nob_sv_utf8_len(Nob_String_View sv, size_t *bytes_overrun) +{ + size_t i = 0; + size_t n = 0; + while (true) { + if (i >= sv.count) { + if (bytes_overrun) *bytes_overrun = i - sv.count; + return n; + } + i += nob_bytes_for_utf8[(uint8_t)sv.data[i]]; + n += 1; + } + NOB_UNREACHABLE("sv_utf8_len"); + return 0; +} + #ifdef _WIN32 // Base on https://stackoverflow.com/a/75644008 @@ -1016,6 +1067,17 @@ NOBDEF char *nob_win32_error_message(DWORD err) { #endif // _WIN32 +NOBDEF void nob__panicf(const char *file, int line, const char *label, const char *format, ...) +{ + fprintf(stderr, "%s:%d: %s: ", file, line, label); + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + abort(); +} + // The implementation idea is stolen from https://github.com/zhiayang/nabs NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...) { @@ -1231,6 +1293,20 @@ NOBDEF int nob_nprocs(void) #endif } +NOBDEF bool nob__cmd_run_opt_with_location(Nob_Cmd *cmd, const char *file, int line, Nob_Cmd_Opt opt) +{ + bool ok = nob_cmd_run_opt(cmd, opt); +#ifdef NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL + if (!ok) { + nob_log(NOB_ERROR, "%s:%d: ERROR: cmd_run failed", file, line); + } +#else + NOB_UNUSED(file); + NOB_UNUSED(line); +#endif // NOB_EXPERIMENTAL_TRACE_CMD_RUN_FAIL + return ok; +} + NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) { bool result = true; @@ -1899,23 +1975,26 @@ NOBDEF void nob_default_log_handler(Nob_Log_Level level, const char *fmt, va_lis { if (level < nob_minimal_log_level) return; + const char *prefix = NULL; switch (level) { case NOB_INFO: - fprintf(stderr, "[INFO] "); + prefix = "[INFO] "; break; case NOB_WARNING: - fprintf(stderr, "[WARNING] "); + prefix = "[WARNING] "; break; case NOB_ERROR: - fprintf(stderr, "[ERROR] "); + prefix = "[ERROR] "; break; case NOB_NO_LOGS: return; default: NOB_UNREACHABLE("Nob_Log_Level"); } - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); + size_t mark = nob_temp_save(); + const char *msg = nob_temp_vsprintf(fmt, args); + fprintf(stderr, "%s%s\n", prefix, msg); + nob_temp_rewind(mark); } NOBDEF 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 NOBDEF void nob_cancer_log_handler(Nob_Log_Level level, const char *fmt, va_list args) { + if (level < nob_minimal_log_level) return; + + const char *prefix = NULL; switch (level) { case NOB_INFO: - fprintf(stderr, "ℹ️ \x1b[36m[INFO]\x1b[0m "); + prefix = "ℹ️ \x1b[36m[INFO]\x1b[0m "; break; case NOB_WARNING: - fprintf(stderr, "⚠️ \x1b[33m[WARNING]\x1b[0m "); + prefix = "⚠️ \x1b[33m[WARNING]\x1b[0m "; break; case NOB_ERROR: - fprintf(stderr, "🚨 \x1b[31m[ERROR]\x1b[0m "); + prefix = "🚨 \x1b[31m[ERROR]\x1b[0m "; break; case NOB_NO_LOGS: return; default: NOB_UNREACHABLE("Nob_Log_Level"); } - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); + size_t mark = nob_temp_save(); + const char *msg = nob_temp_vsprintf(fmt, args); + fprintf(stderr, "%s%s\n", prefix, msg); + nob_temp_rewind(mark); } NOBDEF void nob_log(Nob_Log_Level level, const char *fmt, ...) @@ -2868,7 +2952,10 @@ NOBDEF char *nob_temp_running_executable_path(void) // end of the file after the NOB_IMPLEMENTATION. #ifndef NOB_UNSTRIP_PREFIX #define TODO NOB_TODO + #define TODOF NOB_TODOF #define UNREACHABLE NOB_UNREACHABLE + #define UNREACHABLEF NOB_UNREACHABLEF + #define SVLIT NOB_SVLIT #define UNUSED NOB_UNUSED #define ARRAY_LEN NOB_ARRAY_LEN #define ARRAY_GET NOB_ARRAY_GET @@ -2930,7 +3017,10 @@ NOBDEF char *nob_temp_running_executable_path(void) #define da_remove_unordered nob_da_remove_unordered #define da_foreach nob_da_foreach #define fa_append nob_fa_append - #define swap nob_swap + // C++ has it's own std::swap which may collide with ours + #ifndef __cplusplus + #define swap nob_swap + #endif // __cplusplus #define String_Builder Nob_String_Builder #define read_entire_file nob_read_entire_file #define sb_appendf nob_sb_appendf @@ -3022,6 +3112,8 @@ NOBDEF char *nob_temp_running_executable_path(void) #define sv_ends_with_cstr nob_sv_ends_with_cstr #define sv_from_cstr nob_sv_from_cstr #define sv_from_parts nob_sv_from_parts + #define sv_utf8_len nob_sv_utf8_len + #define bytes_for_utf8 nob_bytes_for_utf8 #define sb_to_sv nob_sb_to_sv #define win32_error_message nob_win32_error_message #define nprocs nob_nprocs @@ -3033,6 +3125,13 @@ NOBDEF char *nob_temp_running_executable_path(void) /* Revision history: + 3.9.0 (2026-07-15) Add NOB_TODOF() and NOB_UNREACHABLEF() + Add NOB_SVLIT() + Add nob_bytes_for_utf8[] and nob_sv_utf8_len() + Add String_View.items alias to String_View.data + Make nob_cmd_run log the location of its failure + Make nob_default_log_handler and nob_cancer_log_handler work better in multi-threaded or multi-process environments (by @ChenXinyu-CHD) + Do not strip the prefix for nob_swap on C++ (by @LoganjdM) 3.8.3 (2026-07-14) Fix "applying zero offset to null pointer" error by clang's UndefinedBehaviorSanitizer 3.8.2 (2026-04-01) Fix the broken type safety of nob_cmd_append() (by @aalmkainzi) 3.8.1 (2026-04-01) Fix annoying clang warning diff --git a/tests/bytes_for_utf8.c b/tests/bytes_for_utf8.c new file mode 100644 index 0000000..67ebd2a --- /dev/null +++ b/tests/bytes_for_utf8.c @@ -0,0 +1,17 @@ +#include +#define NOB_IMPLEMENTATION +#include "nob.h" + +int main() +{ + String_View sv = SVLIT("Привет, Мир!"); + size_t count = 0; + while (sv.count > 0) { + size_t n = bytes_for_utf8[(uint8_t)*sv.data]; + String_View c = sv_chop_left(&sv, n); + printf(SV_Fmt" => %zu\n", SV_Arg(c), n); + count += 1; + } + printf("count = %zu\n", count); + return 0; +} diff --git a/tests/bytes_for_utf8.stdout.txt b/tests/bytes_for_utf8.stdout.txt new file mode 100644 index 0000000..1a8ba62 --- /dev/null +++ b/tests/bytes_for_utf8.stdout.txt @@ -0,0 +1,13 @@ +П => 2 +р => 2 +и => 2 +в => 2 +е => 2 +т => 2 +, => 1 + => 1 +М => 2 +и => 2 +р => 2 +! => 1 +count = 12 diff --git a/tests/bytes_for_utf8.win32.stdout.txt b/tests/bytes_for_utf8.win32.stdout.txt new file mode 100644 index 0000000..94a20be --- /dev/null +++ b/tests/bytes_for_utf8.win32.stdout.txt @@ -0,0 +1,13 @@ +П => 2 +р => 2 +и => 2 +в => 2 +е => 2 +т => 2 +, => 1 + => 1 +М => 2 +и => 2 +р => 2 +! => 1 +count = 12 diff --git a/tests/sv_foreach.c b/tests/sv_foreach.c new file mode 100644 index 0000000..0f6cb3d --- /dev/null +++ b/tests/sv_foreach.c @@ -0,0 +1,13 @@ +#include + +#define NOB_IMPLEMENTATION +#include "nob.h" + +int main() +{ + String_View sv = SVLIT("Hello, World"); + da_foreach(const char, x, &sv) { + printf("%c\n", *x); + } + return 0; +} diff --git a/tests/sv_foreach.stdout.txt b/tests/sv_foreach.stdout.txt new file mode 100644 index 0000000..ad45a32 --- /dev/null +++ b/tests/sv_foreach.stdout.txt @@ -0,0 +1,12 @@ +H +e +l +l +o +, + +W +o +r +l +d diff --git a/tests/sv_foreach.win32.stdout.txt b/tests/sv_foreach.win32.stdout.txt new file mode 100644 index 0000000..326c274 --- /dev/null +++ b/tests/sv_foreach.win32.stdout.txt @@ -0,0 +1,12 @@ +H +e +l +l +o +, + +W +o +r +l +d