git subrepo pull (merge) --force deps/libchdr
[pcsx_rearmed.git] / deps / libchdr / deps / zstd-1.5.5 / build / single_file_libs / combine.sh
CommitLineData
648db22b 1#!/bin/sh -e
2
3# Tool to bundle multiple C/C++ source files, inlining any includes.
4#
5# TODO: ROOTS, FOUND, etc., as arrays (since they fail on paths with spaces)
6#
7# Author: Carl Woffenden, Numfum GmbH (this script is released under a CC0 license/Public Domain)
8
9# Common file roots
10ROOTS="."
11
12# -x option excluded includes
13XINCS=""
14
15# -k option includes to keep as include directives
16KINCS=""
17
18# Files previously visited
19FOUND=""
20
21# Optional destination file (empty string to write to stdout)
22DESTN=""
23
24# Whether the "#pragma once" directives should be written to the output
25PONCE=0
26
27# Prints the script usage then exits
28usage() {
29 echo "Usage: $0 [-r <path>] [-x <header>] [-k <header>] [-o <outfile>] infile"
30 echo " -r file root search path"
31 echo " -x file to completely exclude from inlining"
32 echo " -k file to exclude from inlining but keep the include directive"
33 echo " -p keep any '#pragma once' directives (removed by default)"
34 echo " -o output file (otherwise stdout)"
35 echo "Example: $0 -r ../my/path - r ../other/path -o out.c in.c"
36 exit 1
37}
38
39# Tests that the grep implementation works as expected (older OSX grep fails)
40test_deps() {
41 if ! echo '#include "foo"' | grep -Eq '^\s*#\s*include\s*".+"'; then
42 echo "Aborting: the grep implementation fails to parse include lines"
43 exit 1
44 fi
45 if ! echo '"foo.h"' | sed -E 's/"([^"]+)"/\1/' | grep -Eq '^foo\.h$'; then
46 echo "Aborting: sed is unavailable or non-functional"
47 exit 1
48 fi
49}
50
51# Test if glob pattern $1 matches subject $2 (see fnmatch(3))
52fnmatch() {
53 case "$2" in
54 $1)
55 return 0
56 ;;
57 esac
58 return 1
59}
60
61# Test if line $1 is local include directive
62is_include_line() {
63 fnmatch "*#*include*" "$1" || return 1
64 printf "%s\n" "$1" | grep -Eq '^\s*#\s*include\s*".+"'
65}
66
67# Test if line $1 is pragma once directive
68is_pragma_once_line() {
69 fnmatch "*#*pragma*once*" "$1" || return 1
70 printf "%s\n" "$1" | grep -Eq '^\s*#\s*pragma\s*once\s*'
71}
72
73# Tests if list $1 has item $2 (returning zero on a match)
74# (originally used grep -Eq "(^|\s*)$2(\$|\s*))
75readonly list_FS="$IFS"
76list_has_item() {
77 # Re: escaping glob pattern special characters in item string:
78 #
79 # bash (tested 3.2.57, 5.1.4), dash (tested 0.5.10.2), NetBSD /bin/sh
80 # (tested 8.2), and Solaris /bin/sh (tested 11.4) require escaping
81 # backslashes in a bracket expression despite POSIX specifying that
82 # backslash loses significance in a bracket expression.
83 #
84 # Conversely, neither FreeBSD /bin/sh (tested 12.2) nor OpenBSD /bin/sh
85 # (tested 7.1) obey backslash-escaping in case statement patterns even
86 # outside bracket expressions, so escape special characters using bracket
87 # expressions.
88 #
89 # Solaris /bin/sh (tested 11.4) requires vertical bar (|) to be escaped.
90 #
91 # All accommodations should behave as expected under strict POSIX semantics.
92 if fnmatch "*[\\*?[|]*" "$2"; then
93 set -- "$1" "$(printf '%s\n' "$2" | sed -e 's/[*?[|]/[&]/g; s/[\]/[\\&]/g')"
94 fi
95 for item_P in "*[$list_FS]$2[$list_FS]*" "*[$list_FS]$2" "$2[$list_FS]*" "$2"; do
96 fnmatch "${item_P}" "$1" && return 0
97 done
98 return 1
99}
100
101# Adds a new line with the supplied arguments to $DESTN (or stdout)
102write_line() {
103 if [ -n "$DESTN" ]; then
104 printf '%s\n' "$@" >> "$DESTN"
105 else
106 printf '%s\n' "$@"
107 fi
108}
109
110log_line() {
111 echo $@ >&2
112}
113
114# Find this file!
115resolve_include() {
116 local srcdir=$1
117 local inc=$2
118 for root in $srcdir $ROOTS; do
119 if [ -f "$root/$inc" ]; then
120 # Try to reduce the file path into a canonical form (so that multiple)
121 # includes of the same file are successfully deduplicated, even if they
122 # are expressed differently.
123 local relpath="$(realpath --relative-to . "$root/$inc" 2>/dev/null)"
124 if [ "$relpath" != "" ]; then # not all realpaths support --relative-to
125 echo "$relpath"
126 return 0
127 fi
128 local relpath="$(realpath "$root/$inc" 2>/dev/null)"
129 if [ "$relpath" != "" ]; then # not all distros have realpath...
130 echo "$relpath"
131 return 0
132 fi
133 # Fallback on Python to reduce the path if the above fails.
134 local relpath=$(python -c "import os,sys; print os.path.relpath(sys.argv[1])" "$root/$inc" 2>/dev/null)
135 if [ "$relpath" != "" ]; then # not all distros have realpath...
136 echo "$relpath"
137 return 0
138 fi
139 # Worst case, fall back to just the root + relative include path. The
140 # problem with this is that it is possible to emit multiple different
141 # resolved paths to the same file, depending on exactly how its included.
142 # Since the main loop below keeps a list of the resolved paths it's
143 # already included, in order to avoid repeated includes, this failure to
144 # produce a canonical/reduced path can lead to multiple inclusions of the
145 # same file. But it seems like the resulting single file library still
146 # works (hurray include guards!), so I guess it's ok.
147 echo "$root/$inc"
148 return 0
149 fi
150 done
151 return 1
152}
153
154# Adds the contents of $1 with any of its includes inlined
155add_file() {
156 local file=$1
157 if [ -n "$file" ]; then
158 log_line "Processing: $file"
159 # Get directory of the current so we can resolve relative includes
160 local srcdir="$(dirname "$file")"
161 # Read the file
162 local line=
163 while IFS= read -r line; do
164 if is_include_line "$line"; then
165 # We have an include directive so strip the (first) file
166 local inc=$(echo "$line" | grep -Eo '".*"' | sed -E 's/"([^"]+)"/\1/' | head -1)
167 local res_inc="$(resolve_include "$srcdir" "$inc")"
168 if list_has_item "$XINCS" "$inc"; then
169 # The file was excluded so error if the source attempts to use it
170 write_line "#error Using excluded file: $inc (re-amalgamate source to fix)"
171 log_line "Excluding: $inc"
172 else
173 if ! list_has_item "$FOUND" "$res_inc"; then
174 # The file was not previously encountered
175 FOUND="$FOUND $res_inc"
176 if list_has_item "$KINCS" "$inc"; then
177 # But the include was flagged to keep as included
178 write_line "/**** *NOT* inlining $inc ****/"
179 write_line "$line"
180 log_line "Not Inlining: $inc"
181 else
182 # The file was neither excluded nor seen before so inline it
183 write_line "/**** start inlining $inc ****/"
184 add_file "$res_inc"
185 write_line "/**** ended inlining $inc ****/"
186 fi
187 else
188 write_line "/**** skipping file: $inc ****/"
189 fi
190 fi
191 else
192 # Skip any 'pragma once' directives, otherwise write the source line
193 local write=$PONCE
194 if [ $write -eq 0 ]; then
195 if ! is_pragma_once_line "$line"; then
196 write=1
197 fi
198 fi
199 if [ $write -ne 0 ]; then
200 write_line "$line"
201 fi
202 fi
203 done < "$file"
204 else
205 write_line "#error Unable to find \"$1\""
206 log_line "Error: Unable to find: \"$1\""
207 fi
208}
209
210while getopts ":r:x:k:po:" opts; do
211 case $opts in
212 r)
213 ROOTS="$ROOTS $OPTARG"
214 ;;
215 x)
216 XINCS="$XINCS $OPTARG"
217 ;;
218 k)
219 KINCS="$KINCS $OPTARG"
220 ;;
221 p)
222 PONCE=1
223 ;;
224 o)
225 DESTN="$OPTARG"
226 ;;
227 *)
228 usage
229 ;;
230 esac
231done
232shift $((OPTIND-1))
233
234if [ -n "$1" ]; then
235 if [ -f "$1" ]; then
236 if [ -n "$DESTN" ]; then
237 printf "" > "$DESTN"
238 fi
239 test_deps
240 log_line "Processing using the slower shell script; this might take a while"
241 add_file "$1"
242 else
243 echo "Input file not found: \"$1\""
244 exit 1
245 fi
246else
247 usage
248fi
249exit 0