Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/nob.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,22 @@ 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
# to, so we need to pull env vars ourselves. Refs:
# 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
2 changes: 2 additions & 0 deletions nob.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
127 changes: 113 additions & 14 deletions nob.h
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -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) \
Expand Down Expand Up @@ -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)
//
Expand Down Expand Up @@ -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:
//
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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"
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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, ...)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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, ...)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions tests/bytes_for_utf8.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <stdio.h>
#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;
}
13 changes: 13 additions & 0 deletions tests/bytes_for_utf8.stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
П => 2
р => 2
и => 2
в => 2
е => 2
т => 2
, => 1
=> 1
М => 2
и => 2
р => 2
! => 1
count = 12
13 changes: 13 additions & 0 deletions tests/bytes_for_utf8.win32.stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
П => 2
р => 2
и => 2
в => 2
е => 2
т => 2
, => 1
=> 1
М => 2
и => 2
р => 2
! => 1
count = 12
13 changes: 13 additions & 0 deletions tests/sv_foreach.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <stdio.h>

#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;
}
12 changes: 12 additions & 0 deletions tests/sv_foreach.stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
H
e
l
l
o
,

W
o
r
l
d
12 changes: 12 additions & 0 deletions tests/sv_foreach.win32.stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
H
e
l
l
o
,

W
o
r
l
d
Loading