1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
# Cross-platform tool resolution and runtime compatibility guard.
#
# shuriken relies on GNU-only extensions of four standard Unix tools -- find
# -printf, stat -c, cp -a, and sort -R (see the "Platform compatibility"
# section of README.md). Those extensions are not supported by the BSD
# variants of the same tools shipped with macOS and FreeBSD. Rather than being
# Linux-only, shuriken resolves each of the four tools to a variable (FIND,
# STAT, CP, SORT) once at startup: on Linux the plain command is already GNU,
# and on macOS/FreeBSD the GNU version is normally installed side-by-side
# under a "g" prefix (gfind, gstat, gcp, gsort, via Homebrew/pkg -- see
# README.md) to avoid clobbering the system tools. This mirrors the
# $SED/$GREP/$DATE tool-selection variables in the sibling gemtexter project
# (github.com/snonux/gemtexter). Every call site elsewhere in the codebase
# that depends on the GNU-only behavior uses these variables instead of the
# bare command name; call sites that only need POSIX-portable behavior are
# left as plain "find"/"sort"/etc and are unaffected by any of this.
#
# require_gnu_tools (called from main, after CLI parsing and before
# run_action) verifies the resolved tools are genuinely GNU before any real
# work begins, so a misconfigured host fails fast with a clear error instead
# of failing confusingly deep inside generation.
# Resolve the binary name for a single bare coreutils/findutils tool name:
# the GNU-prefixed variant (e.g. "gfind" for "find") if one is on PATH,
# otherwise the bare name unchanged. On Linux a "g"-prefixed sibling is not
# normally installed, so this resolves to the bare (already-GNU) name; on
# macOS/FreeBSD it picks up the Homebrew/pkg-installed GNU version. No uname
# check is needed: preferring the g-prefixed binary when present is correct
# on every platform.
resolve_gnu_tool() {
local -r bare_name="$1"
if command -v "g$bare_name" >/dev/null 2>&1; then
printf '%s\n' "g$bare_name"
else
printf '%s\n' "$bare_name"
fi
}
FIND=$(resolve_gnu_tool find)
STAT=$(resolve_gnu_tool stat)
CP=$(resolve_gnu_tool cp)
SORT=$(resolve_gnu_tool sort)
readonly FIND STAT CP SORT
# Quick preflight: confirm each resolved tool self-reports as GNU before
# running the (slower) behavioral probes in require_gnu_tools. This gives a
# fast, specific error on a genuine BSD/macOS host that has no g-prefixed
# tools installed at all, naming exactly which tool and pointing at the
# README install instructions, rather than only failing on the deeper
# behavioral probe below. A tool whose --version output happens to lie (or
# omit a version string) still falls through to the behavioral probe, which
# is authoritative.
verify_gnu_tool_versions() {
local -r tool="$1"
local -r label="$2"
local version_output
version_output=$("$tool" --version 2>/dev/null || true)
if [[ "$version_output" != *GNU* ]]; then
printf 'ERROR: "%s" (%s) does not report itself as GNU.\n' \
"$tool" "$label" >&2
printf 'ERROR: Install GNU coreutils/findutils -- see the\n' >&2
printf 'ERROR: "Platform compatibility" section of README.md.\n' >&2
return 1
fi
}
# Runtime compatibility guard: verify the resolved tools (FIND/STAT/CP/SORT)
# actually behave like the GNU variants shuriken depends on. Each probe
# exercises the exact GNU-only behavior the codebase relies on (rather than
# parsing --help/--version text, which is unstable across implementations),
# so a tool that merely claims to be GNU but lacks the feature still fails
# here. Called from main, after CLI parsing and before run_action, so the
# check runs before any real work begins.
require_gnu_tools() {
local probe_dir
verify_gnu_tool_versions "$FIND" find || return 1
verify_gnu_tool_versions "$STAT" stat || return 1
verify_gnu_tool_versions "$CP" cp || return 1
verify_gnu_tool_versions "$SORT" sort || return 1
probe_dir=$(mktemp -d 2>/dev/null) || return 1
# shellcheck disable=SC2064 # expand probe_dir now, clean up on any return
trap "rm -rf '$probe_dir'" RETURN
probe_gnu_tool_behavior "$probe_dir"
}
# The behavioral half of require_gnu_tools, split out to keep each function
# under ~30 lines (per repo convention): probes FIND/STAT/CP/SORT's actual
# GNU-only behavior in a throwaway temp dir and reports via
# report_gnu_tool_failure on the first failure.
probe_gnu_tool_behavior() {
local -r probe_dir="$1"; shift
local probe_out
local failed=''
# GNU find supports the -printf action; BSD/macOS find does not.
probe_out=$("$FIND" "$probe_dir" -maxdepth 0 -printf '%f\n' 2>/dev/null) \
&& [ -n "$probe_out" ] || failed='find (missing the -printf action)'
# GNU stat uses -c FORMAT; BSD/macOS stat uses -f and rejects -c.
if [ -z "$failed" ]; then
printf 'probe\n' > "$probe_dir/file"
probe_out=$("$STAT" -c '%s' "$probe_dir/file" 2>/dev/null) \
&& [[ "$probe_out" =~ ^[0-9]+$ ]] \
|| failed='stat (missing the -c option)'
fi
# GNU cp supports the -a archive flag; minimal/older BSD cp variants lack it.
if [ -z "$failed" ]; then
mkdir "$probe_dir/src"
printf 'x\n' > "$probe_dir/src/inner"
if ! "$CP" -a "$probe_dir/src" "$probe_dir/dest" 2>/dev/null \
|| [ ! -f "$probe_dir/dest/inner" ]; then
failed='cp (missing the -a option)'
fi
fi
# GNU sort supports -R (random shuffle); BSD sort lacks it.
if [ -z "$failed" ]; then
probe_out=$(printf 'a\nb\nc\n' | "$SORT" -R 2>/dev/null) \
&& [ -n "$probe_out" ] || failed='sort (missing the -R option)'
fi
[ -z "$failed" ] || report_gnu_tool_failure "$failed"
}
# Shared error reporter for require_gnu_tools/probe_gnu_tool_behavior: prints
# the clear "install GNU tools" message naming the offending tool/feature and
# returns 1 (never exits directly, so callers stay in control of unwinding).
report_gnu_tool_failure() {
local -r failed="$1"; shift
printf 'ERROR: shuriken requires the GNU versions of the standard Unix\n' >&2
printf 'ERROR: tools (GNU coreutils/findutils). Non-GNU or unsupported: %s\n' \
"$failed" >&2
printf 'ERROR: On macOS/FreeBSD, install GNU coreutils/findutils (see the\n' >&2
printf 'ERROR: "Platform compatibility" section of README.md) and retry.\n' >&2
return 1
}
|