fordon_greeman / pkger

feat: use tsoding's nob.h, split repos.h into separate recipes Browse files
theidioticdev committed 12d ago parent 451f89f commit 02f84420fd2352d8d3248d864d5dd9500cdd2c74
Showing 17 changed files with 3632 additions and 1315 deletions. UnifiedSplit
+0 −5 TODO deleted file mode 100644
@@ -1,5 +0,0 @@
1 -TODO:
2 - - ✔ bug fix @done 2026-10-19
3 - - ✔ rename slpkger -> pkger @done 2026-10-19
4 - - ✔ test @done 2026-10-19
5 - - □ add more pkgs out of the box
+6 −0 TODO.org new file mode 100644
@@ -0,0 +1,6 @@
1 +* refactors
2 + - split repos.h into recipes/{recipe1.h,..}
3 +
4 +
5 +* cleanup
6 + - switch from //tmp/pkger-<repo_name> to //home/USER//.cache/pkger/<repo_name>
+0 −51 bs.sh deleted file mode 100755
@@ -1,51 +0,0 @@
1 -#!/bin/sh
2 -
3 -#: [ bs.sh ] BUILD SYSTEM in POSIX SHELL
4 -#: usage:
5 -#: ./bs.sh [build|install|clean|uninstall]
6 -#: ./bs.sh -h
7 -#:
8 -
9 -SRC=$(cd "$(dirname "$0")" && pwd)
10 -TARGET="$HOME/.local/bin"
11 -BUILD_CMD="cc -o pkger pkger.c"
12 -INSTALL_CMD="cp pkger $TARGET/pkger"
13 -UNINSTALL_CMD="rm $TARGET/pkger"
14 -
15 -build() {
16 - printf "[BUILD] $BUILD_CMD\n"
17 - $BUILD_CMD
18 - printf "[BUILD] done\n"
19 -}
20 -ins() {
21 - if [ -f "$SRC/pkger" ]; then
22 - printf "[INSTALL] $INSTALL_CMD\n"
23 - $INSTALL_CMD
24 - printf "[INSTALL] done\n"
25 - else
26 - build
27 - printf "[INSTALL] $INSTALL_CMD\n"
28 - $INSTALL_CMD
29 - printf "[INSTALL] done\n"
30 - fi
31 -}
32 -
33 -clean() {
34 - printf "[CLEAN] rm pkger\n"
35 - rm pkger
36 - printf "[CLEAN] done\n"
37 -}
38 -
39 -unins() {
40 - printf "[UNINST] $UNINSTALL_CMD\n"
41 - $UNINSTALL_CMD
42 - printf "[UNINST] done\n"
43 -}
44 -case "$1" in
45 - install) ins ;;
46 - uninstall) unins ;;
47 - build) build ;;
48 - -h) sed -n 's/^#: //p' "$0" ;;
49 - clean) clean ;;
50 - *) printf "Invalid opt\n"; exit 1 ;;
51 -esac
+0 −1152 install.sh deleted file mode 100755 Large diff collapsed, click to expand
@@ -1,1152 +0,0 @@
1 -#!/bin/sh
2 -# GNU Guix --- Functional package management for GNU
3 -# Copyright © 2017 sharlatan <sharlatanus@gmail.com>
4 -# Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
5 -# Copyright © 2018 Efraim Flashner <efraim@flashner.co.il>
6 -# Copyright © 2019–2020, 2022 Tobias Geerinckx-Rice <me@tobias.gr>
7 -# Copyright © 2020 Morgan Smith <Morgan.J.Smith@outlook.com>
8 -# Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com>
9 -# Copyright © 2020 Daniel Brooks <db48x@db48x.net>
10 -# Copyright © 2021 Jakub Kądziołka <kuba@kadziolka.net>
11 -# Copyright © 2021 Chris Marusich <cmmarusich@gmail.com>
12 -# Copyright © 2021, 2022, 2023 Maxim Cournoyer <maxim@guixotic.coop>
13 -# Copyright © 2022 Prafulla Giri <prafulla.giri@protonmail.com>
14 -# Copyright © 2023 Andrew Tropin <andrew@trop.in>
15 -# Copyright © 2020 David A. Redick <david.a.redick@gmail.com>
16 -# Copyright © 2024 Janneke Nieuwenhuizen <janneke@gnu.org>
17 -# Copyright © 2024 Tomas Volf <~@wolfsden.cz>
18 -# Copyright © 2024 Richard Sent <richard@freakingpenguin.com>
19 -# Copyright © 2025 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
20 -# Copyright © 2025 Noé Lopez <noelopez@free.fr>
21 -# Copyright © 2026 Owen T. Heisler <writer@owenh.net>
22 -#
23 -# This file is part of GNU Guix.
24 -#
25 -# GNU Guix is free software; you can redistribute it and/or modify it
26 -# under the terms of the GNU General Public License as published by
27 -# the Free Software Foundation; either version 3 of the License, or (at
28 -# your option) any later version.
29 -#
30 -# GNU Guix is distributed in the hope that it will be useful, but
31 -# WITHOUT ANY WARRANTY; without even the implied warranty of
32 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 -# GNU General Public License for more details.
34 -#
35 -# You should have received a copy of the GNU General Public License
36 -# along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
37 -
38 -# We require Bash but for portability we'd rather not use /bin/bash or
39 -# /usr/bin/env in the shebang, hence this hack.
40 -
41 -# Environment variables
42 -#
43 -# GUIX_BINARY_FILE_NAME
44 -#
45 -# Can be used to override the automatic download mechanism and point
46 -# to a local Guix binary archive filename like
47 -# "/tmp/guix-binary-1.4.0rc2.armhf-linux.tar.xz"
48 -#
49 -# GUIX_ALLOW_OVERWRITE
50 -#
51 -# Instead of aborting to avoid overwriting a previous installations,
52 -# allow copying over /var/guix or /gnu. This can be useful when the
53 -# installation required the user to extract Guix packs under /gnu to
54 -# satisfy its dependencies.
55 -
56 -# shellcheck shell=bash
57 -
58 -# shellcheck disable=2268 # try to support vintage shells
59 -if [ "x$BASH_VERSION" = "x" ]; then
60 - exec bash "$0" "$@"
61 -fi
62 -
63 -set -eo pipefail
64 -
65 -[ "$UID" -eq 0 ] || { echo "This script must be run as root."; exit 1; }
66 -
67 -REQUIRE=(
68 - "dirname"
69 - "readlink"
70 - "wget"
71 - "gpg"
72 - "grep"
73 - "sed"
74 - "sort"
75 - "getent"
76 - "mktemp"
77 - "rm"
78 - "chmod"
79 - "uname"
80 - "groupadd"
81 - "groupdel"
82 - "useradd"
83 - "userdel"
84 - "tail"
85 - "tr"
86 - "xz"
87 -)
88 -
89 -# Add variables using form FOO_INIT_REQUIRE when init system FOO dependencies
90 -# should be checked.
91 -# shellcheck disable=2034 # interpolated by add_init_sys_require
92 -SYSV_INIT_REQUIRE=(
93 - "daemonize"
94 -)
95 -
96 -# Unprivileged guix-daemon requires 'newgidmap'.
97 -SYSTEMD_REQUIRE=(
98 - "newgidmap"
99 -)
100 -
101 -PAS=$'[ \033[32;1mPASS\033[0m ] '
102 -ERR=$'[ \033[31;1mFAIL\033[0m ] '
103 -WAR=$'[ \033[33;1mWARN\033[0m ] '
104 -INF="[ INFO ] "
105 -
106 -DEBUG=0
107 -GNU_URL="https://ftp.gnu.org/gnu/guix/"
108 -
109 -# The following associative array holds set of GPG keys used to sign the
110 -# releases, keyed by their corresponding Codeberg user name.
111 -declare -A GPG_SIGNING_KEYS
112 -GPG_SIGNING_KEYS["civodul"]=3CE464558A84FDC69DB40CFB090B11993D9AEBB5 # ludo
113 -GPG_SIGNING_KEYS["apteryx"]=27D586A4F8900854329FF09F1260E46482E63562 # maxim
114 -GPG_SIGNING_KEYS["efraim"]=A28BF40C3E551372662D14F741AAE7DCCA3D8351 # efraim
115 -PUBLIC_KEYSERVERS="keys.openpgp.org pgpkeys.eu keyserver.ubuntu.com"
116 -
117 -# ------------------------------------------------------------------------------
118 -#+UTILITIES
119 -
120 -_err()
121 -{ # All errors go to stderr.
122 - printf "[%s]: ${ERR}%s\n" "$(date +%s.%3N)" "$1"
123 -}
124 -
125 -_msg()
126 -{ # Default message to stdout.
127 - printf "[%s]: %s\n" "$(date +%s.%3N)" "$1"
128 -}
129 -
130 -_msg_pass()
131 -{
132 - _msg "$PAS$1"
133 -}
134 -
135 -_msg_warn()
136 -{
137 - _msg "$WAR$1"
138 -}
139 -
140 -_msg_info()
141 -{
142 - _msg "$INF$1"
143 -}
144 -
145 -_debug()
146 -{
147 - if [ "${DEBUG}" = '1' ]; then
148 - printf "[%s]: %s\n" "$(date +%s.%3N)" "$1"
149 - fi
150 -}
151 -
152 -die()
153 -{
154 - _err "$*"
155 - exit 1
156 -}
157 -
158 -# Return true if user answered yes, false otherwise. The prompt is
159 -# yes-biased, that is, when the user simply enter newline, it is equivalent to
160 -# answering "yes".
161 -# $1: The prompt question.
162 -prompt_yes_no() {
163 - local -l yn
164 - read -rp "$1 [Y/n]" yn
165 - [[ ! $yn || $yn = y || $yn = yes ]] || return 1
166 -}
167 -
168 -chk_require()
169 -{ # Check that every required command is available.
170 - declare -a warn
171 - local c
172 -
173 - _debug "--- [ ${FUNCNAME[0]} ] ---"
174 -
175 - for c in "$@"; do
176 - command -v "$c" &>/dev/null || warn+=("$c")
177 - done
178 -
179 - [ "${#warn}" -ne 0 ] && die "Missing commands: ${warn[*]}."
180 -
181 - _msg_pass "verification of required commands completed"
182 -}
183 -
184 -add_init_sys_require()
185 -{ # Add the elements of FOO_INIT_SYS to REQUIRE
186 -
187 - # Convert the init system command name captured in INIT_SYS in
188 - # chk_init_sys to uppercase, with hyphens replaced by underscores.
189 - local init_require=${INIT_SYS^^}_REQUIRE[@]
190 - init_require=${init_require//-/_}
191 -
192 - if [[ -n "$init_require" ]]; then
193 - # Have to add piecemeal because ${!foo[@]} performs direct array key
194 - # expansion, not indirect plain array expansion.
195 - for r in "${!init_require}"; do
196 - REQUIRE+=("$r")
197 - done
198 - fi
199 -}
200 -
201 -chk_gpg_keyring()
202 -{ # Check whether the Guix release signing public key is present.
203 - _debug "--- [ ${FUNCNAME[0]} ] ---"
204 - local user_id
205 - local gpg_key_id
206 - local exit_flag
207 -
208 - for user_id in "${!GPG_SIGNING_KEYS[@]}"; do
209 - gpg_key_id=${GPG_SIGNING_KEYS[$user_id]}
210 - codeberg_username=$user_id
211 - # Without --dry-run this command will create a ~/.gnupg owned by root on
212 - # systems where gpg has never been used, causing errors and confusion.
213 - if gpg --dry-run --list-keys "$gpg_key_id" >/dev/null 2>&1; then
214 - continue
215 - fi
216 - if prompt_yes_no "${INF}The following OpenPGP public key is \
217 -required to verify the Guix binary signature: $gpg_key_id.
218 -Would you like me to fetch it for you?"; then
219 - if wget "https://codeberg.org/$codeberg_username.gpg" \
220 - --tries=1 --timeout=30 --no-verbose -O- | gpg --import -; then
221 - continue
222 - fi
223 -
224 - key_obtained=false
225 - # Try to fetch keys from an available keyserver
226 - for key_server in $PUBLIC_KEYSERVERS; do
227 - if gpg --keyserver $key_server --recv-key $gpg_key_id; then
228 - key_obtained=true
229 - break
230 - fi
231 - done
232 -
233 - if $key_obtained; then
234 - continue
235 - fi
236 - fi
237 - # If we reach this point, the key is (still) missing. Report further
238 - # missing keys, if any, but then abort the installation.
239 - _err "Missing OpenPGP public key ($gpg_key_id).
240 -Fetch it with codeberg username:
241 -
242 - wget \"https://codeberg.org/$codeberg_username.gpg\" -O - | \
243 -sudo -i gpg --import -
244 -
245 -If this fails, try to fetch it via a keyserver:
246 -
247 - for key_server in $PUBLIC_KEYSERVERS; do
248 - if sudo -i gpg --keyserver $key_server --recv-key $gpg_key_id; then
249 - break
250 - fi
251 - done"
252 - exit_flag=yes
253 - done
254 - if [ "$exit_flag" = yes ]; then
255 - exit 1
256 - fi
257 -}
258 -
259 -chk_term()
260 -{ # Check for ANSI terminal for color printing.
261 - if [ -t 2 ]; then
262 - if [ "${TERM+set}" = 'set' ]; then
263 - case "$TERM" in
264 - xterm*|rxvt*|urxvt*|linux*|vt*|eterm*|screen*)
265 - ;;
266 - *)
267 - ERR="[ FAIL ] "
268 - PAS="[ PASS ] "
269 - WAR="[ WARN ] "
270 - ;;
271 - esac
272 - fi
273 - fi
274 -}
275 -
276 -chk_init_sys()
277 -{ # Return init system type name.
278 - if [[ $(/sbin/init --version 2>/dev/null) =~ upstart ]]; then
279 - _msg_info "init system is: upstart"
280 - INIT_SYS="upstart"
281 - return 0
282 - elif [[ $(systemctl 2>/dev/null) =~ -\.mount ]]; then
283 - _msg_info "init system is: systemd"
284 - INIT_SYS="systemd"
285 - return 0
286 - elif [[ -f /etc/init.d/cron && ! -h /etc/init.d/cron ]]; then
287 - _msg_info "init system is: sysv-init"
288 - INIT_SYS="sysv-init"
289 - return 0
290 - elif [[ $(openrc --version 2>/dev/null) =~ \(OpenRC ]]; then
291 - _msg_info "init system is: OpenRC"
292 - INIT_SYS="openrc"
293 - return 0
294 - else
295 - INIT_SYS="NA"
296 - _err "Init system could not be detected."
297 - fi
298 -}
299 -
300 -chk_sys_arch()
301 -{ # Check for operating system and architecture type.
302 - local os
303 - local arch
304 -
305 - os="$(uname -s)"
306 - arch="$(uname -m)"
307 -
308 - case "$arch" in
309 - i386 | i486 | i686 | i786 | x86)
310 - local arch=i686
311 - ;;
312 - x86_64 | x86-64 | x64 | amd64)
313 - local arch=x86_64
314 - ;;
315 - aarch64)
316 - local arch=aarch64
317 - ;;
318 - armv7l)
319 - local arch=armhf
320 - ;;
321 - ppc64le | powerpc64le)
322 - local arch=powerpc64le
323 - ;;
324 - riscv64)
325 - local arch=riscv64
326 - ;;
327 - *)
328 - die "Unsupported CPU type: ${arch}"
329 - esac
330 -
331 - case "$os" in
332 - Linux | linux)
333 - local os=linux
334 - ;;
335 - *)
336 - die "Your operation system (${os}) is not supported."
337 - esac
338 -
339 - ARCH_OS="${arch}-${os}"
340 -}
341 -
342 -chk_sys_nscd()
343 -{ # Check if nscd is up and suggest to start it or install it
344 - if [ "$(type -P pidof)" ]; then
345 - if [ ! "$(pidof nscd)" ]; then
346 - _msg_warn "We recommend installing and/or starting your distribution 'nscd' service"
347 - _msg_warn "Please read 'info guix \"Application Setup\"' about \"Name Service Switch\""
348 - fi
349 - else
350 - _msg_info "We cannot determine if your distribution 'nscd' service is running"
351 - _msg_info "Please read 'info guix \"Application Setup\"' about \"Name Service Switch\""
352 - fi
353 -}
354 -
355 -chk_existing()
356 -{ # Avoid clobbering existing installations.
357 - _debug "--- [ ${FUNCNAME[0]} ] ---"
358 -
359 - if [[ -e /var/guix && -e /gnu ]]; then
360 - if [ -n "$GUIX_ALLOW_OVERWRITE" ]; then
361 - _msg_warn "Overwriting existing installation!"
362 - else
363 - die "A previous Guix installation was found. Refusing to overwrite."
364 - fi
365 - fi
366 -}
367 -
368 -# Configure substitute discovery according to user's preferences.
369 -# $1 is the installed service file to edit.
370 -configure_substitute_discovery() {
371 - if grep -q -- '--discover=no' "$1" && \
372 - prompt_yes_no "Would you like the Guix daemon to automatically \
373 -discover substitute servers on the local network?"; then
374 - sed -i 's/--discover=no/--discover=yes/' "$1"
375 - fi
376 -}
377 -
378 -# ------------------------------------------------------------------------------
379 -#+MAIN
380 -
381 -guix_get_bin_list()
382 -{ # Scan GNU archive and save list of binaries
383 - local gnu_url="$1"
384 - local -a bin_ver_ls
385 - local latest_ver
386 - local default_ver
387 -
388 - _debug "--- [ ${FUNCNAME[0]} ] ---"
389 -
390 - # Filter only version and architecture
391 - bin_ver_ls=("$(wget "$gnu_url" --no-verbose -O- \
392 - | sed -n -e 's/.*guix-binary-\([0-9.]*[a-z0-9]*\)\..*.tar.xz.*/\1/p' \
393 - | sort -Vu)")
394 -
395 - latest_ver="$(echo "${bin_ver_ls[0]}" \
396 - | grep -oE "([0-9]{1,2}\.){2}[0-9]{1,2}[a-z0-9]*" \
397 - | tail -n1)"
398 -
399 - default_ver="guix-binary-${latest_ver}.${ARCH_OS}"
400 -
401 - if [[ "${#bin_ver_ls}" -ne "0" ]]; then
402 - _msg_pass "Release for your system: ${default_ver}"
403 - else
404 - die "Could not obtain list of Guix releases."
405 - fi
406 -
407 - # Use default to download according to the list and local ARCH_OS.
408 - BIN_VER="${default_ver}"
409 -}
410 -
411 -guix_get_bin()
412 -{ # Download and verify binary package.
413 - local url="$1"
414 - local bin_ver="$2"
415 - local dl_path="$3"
416 - local wget_args=()
417 -
418 - _debug "--- [ ${FUNCNAME[0]} ] ---"
419 -
420 - _msg_info "Downloading Guix release archive"
421 -
422 - wget --help | grep -q '\--show-progress' \
423 - && wget_args=("--no-verbose" "--show-progress")
424 -
425 - if wget "${wget_args[@]}" -P "$dl_path" \
426 - "${url}/${bin_ver}.tar.xz" "${url}/${bin_ver}.tar.xz.sig"; then
427 - _msg_pass "download completed."
428 - else
429 - die "could not download ${url}/${bin_ver}.tar.xz."
430 - fi
431 -
432 - pushd "${dl_path}" >/dev/null
433 - if gpg --verify "${bin_ver}.tar.xz.sig" >/dev/null 2>&1; then
434 - _msg_pass "Signature is valid."
435 - popd >/dev/null
436 - else
437 - die "could not verify the signature."
438 - fi
439 -}
440 -
441 -sys_create_store()
442 -{ # Unpack and install /gnu/store and /var/guix
443 - local pkg="$1"
444 - local tmp_path="$2"
445 -
446 - cd "$tmp_path"
447 - _msg_info "Installing /var/guix and /gnu..."
448 - # Strip (skip) the leading ‘.’ component, which fails on read-only ‘/’.
449 - #
450 - # TODO: Eventually extract with ‘--owner=guix-daemon’ when installing
451 - # and unprivileged guix-daemon service; for now, this script may install
452 - # from both an old release that does not support unprivileged guix-daemon
453 - # and a new release that does, so ‘chown -R’ later if needed.
454 - tar --extract --strip-components=1 --file "$pkg" -C /
455 -
456 - _msg_info "Linking the root user's profile"
457 - mkdir -p ~root/.config/guix
458 - ln -sf /var/guix/profiles/per-user/root/current-guix \
459 - ~root/.config/guix/current
460 -
461 - GUIX_PROFILE=~root/.config/guix/current
462 - # The profile just prepends to search paths, which is not needed for
463 - # effective linting.
464 - # shellcheck disable=SC1091
465 - source "${GUIX_PROFILE}/etc/profile"
466 - _msg_pass "activated root profile at ${GUIX_PROFILE}"
467 -}
468 -
469 -sys_delete_store()
470 -{
471 - _msg_info "removing /var/guix"
472 - rm -rf /var/guix
473 -
474 - _msg_info "removing /gnu"
475 - rm -rf /gnu
476 -
477 - _msg_info "removing ~root/.config/guix"
478 - rm -rf ~root/.config/guix
479 -}
480 -
481 -create_account()
482 -{
483 - local user="$1"
484 - local group="$2"
485 - local supplementary_groups="$3"
486 - local comment="$4"
487 -
488 - if id "$user" &>/dev/null; then
489 - _msg_info "user '$user' is already in the system, reset"
490 - usermod -g "$group" -G "$supplementary_groups" \
491 - -d /var/empty -s "$(command -v nologin)" \
492 - -c "$comment" "$user"
493 - else
494 - useradd -g "$group" -G "$supplementary_groups" \
495 - -d /var/empty -s "$(command -v nologin)" \
496 - -c "$comment" --system "$user"
497 - _msg_pass "user added <$user>"
498 - fi
499 -}
500 -
501 -install_unprivileged_daemon()
502 -{ # Return true when installing guix-daemon running without privileges.
503 - [ "$INIT_SYS" = systemd ] && \
504 - grep -q "User=guix-daemon" \
505 - ~root/.config/guix/current/lib/systemd/system/guix-daemon.service
506 -}
507 -
508 -sys_create_build_user()
509 -{ # Create the group and user accounts for build users.
510 -
511 - _debug "--- [ ${FUNCNAME[0]} ] ---"
512 -
513 - if getent group kvm > /dev/null; then
514 - _msg_info "group kvm exists and build users will be added to it"
515 - local KVMGROUP=,kvm
516 - fi
517 -
518 - if install_unprivileged_daemon
519 - then
520 - _msg_info "installing guix-daemon to run as an unprivileged user"
521 -
522 - # Installing guix-daemon to run as a non-root user requires
523 - # unprivileged user namespaces.
524 - if [ -f /proc/sys/kernel/unprivileged_userns_clone ] \
525 - && [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" -ne 1 ]
526 - then
527 - echo 1 > /proc/sys/kernel/unprivileged_userns_clone || \
528 - _err "failed to enable unprivileged user namespaces"
529 -
530 - _msg_warn "Unprivileged user namespaces were disabled and have been enabled now."
531 - _msg_warn "This Linux feature is required by guix-daemon. To enable it permanently, run:"
532 - _msg_warn ' echo 1 > /proc/sys/kernel/unprivileged_userns_clone'
533 - _msg_warn "from the relevant startup script."
534 - fi
535 -
536 -
537 - if getent group guix-daemon > /dev/null; then
538 - _msg_info "group guix-daemon exists"
539 - else
540 - groupadd --system guix-daemon
541 - _msg_pass "group guix-daemon created"
542 - fi
543 -
544 - create_account guix-daemon guix-daemon \
545 - guix-daemon"$KVMGROUP" \
546 - "Unprivileged Guix Daemon User"
547 -
548 - if getent group kvm > /dev/null; then
549 - # Allow 'newgidmap' to map the "kvm" group.
550 - local kvmgid="$(getent group kvm | cut -f3 -d:)"
551 - _msg_info "allowing kvm mapping (GID $kvmgid) for unprivileged guix-daemon"
552 - echo "guix-daemon:$kvmgid:1" >> /etc/subgid
553 - fi
554 -
555 - # ‘tar xf’ creates root:root files. Change that.
556 - chown -R guix-daemon:guix-daemon /gnu /var/guix
557 - chown -R root:root /var/guix/profiles/per-user/root
558 -
559 - # The unprivileged daemon cannot create the log directory by itself.
560 - mkdir -p /var/log/guix
561 - chown guix-daemon:guix-daemon /var/log/guix
562 - chmod 755 /var/log/guix
563 - else
564 - if getent group guixbuild > /dev/null; then
565 - _msg_info "group guixbuild exists"
566 - else
567 - groupadd --system guixbuild
568 - _msg_pass "group <guixbuild> created"
569 - fi
570 -
571 - for i in $(seq -w 1 10); do
572 - create_account "guixbuilder${i}" "guixbuild" \
573 - "guixbuild${KVMGROUP}" \
574 - "Guix build user $i"
575 - done
576 - fi
577 -}
578 -
579 -sys_delete_build_user()
580 -{
581 - for i in $(seq -w 1 10); do
582 - if id -u "guixbuilder${i}" &>/dev/null; then
583 - userdel -f guixbuilder"$i"
584 - fi
585 - done
586 -
587 - _msg_info "delete group guixbuild"
588 - if getent group guixbuild &>/dev/null; then
589 - groupdel -f guixbuild
590 - fi
591 -
592 - _msg_info "remove guix-daemon user"
593 - if id guix-daemon &>/dev/null; then
594 - userdel -f guix-daemon
595 - fi
596 - if getent group guix-daemon &>/dev/null; then
597 - groupdel -f guix-daemon
598 - fi
599 -}
600 -
601 -sys_enable_guix_daemon()
602 -{ # Run the daemon, and set it to automatically start on boot.
603 -
604 - local info_path
605 - local local_bin
606 - local var_guix
607 -
608 - _debug "--- [ ${FUNCNAME[0]} ] ---"
609 -
610 - info_path="/usr/local/share/info"
611 - local_bin="/usr/local/bin"
612 - var_guix="/var/guix/profiles/per-user/root/current-guix"
613 -
614 - case "$INIT_SYS" in
615 - upstart)
616 - { initctl reload-configuration;
617 - cp ~root/.config/guix/current/lib/upstart/system/guix-daemon.conf \
618 - /etc/init/ &&
619 - configure_substitute_discovery /etc/init/guix-daemon.conf &&
620 - start guix-daemon; } &&
621 - _msg_pass "enabled Guix daemon via upstart"
622 - ;;
623 - systemd)
624 - { install_unit()
625 - {
626 - local dest="/etc/systemd/system/$1"
627 - rm -f "$dest"
628 - cp ~root/.config/guix/current/lib/systemd/system/"$1" "$dest"
629 - chmod 664 "$dest"
630 - systemctl daemon-reload
631 - systemctl enable "$1"
632 - }
633 -
634 - install_unit guix-daemon.service
635 -
636 - configure_substitute_discovery \
637 - /etc/systemd/system/guix-daemon.service
638 -
639 - # Install after guix-daemon.service to avoid a harmless warning.
640 - # systemd .mount units must be named after the target directory.
641 - install_unit gnu-store.mount
642 -
643 - systemctl daemon-reload &&
644 - systemctl start guix-daemon &&
645 - systemctl start gnu-store.mount; } &&
646 - _msg_pass "enabled Guix daemon via systemd"
647 - ;;
648 - sysv-init)
649 - { mkdir -p /etc/init.d;
650 - cp ~root/.config/guix/current/etc/init.d/guix-daemon \
651 - /etc/init.d/guix-daemon;
652 - chmod 775 /etc/init.d/guix-daemon;
653 -
654 - configure_substitute_discovery /etc/init.d/guix-daemon
655 -
656 - update-rc.d guix-daemon defaults &&
657 - update-rc.d guix-daemon enable &&
658 - service guix-daemon start; } &&
659 - _msg_pass "enabled Guix daemon via sysv"
660 - ;;
661 - openrc)
662 - { mkdir -p /etc/init.d;
663 - cp ~root/.config/guix/current/etc/openrc/guix-daemon \
664 - /etc/init.d/guix-daemon;
665 - chmod 775 /etc/init.d/guix-daemon;
666 -
667 - configure_substitute_discovery /etc/init.d/guix-daemon
668 -
669 - rc-update add guix-daemon default &&
670 - rc-service guix-daemon start; } &&
671 - _msg_pass "enabled Guix daemon via OpenRC"
672 - ;;
673 - NA|*)
674 - _err "unsupported init system; run the daemon manually:"
675 - echo " ~root/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild"
676 - ;;
677 - esac
678 -
679 - _msg_info "making the guix command available to other users"
680 -
681 - [ -e "$local_bin" ] || mkdir -p "$local_bin"
682 - ln -sf "${var_guix}/bin/guix" "$local_bin"
683 -
684 - [ -e "$info_path" ] || mkdir -p "$info_path"
685 - for i in "${var_guix}"/share/info/*; do
686 - ln -sf "$i" "$info_path"
687 - done
688 -}
689 -
690 -sys_delete_guix_daemon()
691 -{ # Disabled, stop and remove the various guix daemons.
692 -
693 - local info_path
694 - local local_bin
695 - local var_guix
696 -
697 - _debug "--- [ ${FUNCNAME[0]} ] ---"
698 -
699 - info_path="/usr/local/share/info"
700 - local_bin="/usr/local/bin"
701 -
702 -
703 - case "$INIT_SYS" in
704 - upstart)
705 - _msg_info "stopping guix-daemon"
706 - stop guix-daemon
707 - _msg_info "removing guix-daemon"
708 - rm /etc/init/guix-daemon.conf
709 - ;;
710 -
711 - systemd)
712 - if [ -f /etc/systemd/system/guix-daemon.service ]; then
713 - _msg_info "disabling guix-daemon"
714 - systemctl disable guix-daemon
715 - _msg_info "stopping guix-daemon"
716 - systemctl stop guix-daemon
717 - _msg_info "removing guix-daemon"
718 - rm -f /etc/systemd/system/guix-daemon.service
719 - fi
720 -
721 - if [ -f /etc/systemd/system/gnu-store.mount ]; then
722 - _msg_info "disabling gnu-store.mount"
723 - systemctl disable gnu-store.mount
724 - _msg_info "stopping gnu-store.mount"
725 - systemctl stop gnu-store.mount
726 - _msg_info "removing gnu-store.mount"
727 - rm -f /etc/systemd/system/gnu-store.mount
728 - fi
729 - systemctl daemon-reload
730 - ;;
731 -
732 - sysv-init)
733 - update-rc.d guix-daemon disable
734 - service guix-daemon stop
735 - rm -rf /etc/init.d/guix-daemon
736 - ;;
737 - NA|*)
738 - _err "unsupported init system; disable, stop and remove the daemon manually:"
739 - echo " ~root/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild"
740 - ;;
741 - esac
742 -
743 -
744 - _msg_info "removing $local_bin/guix"
745 - rm -f "$local_bin"/guix
746 -
747 - _msg_info "removing $info_path/guix*"
748 - rm -f "$info_path"/guix*
749 -}
750 -
751 -sys_authorize_build_farms()
752 -{ # authorize the public key(s) of the build farm(s)
753 - local hosts=(
754 - bordeaux.guix.gnu.org
755 - ci.guix.gnu.org
756 - )
757 -
758 - if prompt_yes_no "Permit downloading pre-built package binaries from the \
759 -project's build farms?"; then
760 - for host in "${hosts[@]}"; do
761 - local key=~root/.config/guix/current/share/guix/$host.pub
762 - [ -f "$key" ] \
763 - && guix archive --authorize < "$key" \
764 - && _msg_pass "Authorized public key for $host"
765 - done
766 - if id guix-daemon &>/dev/null; then
767 - # /etc/guix/acl must be readable by the unprivileged guix-daemon.
768 - chown -R guix-daemon:guix-daemon /etc/guix
769 - fi
770 - else
771 - _msg_info "Skipped authorizing build farm public keys"
772 - fi
773 -}
774 -
775 -sys_create_init_profile()
776 -{ # Define for better desktop integration
777 - # This will not take effect until the next shell or desktop session!
778 - [ -d "/etc/profile.d" ] || mkdir /etc/profile.d # Just in case
779 - cat <<"EOF" > /etc/profile.d/zzz-guix.sh
780 -# Explicitly initialize XDG base directory variables to ease compatibility
781 -# with Guix System: see <https://issues.guix.gnu.org/56050#3>.
782 -export XCURSOR_PATH="${XCURSOR_PATH:-$HOME/.local/share/icons:$HOME/.icons:/usr/local/share/icons:/usr/share/icons}"
783 -export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
784 -export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
785 -export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
786 -export XDG_DATA_DIRS="${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}"
787 -export XDG_CONFIG_DIRS="${XDG_CONFIG_DIRS:-/etc/xdg}"
788 -export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
789 -# no default for XDG_RUNTIME_DIR (depends on foreign distro for semantics)
790 -
791 -# `guix pull` profile
792 -GUIX_PROFILE="$HOME/.config/guix/current"
793 -export PATH="$GUIX_PROFILE/bin${PATH:+:}$PATH"
794 -# Add to INFOPATH and MANPATH so the latest Guix documentation is available to
795 -# info and man readers. When INFOPATH is unset, add a trailing colon so Emacs
796 -# searches 'Info-default-directory-list'. When MANPATH is unset, add a
797 -# trailing colon so the system default search path is used.
798 -export INFOPATH="$GUIX_PROFILE/share/info:${INFOPATH:-}"
799 -export MANPATH="$GUIX_PROFILE/share/man:${MANPATH:-}"
800 -# Expose the latest Guix modules to Guile so guix shell and repls spawned by
801 -# e.g. Geiser work out of the box.
802 -export GUILE_LOAD_PATH="$GUIX_PROFILE/share/guile/site/3.0${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}"
803 -export GUILE_LOAD_COMPILED_PATH="$GUIX_PROFILE/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}"
804 -
805 -# User's default profile, if it exists
806 -GUIX_PROFILE="$HOME/.guix-profile"
807 -if [ -f "$GUIX_PROFILE/etc/profile" ]; then
808 - . "$GUIX_PROFILE/etc/profile"
809 -
810 - # see info '(guix) Application Setup'
811 - export GUIX_LOCPATH="$GUIX_PROFILE/lib/locale${GUIX_LOCPATH:+:}$GUIX_LOCPATH"
812 -
813 - # Documentation search paths may be handled by $GUIX_PROFILE/etc/profile if
814 - # the user installs info and man readers via Guix. If the user doesn’t,
815 - # explicitly add to them so documentation for software from ‘guix install’
816 - # is available to the system info and man readers.
817 - case $INFOPATH in
818 - *$GUIX_PROFILE/share/info*) ;;
819 - *) export INFOPATH="$GUIX_PROFILE/share/info:$INFOPATH" ;;
820 - esac
821 - case $MANPATH in
822 - *$GUIX_PROFILE/share/man*) ;;
823 - *) export MANPATH="$GUIX_PROFILE/share/man:$MANPATH"
824 - esac
825 -
826 - case $XDG_DATA_DIRS in
827 - *$GUIX_PROFILE/share*) ;;
828 - *) export XDG_DATA_DIRS="$GUIX_PROFILE/share:$XDG_DATA_DIRS"
829 - esac
830 -fi
831 -
832 -# NOTE: Guix Home handles its own profile initialization in ~/.profile. See
833 -# info '(guix) Configuring the Shell'.
834 -
835 -# Clean up after ourselves.
836 -unset GUIX_PROFILE
837 -EOF
838 -}
839 -
840 -sys_create_shell_completion()
841 -{ # Symlink supported shell completions system-wide
842 -
843 - var_guix=/var/guix/profiles/per-user/root/current-guix
844 - bash_completion=/etc/bash_completion.d
845 - zsh_completion=/usr/share/zsh/site-functions
846 - fish_completion=/usr/share/fish/vendor_completions.d
847 -
848 - { # Just in case
849 - for dir_shell in $bash_completion $zsh_completion $fish_completion; do
850 - [ -d "$dir_shell" ] || mkdir -p "$dir_shell"
851 - done;
852 -
853 - # Don't use globing here as we also need to delete the files when
854 - # uninstalling Guix
855 - ln -sf ${var_guix}/etc/bash_completion.d/guix "$bash_completion";
856 - ln -sf ${var_guix}/etc/bash_completion.d/guix-daemon "$bash_completion";
857 - ln -sf ${var_guix}/share/zsh/site-functions/_guix "$zsh_completion";
858 - ln -sf ${var_guix}/share/fish/vendor_completions.d/guix.fish "$fish_completion"; } &&
859 - _msg_pass "installed shell completion"
860 -}
861 -
862 -sys_delete_shell_completion()
863 -{ # Symlink supported shell completions system-wide
864 -
865 - var_guix=/var/guix/profiles/per-user/root/current-guix
866 - bash_completion=/etc/bash_completion.d
867 - zsh_completion=/usr/share/zsh/site-functions
868 - fish_completion=/usr/share/fish/vendor_completions.d
869 -
870 - _msg_info "removing shell completion"
871 -
872 - rm -f "$bash_completion"/guix;
873 - rm -f "$bash_completion"/guix-daemon;
874 - rm -f "$zsh_completion"/_guix;
875 - rm -f "$fish_completion"/guix.fish;
876 -}
877 -
878 -sys_customize_bashrc()
879 -{
880 - prompt_yes_no "Customize users Bash shell prompt for Guix?" || return 0
881 -
882 - for bashrc in /home/*/.bashrc /root/.bashrc; do
883 - test -f "$bashrc" || continue
884 - # shellcheck disable=SC2016 # intended search for variable reference
885 - grep -Fq '$GUIX_ENVIRONMENT' "$bashrc" && continue
886 - cp "${bashrc}" "${bashrc}.bak"
887 - # shellcheck disable=SC2016,SC2028 # intended literal shell output
888 - echo '
889 -# Automatically added by the Guix install script.
890 -if [ -n "$GUIX_ENVIRONMENT" ]; then
891 - if [[ $PS1 =~ (.*)"\\$" ]]; then
892 - PS1="${BASH_REMATCH[1]} [env]\\\$ "
893 - fi
894 -fi
895 -' >> "$bashrc"
896 - done
897 - _msg_pass "Bash shell prompt successfully customized for Guix"
898 -}
899 -
900 -sys_maybe_setup_selinux()
901 -{
902 - if ! [ -f /sys/fs/selinux/policy ]
903 - then
904 - return
905 - fi
906 -
907 - local c
908 - for c in semodule restorecon
909 - do
910 - if ! command -v "$c" &>/dev/null
911 - then
912 - return
913 - fi
914 - done
915 -
916 - prompt_yes_no "Install SELinux policy that might be required to run guix-daemon?" \
917 - || return 0
918 -
919 - local var_guix=/var/guix/profiles/per-user/root/current-guix
920 - semodule -i "${var_guix}/share/selinux/guix-daemon.cil"
921 - restorecon -R /gnu /var/guix
922 -}
923 -
924 -sys_maybe_setup_apparmor()
925 -{
926 - if ! [ -f /sys/module/apparmor/parameters/enabled ]
927 - then
928 - return
929 - fi
930 -
931 - if ! command -v apparmor_parser &> /dev/null
932 - then
933 - return
934 - fi
935 -
936 - prompt_yes_no "Install AppArmor profile that might be required to run guix-daemon?" \
937 - || return 0
938 -
939 - local var_guix=/var/guix/profiles/per-user/root/current-guix
940 - local apparmor_sources="${var_guix}/etc/apparmor.d/"
941 -
942 - if ! [ -d "$apparmor_sources" ]
943 - then
944 - _err "This version of Guix doesn’t contain AppArmor profiles."
945 - return
946 - fi
947 -
948 - cp -f -t /etc/apparmor.d/tunables "$apparmor_sources/tunables/guix"
949 - cp -f -t /etc/apparmor.d "$apparmor_sources/guix-daemon"
950 - cp -f -t /etc/apparmor.d "$apparmor_sources/guix"
951 - apparmor_parser --warn=all -r /etc/apparmor.d/guix-daemon
952 - apparmor_parser --warn=all -r /etc/apparmor.d/guix
953 -
954 - _msg_pass "apparmor profiles installed and loaded"
955 -}
956 -
957 -sys_delete_apparmor_profiles()
958 -{
959 - # Not a big deal if the apparmor_parser commands fail as they only apply
960 - # for the current boot, we still want to go on and remove the files.
961 - if [ -f "/etc/apparmor.d/guix" ]
962 - then
963 - _msg_info "removing /etc/apparmor.d/guix"
964 - apparmor_parser -R /etc/apparmor.d/guix || true
965 - rm -f "/etc/apparmor.d/guix"
966 - fi
967 -
968 - if [ -f "/etc/apparmor.d/guix-daemon" ]
969 - then
970 - _msg_info "removing /etc/apparmor.d/guix-daemon"
971 - apparmor_parser -R /etc/apparmor.d/guix-daemon || true
972 - rm -f "/etc/apparmor.d/guix-daemon"
973 - fi
974 -
975 - if [ -f "/etc/apparmor.d/tunables/guix" ]
976 - then
977 - _msg_info "removing /etc/apparmor.d/tunables/guix"
978 - rm -f "/etc/apparmor.d/tunables/guix"
979 - fi
980 -}
981 -
982 -sys_delete_init_profile()
983 -{
984 - _msg_info "removing /etc/profile.d/zzz-guix.sh"
985 - rm -f /etc/profile.d/zzz-guix.sh
986 -}
987 -
988 -sys_delete_user_profiles()
989 -{
990 - _msg_info "removing ~root/.guix-profile"
991 - rm -f ~root/.guix-profile
992 - rm -rf ~root/.cache/guix
993 -
994 - _msg_info "removing .guix-profile, .cache/guix and .config/guix of all /home users"
995 - rm -rf /home/*/{.guix-profile,{.cache,.config}/guix}
996 -}
997 -
998 -welcome()
999 -{
1000 - local uninstall_flag="$1"
1001 - local char
1002 - cat<<"EOF"
1003 - ��������� ���������
1004 - ��������������������������������������� ���������������������������������������
1005 - ������������������������������������������ ���������������������������������������
1006 - ��������������������������������� ������������������������������
1007 - ������������������ ������������������
1008 - ��������������� ������������������
1009 - ��������������� ���������������
1010 - ������������������ ���������������
1011 - ��������������� ���������������
1012 - ��������������� ���������������
1013 - ���������������������������������
1014 - ���������������������������
1015 - ���������������������
1016 - _____ _ _ _ _ _____ _
1017 - / ____| \ | | | | | / ____| (_)
1018 - | | __| \| | | | | | | __ _ _ ___ __
1019 - | | |_ | . ' | | | | | | |_ | | | | \ \/ /
1020 - | |__| | |\ | |__| | | |__| | |_| | |> <
1021 - \_____|_| \_|\____/ \_____|\__,_|_/_/\_\
1022 -
1023 -https://www.gnu.org/software/guix/
1024 -EOF
1025 -
1026 - if [ '--uninstall' = "$uninstall_flag" ]; then
1027 - echo "${WARN}This script will uninstall GNU Guix from your system"
1028 - echo "To install, run this script with no parameters."
1029 - else
1030 - echo "This script installs GNU Guix on your system"
1031 - echo "To uninstall, pass in the '--uninstall' parameter."
1032 - fi
1033 -
1034 - # Don't use ‘read -p’ here! It won't display when run non-interactively.
1035 - echo -n "Press return to continue..."$'\r'
1036 - if ! read -r char; then
1037 - echo
1038 - die "Can't read standard input. Hint: don't pipe scripts into a shell."
1039 - fi
1040 - if [ "$char" ]; then
1041 - echo
1042 - echo "...that ($char) was not a return!"
1043 - _msg_warn "Use newlines to automate installation, e.g.: yes '' | ${0##*/}"
1044 - _msg_warn "Any other method is unsupported and likely to break in future."
1045 - fi
1046 -}
1047 -
1048 -main_install()
1049 -{
1050 - local tmp_path
1051 - welcome
1052 -
1053 - _msg "Starting installation ($(date))"
1054 -
1055 - chk_term
1056 - chk_init_sys
1057 - add_init_sys_require
1058 - chk_require "${REQUIRE[@]}"
1059 - chk_sys_arch
1060 - chk_sys_nscd
1061 - chk_existing
1062 -
1063 - _msg_info "system is ${ARCH_OS}"
1064 -
1065 - umask 0022
1066 - tmp_path="$(mktemp -t -d guix.XXXXXX)"
1067 -
1068 - if [ -z "${GUIX_BINARY_FILE_NAME}" ]; then
1069 - chk_gpg_keyring
1070 - guix_get_bin_list "${GNU_URL}"
1071 - guix_get_bin "${GNU_URL}" "${BIN_VER}" "$tmp_path"
1072 - GUIX_BINARY_FILE_NAME=${BIN_VER}.tar.xz
1073 - else
1074 - if ! [[ $GUIX_BINARY_FILE_NAME =~ $ARCH_OS ]]; then
1075 - _err "$ARCH_OS not in ${GUIX_BINARY_FILE_NAME}; aborting"
1076 - fi
1077 - _msg_info "Using manually provided binary ${GUIX_BINARY_FILE_NAME}"
1078 - GUIX_BINARY_FILE_NAME=$(realpath "$GUIX_BINARY_FILE_NAME")
1079 - fi
1080 -
1081 - sys_create_store "${GUIX_BINARY_FILE_NAME}" "${tmp_path}"
1082 - sys_create_build_user
1083 - sys_maybe_setup_selinux
1084 - sys_maybe_setup_apparmor
1085 - sys_enable_guix_daemon
1086 - sys_authorize_build_farms
1087 - sys_create_init_profile
1088 - sys_create_shell_completion
1089 - sys_customize_bashrc
1090 -
1091 - _msg_info "cleaning up ${tmp_path}"
1092 - rm -r "${tmp_path}"
1093 -
1094 - _msg_pass "Guix has successfully been installed!"
1095 - _msg_info "Run 'info guix' to read the manual."
1096 -
1097 - # Required to source /etc/profile in desktop environments.
1098 - _msg_info "Please log out and back in to complete the installation."
1099 - }
1100 -
1101 -main_uninstall()
1102 -{
1103 - welcome --uninstall
1104 - _msg "Starting uninstall process ($(date))"
1105 -
1106 - chk_term
1107 - chk_require "${REQUIRE[@]}"
1108 - # it's ok to leave the gpg key
1109 - chk_init_sys
1110 - chk_sys_arch
1111 -
1112 - _msg_info "system is ${ARCH_OS}"
1113 -
1114 - # stop the build, package system.
1115 - sys_delete_guix_daemon
1116 - # stop people from accessing their profiles.
1117 - sys_delete_user_profiles
1118 - sys_delete_apparmor_profiles
1119 - # kill guix off all the guts of guix
1120 - sys_delete_store
1121 - # clean up the system
1122 - sys_delete_init_profile
1123 - sys_delete_build_user
1124 - sys_delete_shell_completion
1125 -
1126 - # these directories are created on the fly during usage.
1127 - _msg_info "removing /etc/guix"
1128 - rm -rf /etc/guix
1129 - _msg_info "removing /var/log/guix"
1130 - rm -rf /var/log/guix
1131 -
1132 - _msg_pass "Guix has successfully been uninstalled!"
1133 -}
1134 -
1135 -main()
1136 -{
1137 - # expect no parameters
1138 - # or '--uninstall'
1139 - if [ 0 -eq $# ]; then
1140 - main_install
1141 - else
1142 - local uninstall_flag="$1"
1143 - if [ '--uninstall' = "${uninstall_flag}" ]; then
1144 - main_uninstall
1145 - else
1146 - echo "unsupported parameters: $*"
1147 - exit 1
1148 - fi
1149 - fi
1150 -}
1151 -
1152 -main "$@"
+0 −0 nob new file mode 100755, Binary files /dev/null and b/nob differ
+22 −0 nob.c new file mode 100644
@@ -0,0 +1,22 @@
1 +
2 +#define NOB_IMPLEMENTATION
3 +#include "nob.h"
4 +
5 +int
6 +main (int argc, char **argv)
7 +{
8 + NOB_GO_REBUILD_URSELF(argc, argv);
9 +
10 + Cmd cmd = {0};
11 + cmd_append (&cmd, "cc", "-Wall", "-Wextra", "-o", "pkger", "pkger.c");
12 + if (!cmd_run(&cmd)) return 1;
13 +
14 + char *home = getenv("HOME");
15 + if (home)
16 + {
17 + const char *dest = nob_temp_sprintf("%s/.local/bin/pkger", home);
18 + if (!nob_copy_file ("pkger", dest)) return 1;
19 + nob_log (NOB_INFO, "installed to %s", dest);
20 + }
21 + return 0;
22 +}
+3399 −0 nob.h new file mode 100644 Large diff collapsed, click to expand
@@ -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, &quoted);
1573 + nob_sb_append_null(&quoted);
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 +*/
+0 −0 pkger new file mode 100755, Binary files /dev/null and b/pkger differ
+86 −84 pkger.c
@@ -6,7 +6,7 @@
6 #include <sys/stat.h> 6 #include <sys/stat.h>
7 #include <dirent.h> 7 #include <dirent.h>
8 #include <errno.h> 8 #include <errno.h>
9 -#include "repos.h" 9 +#include "recipe.h"
10 10
11 int 11 int
12 run_cmd (char *const argv[]) 12 run_cmd (char *const argv[])
@@ -44,96 +44,98 @@ run_shell_string (const char *cmd)
44 void 44 void
45 install_repo (const Repository *repo) 45 install_repo (const Repository *repo)
46 { 46 {
47 - printf ("=== Installing %s ===\n", repo->name); 47 + printf ("[INSTALL] %s\n", repo->name);
48 48
49 char repo_dir[256]; 49 char repo_dir[256];
50 - snprintf (repo_dir, sizeof (repo_dir), "/tmp/pkger-%s", repo->name); 50 + char *home = getenv("HOME");
51 + if (home) snprintf (repo_dir, sizeof (repo_dir), "%s/.cache/pkger/%s", home, repo->name);
51 52
52 - char fetch_str[512]; 53 + char fetch_str[512];
53 - snprintf (fetch_str, sizeof (fetch_str), repo->fetchFmt, repo->fetchCmd, repo->url, repo_dir); 54 + snprintf (fetch_str, sizeof (fetch_str), repo->fetchFmt, repo->fetchCmd,
54 - if (!run_shell_string (fetch_str)) 55 + repo->url, repo_dir);
55 - { 56 + if (!run_shell_string (fetch_str))
56 - fprintf (stderr, "[ERROR] Failed to fetch package.\n"); 57 + {
57 - return; 58 + fprintf (stderr, "[ERROR] Failed to fetch package.\n");
58 - } 59 + return;
59 - 60 + }
60 - if (chdir (repo_dir) != 0) 61 +
61 - { 62 + if (chdir (repo_dir) != 0)
62 - perror ("[ERROR] chdir failed"); 63 + {
63 - return; 64 + perror ("[ERROR] chdir failed");
64 - } 65 + return;
65 -
66 - if (!run_shell_string (repo->buildCmd))
67 - {
68 - fprintf (stderr, "Build failed.\n");
69 - return;
70 - }
71 -
72 - if (!run_shell_string (repo->installCmd))
73 - {
74 - fprintf (stderr, "Install failed.\n");
75 - return;
76 - }
77 -
78 - printf ("Successfully installed %s!\n", repo->name);
79 } 66 }
80 67
68 + if (!run_shell_string (repo->buildCmd))
69 + {
70 + fprintf (stderr, "Build failed.\n");
71 + return;
72 + }
73 +
74 + if (!run_shell_string (repo->installCmd))
75 + {
76 + fprintf (stderr, "Install failed.\n");
77 + return;
78 + }
79 +
80 + printf ("[INSTALL] installed %s\n", repo->name);
81 +}
82 +
81 void 83 void
82 uninstall_repo (const Repository *repo) 84 uninstall_repo (const Repository *repo)
83 - { 85 +{
84 - char repo_dir[256]; 86 + char repo_dir[256];
85 - snprintf (repo_dir, sizeof (repo_dir), "/tmp/pkger-%s", repo->name); 87 + snprintf (repo_dir, sizeof (repo_dir), "/tmp/pkger-%s", repo->name);
86 - 88 +
87 - if (chdir (repo_dir) != 0) 89 + if (chdir (repo_dir) != 0)
88 - { 90 + {
89 - perror ("[ERROR] chdir failed"); 91 + perror ("[ERROR] chdir failed");
90 - return; 92 + return;
91 - } 93 + }
92 - 94 +
93 - printf ("=== Uninstalling %s ===\n", repo->name); 95 + printf ("[UNINSTALL] %s\n", repo->name);
94 - if (!run_shell_string (repo->uninstallCmd)) 96 + if (!run_shell_string (repo->uninstallCmd))
95 - { 97 + {
96 - fprintf (stderr, "Uninstall failed.\n"); 98 + fprintf (stderr, "[ERROR] uninstall failed.\n");
97 - } 99 + }
98 - } 100 +}
99 101
100 int 102 int
101 main (int argc, char *argv[]) 103 main (int argc, char *argv[])
102 - { 104 +{
103 - if (argc < 3) 105 + if (argc < 3)
104 - { 106 + {
105 - printf ("Usage: %s <install|uninstall> <repo_name>\n", argv[0]); 107 + printf ("Usage: %s <install|uninstall> <repo_name>\n", argv[0]);
106 - printf ("\nAvailable repositories:\n"); 108 + printf ("\nAvailable packages:\n");
107 - for (size_t i = 0; i < REPO_COUNT; i++) 109 + for (size_t i = 0; i < REPO_COUNT; i++)
108 - { 110 + {
109 - printf (" - %s\n", REPOS[i].name); 111 + printf (" - %s\n", REPOS[i].name);
110 - } 112 + }
111 - return 1; 113 + return 1;
112 - } 114 + }
113 - 115 +
114 - const char *action = argv[1]; 116 + const char *action = argv[1];
115 - const char *target = argv[2]; 117 + const char *target = argv[2];
116 - 118 +
117 - for (size_t i = 0; i < REPO_COUNT; i++) 119 + for (size_t i = 0; i < REPO_COUNT; i++)
118 - { 120 + {
119 - if (strcmp (REPOS[i].name, target) == 0) 121 + if (strcmp (REPOS[i].name, target) == 0)
120 - { 122 + {
121 - if (strcmp (action, "install") == 0) 123 + if (strcmp (action, "install") == 0)
122 - { 124 + {
123 - install_repo (&REPOS[i]); 125 + install_repo (&REPOS[i]);
124 - } 126 + }
125 - else if (strcmp (action, "uninstall") == 0) 127 + else if (strcmp (action, "uninstall") == 0)
126 - { 128 + {
127 - uninstall_repo (&REPOS[i]); 129 + uninstall_repo (&REPOS[i]);
128 - } 130 + }
129 - else 131 + else
130 - { 132 + {
131 - fprintf (stderr, "Unknown action: %s\n", action); 133 + fprintf (stderr, "Unknown action: %s\n", action);
132 - } 134 + }
133 - return 0; 135 + return 0;
134 - } 136 + }
135 - } 137 + }
136 - 138 +
137 - fprintf (stderr, "Repository '%s' not found.\n", target); 139 + fprintf (stderr, "Repository '%s' not found.\n", target);
138 - return 1; 140 + return 1;
139 - } 141 +}
+2 −3 readme.org
@@ -1,4 +1,3 @@
1 -
2 #+TITLE: pkger 1 #+TITLE: pkger
3 #+AUTHOR: theidioticdev 2 #+AUTHOR: theidioticdev
4 3
@@ -21,8 +20,8 @@ it uses a suckless-like approach to installing pkgs by editiing =repos.h= like t
21 { 20 {
22 .name = "guix", 21 .name = "guix",
23 .url = "https://guix.gnu.org/install.sh", 22 .url = "https://guix.gnu.org/install.sh",
24 - .fetchFmt = "%s %s -P %s", 23 + .fetchFmt = "%s %s -P %s",
25 - .fetchCmd = "wget", 24 + .fetchCmd = "wget",
26 .buildCmd = "chmod +x install.sh", 25 .buildCmd = "chmod +x install.sh",
27 .installCmd = "./install.sh", 26 .installCmd = "./install.sh",
28 .uninstallCmd = "./install.sh --uninstall"} 27 .uninstallCmd = "./install.sh --uninstall"}
+32 −0 recipe.h new file mode 100644
@@ -0,0 +1,32 @@
1 +#ifndef REPOS_H
2 +#define REPOS_H
3 +
4 +typedef struct
5 +{
6 + const char *name;
7 + const char *url;
8 + const char *fetchCmd;
9 + const char *fetchFmt;
10 + const char *buildCmd;
11 + const char *installCmd;
12 + const char *uninstallCmd;
13 +} Repository;
14 +
15 +#include "recipes/emacs-noimg.h"
16 +#include "recipes/guix.h"
17 +#include "recipes/kilo.h"
18 +#include "recipes/sfetch.h"
19 +#include "recipes/oxwm.h"
20 +
21 +
22 +static const Repository REPOS[] =
23 + {
24 + emacs_noimg,
25 + guix,
26 + kilo,
27 + sfetch,
28 + oxwm,
29 + };
30 +
31 +static const size_t REPO_COUNT = sizeof (REPOS) / sizeof (REPOS[0]);
32 +#endif
+13 −0 recipes/emacs-noimg.h new file mode 100644
@@ -0,0 +1,13 @@
1 +
2 +#include "../recipe.h"
3 +static const Repository emacs_noimg =
4 + {
5 + .name = "emacs-no-img",
6 + .url = "https://ftpmirror.gnu.org/emacs/emacs-31.1.tar.gz",
7 + .fetchCmd = "wget",
8 + .fetchFmt = "%s %s -P %s",
9 +
10 + .buildCmd = "tar -xvf emacs-31.1.tar.gz && cd emacs-31.1 && ./configure --without-tree-sitter --without-gif --without-png --without-jpeg --without-rsvg --without-tiff --without-imagemagick && make -j$(nproc)",
11 + .installCmd = "cd emacs-31.1 && sudo make install",
12 + .uninstallCmd = "cd emacs-31.1 && sudo make uninstall"
13 + };
+13 −0 recipes/guix.h new file mode 100644
@@ -0,0 +1,13 @@
1 +#include "../recipe.h"
2 +
3 +static const Repository guix =
4 + {
5 + .name = "guix",
6 + .url = "https://guix.gnu.org/install.sh",
7 + .fetchFmt = "%s %s -P %s",
8 + .fetchCmd = "wget",
9 + .buildCmd = "chmod +x install.sh",
10 + .installCmd = "./install.sh",
11 + .uninstallCmd = "./install.sh --uninstall"
12 +
13 + };
+11 −0 recipes/kilo.h new file mode 100644
@@ -0,0 +1,11 @@
1 +#include "../recipe.h"
2 +static const Repository kilo =
3 + {
4 + .name = "kilo",
5 + .url = "https://github.com/antirez/kilo",
6 + .fetchCmd = "git clone",
7 + .fetchFmt = "%s %s %s",
8 + .buildCmd = "make",
9 + .installCmd = "sudo cp kilo /usr/local/bin/kilo",
10 + .uninstallCmd = "sudo rm /usr/local/bin/kilo"
11 + };
+12 −0 recipes/oxwm.h new file mode 100644
@@ -0,0 +1,12 @@
1 +#include "../recipe.h"
2 +
3 +static const Repository oxwm =
4 + {
5 + .name = "oxwm",
6 + .url = "https://github.com/tonybanters/oxwm",
7 + .fetchFmt = "%s %s %s",
8 + .fetchCmd = "git clone",
9 + .buildCmd = "zig build -Doptimize=ReleaseSmall",
10 + .installCmd = "zig build -Doptimize=ReleaseSmall --prefix /usr/local",
11 + .uninstallCmd = "rm /usr/local/bin/oxwm"
12 + };
+10 −0 recipes/sfetch.h new file mode 100644
@@ -0,0 +1,10 @@
1 +#include "../recipe.h"
2 +static const Repository sfetch = {
3 + .name = "sfetch",
4 + .url = "https://codeberg.org/theidioticdev/sfetch",
5 + .fetchFmt = "%s %s %s",
6 + .fetchCmd = "git clone",
7 + .buildCmd = "./build",
8 + .installCmd = "sudo ./build --install",
9 + .uninstallCmd = "sudo ./build --uninstall"
10 +};
+26 −20 repos.h
@@ -15,33 +15,39 @@ typedef struct
15 15
16 static const Repository REPOS[] = 16 static const Repository REPOS[] =
17 { 17 {
18 - /* add your repos here... */
19 { 18 {
20 - .name = "sfetch", 19 + .name = "oxwm",
21 - .url = "https://codeberg.org/theidioticdev/sfetch", 20 + .url = "https://github.com/tonybanters/oxwm",
22 - .fetchCmd = "git clone", 21 + .fetchFmt = "%s %s %s",
23 - /* command repo dir" */
24 - .fetchFmt = "%s %s %s",
25 - .buildCmd = "./build",
26 - .installCmd = "./build --install",
27 - .uninstallCmd = "./build --uninstall"},
28 - {
29 - .name = "lsa",
30 - .url = "https://git.sr.ht/~trondelag__/lsa",
31 .fetchCmd = "git clone", 22 .fetchCmd = "git clone",
32 - .fetchFmt = "%s %s %s", 23 + .buildCmd = "zig build -Doptimize=ReleaseSmall",
33 - .buildCmd = "cc -std=c89 lsa.c -o lsa", 24 + .installCmd = "zig build -Doptimize=ReleaseSmall --prefix /usr/local",
34 - .installCmd = "cp lsa /usr/local/bin/lsa", 25 + .uninstallCmd = "rm /usr/local/bin/oxwm"},
35 - .uninstallCmd = "rm lsa /usr/local/bin/lsa"},
36 { 26 {
37 .name = "guix", 27 .name = "guix",
38 .url = "https://guix.gnu.org/install.sh", 28 .url = "https://guix.gnu.org/install.sh",
39 - .fetchFmt = "%s %s -P %s", 29 + .fetchFmt = "%s %s -P %s",
40 - .fetchCmd = "wget", 30 + .fetchCmd = "wget",
41 .buildCmd = "chmod +x install.sh", 31 .buildCmd = "chmod +x install.sh",
42 .installCmd = "./install.sh", 32 .installCmd = "./install.sh",
43 - .uninstallCmd = "./install.sh --uninstall"} 33 + .uninstallCmd = "./install.sh --uninstall"},
34 + {
35 + .name = "kilo",
36 + .url = "https://github.com/antirez/kilo",
37 + .fetchCmd = "git clone",
38 + .fetchFmt = "%s %s %s",
39 + .buildCmd = "make",
40 + .installCmd = "cp kilo /usr/local/bin/kilo",
41 + .uninstallCmd = "rm /usr/local/bin/kilo"},
42 + {
43 + .name = "emacs",
44 + .url = "https://ftpmirror.gnu.org/emacs/emacs-31.1.tar.gz",
45 + .fetchCmd = "wget",
46 + .fetchFmt = "%s %s -P %s",
47 + .buildCmd = "tar -xvf emacs-31.1.tar.gz && cd emacs-31.1 && ./configure --with-x-toolkit=lucid && make -j$(nproc)",
48 + .installCmd = "cd emacs-31.1 && make install",
49 + .uninstallCmd = "cd emacs-31.1 && make uninstall"}
44 }; 50 };
45 51
46 -static const size_t REPO_COUNT = sizeof (REPOS) / sizeof (REPOS[0]); 52 +Static const size_t REPO_COUNT = sizeof (REPOS) / sizeof (REPOS[0]);
47 #endif 53 #endif