mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2025-07-25 03:01:59 +00:00
🧑💻 Enhance build example scripts
This commit is contained in:
parent
4220491a23
commit
e0b045da49
3 changed files with 156 additions and 65 deletions
buildroot/bin
|
@ -2,16 +2,17 @@
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
#
|
#
|
||||||
# build_all_examples [-b|--branch=<branch>] - Branch to fetch from Configurations repo
|
# build_all_examples [-b|--branch=<branch>] - Branch to fetch from Configurations repo (import-2.1.x)
|
||||||
# [-c|--continue] - Continue the paused build
|
# [-c|--continue] - Continue the paused build
|
||||||
# [-d|--debug] - Print extra debug output
|
# [-p|--purge] - Purge the status file and start over
|
||||||
# [-i|--ini] - Archive ini/json/yml files in the temp config folder
|
# [-s|--skip] - Continue the paused build, skipping one
|
||||||
# [-l|--limit=#] - Limit the number of builds in this run
|
|
||||||
# [-n|--nobuild] - Don't actually build anything.
|
|
||||||
# [-r|--resume=<path>] - Start at some config in the filesystem order
|
# [-r|--resume=<path>] - Start at some config in the filesystem order
|
||||||
# [-s|--skip] - Do the thing
|
# [-e|--export=N] - Set CONFIG_EXPORT and export into each config folder
|
||||||
#
|
# [-d|--debug] - Print extra debug output (after)
|
||||||
# build_all_examples [...] branch [resume-from]
|
# [-l|--limit=#] - Limit the number of builds in this run
|
||||||
|
# [-n|--nobuild] - Don't actually build anything
|
||||||
|
# [-f|--nofail] - Don't stop on a failed build
|
||||||
|
# [-h|--help] - Print usage and exit
|
||||||
#
|
#
|
||||||
|
|
||||||
HERE=`dirname $0`
|
HERE=`dirname $0`
|
||||||
|
@ -21,12 +22,19 @@ HERE=`dirname $0`
|
||||||
GITREPO=https://github.com/MarlinFirmware/Configurations.git
|
GITREPO=https://github.com/MarlinFirmware/Configurations.git
|
||||||
STAT_FILE=./.pio/.buildall
|
STAT_FILE=./.pio/.buildall
|
||||||
|
|
||||||
usage() { echo "
|
usage() { echo "Usage:
|
||||||
Usage: $SELF [-b|--branch=<branch>] [-d|--debug] [-i|--ini] [-r|--resume=<path>]
|
|
||||||
$SELF [-b|--branch=<branch>] [-d|--debug] [-i|--ini] [-c|--continue]
|
build_all_examples [-b|--branch=<branch>] - Branch to fetch from Configurations repo (import-2.1.x)
|
||||||
$SELF [-b|--branch=<branch>] [-d|--debug] [-i|--ini] [-s|--skip]
|
[-c|--continue] - Continue the paused build
|
||||||
$SELF [-b|--branch=<branch>] [-d|--debug] [-n|--nobuild]
|
[-p|--purge] - Purge the status file and start over
|
||||||
$SELF [...] branch [resume-point]
|
[-s|--skip] - Continue the paused build, skipping one
|
||||||
|
[-r|--resume=<path>] - Start at some config in the filesystem order
|
||||||
|
[-e|--export=N] - Set CONFIG_EXPORT and export into each config folder
|
||||||
|
[-d|--debug] - Print extra debug output (after)
|
||||||
|
[-l|--limit=#] - Limit the number of builds in this run
|
||||||
|
[-n|--nobuild] - Don't actually build anything
|
||||||
|
[-f|--nofail] - Don't stop on a failed build
|
||||||
|
[-h|--help] - Print usage and exit
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,28 +44,32 @@ unset FIRST_CONF
|
||||||
EXIT_USAGE=
|
EXIT_USAGE=
|
||||||
LIMIT=1000
|
LIMIT=1000
|
||||||
|
|
||||||
while getopts 'b:cdhil:nqr:sv-:' OFLAG; do
|
while getopts 'b:ce:fdhl:npr:sv-:' OFLAG; do
|
||||||
case "${OFLAG}" in
|
case "${OFLAG}" in
|
||||||
b) BRANCH=$OPTARG ; bugout "Branch: $BRANCH" ;;
|
b) BRANCH=$OPTARG ; bugout "Branch: $BRANCH" ;;
|
||||||
r) FIRST_CONF="$OPTARG" ; bugout "Resume: $FIRST_CONF" ;;
|
f) NOFAIL=1 ; bugout "Continue on Fail" ;;
|
||||||
|
r) ISRES=1 ; FIRST_CONF="$OPTARG" ; bugout "Resume: $FIRST_CONF" ;;
|
||||||
c) CONTINUE=1 ; bugout "Continue" ;;
|
c) CONTINUE=1 ; bugout "Continue" ;;
|
||||||
s) CONTSKIP=1 ; bugout "Continue, skipping" ;;
|
s) CONTSKIP=1 ; bugout "Continue, skipping" ;;
|
||||||
i) COPY_INI=1 ; bugout "Archive INI/JSON/YML files" ;;
|
e) CEXPORT="$OPTARG" ; bugout "Export $CEXPORT" ;;
|
||||||
h) EXIT_USAGE=1 ; break ;;
|
h) EXIT_USAGE=1 ; break ;;
|
||||||
l) LIMIT=$OPTARG ; bugout "Limit to $LIMIT build(s)" ;;
|
l) LIMIT=$OPTARG ; bugout "Limit to $LIMIT build(s)" ;;
|
||||||
d|v) DEBUG=1 ; bugout "Debug ON" ;;
|
d|v) DEBUG=1 ; bugout "Debug ON" ;;
|
||||||
n) DRYRUN=1 ; bugout "Dry Run" ;;
|
n) DRYRUN=1 ; bugout "Dry Run" ;;
|
||||||
|
p) PURGE=1 ; bugout "Purge stat file" ;;
|
||||||
-) IFS="=" read -r ONAM OVAL <<< "$OPTARG"
|
-) IFS="=" read -r ONAM OVAL <<< "$OPTARG"
|
||||||
case "$ONAM" in
|
case "$ONAM" in
|
||||||
branch) BRANCH=$OVAL ; bugout "Branch: $BRANCH" ;;
|
branch) BRANCH=$OVAL ; bugout "Branch: $BRANCH" ;;
|
||||||
resume) FIRST_CONF="$OVAL" ; bugout "Resume: $FIRST_CONF" ;;
|
nofail) NOFAIL=1 ; bugout "Continue on Fail" ;;
|
||||||
|
resume) ISRES=1 ; FIRST_CONF="$OVAL" ; bugout "Resume: $FIRST_CONF" ;;
|
||||||
continue) CONTINUE=1 ; bugout "Continue" ;;
|
continue) CONTINUE=1 ; bugout "Continue" ;;
|
||||||
skip) CONTSKIP=2 ; bugout "Continue, skipping" ;;
|
skip) CONTSKIP=1 ; bugout "Continue, skipping" ;;
|
||||||
|
export) CEXPORT="$OVAL" ; bugout "Export $EXPORT" ;;
|
||||||
limit) LIMIT=$OVAL ; bugout "Limit to $LIMIT build(s)" ;;
|
limit) LIMIT=$OVAL ; bugout "Limit to $LIMIT build(s)" ;;
|
||||||
ini) COPY_INI=1 ; bugout "Archive INI/JSON/YML files" ;;
|
|
||||||
help) [[ -z "$OVAL" ]] || perror "option can't take value $OVAL" $ONAM ; EXIT_USAGE=1 ;;
|
help) [[ -z "$OVAL" ]] || perror "option can't take value $OVAL" $ONAM ; EXIT_USAGE=1 ;;
|
||||||
debug) DEBUG=1 ; bugout "Debug ON" ;;
|
debug) DEBUG=1 ; bugout "Debug ON" ;;
|
||||||
nobuild) DRYRUN=1 ; bugout "Dry Run" ;;
|
nobuild) DRYRUN=1 ; bugout "Dry Run" ;;
|
||||||
|
purge) PURGE=1 ; bugout "Purge stat file" ;;
|
||||||
*) EXIT_USAGE=2 ; echo "$SELF: unrecognized option \`--$ONAM'" ; break ;;
|
*) EXIT_USAGE=2 ; echo "$SELF: unrecognized option \`--$ONAM'" ; break ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
@ -65,21 +77,20 @@ while getopts 'b:cdhil:nqr:sv-:' OFLAG; do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Extra arguments count as BRANCH, FIRST_CONF
|
# Check for mixed continue, skip, resume arguments. Only one should be used.
|
||||||
shift $((OPTIND - 1))
|
((CONTINUE + CONTSKIP + ISRES + PURGE > 1)) && { echo "Don't mix -c, -p, -s, and -r options" ; echo ; EXIT_USAGE=2 ; }
|
||||||
[[ $# > 0 ]] && { BRANCH=$1 ; shift 1 ; bugout "BRANCH=$BRANCH" ; }
|
|
||||||
[[ $# > 0 ]] && { FIRST_CONF=$1 ; shift 1 ; bugout "FIRST_CONF=$FIRST_CONF" ; }
|
|
||||||
[[ $# > 0 ]] && { EXIT_USAGE=2 ; echo "too many arguments" ; }
|
|
||||||
|
|
||||||
|
# Exit with helpful usage information
|
||||||
((EXIT_USAGE)) && { usage ; let EXIT_USAGE-- ; exit $EXIT_USAGE ; }
|
((EXIT_USAGE)) && { usage ; let EXIT_USAGE-- ; exit $EXIT_USAGE ; }
|
||||||
|
|
||||||
echo "This script downloads each Configuration and attempts to build it."
|
echo
|
||||||
echo "On failure the last-built configs will be left in your working copy."
|
echo "This script downloads all example configs and attempts to build them."
|
||||||
|
echo "On failure the last-built configs are left in your working copy."
|
||||||
echo "Restore your configs with 'git checkout -f' or 'git reset --hard HEAD'."
|
echo "Restore your configs with 'git checkout -f' or 'git reset --hard HEAD'."
|
||||||
|
echo
|
||||||
|
|
||||||
if [[ -f "$STAT_FILE" ]]; then
|
[[ -n $PURGE ]] && rm -f "$STAT_FILE"
|
||||||
IFS='*' read BRANCH FIRST_CONF <"$STAT_FILE"
|
[[ -z $FIRST_CONF && -f $STAT_FILE ]] && IFS='*' read BRANCH FIRST_CONF <"$STAT_FILE"
|
||||||
fi
|
|
||||||
|
|
||||||
# If -c is given start from the last attempted build
|
# If -c is given start from the last attempted build
|
||||||
if ((CONTINUE)); then
|
if ((CONTINUE)); then
|
||||||
|
@ -97,9 +108,9 @@ elif ((CONTSKIP)); then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if the current repository has unmerged changes
|
# Check if the current repository has unmerged changes
|
||||||
if [[ $SKIP_CONF ]]; then
|
if ((SKIP_CONF)); then
|
||||||
echo "Skipping $FIRST_CONF"
|
echo "Skipping $FIRST_CONF"
|
||||||
elif [[ $FIRST_CONF ]]; then
|
elif [[ -n $FIRST_CONF ]]; then
|
||||||
echo "Resuming from $FIRST_CONF"
|
echo "Resuming from $FIRST_CONF"
|
||||||
else
|
else
|
||||||
git diff --quiet || { echo "The working copy is modified. Commit or stash changes before proceeding."; exit ; }
|
git diff --quiet || { echo "The working copy is modified. Commit or stash changes before proceeding."; exit ; }
|
||||||
|
@ -138,36 +149,30 @@ for CONF in $CONF_TREE ; do
|
||||||
# ...if skipping, don't build this one
|
# ...if skipping, don't build this one
|
||||||
compgen -G "${CONF}Con*.h" > /dev/null || continue
|
compgen -G "${CONF}Con*.h" > /dev/null || continue
|
||||||
|
|
||||||
|
# Command arguments for 'build_example'
|
||||||
|
CARGS=("-b" "$TMP" "-c" "$DIR")
|
||||||
|
|
||||||
|
# Exporting? Add -e argument
|
||||||
|
((CEXPORT)) && CARGS+=("-e" "$CEXPORT")
|
||||||
|
|
||||||
|
# Continue on fail? Add -n argument
|
||||||
|
((NOFAIL)) && CARGS+=("-n")
|
||||||
|
|
||||||
# Build or print build command for --nobuild
|
# Build or print build command for --nobuild
|
||||||
if [[ $DRYRUN ]]; then
|
if [[ $DRYRUN ]]; then
|
||||||
echo -e "\033[0;32m[DRYRUN] build_example internal \"$TMP\" \"$DIR\"\033[0m"
|
echo -e "\033[0;32m[DRYRUN] build_example ${CARGS[@]}\033[0m"
|
||||||
else
|
else
|
||||||
# Remember where we are in case of failure
|
# Remember where we are in case of failure
|
||||||
echo "${BRANCH}*${DIR}" >"$STAT_FILE"
|
echo "${BRANCH}*${DIR}" >"$STAT_FILE"
|
||||||
# Build folder is unknown so delete all report files
|
((DEBUG)) && echo "\"$HERE/build_example\" ${CARGS[@]}"
|
||||||
if [[ $COPY_INI ]]; then
|
"$HERE"/build_example "${CARGS[@]}" || { echo "Failed to build $DIR" ; exit ; }
|
||||||
IFIND='find ./.pio/build/ -name "config.ini" -o -name "schema.json" -o -name "schema.yml"'
|
|
||||||
$IFIND -exec rm "{}" \;
|
|
||||||
fi
|
|
||||||
((DEBUG)) && echo "\"$HERE/build_example\" internal \"$TMP\" \"$DIR\""
|
|
||||||
"$HERE/build_example" internal "$TMP" "$DIR" || { echo "Failed to build $DIR"; exit ; }
|
|
||||||
# Build folder is unknown so copy all report files
|
|
||||||
[[ $COPY_INI ]] && $IFIND -exec cp "{}" "$CONF" \;
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
((--LIMIT)) || { echo "Limit reached" ; PAUSE=1 ; break ; }
|
((--LIMIT)) || { echo "Limit reached" ; PAUSE=1 ; break ; }
|
||||||
|
|
||||||
|
echo ; echo
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Delete the build state if not paused early
|
# Delete the build state if not paused early
|
||||||
[[ $PAUSE ]] || rm "$STAT_FILE"
|
[[ $PAUSE ]] || rm "$STAT_FILE"
|
||||||
|
|
||||||
# Delete the temp folder if not preserving generated INI files
|
|
||||||
if [[ -e "$TMP/config/examples" ]]; then
|
|
||||||
if [[ $COPY_INI ]]; then
|
|
||||||
OPEN=$( which gnome-open xdg-open open | head -n1 )
|
|
||||||
$OPEN "$TMP"
|
|
||||||
elif [[ ! $PAUSE ]]; then
|
|
||||||
rm -rf "$TMP"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
|
@ -1,26 +1,69 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# build_example
|
# Usage:
|
||||||
#
|
#
|
||||||
# Usage: build_example internal config-home config-folder
|
# build_example -b|--base=<folder> - Configurations root folder (e.g., ./.pio/build-BRANCH)
|
||||||
|
# -c|--config=<path> - Path of the configs to build (within config/examples)
|
||||||
|
# [-e|--export=N] - Set CONFIG_EXPORT before build and export into the config folder
|
||||||
|
# [-n|--nofail] - Don't stop on a failed build
|
||||||
|
# [-r|--reveal] - Reveal the config folder after the build
|
||||||
|
# [-h|--help] - Print usage and exit
|
||||||
|
# [--allow] - Allow this script to run standalone
|
||||||
#
|
#
|
||||||
|
|
||||||
HERE=`dirname $0`
|
HERE=`dirname $0`
|
||||||
|
|
||||||
. "$HERE/mfutil"
|
. "$HERE/mfutil"
|
||||||
|
|
||||||
# Require 'internal' as the first argument
|
# Get arguments
|
||||||
[[ "$1" == "internal" ]] || { echo "Don't call this script directly, use build_all_examples instead." ; exit 1 ; }
|
CLEANER=1
|
||||||
|
ALLOW=""
|
||||||
|
BASE=""
|
||||||
|
CONFIG=""
|
||||||
|
REVEAL=""
|
||||||
|
EXPNUM=""
|
||||||
|
NOFAIL=""
|
||||||
|
while getopts 'b:c:e:hinr-:' OFLAG; do
|
||||||
|
case "${OFLAG}" in
|
||||||
|
b) BASE="$OPTARG" ;;
|
||||||
|
c) CONFIG="$OPTARG" ;;
|
||||||
|
e) EXPNUM="$OPTARG" ;;
|
||||||
|
h) EXIT_USAGE=1 ; break ;;
|
||||||
|
n) NOFAIL=1 ;;
|
||||||
|
r) REVEAL=1 ;;
|
||||||
|
-) IFS="=" read -r ONAM OVAL <<< "$OPTARG"
|
||||||
|
case "$ONAM" in
|
||||||
|
allow) ALLOW=1 ;;
|
||||||
|
base) BASE="$OVAL" ;;
|
||||||
|
config) CONFIG="$OVAL" ;;
|
||||||
|
export) EXPNUM="$OVAL" ;;
|
||||||
|
help) EXIT_USAGE=1 ; break ;;
|
||||||
|
nofail) NOFAIL=1 ;;
|
||||||
|
reveal) REVEAL=1 ;;
|
||||||
|
*) EXIT_USAGE=2 ; echo "$SELF: unrecognized option \`--$ONAM'" ; break ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
echo "Testing $3:"
|
[[ $ALLOW || $SHLVL -gt 2 ]] || { echo "Don't call this script directly, use build_all_examples instead." ; exit 1 ; }
|
||||||
|
|
||||||
SUB=$2/config/examples/$3
|
# Make sure the examples exist
|
||||||
[[ -d "$SUB" ]] || { echo "$SUB is not a good path" ; exit 1 ; }
|
SUB1="$BASE/config/examples"
|
||||||
|
[[ -d "$SUB1" ]] || { echo "--base $BASE doesn't contain config/examples" ; exit 1 ; }
|
||||||
|
|
||||||
|
# Make sure the specific config folder exists
|
||||||
|
SUB=$SUB1/$CONFIG
|
||||||
|
[[ -d "$SUB" ]] || { echo "--config $CONFIG doesn't exist" ; exit 1 ; }
|
||||||
|
|
||||||
compgen -G "${SUB}Con*.h" > /dev/null || { echo "No configuration files found in $SUB" ; exit 1 ; }
|
compgen -G "${SUB}Con*.h" > /dev/null || { echo "No configuration files found in $SUB" ; exit 1 ; }
|
||||||
|
|
||||||
|
# Delete any previous exported configs
|
||||||
|
rm -f Marlin/Config.h Marlin/Config-export.h
|
||||||
|
|
||||||
echo "Getting configuration files from $SUB"
|
echo "Getting configuration files from $SUB"
|
||||||
cp "$2/config/default"/*.h Marlin/
|
cp "$BASE/config/default"/*.h Marlin/
|
||||||
|
cp "$SUB"/Config.h Marlin/ 2>/dev/null
|
||||||
cp "$SUB"/Configuration.h Marlin/ 2>/dev/null
|
cp "$SUB"/Configuration.h Marlin/ 2>/dev/null
|
||||||
cp "$SUB"/Configuration_adv.h Marlin/ 2>/dev/null
|
cp "$SUB"/Configuration_adv.h Marlin/ 2>/dev/null
|
||||||
cp "$SUB"/_Bootscreen.h Marlin/ 2>/dev/null
|
cp "$SUB"/_Bootscreen.h Marlin/ 2>/dev/null
|
||||||
|
@ -35,9 +78,52 @@ rm Marlin/Configuration.h~
|
||||||
unset IFS; set +f
|
unset IFS; set +f
|
||||||
|
|
||||||
# Suppress fatal warnings
|
# Suppress fatal warnings
|
||||||
echo -e "\n#define NO_CONTROLLER_CUSTOM_WIRING_WARNING" >> Marlin/Configuration.h
|
if ((CLEANER)); then
|
||||||
|
opt_add NO_CONTROLLER_CUSTOM_WIRING_WARNING
|
||||||
|
opt_add NO_AUTO_ASSIGN_WARNING
|
||||||
|
opt_add NO_CREALITY_DRIVER_WARNING
|
||||||
|
opt_add DIAG_JUMPERS_REMOVED
|
||||||
|
opt_add DIAG_PINS_REMOVED
|
||||||
|
opt_add NO_MK3_FAN_PINS_WARNING
|
||||||
|
opt_add NO_USER_FEEDBACK_WARNING
|
||||||
|
opt_add NO_Z_SAFE_HOMING_WARNING
|
||||||
|
opt_add NO_LCD_CONTRAST_WARNING
|
||||||
|
opt_add NO_MICROPROBE_WARNING
|
||||||
|
opt_add NO_CONFIGURATION_EMBEDDING_WARNING
|
||||||
|
fi
|
||||||
|
|
||||||
|
FNAME=("-name" "marlin_config.json" \
|
||||||
|
"-o" "-name" "config.ini" \
|
||||||
|
"-o" "-name" "schema.json" \
|
||||||
|
"-o" "-name" "schema.yml")
|
||||||
|
|
||||||
|
# If EXPNUM is set then apply to the config before build
|
||||||
|
if [[ $EXPNUM ]]; then
|
||||||
|
opt_set CONFIG_EXPORT $EXPNUM
|
||||||
|
# Clean up old exports
|
||||||
|
find ./.pio/build \( "${FNAME[@]}" \) -exec rm "{}" \;
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
echo "Building the firmware now..."
|
echo "Building the firmware now..."
|
||||||
"$HERE/mftest" -s -a -n1 || { echo "Failed"; exit 1; }
|
"$HERE/mftest" -s -a -n1 ; ERR=$?
|
||||||
|
|
||||||
echo "Success"
|
[[ $ERR -eq 0 ]] && echo "Success" || echo "Failed"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Copy exports back to the configs
|
||||||
|
if [[ $EXPNUM ]]; then
|
||||||
|
echo "Exporting $EXPNUM"
|
||||||
|
[[ -f Marlin/Config-export.h ]] && { cp Marlin/Config-export.h "$SUB"/Config.h ; }
|
||||||
|
find ./.pio/build/ "${FNAME[@]}" -exec cp "{}" "$SUB" \;
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exit with error unless --nofail is set
|
||||||
|
[[ $ERR -gt 0 && -z $NOFAIL ]] && exit $ERR
|
||||||
|
|
||||||
|
# Reveal the configs after the build, if requested
|
||||||
|
((REVEAL)) && { echo "Revealing $SUB" ; open "$SUB" ; }
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
[[ -d Marlin/src ]] || { echo "Please 'cd' to the Marlin repo root." ; exit 1 ; }
|
[[ -d Marlin/src ]] || { echo "Please 'cd' to the Marlin repo root." ; exit 1 ; }
|
||||||
|
|
||||||
which pio || { echo "Make sure 'pio' is in your execution PATH." ; exit 1 ; }
|
which pio >/dev/null || { echo "Make sure 'pio' is in your execution PATH." ; exit 1 ; }
|
||||||
|
|
||||||
perror() { echo -e "$0: \033[0;31m$1 -- $2\033[0m" ; }
|
perror() { echo -e "$0: \033[0;31m$1 -- $2\033[0m" ; }
|
||||||
errout() { echo -e "\033[0;31m$1\033[0m" ; }
|
errout() { echo -e "\033[0;31m$1\033[0m" ; }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue