-
-
Notifications
You must be signed in to change notification settings - Fork 29
/
build
executable file
·427 lines (350 loc) · 13.2 KB
/
build
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
#!/usr/bin/env bash
# This script support Bash: 4.0+
set -o errexit -o nounset -o pipefail -o errtrace
(shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit
SCRIPT_PATH="$(realpath "$0")"
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
readonly SCRIPT_PATH SCRIPT_DIR
# shellcheck source=./tools/colors.bash
source "$SCRIPT_DIR/tools/colors.bash"
# shellcheck source=./src/load_internals.bash
source "$SCRIPT_DIR/src/load_internals.bash"
_lobash.import_internals basic_meta_types module_meta rm erase_line
_lobash.imports ask choose parse_args array_include union_array is_tty_available is_gnu_sed relative
SUPPORTED_BASH_VERISONS=(4.0 4.1 4.2 4.3 4.4)
if l.is_gnu_sed; then
sedi() { sed -i "$@"; }
else
sedi() { sed -i '' "$@"; }
fi
is_valid_lobash_prefix() {
local prefix=$1
[[ ${prefix%%.}. == "$prefix" ]] && return 0
[[ ${prefix%%-}- == "$prefix" ]] && return 0
[[ ${prefix%%_}_ == "$prefix" ]] && return 0
return 1
}
init_bash_min_version() {
if [[ -n ${BASHVER:-} ]]; then
BASH_MIN_VERSION=$BASHVER
echo -e "${GREY}Build Lobash for Bash ${BASH_MIN_VERSION}+${RESET_ALL}"
return
fi
local use_defualt
set +e
read -r -d '' text <<EOF
Default to generate Lobash for Bash 4.4+.
If you want to run Lobash with lower-version Bash, enter "N".
Use Lobash for Bash 4.4+?
EOF
set -e
if l.is_tty_available; then
use_defualt=$(l.ask "$text" Y)
else
use_defualt=YES
fi
if [[ $use_defualt == YES ]]; then
BASH_MIN_VERSION=4.4
echo -e "${GREY}YES. Build Lobash for Bash ${BASH_MIN_VERSION}+${RESET_ALL}"
elif [[ $use_defualt == NO ]]; then
echo -e "${GREY}NO.${RESET_ALL}"
echo "Choose the minimum version of Bash in which Lobash will run:"
BASH_MIN_VERSION=$(l.choose "${SUPPORTED_BASH_VERISONS[@]}")
echo -e "${GREY}Build Lobash for Bash ${BASH_MIN_VERSION}+${RESET_ALL}"
else
echo "Process terminated. Please enter YES or NO." >&2
return 4
fi
}
check_bash_version() {
if ! l.array_include SUPPORTED_BASH_VERISONS "$BASH_MIN_VERSION"; then
echo "Invalid BASH_MIN_VERSION: $BASH_MIN_VERSION" >&2
return 3
fi
}
declare -A inited_module
init_module() {
local module_name=$1
[[ -n ${inited_module[$module_name]:-} ]] && return
inited_module[$module_name]=true
module_names+=("$module_name")
_lobash.scan_module_metadata "$module_name"
local deps dep
deps=$(_lobash.get_module_metadata "$module_name" "Dependent")
if [[ -n "$deps" ]]; then
# shellcheck disable=2206
deps=(${deps//,/ })
for dep in "${deps[@]}"; do
init_module "$dep"
done
fi
}
init_with_config_file() {
read -r PREFIX < <(grep -E '^PREFIX:' "$CONFIG_PATH" | sed -E 's/^PREFIX: ?(.+)/\1/')
read -r BASH_MIN_VERSION < <(grep -E '^BASH_MIN_VERSION:' "$CONFIG_PATH" | sed -E 's/^BASH_MIN_VERSION: ?(.+)/\1/')
check_bash_version
echo -e "${GREY}Build Lobash for Bash ${BASH_MIN_VERSION}+${RESET_ALL}"
module_names=()
local category_names module_name
readarray -t category_names < <(grep -E '^- \[x\]' "$CONFIG_PATH" | sed -E 's/^- \[x\] +(.+)/\1/')
for category_name in "${category_names[@]}"; do
echo -e "${GREY}To import category ${category_name}${RESET_ALL}"
while read -r module_name; do
init_module "$module_name"
done <"$SCRIPT_DIR/src/internals/categories/${category_name,,}"
done
while read -r module_name; do
init_module "$module_name"
done < <(grep -E '^ - \[x\]' "$CONFIG_PATH" | sed -E 's/^ - \[x\] +(.+)/\1/')
# shellcheck disable=2207
module_names=($(l.union_array module_names))
}
# Supported functions: VERBOSE_1, VERBOSE_2, VERBOSE_3, VERBOSE_4
# Usage: VERBOSE_i <message>
# ./build -v=3
# ./build -v
init_verbose() {
if [[ -n ${opts[v]:-} ]]; then
if [[ ${opts[v]} == true ]]; then
VERBOSE=1
else
VERBOSE=${opts[v]}
fi
else
VERBOSE=0
fi
local i=1
while ((i <= 4)); do
if ((i <= VERBOSE)); then
eval "VERBOSE_$i() { printf '%b[v=$i][%s:%s] %s%b\n' \"\$GREY\" \"\${BASH_LINENO}\" \"\${FUNCNAME[1]}\" \"\$1\" \"\$RESET_ALL\"; }"
else
eval "VERBOSE_$i() { true; }"
fi
i=$((i + 1))
done
}
usage() {
cat <<EOF
Usage: $0 [OPTIONS] [<output>]
Options:
-h, --help Show usage.
-c, --config <filepath> Config file for building. If set, option "-p" will not work.
-p <prefix> Prefix of variable names. [Default: l.]
-y, --yes Overwrite output.
-v <level> Show verbose. 0 means off. [Value: 0~4] [Default: 0]
-m <BASHVER>, --min <BASHVER> Generated lobash supports the minimum version of Bash
Arguments:
<output> Output the lobash.bash file
EOF
}
# Reuse the last UNIQ_KEY if found
set_uniq_key() {
UNIQ_KEY=$(grep -E '^# UNIQ_KEY: ' "$TARGET" 2>/dev/null | sed -E 's/^# UNIQ_KEY: (.+)/\1/' || true)
if [[ -n $UNIQ_KEY ]]; then return 0; fi
# Lobash built by Lobash 0.4 has not "# UNIQ_KEY" label. So try to get it from "readonly _LOBASH_[_0-9]+_PREFIX="
UNIQ_KEY=$(grep -E '^readonly _LOBASH_[_0-9]+_PREFIX=' "$TARGET" 2>/dev/null | sed -E 's/^readonly _LOBASH_([-_0-9]+)_PREFIX=.+/\1/' || true)
if [[ -n $UNIQ_KEY ]]; then return 0; fi
# If not found UNIQ_KEY, generate one based on current lobash version and time.
UNIQ_KEY=${VERSION//[^[:alnum:]]/_}_$(($(date '+%s') - LOBASH_POUCH_TIME))_$RANDOM
}
init() {
if [[ ${opts[h]:-${opts[help]:-}} == true ]]; then
usage
exit 0
fi
init_verbose
if ((${#args[@]} == 0)); then
TARGET=$PWD/lobash.bash
else
if [[ -d ${args[0]} ]]; then
TARGET=${args[0]}/lobash.bash
else
TARGET=${args[0]}
fi
fi
PREFIX=${opts[p]:-${PREFIX:-l.}}
CONFIG_PATH=${opts[c]:-${opts[config]:-}}
BASHVER=${opts[m]:-${opts[min]:-${BASHVER:-}}}
if [[ -z ${CONFIG_PATH} ]]; then
init_bash_min_version
module_names=()
for path in "$SCRIPT_DIR"/src/modules/*.bash; do
module_name=$(basename "$path" .bash)
module_names+=("$module_name")
done
else
if [[ ! -f ${CONFIG_PATH} ]]; then
echo "Not found config file in path: $CONFIG_PATH" >&2
return 3
fi
echo "Found config: $CONFIG_PATH"
# Load PREFIX, BASH_MIN_VERSION and options from config
init_with_config_file
fi
PUBLIC_CONST_PREFIX=${PREFIX^^}
PUBLIC_CONST_PREFIX=${PUBLIC_CONST_PREFIX//[^[:alnum:]]/_}
VERBOSE_1 "BASH_MIN_VERSION=$BASH_MIN_VERSION PREFIX=$PREFIX PUBLIC_CONST_PREFIX=$PUBLIC_CONST_PREFIX TARGET=$TARGET"
VERBOSE_1 "To import modules: ${module_names[*]}"
if ! is_valid_lobash_prefix "$PREFIX"; then
echo -e "${RED}Invalid PREFIX=\"$PREFIX\". PREFIX must end with one of '_', '-', '.'${RESET_ALL}" >&2
return 4
fi
VERBOSE_1 "_LOBASH_OS=$_LOBASH_OS"
VERSION=$(cat "$SCRIPT_DIR/VERSION")
VERBOSE_1 "VERSION=$VERSION"
# LOBASH_POUCH_TIME must never change
LOBASH_POUCH_TIME=1561389473
set_uniq_key
VERBOSE_1 "UNIQ_KEY=$UNIQ_KEY"
if [[ $_LOBASH_OS == 'Linux' ]]; then
WORD_BOUNDARY='\b'
elif [[ $_LOBASH_OS == 'MacOS' ]]; then
WORD_BOUNDARY='[[:<:]]'
else
echo "Unexpected _LOBASH_OS=$_LOBASH_OS" >&2
exit 3
fi
VERBOSE_1 "WORD_BOUNDARY=$WORD_BOUNDARY"
VERBOSE_1 "_LOBASH_PUBLIC_CONST_PREFIX=$_LOBASH_PUBLIC_CONST_PREFIX"
}
clean() {
local OVERWRITE=${opts[y]:-${opts[yes]:-${OVERWRITE:-false}}}
if ! l.is_tty_available; then OVERWRITE=true; fi
VERBOSE_1 "To clean with OVERWRITE=${OVERWRITE}"
if [[ $OVERWRITE != true ]] && [[ -f $TARGET ]]; then
local answer
answer=$(l.ask "Existed file: ${TARGET}. Overwrite it?" N)
echo -e "${GREY}$answer${RESET_ALL}"
case $answer in
YES) ;;
*)
echo "Not overwrite it. No new Lobash file generated."
exit 0
;;
esac
fi
VERBOSE_1 "To rm $TARGET"
_lobash.rm "$TARGET"
VERBOSE_1 "Removed $TARGET"
mkdir -p "$(dirname "$TARGET")"
}
write() {
printf -- '%s\n' "$*" >>"$TARGET"
}
fwrite() {
local prefixes="${_LOBASH_INTERNAL_FUNC_PREFIX}|${_LOBASH_INTERNAL_CONST_PREFIX}|${_LOBASH_PRIVATE_FUNC_PREFIX}|${_LOBASH_PRIVATE_CONST_PREFIX}"
prefixes=${prefixes//\./\\.}
VERBOSE_2 "To read content from $1"
VERBOSE_3 "sed -E \"/^# /d;s/${WORD_BOUNDARY}($prefixes)([_a-zA-Z0-9]+)/\\1${UNIQ_KEY}_\\2/g\""
VERBOSE_3 "sed -E \"s/${WORD_BOUNDARY}${_LOBASH_PUBLIC_CONST_PREFIX}([_a-zA-Z0-9]+)/${PUBLIC_CONST_PREFIX}\\1/g\""
if [[ $PREFIX == 'l.' ]]; then
<"$1" sed -E "/^# /d;s/${WORD_BOUNDARY}($prefixes)([_a-zA-Z0-9]+)/\\1${UNIQ_KEY}_\\2/g" |
sed -E "s/${WORD_BOUNDARY}${_LOBASH_PUBLIC_CONST_PREFIX}([_a-zA-Z0-9]+)/${PUBLIC_CONST_PREFIX}\\1/g" \
>>"$TARGET"
else
VERBOSE_3 "sed -E \"s/${WORD_BOUNDARY}${_LOBASH_PUBLIC_FUNC_PREFIX//\./\\.}([_a-zA-Z0-9]+)/${PREFIX}\\1/g\""
<"$1" sed -E "/^# /d;s/${WORD_BOUNDARY}($prefixes)([_a-zA-Z0-9]+)/\\1${UNIQ_KEY}_\\2/g" |
sed -E "s/${WORD_BOUNDARY}${_LOBASH_PUBLIC_CONST_PREFIX}([_a-zA-Z0-9]+)/${PUBLIC_CONST_PREFIX}\\1/g" |
sed -E "s/${WORD_BOUNDARY}${_LOBASH_PUBLIC_FUNC_PREFIX//\./\\.}([_a-zA-Z0-9]+)/${PREFIX}\\1/g" \
>>"$TARGET"
fi
}
generate() {
VERBOSE_1 "To generate lobash file: $TARGET"
local VERSION_TAG git_commit
git_commit=$(git -C "$SCRIPT_DIR" rev-parse HEAD || true)
if [[ -n $git_commit ]]; then
VERSION_TAG="$VERSION ($git_commit)"
else
VERSION_TAG="$VERSION"
fi
local cmd
if [[ -z $CONFIG_PATH ]]; then
cmd="lobash-gen -p '$PREFIX' -m ${BASH_MIN_VERSION}${CONFIG_PATH:+ -c $CONFIG_PATH} $(basename "$TARGET")"
else
cmd="lobash-gen -c $(l.relative "$TARGET" "$CONFIG_PATH") $(basename "$TARGET")"
fi
VERBOSE_1 "To write header"
write '# This file is generated by https://github.com/adoyle-h/lobash'
write "# Command: $cmd"
write '# Author: ADoyle <[email protected]>'
write '# License: Apache License Version 2.0'
write "# Version: $VERSION_TAG"
write "# Prefix: $PREFIX"
write "# Bash Minimum Version: ${BASH_MIN_VERSION}"
write "# UNIQ_KEY: ${UNIQ_KEY}"
write "# Included Modules: ${module_names[*]}"
local text
if [[ ${BASH_MIN_VERSION} =~ ^4.(0|1|2|3)$ ]]; then
echo -e "${YELLOW}Note: Not all features are supported in Bash ${BASH_MIN_VERSION}.\\nPlease read this link: https://github.com/adoyle-h/lobash/blob/master/docs/with-lower-version-bash.md${RESET_ALL}"
write "#"
write "# Note: Not all features are supported in Bash ${BASH_MIN_VERSION}."
write "# Please read this link: https://github.com/adoyle-h/lobash/blob/master/docs/with-lower-version-bash.md"
fi
write ''
write '######################## Lobash Internals ########################'
VERBOSE_1 "To load src/internals/basic_internals.bash"
# shellcheck source=./src/internals/basic_internals.bash
source "$SCRIPT_DIR"/src/internals/basic_internals.bash
printf '%s\n' "Writing Lobash Internals..."
for name in "${_LOBASH_DIST_INTERNALS[@]}"; do
write ''
fwrite "$SCRIPT_DIR/src/internals/$name.bash"
done
VERBOSE_1 "To replace PREFIX=$PREFIX in file=$TARGET"
sedi -E "s/^readonly ${_LOBASH_INTERNAL_CONST_PREFIX}${UNIQ_KEY}_PUBLIC_FUNC_PREFIX=.+/readonly _LOBASH_${UNIQ_KEY}_PUBLIC_FUNC_PREFIX=$PREFIX/" "$TARGET"
sedi -E "s/^readonly ${_LOBASH_INTERNAL_CONST_PREFIX}${UNIQ_KEY}_PUBLIC_CONST_PREFIX=.+/readonly _LOBASH_${UNIQ_KEY}_PUBLIC_CONST_PREFIX=${PUBLIC_CONST_PREFIX}/" "$TARGET"
sedi -E "s/^readonly ${_LOBASH_INTERNAL_CONST_PREFIX}${UNIQ_KEY}_PREFIX=.+/readonly _LOBASH_${UNIQ_KEY}_PREFIX=$PREFIX/" "$TARGET"
sedi -E "s/^readonly ${_LOBASH_INTERNAL_CONST_PREFIX}${UNIQ_KEY}_MIN_BASHVER=.+/readonly _LOBASH_${UNIQ_KEY}_MIN_BASHVER=$BASH_MIN_VERSION/" "$TARGET"
write ''
write '######################## Module Methods ########################'
local compare module_name bashver
local -A skips
echo "Scanning Module Metadatas..."
for module_name in "${module_names[@]}"; do
_lobash.erase_line
printf '%s' "To scan module: $module_name"
_lobash.scan_module_metadata "$module_name"
bashver=$(_lobash.get_module_metadata "$module_name" "Bash")
compare=$(_lobash.semver_compare "$BASH_MIN_VERSION" "$bashver")
if ((compare < 0)); then
skips[$module_name]=true
fi
done
printf '\n%s\n' "Writing Module Contents..."
for module_name in "${module_names[@]}"; do
if [[ -z ${skips[$module_name]:-} ]]; then
fwrite "$SCRIPT_DIR/src/modules/$module_name.bash"
fi
done
write ''
write '######################## Skipped Modules ########################'
echo "Writing Skipped Modules..."
for module_name in "${module_names[@]}"; do
if [[ -n ${skips[$module_name]:-} ]]; then
bashver=$(_lobash.get_module_metadata "$module_name" "Bash")
echo -e "${YELLOW}Note${RESET_ALL}: Module '${CYAN}$module_name${RESET_ALL}' is not included in built file. It only supports Bash ${BLUE}${bashver}+${RESET_ALL}, and current Bash Minimum Version is ${BLUE}$BASH_MIN_VERSION${RESET_ALL}."
write "# Module '$module_name' is not included in built file. It only supports Bash ${bashver}+, and current Bash Minimum Version is $BASH_MIN_VERSION."
fi
done
set +u
local skip_count=${#skips[@]}
echo "Imported $((${#module_names[@]} - skip_count)) modules. Skipped ${skip_count} modules."
set -u
echo -e "Generated Lobash file: ${GREEN}$TARGET${RESET_ALL}"
}
declare -A opts=()
declare -a args=()
declare -A opts_def=(
[opts]=opts
[args]=args
[-y --yes]='bool'
[-h --help]='bool'
)
main() {
l.parse_args opts_def "$@"
init
clean
generate "$@"
}
main "$@"