-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmake_single_header.sh
More file actions
executable file
·158 lines (133 loc) · 5.17 KB
/
make_single_header.sh
File metadata and controls
executable file
·158 lines (133 loc) · 5.17 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env bash
# make_single_header.sh
# Generates single_include/gaia.h from include/gaia.h.
#
# Uses a pure shell include-walker: only #include directives that resolve to a
# file under include/ are inlined. Everything else — system headers, external
# dependencies, preprocessor definitions, conditionals — passes through verbatim.
#
# The output is then formatted with clang-format using the project's
# .clang-format config (auto-discovered via --style=file).
#
# Usage:
# ./make_single_header.sh [clang-format-executable]
#
# Examples:
# ./make_single_header.sh # auto-detects clang-format on PATH
# ./make_single_header.sh clang-format-17
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"
INPUT="$REPO_ROOT/include/gaia.h"
OUTPUT="$REPO_ROOT/single_include/gaia.h"
INCLUDE_DIR="$REPO_ROOT/include"
# ---------------------------------------------------------------------------
# Resolve clang-format
# ---------------------------------------------------------------------------
resolve_clang_format() {
local arg="${1:-}"
if [[ -n "$arg" ]]; then
if ! command -v "$arg" &>/dev/null; then
echo "ERROR: '$arg' not found on PATH." >&2; exit 1
fi
CLANG_FORMAT="$arg"
return
fi
for candidate in clang-format clang-format-19 clang-format-18 clang-format-17; do
if command -v "$candidate" &>/dev/null; then
CLANG_FORMAT="$candidate"
return
fi
done
echo "ERROR: clang-format not found. Install clang-format and make sure it is on PATH." >&2
exit 1
}
# ---------------------------------------------------------------------------
# Shell include-walker
#
# Rules:
# - #include "..." or #include <...> that resolves under INCLUDE_DIR -> inline
# - Everything else passes through verbatim (system headers, #define, #if, etc.)
# - #pragma once stripped from inlined files (output has its own at the top)
# - Already-visited files skipped (honours #pragma once semantics)
# ---------------------------------------------------------------------------
walker() {
local file
file="$(realpath "$1")"
# Already visited — skip (pragma once semantics)
if grep -qF "$file" "$VISITED_FILE" 2>/dev/null; then
return
fi
echo "$file" >> "$VISITED_FILE"
local dir
dir="$(dirname "$file")"
while IFS= read -r line || [[ -n "$line" ]]; do
# Strip #pragma once from inlined files
if [[ "$line" =~ ^[[:space:]]*#[[:space:]]*pragma[[:space:]]+once[[:space:]]*$ ]]; then
continue
fi
# Check for a #include directive
if [[ "$line" =~ ^[[:space:]]*#[[:space:]]*include[[:space:]]*([\"<])([^\"\>]+)[\"\>] ]]; then
local delimiter="${BASH_REMATCH[1]}"
local inc="${BASH_REMATCH[2]}"
local resolved=""
# Angle-bracket includes (<...>) are always system/external — never inline.
if [[ "$delimiter" == "<" ]]; then
echo "$line"
continue
fi
# Quoted includes ("...") — attempt to resolve inside INCLUDE_DIR.
# 1. Relative to the current file
if [[ -f "$dir/$inc" ]]; then
local cand
cand="$(realpath "$dir/$inc")"
[[ "$cand" == "$INCLUDE_DIR"* ]] && resolved="$cand"
fi
# 2. Relative to INCLUDE_DIR
if [[ -z "$resolved" && -f "$INCLUDE_DIR/$inc" ]]; then
resolved="$(realpath "$INCLUDE_DIR/$inc")"
fi
# 3. Recursive search under INCLUDE_DIR by basename.
# Catches cases where a header is included by filename only (e.g.
# "version.h") but lives at a subdirectory path inside INCLUDE_DIR
# (e.g. include/gaia/config/version.h).
if [[ -z "$resolved" ]]; then
local base="${inc##*/}"
local found
found="$(find "$INCLUDE_DIR" -name "$base" 2>/dev/null | head -1)"
if [[ -n "$found" ]]; then
resolved="$(realpath "$found")"
fi
fi
if [[ -n "$resolved" ]]; then
walker "$resolved" # internal -> recurse
else
echo "$line" # external -> keep verbatim
fi
else
echo "$line" # everything else -> keep verbatim
fi
done < "$file"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
CLANG_FORMAT=""
VISITED_FILE=""
resolve_clang_format "${1:-}"
VISITED_FILE="$(mktemp)"
trap 'rm -f "$VISITED_FILE"' EXIT
echo "Input : $INPUT"
echo "Output : $OUTPUT"
echo "clang-format : $CLANG_FORMAT"
mkdir -p "$(dirname "$OUTPUT")"
rm -f "$OUTPUT"
{
echo "// Amalgamated single-header build of $(basename "$INPUT")"
echo "// Generated by make_single_header.sh — do not edit manually."
echo "#pragma once"
echo ""
walker "$INPUT"
} > "$OUTPUT"
echo "Formatting : $OUTPUT"
"$CLANG_FORMAT" -i --style=file "$OUTPUT"
echo "Done: $OUTPUT ($(wc -l < "$OUTPUT") lines)"