Skip to content

Commit eff0516

Browse files
authored
Merge pull request #254 from tsoding/v3.9.0
Release v3.9.0
2 parents bafe9a6 + b25a85c commit eff0516

9 files changed

Lines changed: 199 additions & 16 deletions

File tree

.github/workflows/nob.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,22 @@ jobs:
4545
steps:
4646
- name: Clone GIT repo
4747
uses: actions/checkout@v4
48+
- name: vswhere
49+
run: vswhere
4850
- name: Build tests
4951
shell: cmd
5052
# cl.exe isn't available as-is in Github images because they don't want
5153
# to, so we need to pull env vars ourselves. Refs:
5254
# https://github.com/actions/runner-images/issues/6205#issuecomment-1241573412
5355
# https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#create-your-own-command-prompt-shortcut
5456
run: |
55-
call "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat"
57+
call "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat"
5658
cl.exe ${{ matrix.mode }} /Fe:nob nob.c
5759
nob.exe
5860
- name: Build how_to-s
5961
shell: cmd
6062
run: |
61-
call "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat"
63+
call "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat"
6264
cd how_to/
6365
cl.exe ${{ matrix.mode }} /Fe:nob nob.c
6466
nob.exe

nob.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const char *test_names[] = {
1818
"cmd_run_dont_reset",
1919
"chain",
2020
"private_functions_inside_public_macros",
21+
"bytes_for_utf8",
22+
"sv_foreach",
2123
};
2224
#define test_names_count ARRAY_LEN(test_names)
2325

nob.h

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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
@@ -80,6 +80,8 @@
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
@@ -201,9 +203,13 @@
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.
542548
NOBDEF 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

903910
typedef 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

908918
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);
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

953988
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)
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
10201082
NOBDEF 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+
12341310
NOBDEF 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

19212000
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
19272006

19282007
NOBDEF 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

19492033
NOBDEF 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

tests/bytes_for_utf8.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <stdio.h>
2+
#define NOB_IMPLEMENTATION
3+
#include "nob.h"
4+
5+
int main()
6+
{
7+
String_View sv = SVLIT("Привет, Мир!");
8+
size_t count = 0;
9+
while (sv.count > 0) {
10+
size_t n = bytes_for_utf8[(uint8_t)*sv.data];
11+
String_View c = sv_chop_left(&sv, n);
12+
printf(SV_Fmt" => %zu\n", SV_Arg(c), n);
13+
count += 1;
14+
}
15+
printf("count = %zu\n", count);
16+
return 0;
17+
}

tests/bytes_for_utf8.stdout.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
П => 2
2+
р => 2
3+
и => 2
4+
в => 2
5+
е => 2
6+
т => 2
7+
, => 1
8+
=> 1
9+
М => 2
10+
и => 2
11+
р => 2
12+
! => 1
13+
count = 12
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
П => 2
2+
р => 2
3+
и => 2
4+
в => 2
5+
е => 2
6+
т => 2
7+
, => 1
8+
=> 1
9+
М => 2
10+
и => 2
11+
р => 2
12+
! => 1
13+
count = 12

tests/sv_foreach.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <stdio.h>
2+
3+
#define NOB_IMPLEMENTATION
4+
#include "nob.h"
5+
6+
int main()
7+
{
8+
String_View sv = SVLIT("Hello, World");
9+
da_foreach(const char, x, &sv) {
10+
printf("%c\n", *x);
11+
}
12+
return 0;
13+
}

tests/sv_foreach.stdout.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
H
2+
e
3+
l
4+
l
5+
o
6+
,
7+
8+
W
9+
o
10+
r
11+
l
12+
d

tests/sv_foreach.win32.stdout.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
H
2+
e
3+
l
4+
l
5+
o
6+
,
7+
8+
W
9+
o
10+
r
11+
l
12+
d

0 commit comments

Comments
 (0)