# Copyright 1999-2009 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: /var/cvsroot/gentoo-x86/eclass/flag-o-matic.eclass,v 1.134 2009/04/05 08:22:29 grobian Exp $ # @ECLASS: flag-o-matic.eclass # @MAINTAINER: # toolchain@gentoo.org # @BLURB: common functions to manipulate and query toolchain flags # @DESCRIPTION: # This eclass contains a suite of functions to help developers sanely # and safely manage toolchain flags in their builds. inherit eutils toolchain-funcs multilib ################ DEPRECATED functions ################ # The following are still present to avoid breaking existing # code more than necessary; however they are deprecated. Please # use gcc-specs-* from toolchain-funcs.eclass instead, if you # need to know which hardened techs are active in the compiler. # See bug #100974 # # has_hardened # has_pie # has_pic # has_ssp_all # has_ssp # {C,CXX,F,FC}FLAGS that we allow in strip-flags # Note: shell globs and character lists are allowed setup-allowed-flags() { if [[ -z ${ALLOWED_FLAGS} ]] ; then export ALLOWED_FLAGS="-pipe" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -O -O0 -O1 -O2 -mcpu -march -mtune" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -fstack-protector -fstack-protector-all" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -fbounds-checking -fno-strict-overflow" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -fno-PIE -fno-pie -fno-unit-at-a-time" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -g -g[0-9] -ggdb -ggdb[0-9] -gstabs -gstabs+" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -fno-ident" export ALLOWED_FLAGS="${ALLOWED_FLAGS} -W* -w" fi # allow a bunch of flags that negate features / control ABI ALLOWED_FLAGS="${ALLOWED_FLAGS} -fno-stack-protector -fno-stack-protector-all \ -fno-strict-aliasing -fno-bounds-checking -fstrict-overflow" ALLOWED_FLAGS="${ALLOWED_FLAGS} -mregparm -mno-app-regs -mapp-regs \ -mno-mmx -mno-sse -mno-sse2 -mno-sse3 -mno-3dnow \ -mips1 -mips2 -mips3 -mips4 -mips32 -mips64 -mips16 \ -msoft-float -mno-soft-float -mhard-float -mno-hard-float -mfpu \ -mieee -mieee-with-inexact -mschedule \ -mtls-direct-seg-refs -mno-tls-direct-seg-refs \ -mflat -mno-flat -mno-faster-structs -mfaster-structs \ -m32 -m64 -mabi -mlittle-endian -mbig-endian -EL -EB -fPIC \ -mlive-g0 -mcmodel -mstack-bias -mno-stack-bias \ -msecure-plt -m*-toc -D* -U*" # {C,CXX,F,FC}FLAGS that we are think is ok, but needs testing # NOTE: currently -Os have issues with gcc3 and K6* arch's export UNSTABLE_FLAGS="-Os -O3 -freorder-blocks" return 0 } # inverted filters for hardened compiler. This is trying to unpick # the hardened compiler defaults. _filter-hardened() { local f for f in "$@" ; do case "${f}" in # Ideally we should only concern ourselves with PIE flags, # not -fPIC or -fpic, but too many places filter -fPIC without # thinking about -fPIE. -fPIC|-fpic|-fPIE|-fpie|-Wl,pie|-pie) gcc-specs-pie || continue is-flagq -nopie || append-flags -nopie;; -fstack-protector) gcc-specs-ssp || continue is-flagq -fno-stack-protector || append-flags $(test-flags -fno-stack-protector);; -fstack-protector-all) gcc-specs-ssp-to-all || continue is-flagq -fno-stack-protector-all || append-flags $(test-flags -fno-stack-protector-all);; -fno-strict-overflow) gcc-specs-nostrict || continue is-flagq -fstrict-overflow || append-flags $(test-flags -fstrict-overflow);; esac done } # Remove occurrences of strings from variable given in $1 # Strings removed are matched as globs, so for example # '-O*' would remove -O1, -O2 etc. _filter-var() { local f x VAR VAL declare -a new VAR=$1 shift eval VAL=\${${VAR}} for f in ${VAL}; do for x in "$@"; do # Note this should work with globs like -O* [[ ${f} == ${x} ]] && continue 2 done eval new\[\${\#new\[@]}]=\${f} done eval export ${VAR}=\${new\[*]} } # @FUNCTION: filter-flags # @USAGE: # @DESCRIPTION: # Remove particular from {C,CPP,CXX,F,FC}FLAGS. Accepts shell globs. filter-flags() { _filter-hardened "$@" _filter-var CFLAGS "$@" _filter-var CPPFLAGS "$@" _filter-var CXXFLAGS "$@" _filter-var FFLAGS "$@" _filter-var FCFLAGS "$@" return 0 } # @FUNCTION: filter-lfs-flags # @DESCRIPTION: # Remove flags that enable Large File Support. filter-lfs-flags() { [[ -n $@ ]] && die "filter-lfs-flags takes no arguments" filter-flags -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE } # @FUNCTION: append-cppflags # @USAGE: # @DESCRIPTION: # Add extra to the current CPPFLAGS. append-cppflags() { [[ -z $* ]] && return 0 export CPPFLAGS="${CPPFLAGS} $*" return 0 } # @FUNCTION: append-cxxflags # @USAGE: # @DESCRIPTION: # Add extra to the current CXXFLAGS. append-cxxflags() { [[ -z $* ]] && return 0 export CXXFLAGS="${CXXFLAGS} $*" return 0 } # @FUNCTION: append-fflags # @USAGE: # @DESCRIPTION: # Add extra to the current {F,FC}FLAGS. append-fflags() { [[ -z $* ]] && return 0 export FFLAGS="${FFLAGS} $*" export FCFLAGS="${FCFLAGS} $*" return 0 } # @FUNCTION: append-lfs-flags # @DESCRIPTION: # Add flags that enable Large File Support. append-lfs-flags() { [[ -n $@ ]] && die "append-lfs-flags takes no arguments" append-cppflags -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE } # @FUNCTION: append-flags # @USAGE: # @DESCRIPTION: # Add extra to your current {C,CXX,F,FC}FLAGS. append-flags() { [[ -z $* ]] && return 0 export CFLAGS="${CFLAGS} $*" export CXXFLAGS="${CXXFLAGS} $*" export FFLAGS="${FFLAGS} $*" export FCFLAGS="${FCFLAGS} $*" return 0 } # @FUNCTION: replace-flags # @USAGE: # @DESCRIPTION: # Replace the flag with . Accepts shell globs for . replace-flags() { [[ $# != 2 ]] \ && echo && eerror "Usage: replace-flags " \ && die "replace-flags takes 2 arguments, not $#" local f fset declare -a new_CFLAGS new_CXXFLAGS new_FFLAGS new_FCFLAGS for fset in CFLAGS CXXFLAGS FFLAGS FCFLAGS; do # Looping over the flags instead of using a global # substitution ensures that we're working with flag atoms. # Otherwise globs like -O* have the potential to wipe out the # list of flags. for f in ${!fset}; do # Note this should work with globs like -O* [[ ${f} == ${1} ]] && f=${2} eval new_${fset}\[\${\#new_${fset}\[@]}]=\${f} done eval export ${fset}=\${new_${fset}\[*]} done return 0 } # @FUNCTION: replace-cpu-flags # @USAGE: # @DESCRIPTION: # Replace cpu flags (like -march/-mcpu/-mtune) that select the cpu # with flags that select the cpu. Accepts shell globs for . replace-cpu-flags() { local newcpu="$#" ; newcpu="${!newcpu}" while [ $# -gt 1 ] ; do # quote to make sure that no globbing is done (particularly on # ${oldcpu}) prior to calling replace-flags replace-flags "-march=${1}" "-march=${newcpu}" replace-flags "-mcpu=${1}" "-mcpu=${newcpu}" replace-flags "-mtune=${1}" "-mtune=${newcpu}" shift done return 0 } _is_flagq() { local x for x in ${!1} ; do [[ ${x} == $2 ]] && return 0 done return 1 } # @FUNCTION: is-flagq # @USAGE: # @DESCRIPTION: # Returns shell true if is in {C,CXX,F,FC}FLAGS, else returns shell false. Accepts shell globs. is-flagq() { [[ -n $2 ]] && die "Usage: is-flag " _is_flagq CFLAGS $1 || _is_flagq CXXFLAGS $1 || _is_flagq FFLAGS $1 || _is_flagq FCFLAGS $1 } # @FUNCTION: is-flag # @USAGE: # @DESCRIPTION: # Echo's "true" if flag is set in {C,CXX,F,FC}FLAGS. Accepts shell globs. is-flag() { is-flagq "$@" && echo true } # @FUNCTION: is-ldflagq # @USAGE: # @DESCRIPTION: # Returns shell true if is in LDFLAGS, else returns shell false. Accepts shell globs. is-ldflagq() { [[ -n $2 ]] && die "Usage: is-ldflag " _is_flagq LDFLAGS $1 } # @FUNCTION: is-ldflag # @USAGE: # @DESCRIPTION: # Echo's "true" if flag is set in LDFLAGS. Accepts shell globs. is-ldflag() { is-ldflagq "$@" && echo true } # @FUNCTION: filter-mfpmath # @USAGE: # @DESCRIPTION: # Remove specified math types from the fpmath flag. For example, if the user # has -mfpmath=sse,386, running `filter-mfpmath sse` will leave the user with # -mfpmath=386. filter-mfpmath() { local orig_mfpmath new_math prune_math # save the original -mfpmath flag orig_mfpmath=$(get-flag -mfpmath) # get the value of the current -mfpmath flag new_math=$(get-flag mfpmath) new_math=" ${new_math//,/ } " # figure out which math values are to be removed prune_math="" for prune_math in "$@" ; do new_math=${new_math/ ${prune_math} / } done new_math=$(echo ${new_math}) new_math=${new_math// /,} if [[ -z ${new_math} ]] ; then # if we're removing all user specified math values are # slated for removal, then we just filter the flag filter-flags ${orig_mfpmath} else # if we only want to filter some of the user specified # math values, then we replace the current flag replace-flags ${orig_mfpmath} -mfpmath=${new_math} fi return 0 } # @FUNCTION: strip-flags # @DESCRIPTION: # Strip C[XX]FLAGS of everything except known good/safe flags. strip-flags() { local x y flag NEW_CFLAGS NEW_CXXFLAGS NEW_FFLAGS NEW_FCFLAGS setup-allowed-flags local NEW_CFLAGS="" local NEW_CXXFLAGS="" local NEW_FFLAGS="" local NEW_FCFLAGS="" # Allow unstable C[XX]FLAGS if we are using unstable profile ... if has ~$(tc-arch) ${ACCEPT_KEYWORDS} ; then ALLOWED_FLAGS="${ALLOWED_FLAGS} ${UNSTABLE_FLAGS}" fi set -f # disable pathname expansion for x in ${CFLAGS}; do for y in ${ALLOWED_FLAGS}; do flag=${x%%=*} if [ "${flag%%${y}}" = "" ] ; then NEW_CFLAGS="${NEW_CFLAGS} ${x}" break fi done done for x in ${CXXFLAGS}; do for y in ${ALLOWED_FLAGS}; do flag=${x%%=*} if [ "${flag%%${y}}" = "" ] ; then NEW_CXXFLAGS="${NEW_CXXFLAGS} ${x}" break fi done done for x in ${FFLAGS}; do for y in ${ALLOWED_FLAGS}; do flag=${x%%=*} if [ "${flag%%${y}}" = "" ] ; then NEW_FFLAGS="${NEW_FFLAGS} ${x}" break fi done done for x in ${FCFLAGS}; do for y in ${ALLOWED_FLAGS}; do flag=${x%%=*} if [ "${flag%%${y}}" = "" ] ; then NEW_FCFLAGS="${NEW_FCFLAGS} ${x}" break fi done done # In case we filtered out all optimization flags fallback to -O2 if [ "${CFLAGS/-O}" != "${CFLAGS}" -a "${NEW_CFLAGS/-O}" = "${NEW_CFLAGS}" ]; then NEW_CFLAGS="${NEW_CFLAGS} -O2" fi if [ "${CXXFLAGS/-O}" != "${CXXFLAGS}" -a "${NEW_CXXFLAGS/-O}" = "${NEW_CXXFLAGS}" ]; then NEW_CXXFLAGS="${NEW_CXXFLAGS} -O2" fi if [ "${FFLAGS/-O}" != "${FFLAGS}" -a "${NEW_FFLAGS/-O}" = "${NEW_FFLAGS}" ]; then NEW_FFLAGS="${NEW_FFLAGS} -O2" fi if [ "${FCFLAGS/-O}" != "${FCFLAGS}" -a "${NEW_FCFLAGS/-O}" = "${NEW_FCFLAGS}" ]; then NEW_FCFLAGS="${NEW_FCFLAGS} -O2" fi set +f # re-enable pathname expansion export CFLAGS="${NEW_CFLAGS}" export CXXFLAGS="${NEW_CXXFLAGS}" export FFLAGS="${NEW_FFLAGS}" export FCFLAGS="${NEW_FCFLAGS}" return 0 } test-flag-PROG() { local comp=$1 local flags="$2" [[ -z ${comp} || -z ${flags} ]] && \ return 1 local PROG=$(tc-get${comp}) ${PROG} ${flags} -S -o /dev/null -xc /dev/null \ > /dev/null 2>&1 } # @FUNCTION: test-flag-CC # @USAGE: # @DESCRIPTION: # Returns shell true if is supported by the C compiler, else returns shell false. test-flag-CC() { test-flag-PROG "CC" "$1"; } # @FUNCTION: test-flag-CXX # @USAGE: # @DESCRIPTION: # Returns shell true if is supported by the C++ compiler, else returns shell false. test-flag-CXX() { test-flag-PROG "CXX" "$1"; } # @FUNCTION: test-flag-F77 # @USAGE: # @DESCRIPTION: # Returns shell true if is supported by the Fortran 77 compiler, else returns shell false. test-flag-F77() { test-flag-PROG "F77" "$1"; } # @FUNCTION: test-flag-FC # @USAGE: # @DESCRIPTION: # Returns shell true if is supported by the Fortran 90 compiler, else returns shell false. test-flag-FC() { test-flag-PROG "FC" "$1"; } test-flags-PROG() { local comp=$1 local flags local x shift [[ -z ${comp} ]] && return 1 x="" for x in "$@" ; do test-flag-${comp} "${x}" && flags="${flags}${flags:+ }${x}" done echo "${flags}" # Just bail if we dont have any flags [[ -n ${flags} ]] } # @FUNCTION: test-flags-CC # @USAGE: # @DESCRIPTION: # Returns shell true if are supported by the C compiler, else returns shell false. test-flags-CC() { test-flags-PROG "CC" "$@"; } # @FUNCTION: test-flags-CXX # @USAGE: # @DESCRIPTION: # Returns shell true if are supported by the C++ compiler, else returns shell false. test-flags-CXX() { test-flags-PROG "CXX" "$@"; } # @FUNCTION: test-flags-F77 # @USAGE: # @DESCRIPTION: # Returns shell true if are supported by the Fortran 77 compiler, else returns shell false. test-flags-F77() { test-flags-PROG "F77" "$@"; } # @FUNCTION: test-flags-FC # @USAGE: # @DESCRIPTION: # Returns shell true if are supported by the Fortran 90 compiler, else returns shell false. test-flags-FC() { test-flags-PROG "FC" "$@"; } # @FUNCTION: test-flags # @USAGE: # @DESCRIPTION: # Short-hand that should hopefully work for both C and C++ compiler, but # its really only present due to the append-flags() abomination. test-flags() { test-flags-CC "$@"; } # @FUNCTION: test_flag # @DESCRIPTION: # DEPRICIATED, use test-flags() test_flag() { ewarn "test_flag: deprecated, please use test-flags()!" >&2 test-flags-CC "$@" } # @FUNCTION: test_version_info # @USAGE: # @DESCRIPTION: # Returns shell true if the current C compiler version matches , else returns shell false. # Accepts shell globs. test_version_info() { if [[ $($(tc-getCC) --version 2>&1) == *$1* ]]; then return 0 else return 1 fi } # @FUNCTION: strip-unsupported-flags # @DESCRIPTION: # Strip {C,CXX,F,FC}FLAGS of any flags not supported by the active toolchain. strip-unsupported-flags() { export CFLAGS=$(test-flags-CC ${CFLAGS}) export CXXFLAGS=$(test-flags-CXX ${CXXFLAGS}) export FFLAGS=$(test-flags-F77 ${FFLAGS}) export FCFLAGS=$(test-flags-FC ${FCFLAGS}) } # @FUNCTION: get-flag # @USAGE: # @DESCRIPTION: # Find and echo the value for a particular flag. Accepts shell globs. get-flag() { local f findflag="$1" # this code looks a little flaky but seems to work for # everything we want ... # for example, if CFLAGS="-march=i686": # `get-flag -march` == "-march=i686" # `get-flag march` == "i686" for f in ${CFLAGS} ${CXXFLAGS} ${FFLAGS} ${FCFLAGS} ; do if [ "${f/${findflag}}" != "${f}" ] ; then printf "%s\n" "${f/-${findflag}=}" return 0 fi done return 1 } # @FUNCTION: has_hardened # @DESCRIPTION: # DEPRECATED - use gcc-specs-relro or gcc-specs-now from toolchain-funcs has_hardened() { ewarn "has_hardened: deprecated, please use gcc-specs-{relro,now}()!" >&2 test_version_info Hardened && return 0 # The specs file wont exist unless gcc has GCC_SPECS support [[ -f ${GCC_SPECS} && ${GCC_SPECS} != ${GCC_SPECS/hardened/} ]] } # @FUNCTION: has_pic # @DESCRIPTION: # DEPRECATED - use gcc-specs-pie from toolchain-funcs # indicate whether PIC is set has_pic() { ewarn "has_pic: deprecated, please use gcc-specs-pie()!" >&2 [[ ${CFLAGS/-fPIC} != ${CFLAGS} || \ ${CFLAGS/-fpic} != ${CFLAGS} ]] || \ gcc-specs-pie } # @FUNCTION: has_pie # @DESCRIPTION: # DEPRECATED - use gcc-specs-pie from toolchain-funcs # indicate whether PIE is set has_pie() { ewarn "has_pie: deprecated, please use gcc-specs-pie()!" >&2 [[ ${CFLAGS/-fPIE} != ${CFLAGS} || \ ${CFLAGS/-fpie} != ${CFLAGS} ]] || \ gcc-specs-pie } # @FUNCTION: has_ssp_all # @DESCRIPTION: # DEPRECATED - use gcc-specs-ssp from toolchain-funcs # indicate whether code for SSP is being generated for all functions has_ssp_all() { ewarn "has_ssp_all: deprecated, please use gcc-specs-ssp()!" >&2 # note; this matches only -fstack-protector-all [[ ${CFLAGS/-fstack-protector-all} != ${CFLAGS} || \ -n $(echo | $(tc-getCC) ${CFLAGS} -E -dM - | grep __SSP_ALL__) ]] || \ gcc-specs-ssp-to-all } # @FUNCTION: has_ssp # @DESCRIPTION: # DEPRECATED - use gcc-specs-ssp from toolchain-funcs # indicate whether code for SSP is being generated has_ssp() { ewarn "has_ssp: deprecated, please use gcc-specs-ssp()!" >&2 # note; this matches both -fstack-protector and -fstack-protector-all [[ ${CFLAGS/-fstack-protector} != ${CFLAGS} || \ -n $(echo | $(tc-getCC) ${CFLAGS} -E -dM - | grep __SSP__) ]] || \ gcc-specs-ssp } # @FUNCTION: has_m64 # @DESCRIPTION: # This doesn't test if the flag is accepted, it tests if the flag actually # WORKS. Non-multilib gcc will take both -m32 and -m64. If the flag works # return code is 0, else the return code is 1. has_m64() { # this doesnt test if the flag is accepted, it tests if the flag # actually -WORKS-. non-multilib gcc will take both -m32 and -m64! # please dont replace this function with test_flag in some future # clean-up! local temp="$(emktemp)" echo "int main() { return(0); }" > "${temp}".c MY_CC=$(tc-getCC) ${MY_CC/ .*/} -m64 -o "$(emktemp)" "${temp}".c > /dev/null 2>&1 local ret=$? rm -f "${temp}".c [[ ${ret} != 1 ]] && return 0 return 1 } # @FUNCTION: has_m32 # @DESCRIPTION: # This doesn't test if the flag is accepted, it tests if the flag actually # WORKS. Non-mulilib gcc will take both -m32 and -64. If the flag works return # code is 0, else return code is 1. has_m32() { # this doesnt test if the flag is accepted, it tests if the flag # actually -WORKS-. non-multilib gcc will take both -m32 and -m64! # please dont replace this function with test_flag in some future # clean-up! [ "$(tc-arch)" = "amd64" ] && has_multilib_profile && return 0 local temp=$(emktemp) echo "int main() { return(0); }" > "${temp}".c MY_CC=$(tc-getCC) ${MY_CC/ .*/} -m32 -o "$(emktemp)" "${temp}".c > /dev/null 2>&1 local ret=$? rm -f "${temp}".c [[ ${ret} != 1 ]] && return 0 return 1 } # @FUNCTION: replace-sparc64-flags # @DESCRIPTION: # Sets mcpu to v8 and uses the original value as mtune if none specified. replace-sparc64-flags() { local SPARC64_CPUS="ultrasparc3 ultrasparc v9" if [ "${CFLAGS/mtune}" != "${CFLAGS}" ]; then for x in ${SPARC64_CPUS}; do CFLAGS="${CFLAGS/-mcpu=${x}/-mcpu=v8}" done else for x in ${SPARC64_CPUS}; do CFLAGS="${CFLAGS/-mcpu=${x}/-mcpu=v8 -mtune=${x}}" done fi if [ "${CXXFLAGS/mtune}" != "${CXXFLAGS}" ]; then for x in ${SPARC64_CPUS}; do CXXFLAGS="${CXXFLAGS/-mcpu=${x}/-mcpu=v8}" done else for x in ${SPARC64_CPUS}; do CXXFLAGS="${CXXFLAGS/-mcpu=${x}/-mcpu=v8 -mtune=${x}}" done fi export CFLAGS CXXFLAGS } # @FUNCTION: append-ldflags # @USAGE: # @DESCRIPTION: # Add extra to the current LDFLAGS. append-ldflags() { [[ -z $* ]] && return 0 local flag for flag in "$@"; do [[ ${flag} == -l* ]] && \ ewarn "Appending a library link instruction (${flag}); libraries to link to should not be passed through LDFLAGS" done export LDFLAGS="${LDFLAGS} $*" return 0 } # @FUNCTION: filter-ldflags # @USAGE: # @DESCRIPTION: # Remove particular from LDFLAGS. Accepts shell globs. filter-ldflags() { _filter-var LDFLAGS "$@" return 0 } # @FUNCTION: raw-ldflags # @USAGE: # @DESCRIPTION: # Turn C style ldflags (-Wl,-foo) into straight ldflags - the results # are suitable for passing directly to 'ld'; note LDFLAGS is usually passed # to gcc where it needs the '-Wl,'. raw-ldflags() { local x input="$@" [[ -z ${input} ]] && input=${LDFLAGS} set -- for x in ${input} ; do x=${x#-Wl,} set -- "$@" ${x//,/ } done echo "$@" } # @FUNCTION: bindnow-flags # @RETURN: Returns the flags to enable "now" binding in the current selected linker. # @DESCRIPTION: # DEPRECATED - Gets the flags needed for "NOW" binding bindnow-flags() { ewarn "QA: stop using the bindnow-flags function ... simply drop it from your ebuild" } # Some tests for when we screw with things and want to make # sure we didn't break anything #TESTS() { # CFLAGS="-a -b -c=1" # CXXFLAGS="-x -y -z=2" # LDFLAGS="-l -m -n=3" # # die() { exit 1; } # (is-flag 1 2 3) && die # (is-ldflag 1 2 3) && die # # is-flagq -l && die # is-ldflagq -a && die # is-flagq -a || die # is-flagq -x || die # is-ldflagq -n=* || die # is-ldflagq -n && die # # strip-unsupported-flags # [[ ${CFLAGS} == "-c=1" ]] || die # [[ ${CXXFLAGS} == "-y -z=2" ]] || die # # echo "All tests pass" #} #TESTS