diff --git a/pkgs/build-support/setup-hooks/autoreconf.sh b/pkgs/build-support/setup-hooks/autoreconf.sh index 6ce879ac092de..763ea649c1c43 100644 --- a/pkgs/build-support/setup-hooks/autoreconf.sh +++ b/pkgs/build-support/setup-hooks/autoreconf.sh @@ -2,6 +2,11 @@ preConfigurePhases="${preConfigurePhases:-} autoreconfPhase" autoreconfPhase() { runHook preAutoreconf - autoreconf ${autoreconfFlags:---install --force --verbose} + + local flagsArray=() + : "${autoreconfFlags:=--install --force --verbose}" + concatTo flagsArray autoreconfFlags + + autoreconf "${flagsArray[@]}" runHook postAutoreconf } diff --git a/pkgs/by-name/cm/cmake/setup-hook.sh b/pkgs/by-name/cm/cmake/setup-hook.sh index 9ca4a6abeebc2..7ceb24227857d 100755 --- a/pkgs/by-name/cm/cmake/setup-hook.sh +++ b/pkgs/by-name/cm/cmake/setup-hook.sh @@ -38,7 +38,7 @@ cmakeConfigurePhase() { fi if [ -z "${dontAddPrefix-}" ]; then - cmakeFlags="-DCMAKE_INSTALL_PREFIX=$prefix $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_INSTALL_PREFIX=$prefix" fi # We should set the proper `CMAKE_SYSTEM_NAME`. @@ -47,21 +47,21 @@ cmakeConfigurePhase() { # Unfortunately cmake seems to expect absolute paths for ar, ranlib, and # strip. Otherwise they are taken to be relative to the source root of the # package being built. - cmakeFlags="-DCMAKE_CXX_COMPILER=$CXX $cmakeFlags" - cmakeFlags="-DCMAKE_C_COMPILER=$CC $cmakeFlags" - cmakeFlags="-DCMAKE_AR=$(command -v $AR) $cmakeFlags" - cmakeFlags="-DCMAKE_RANLIB=$(command -v $RANLIB) $cmakeFlags" - cmakeFlags="-DCMAKE_STRIP=$(command -v $STRIP) $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_CXX_COMPILER=$CXX" + prependToVar cmakeFlags "-DCMAKE_C_COMPILER=$CC" + prependToVar cmakeFlags "-DCMAKE_AR=$(command -v $AR)" + prependToVar cmakeFlags "-DCMAKE_RANLIB=$(command -v $RANLIB)" + prependToVar cmakeFlags "-DCMAKE_STRIP=$(command -v $STRIP)" # on macOS we want to prefer Unix-style headers to Frameworks # because we usually do not package the framework - cmakeFlags="-DCMAKE_FIND_FRAMEWORK=LAST $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_FIND_FRAMEWORK=LAST" # we never want to use the global macOS SDK - cmakeFlags="-DCMAKE_OSX_SYSROOT= $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_OSX_SYSROOT=" # correctly detect our clang compiler - cmakeFlags="-DCMAKE_POLICY_DEFAULT_CMP0025=NEW $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_POLICY_DEFAULT_CMP0025=NEW" # This installs shared libraries with a fully-specified install # name. By default, cmake installs shared libraries with just the @@ -70,7 +70,7 @@ cmakeConfigurePhase() { # libraries are in a system path or in the same directory as the # executable. This flag makes the shared library accessible from its # nix/store directory. - cmakeFlags="-DCMAKE_INSTALL_NAME_DIR=${!outputLib}/lib $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_INSTALL_NAME_DIR=${!outputLib}/lib" # The docdir flag needs to include PROJECT_NAME as per GNU guidelines, # try to extract it from CMakeLists.txt. @@ -93,39 +93,42 @@ cmakeConfigurePhase() { # This ensures correct paths with multiple output derivations # It requires the project to use variables from GNUInstallDirs module # https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html - cmakeFlags="-DCMAKE_INSTALL_BINDIR=${!outputBin}/bin $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_SBINDIR=${!outputBin}/sbin $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_INCLUDEDIR=${!outputInclude}/include $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_OLDINCLUDEDIR=${!outputInclude}/include $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_MANDIR=${!outputMan}/share/man $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_INFODIR=${!outputInfo}/share/info $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_DOCDIR=${!outputDoc}/share/doc/${shareDocName} $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_LIBDIR=${!outputLib}/lib $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_LIBEXECDIR=${!outputLib}/libexec $cmakeFlags" - cmakeFlags="-DCMAKE_INSTALL_LOCALEDIR=${!outputLib}/share/locale $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_INSTALL_BINDIR=${!outputBin}/bin" + prependToVar cmakeFlags "-DCMAKE_INSTALL_SBINDIR=${!outputBin}/sbin" + prependToVar cmakeFlags "-DCMAKE_INSTALL_INCLUDEDIR=${!outputInclude}/include" + prependToVar cmakeFlags "-DCMAKE_INSTALL_OLDINCLUDEDIR=${!outputInclude}/include" + prependToVar cmakeFlags "-DCMAKE_INSTALL_MANDIR=${!outputMan}/share/man" + prependToVar cmakeFlags "-DCMAKE_INSTALL_INFODIR=${!outputInfo}/share/info" + prependToVar cmakeFlags "-DCMAKE_INSTALL_DOCDIR=${!outputDoc}/share/doc/${shareDocName}" + prependToVar cmakeFlags "-DCMAKE_INSTALL_LIBDIR=${!outputLib}/lib" + prependToVar cmakeFlags "-DCMAKE_INSTALL_LIBEXECDIR=${!outputLib}/libexec" + prependToVar cmakeFlags "-DCMAKE_INSTALL_LOCALEDIR=${!outputLib}/share/locale" # Don’t build tests when doCheck = false if [ -z "${doCheck-}" ]; then - cmakeFlags="-DBUILD_TESTING=OFF $cmakeFlags" + prependToVar cmakeFlags "-DBUILD_TESTING=OFF" fi # Always build Release, to ensure optimisation flags - cmakeFlags="-DCMAKE_BUILD_TYPE=${cmakeBuildType:-Release} $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_BUILD_TYPE=${cmakeBuildType:-Release}" # Disable user package registry to avoid potential side effects # and unecessary attempts to access non-existent home folder # https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#disabling-the-package-registry - cmakeFlags="-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON $cmakeFlags" - cmakeFlags="-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF $cmakeFlags" - cmakeFlags="-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF $cmakeFlags" + prependToVar cmakeFlags "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON" + prependToVar cmakeFlags "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF" + prependToVar cmakeFlags "-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF" if [ "${buildPhase-}" = ninjaBuildPhase ]; then - cmakeFlags="-GNinja $cmakeFlags" + prependToVar cmakeFlags "-GNinja" fi - echo "cmake flags: $cmakeFlags ${cmakeFlagsArray[@]}" + local flagsArray=() + concatTo flagsArray cmakeFlags cmakeFlagsArray - cmake "$cmakeDir" $cmakeFlags "${cmakeFlagsArray[@]}" + echoCmd 'cmake flags' "${flagsArray[@]}" + + cmake "$cmakeDir" "${flagsArray[@]}" if ! [[ -v enableParallelBuilding ]]; then enableParallelBuilding=1 diff --git a/pkgs/by-name/lo/local-ai/package.nix b/pkgs/by-name/lo/local-ai/package.nix index 498cc96a454b9..56b2ba80bf4a9 100644 --- a/pkgs/by-name/lo/local-ai/package.nix +++ b/pkgs/by-name/lo/local-ai/package.nix @@ -488,7 +488,7 @@ let ''${enableParallelBuilding:+-j''${NIX_BUILD_CORES}} SHELL=$SHELL ) - _accumFlagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray + concatTo flagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray echoCmd 'build flags' "''${flagsArray[@]}" make build "''${flagsArray[@]}" unset flagsArray diff --git a/pkgs/by-name/me/meson/setup-hook.sh b/pkgs/by-name/me/meson/setup-hook.sh index 8266645452277..55ea8b5c1d973 100644 --- a/pkgs/by-name/me/meson/setup-hook.sh +++ b/pkgs/by-name/me/meson/setup-hook.sh @@ -21,11 +21,10 @@ mesonConfigurePhase() { "--localedir=${!outputLib}/share/locale" "-Dauto_features=${mesonAutoFeatures:-enabled}" "-Dwrap_mode=${mesonWrapMode:-nodownload}" - ${crossMesonFlags} "--buildtype=${mesonBuildType:-plain}" ) - _accumFlagsArray mesonFlags mesonFlagsArray + concatTo flagsArray mesonFlags mesonFlagsArray echoCmd 'mesonConfigurePhase flags' "${flagsArray[@]}" @@ -50,7 +49,8 @@ mesonConfigurePhase() { mesonCheckPhase() { runHook preCheck - local flagsArray=($mesonCheckFlags "${mesonCheckFlagsArray[@]}") + local flagsArray=() + concatTo flagsArray mesonCheckFlags mesonCheckFlagsArray echoCmd 'mesonCheckPhase flags' "${flagsArray[@]}" meson test --no-rebuild --print-errorlogs "${flagsArray[@]}" @@ -64,12 +64,9 @@ mesonInstallPhase() { local flagsArray=() if [[ -n "$mesonInstallTags" ]]; then - flagsArray+=("--tags" "${mesonInstallTags// /,}") + flagsArray+=("--tags" "$(concatStringsSep "," mesonInstallTags)") fi - flagsArray+=( - $mesonInstallFlags - "${mesonInstallFlagsArray[@]}" - ) + concatTo flagsArray mesonInstallFlags mesonInstallFlagsArray echoCmd 'mesonInstallPhase flags' "${flagsArray[@]}" meson install --no-rebuild "${flagsArray[@]}" diff --git a/pkgs/by-name/ni/ninja/setup-hook.sh b/pkgs/by-name/ni/ninja/setup-hook.sh index 7fa5e4675f39b..4f3bc5b5acfa4 100644 --- a/pkgs/by-name/ni/ninja/setup-hook.sh +++ b/pkgs/by-name/ni/ninja/setup-hook.sh @@ -1,3 +1,5 @@ +# shellcheck shell=bash + ninjaBuildPhase() { runHook preBuild @@ -9,9 +11,9 @@ ninjaBuildPhase() { fi local flagsArray=( - -j$buildCores - $ninjaFlags "${ninjaFlagsArray[@]}" + "-j$buildCores" ) + concatTo flagsArray ninjaFlags ninjaFlagsArray echoCmd 'build flags' "${flagsArray[@]}" TERM=dumb ninja "${flagsArray[@]}" @@ -24,7 +26,7 @@ ninjaCheckPhase() { if [ -z "${checkTarget:-}" ]; then if ninja -t query test >/dev/null 2>&1; then - checkTarget=test + checkTarget="test" fi fi @@ -38,10 +40,9 @@ ninjaCheckPhase() { fi local flagsArray=( - -j$buildCores - $ninjaFlags "${ninjaFlagsArray[@]}" - $checkTarget + "-j$buildCores" ) + concatTo flagsArray ninjaFlags ninjaFlagsArray checkTarget echoCmd 'check flags' "${flagsArray[@]}" TERM=dumb ninja "${flagsArray[@]}" @@ -62,10 +63,10 @@ ninjaInstallPhase() { # shellcheck disable=SC2086 local flagsArray=( - -j$buildCores - $ninjaFlags "${ninjaFlagsArray[@]}" - ${installTargets:-install} + "-j$buildCores" ) + : "${installTargets:=install}" + concatTo flagsArray ninjaFlags ninjaFlagsArray installTargets echoCmd 'install flags' "${flagsArray[@]}" TERM=dumb ninja "${flagsArray[@]}" @@ -73,14 +74,14 @@ ninjaInstallPhase() { runHook postInstall } -if [ -z "${dontUseNinjaBuild-}" -a -z "${buildPhase-}" ]; then +if [ -z "${dontUseNinjaBuild-}" ] && [ -z "${buildPhase-}" ]; then buildPhase=ninjaBuildPhase fi -if [ -z "${dontUseNinjaCheck-}" -a -z "${checkPhase-}" ]; then +if [ -z "${dontUseNinjaCheck-}" ] && [ -z "${checkPhase-}" ]; then checkPhase=ninjaCheckPhase fi -if [ -z "${dontUseNinjaInstall-}" -a -z "${installPhase-}" ]; then +if [ -z "${dontUseNinjaInstall-}" ] && [ -z "${installPhase-}" ]; then installPhase=ninjaInstallPhase fi diff --git a/pkgs/stdenv/generic/setup.sh b/pkgs/stdenv/generic/setup.sh index 40bf6554183c6..5c9b2c1064db2 100644 --- a/pkgs/stdenv/generic/setup.sh +++ b/pkgs/stdenv/generic/setup.sh @@ -280,16 +280,16 @@ prependToVar() { fi # check if variable already exist and if it does then do extra checks - if declare -p "$1" 2> /dev/null | grep -q '^'; then - type="$(declare -p "$1")" - if [[ "$type" =~ "declare -A" ]]; then - echo "prependToVar(): ERROR: trying to use prependToVar on an associative array." >&2 - return 1 - elif [[ "$type" =~ "declare -a" ]]; then - useArray=true - else - useArray=false - fi + if type=$(declare -p "$1" 2> /dev/null); then + case "${type#* }" in + -A*) + echo "prependToVar(): ERROR: trying to use prependToVar on an associative array." >&2 + return 1 ;; + -a*) + useArray=true ;; + *) + useArray=false ;; + esac fi shift @@ -313,16 +313,16 @@ appendToVar() { fi # check if variable already exist and if it does then do extra checks - if declare -p "$1" 2> /dev/null | grep -q '^'; then - type="$(declare -p "$1")" - if [[ "$type" =~ "declare -A" ]]; then - echo "appendToVar(): ERROR: trying to use appendToVar on an associative array, use variable+=([\"X\"]=\"Y\") instead." >&2 - return 1 - elif [[ "$type" =~ "declare -a" ]]; then - useArray=true - else - useArray=false - fi + if type=$(declare -p "$1" 2> /dev/null); then + case "${type#* }" in + -A*) + echo "appendToVar(): ERROR: trying to use appendToVar on an associative array, use variable+=([\"X\"]=\"Y\") instead." >&2 + return 1 ;; + -a*) + useArray=true ;; + *) + useArray=false ;; + esac fi shift @@ -334,36 +334,57 @@ appendToVar() { fi } -# Accumulate into `flagsArray` the flags from the named variables. +# Accumulate flags from the named variables $2+ into the indexed array $1. # -# If __structuredAttrs, the variables are all treated as arrays -# and simply concatenated onto `flagsArray`. -# -# If not __structuredAttrs, then: -# * Each variable is treated as a string, and split on whitespace; -# * except variables whose names end in "Array", which are treated -# as arrays. -_accumFlagsArray() { - local name - if [ -n "$__structuredAttrs" ]; then - for name in "$@"; do - local -n nameref="$name" - flagsArray+=( ${nameref+"${nameref[@]}"} ) - done - else - for name in "$@"; do +# Arrays are simply concatenated, strings are split on whitespace. +concatTo() { + local -n targetref="$1"; shift + local name type + for name in "$@"; do + if type=$(declare -p "$name" 2> /dev/null); then local -n nameref="$name" - case "$name" in - *Array) - # shellcheck disable=SC2206 - flagsArray+=( ${nameref+"${nameref[@]}"} ) ;; + case "${type#* }" in + -A*) + echo "concatTo(): ERROR: trying to use concatTo on an associative array." >&2 + return 1 ;; + -a*) + targetref+=( "${nameref[@]}" ) ;; *) # shellcheck disable=SC2206 - flagsArray+=( ${nameref-} ) ;; + targetref+=( ${nameref-} ) ;; esac - done - fi + fi + done +} +# Concatenate a list of strings ($2) with a separator ($1) between each element. +# The list can be an indexed array of strings or a single string. A single string +# is split on spaces and then concatenated with the separator. +# +# $ flags="lorem ipsum dolor sit amet" +# $ concatStringsSep ";" flags +# lorem;ipsum;dolor;sit;amet +# +# $ flags=("lorem ipsum" "dolor" "sit amet") +# $ concatStringsSep ";" flags +# lorem ipsum;dolor;sit amet +concatStringsSep() { + local sep="$1" + local name="$2" + local type oldifs + if type=$(declare -p "$name" 2> /dev/null); then + local -n nameref="$name" + case "${type#* }" in + -A*) + echo "concatStringsSep(): ERROR: trying to use concatStringsSep on an associative array." >&2 + return 1 ;; + -a*) + local IFS="$sep" + echo -n "${nameref[*]}" ;; + *) + echo -n "${nameref// /"${sep}"}" ;; + esac + fi } # Add $1/lib* into rpaths. @@ -1177,12 +1198,7 @@ unpackPhase() { fi local -a srcsArray - if [ -n "$__structuredAttrs" ]; then - srcsArray=( "${srcs[@]}" ) - else - # shellcheck disable=SC2206 - srcsArray=( $srcs ) - fi + concatTo srcsArray srcs # To determine the source directory created by unpacking the # source archives, we record the contents of the current @@ -1247,13 +1263,7 @@ patchPhase() { runHook prePatch local -a patchesArray - if [ -n "$__structuredAttrs" ]; then - # shellcheck disable=SC2206 - patchesArray=( ${patches:+"${patches[@]}"} ) - else - # shellcheck disable=SC2206 - patchesArray=( ${patches:-} ) - fi + concatTo patchesArray patches for i in "${patchesArray[@]}"; do echo "applying patch $i" @@ -1274,12 +1284,8 @@ patchPhase() { esac local -a flagsArray - if [ -n "$__structuredAttrs" ]; then - flagsArray=( "${patchFlags[@]:--p1}" ) - else - # shellcheck disable=SC2086,SC2206 - flagsArray=( ${patchFlags:--p1} ) - fi + : "${patchFlags:=-p1}" + concatTo flagsArray patchFlags # "2>&1" is a hack to make patch fail if the decompressor fails (nonexistent patch, etc.) # shellcheck disable=SC2086 $uncompress < "$i" 2>&1 | patch "${flagsArray[@]}" @@ -1365,7 +1371,7 @@ configurePhase() { if [ -n "$configureScript" ]; then local -a flagsArray - _accumFlagsArray configureFlags configureFlagsArray + concatTo flagsArray configureFlags configureFlagsArray echoCmd 'configure flags' "${flagsArray[@]}" # shellcheck disable=SC2086 @@ -1392,7 +1398,7 @@ buildPhase() { ${enableParallelBuilding:+-j${NIX_BUILD_CORES}} SHELL="$SHELL" ) - _accumFlagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray + concatTo flagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray echoCmd 'build flags' "${flagsArray[@]}" make ${makefile:+-f $makefile} "${flagsArray[@]}" @@ -1431,16 +1437,8 @@ checkPhase() { SHELL="$SHELL" ) - _accumFlagsArray makeFlags makeFlagsArray - if [ -n "$__structuredAttrs" ]; then - flagsArray+=( "${checkFlags[@]:-VERBOSE=y}" ) - else - # shellcheck disable=SC2206 - flagsArray+=( ${checkFlags:-VERBOSE=y} ) - fi - _accumFlagsArray checkFlagsArray - # shellcheck disable=SC2206 - flagsArray+=( ${checkTarget} ) + : "${checkFlags:=VERBOSE=y}" + concatTo flagsArray makeFlags makeFlagsArray checkFlags checkFlagsArray checkTarget echoCmd 'check flags' "${flagsArray[@]}" make ${makefile:+-f $makefile} "${flagsArray[@]}" @@ -1473,13 +1471,9 @@ installPhase() { ${enableParallelInstalling:+-j${NIX_BUILD_CORES}} SHELL="$SHELL" ) - _accumFlagsArray makeFlags makeFlagsArray installFlags installFlagsArray - if [ -n "$__structuredAttrs" ]; then - flagsArray+=( "${installTargets[@]:-install}" ) - else - # shellcheck disable=SC2206 - flagsArray+=( ${installTargets:-install} ) - fi + + : "${installTargets:=install}" + concatTo flagsArray makeFlags makeFlagsArray installFlags installFlagsArray installTargets echoCmd 'install flags' "${flagsArray[@]}" make ${makefile:+-f $makefile} "${flagsArray[@]}" @@ -1562,10 +1556,9 @@ installCheckPhase() { SHELL="$SHELL" ) - _accumFlagsArray makeFlags makeFlagsArray \ - installCheckFlags installCheckFlagsArray - # shellcheck disable=SC2206 - flagsArray+=( ${installCheckTarget:-installcheck} ) + : "${installCheckTarget:=installcheck}" + concatTo flagsArray makeFlags makeFlagsArray \ + installCheckFlags installCheckFlagsArray installCheckTarget echoCmd 'installcheck flags' "${flagsArray[@]}" make ${makefile:+-f $makefile} "${flagsArray[@]}" @@ -1580,9 +1573,8 @@ distPhase() { runHook preDist local flagsArray=() - _accumFlagsArray distFlags distFlagsArray - # shellcheck disable=SC2206 - flagsArray+=( ${distTarget:-dist} ) + : "${distTarget:=dist}" + concatTo flagsArray distFlags distFlagsArray distTarget echo 'dist flags: %q' "${flagsArray[@]}" make ${makefile:+-f $makefile} "${flagsArray[@]}" diff --git a/pkgs/test/stdenv/default.nix b/pkgs/test/stdenv/default.nix index 00f1ce90ef3ad..a5b571b5a9f43 100644 --- a/pkgs/test/stdenv/default.nix +++ b/pkgs/test/stdenv/default.nix @@ -74,19 +74,19 @@ let declare -p string declare -A associativeArray=(["X"]="Y") - [[ $(appendToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not catch prepending associativeArray" && false) - [[ $(prependToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not catch prepending associativeArray" && false) + [[ $(appendToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "appendToVar did not throw appending to associativeArray" && false) + [[ $(prependToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not throw prepending associativeArray" && false) [[ $string == "world testing-string hello" ]] || (echo "'\$string' was not 'world testing-string hello'" && false) # test appending to a unset variable appendToVar nonExistant created hello - typeset -p nonExistant + declare -p nonExistant if [[ -n $__structuredAttrs ]]; then [[ "''${nonExistant[@]}" == "created hello" ]] else # there's a extra " " in front here and a extra " " in the end of prependToVar - # shouldn't matter because these functions will mostly be used for $*Flags and the Flag variable will in most cases already exit + # shouldn't matter because these functions will mostly be used for $*Flags and the Flag variable will in most cases already exist [[ "$nonExistant" == " created hello" ]] fi @@ -96,6 +96,65 @@ let ''; } // extraAttrs); + testConcatTo = { name, stdenv', extraAttrs ? { } }: + stdenv'.mkDerivation + ({ + inherit name; + + string = "a b"; + list = ["c" "d"]; + + passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ]; + buildCommand = '' + declare -A associativeArray=(["X"]="Y") + [[ $(concatTo nowhere associativeArray 2>&1) =~ "trying to use" ]] || (echo "concatTo did not throw concatenating associativeArray" && false) + + declare -a flagsArray + concatTo flagsArray string list + declare -p flagsArray + [[ "''${flagsArray[0]}" == "a" ]] || (echo "'\$flagsArray[0]' was not 'a'" && false) + [[ "''${flagsArray[1]}" == "b" ]] || (echo "'\$flagsArray[1]' was not 'b'" && false) + [[ "''${flagsArray[2]}" == "c" ]] || (echo "'\$flagsArray[2]' was not 'c'" && false) + [[ "''${flagsArray[3]}" == "d" ]] || (echo "'\$flagsArray[3]' was not 'd'" && false) + + # test concatenating to unset variable + concatTo nonExistant string list + declare -p nonExistant + [[ "''${nonExistant[0]}" == "a" ]] || (echo "'\$nonExistant[0]' was not 'a'" && false) + [[ "''${nonExistant[1]}" == "b" ]] || (echo "'\$nonExistant[1]' was not 'b'" && false) + [[ "''${nonExistant[2]}" == "c" ]] || (echo "'\$nonExistant[2]' was not 'c'" && false) + [[ "''${nonExistant[3]}" == "d" ]] || (echo "'\$nonExistant[3]' was not 'd'" && false) + + eval "$extraTest" + + touch $out + ''; + } // extraAttrs); + + testConcatStringsSep = { name, stdenv' }: + stdenv'.mkDerivation + { + inherit name; + + # NOTE: Testing with "&" as separator is intentional, because unquoted + # "&" has a special meaning in the "${var//pattern/replacement}" syntax. + # Cf. https://github.com/NixOS/nixpkgs/pull/318614#discussion_r1706191919 + passAsFile = [ "buildCommand" ]; + buildCommand = '' + declare -A associativeArray=(["X"]="Y") + [[ $(concatStringsSep ";" associativeArray 2>&1) =~ "trying to use" ]] || (echo "concatStringsSep did not throw concatenating associativeArray" && false) + + string="lorem ipsum dolor sit amet" + stringWithSep="$(concatStringsSep "&" string)" + [[ "$stringWithSep" == "lorem&ipsum&dolor&sit&amet" ]] || (echo "'\$stringWithSep' was not 'lorem&ipsum&dolor&sit&amet'" && false) + + array=("lorem ipsum" "dolor" "sit amet") + arrayWithSep="$(concatStringsSep "&" array)" + [[ "$arrayWithSep" == "lorem ipsum&dolor&sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum&dolor&sit amet'" && false) + + touch $out + ''; + }; in { @@ -196,6 +255,16 @@ in stdenv' = bootStdenv; }; + test-concat-to = testConcatTo { + name = "test-concat-to"; + stdenv' = bootStdenv; + }; + + test-concat-strings-sep = testConcatStringsSep { + name = "test-concat-strings-sep"; + stdenv' = bootStdenv; + }; + test-structured-env-attrset = testEnvAttrset { name = "test-structured-env-attrset"; stdenv' = bootStdenv; @@ -255,6 +324,29 @@ in }; }; + test-concat-to = testConcatTo { + name = "test-concat-to-structuredAttrsByDefault"; + stdenv' = bootStdenvStructuredAttrsByDefault; + extraAttrs = { + # test that whitespace is kept in the bash array for structuredAttrs + listWithSpaces = [ "c c" "d d" ]; + extraTest = '' + declare -a flagsWithSpaces + concatTo flagsWithSpaces string listWithSpaces + declare -p flagsWithSpaces + [[ "''${flagsWithSpaces[0]}" == "a" ]] || (echo "'\$flagsWithSpaces[0]' was not 'a'" && false) + [[ "''${flagsWithSpaces[1]}" == "b" ]] || (echo "'\$flagsWithSpaces[1]' was not 'b'" && false) + [[ "''${flagsWithSpaces[2]}" == "c c" ]] || (echo "'\$flagsWithSpaces[2]' was not 'c c'" && false) + [[ "''${flagsWithSpaces[3]}" == "d d" ]] || (echo "'\$flagsWithSpaces[3]' was not 'd d'" && false) + ''; + }; + }; + + test-concat-strings-sep = testConcatStringsSep { + name = "test-concat-strings-sep-structuredAttrsByDefault"; + stdenv' = bootStdenvStructuredAttrsByDefault; + }; + test-golden-example-structuredAttrs = let goldenSh = earlyPkgs.writeText "goldenSh" ''