From 0a22f98191a70fb7ed63a94fed28e41ced54be67 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Sat, 1 Mar 2025 00:57:01 +0000 Subject: [PATCH] arrayUtilities: init --- .../arrayDifference/arrayDifference.bash | 50 +++++ .../arrayDifference/package.nix | 20 ++ .../arrayUtilities/arrayDifference/tests.nix | 91 ++++++++ .../arrayReplace/arrayReplace.bash | 70 ++++++ .../arrayUtilities/arrayReplace/package.nix | 22 ++ .../arrayUtilities/arrayReplace/tests.nix | 102 +++++++++ .../arraysAreEqual/arraysAreEqual.bash | 31 +++ .../arrayUtilities/arraysAreEqual/package.nix | 16 ++ .../arrayUtilities/arraysAreEqual/tests.nix | 75 +++++++ .../computeFrequencyMap.bash | 42 ++++ .../computeFrequencyMap/package.nix | 20 ++ .../computeFrequencyMap/tests.nix | 174 +++++++++++++++ .../deduplicateArray/deduplicateArray.bash | 60 +++++ .../deduplicateArray/package.nix | 20 ++ .../arrayUtilities/deduplicateArray/tests.nix | 122 +++++++++++ .../getElfFiles/getElfFiles.bash | 44 ++++ .../arrayUtilities/getElfFiles/package.nix | 20 ++ .../arrayUtilities/getMapKeys/getMapKeys.bash | 33 +++ .../arrayUtilities/getMapKeys/package.nix | 22 ++ .../getRunpathEntries/getRunpathEntries.bash | 36 +++ .../getRunpathEntries/package.nix | 20 ++ .../isDeclaredArray/isDeclaredArray.bash | 11 + .../isDeclaredArray/package.nix | 14 ++ .../isDeclaredMap/isDeclaredMap.bash | 11 + .../arrayUtilities/isDeclaredMap/package.nix | 14 ++ .../mapIsSubmap/mapIsSubmap.bash | 32 +++ .../arrayUtilities/mapIsSubmap/package.nix | 16 ++ .../mapsAreEqual/mapsAreEqual.bash | 33 +++ .../arrayUtilities/mapsAreEqual/package.nix | 20 ++ .../occursInArray/occursInArray.bash | 28 +++ .../arrayUtilities/occursInArray/package.nix | 16 ++ .../occursInMapKeys/occursInMapKeys.bash | 30 +++ .../occursInMapKeys/package.nix | 22 ++ .../occursOnlyOrAfterInArray.bash | 51 +++++ .../occursOnlyOrAfterInArray/package.nix | 16 ++ .../occursOnlyOrBeforeInArray.bash | 38 ++++ .../occursOnlyOrBeforeInArray/package.nix | 16 ++ .../occursOnlyOrBeforeInArray/tests.nix | 206 ++++++++++++++++++ .../arrayUtilities/sortArray/package.nix | 16 ++ .../arrayUtilities/sortArray/sortArray.bash | 41 ++++ .../arrayUtilities/sortArray/tests.nix | 102 +++++++++ pkgs/test/default.nix | 10 + pkgs/top-level/all-packages.nix | 11 + 43 files changed, 1844 insertions(+) create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/arrayDifference.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/tests.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/arrayReplace.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/tests.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/arraysAreEqual.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/tests.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/computeFrequencyMap.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/tests.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/deduplicateArray.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/tests.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/getElfFiles.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/getMapKeys.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/getRunpathEntries.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/isDeclaredArray.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/isDeclaredMap.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/mapIsSubmap.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/mapsAreEqual.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/occursInArray.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/occursInMapKeys.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/occursOnlyOrAfterInArray.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/occursOnlyOrBeforeInArray.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/tests.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/sortArray/package.nix create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/sortArray/sortArray.bash create mode 100644 pkgs/build-support/setup-hooks/arrayUtilities/sortArray/tests.nix diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/arrayDifference.bash b/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/arrayDifference.bash new file mode 100644 index 00000000000000..68dda0fd7b0f6c --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/arrayDifference.bash @@ -0,0 +1,50 @@ +# shellcheck shell=bash + +# arrayDifference +# Computes the difference of two arrays. +# Arguments: +# - inputArr1Ref: a reference to an array (not mutated) +# - inputArr2Ref: a reference to an array (not mutated) +# - outputArrRef: a reference to an array (mutated) +# Returns 0. +arrayDifference() { + if (($# != 3)); then + nixErrorLog "expected three arguments!" + nixErrorLog "usage: arrayDifference inputArr1Ref inputArr2Ref outputArrRef" + exit 1 + fi + + local -rn inputArr1Ref="$1" + local -rn inputArr2Ref="$2" + # shellcheck disable=SC2178 + # don't warn about outputArrRef being used as an array because it is an array. + local -rn outputArrRef="$3" + + if ! isDeclaredArray "${!inputArr1Ref}"; then + nixErrorLog "first arugment inputArr1Ref must be an array reference" + exit 1 + fi + + if ! isDeclaredArray "${!inputArr2Ref}"; then + nixErrorLog "second arugment inputArr2Ref must be an array reference" + exit 1 + fi + + if ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "third arugment outputArrRef must be an array reference" + exit 1 + fi + + # TODO: Use an O(n) algorithm instead of O(n^2). + local entry + for entry in "${inputArr1Ref[@]}"; do + if ! occursInArray "$entry" "${!inputArr2Ref}"; then + outputArrRef+=("$entry") + fi + done + + return 0 +} + +# Prevent re-declaration +readonly -f arrayDifference diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/package.nix new file mode 100644 index 00000000000000..6873581380f86a --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/package.nix @@ -0,0 +1,20 @@ +{ + callPackages, + isDeclaredArray, + lib, + makeSetupHook', + occursInArray, +}: +makeSetupHook' { + name = "arrayDifference"; + script = ./arrayDifference.bash; + nativeBuildInputs = [ + isDeclaredArray + occursInArray + ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Computes the difference of two arrays"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/tests.nix new file mode 100644 index 00000000000000..536c30dcc4ea16 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arrayDifference/tests.nix @@ -0,0 +1,91 @@ +# NOTE: Tests related to arrayDifference go here. +{ + arrayDifference, + lib, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs removeAttrs; + inherit (testers) testEqualArrayOrMap; + check = + args: + (testEqualArrayOrMap ( + (removeAttrs args [ "valuesToRemoveArray" ]) + // { + script = '' + set -eu + if isDeclaredArray valuesToRemoveArray; then + nixLog "using valuesToRemoveArray: $(declare -p valuesToRemoveArray)" + else + nixErrorLog "valuesToRemoveArray must be a declared array" + fi + + nixLog "running arrayDifference with valuesArray and valuesToRemoveArray to populate actualArray" + arrayDifference valuesArray valuesToRemoveArray actualArray + ''; + } + )).overrideAttrs + (prevAttrs: { + inherit (args) valuesToRemoveArray; + nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ arrayDifference ]; + }); +in +recurseIntoAttrs { + empty = check { + name = "empty"; + valuesArray = [ ]; + valuesToRemoveArray = [ ]; + expectedArray = [ ]; + }; + singleton = check { + name = "singleton"; + valuesArray = [ "apple" ]; + valuesToRemoveArray = [ ]; + expectedArray = [ "apple" ]; + }; + singleton-made-empty = check { + name = "singleton-made-empty"; + valuesArray = [ "apple" ]; + valuesToRemoveArray = [ "apple" ]; + expectedArray = [ ]; + }; + singleton-none-matching = check { + name = "singleton-none-matching"; + valuesArray = [ "apple" ]; + valuesToRemoveArray = [ "bee" ]; + expectedArray = [ "apple" ]; + }; + preserves-duplicates = check { + name = "preserves-duplicates"; + valuesArray = [ + "apple" + "bee" + "apple" + ]; + valuesToRemoveArray = [ "bee" ]; + expectedArray = [ + "apple" + "apple" + ]; + }; + order-of-first-array-preserved = check { + name = "order-of-first-array-preserved"; + valuesArray = [ + "apple" + "cat" + "bee" + "dog" + "apple" + "bee" + ]; + valuesToRemoveArray = [ + "bee" + "apple" + ]; + expectedArray = [ + "cat" + "dog" + ]; + }; + # TODO: Negative tests. +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/arrayReplace.bash b/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/arrayReplace.bash new file mode 100644 index 00000000000000..708bbc331deebe --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/arrayReplace.bash @@ -0,0 +1,70 @@ +# shellcheck shell=bash + +# arrayReplace +# Replaces all occurrences of each key of inputMapRef in inputArrRef with the values provided by the delimted string in +# the corresponding value of inputMapRef. +# Arguments: +# - inputArrRef: a reference to an array (not mutated) +# - inputMapRef: a reference to an associative array (not mutated) +# - delimiter: a character used to delimit the values in inputMapRef +# - outputArrRef: a reference to an array (mutated) +arrayReplace() { + if (($# != 4)); then + nixErrorLog "expected four arguments!" + nixErrorLog "usage: arrayReplace inputArrRef inputMapRef delimiter outputArrRef" + exit 1 + fi + + local -rn inputArrRef="$1" + local -rn inputMapRef="$2" + local -r delimiter="$3" + # shellcheck disable=SC2178 + local -rn outputArrRef="$4" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "first arugment inputArrRef must be an array reference" + exit 1 + fi + + if ! isDeclaredMap "${!inputMapRef}"; then + nixErrorLog "second arugment inputMapRef must be an associative array reference" + exit 1 + fi + + if ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "third arugment outputArrRef must be an array reference" + exit 1 + fi + + # Early return for empty array and replacement map. + if ((${#inputArrRef[@]} == 0)); then + outputArrRef=() + return 0 + elif ((${#inputMapRef[@]} == 0)); then + outputArrRef=("${inputArrRef[@]}") + return 0 + fi + + local elem + local replacementString + local -a replacementElemArray=() + for elem in "${inputArrRef[@]}"; do + # NOTE: We must use the slow check for key presence because we need to be able to discern between the key being + # absent and the key being present with an empty string as the value. + if occursInMapKeys "$elem" "${!inputMapRef}"; then + replacementString="${inputMapRef["$elem"]}" + replacementElemArray=() + if [[ -n $replacementString ]]; then + mapfile -d "$delimiter" -t replacementElemArray < <(echo -n "$replacementString") + fi + outputArrRef+=("${replacementElemArray[@]}") + else + outputArrRef+=("$elem") + fi + done + + return 0 +} + +# Prevent re-declaration +readonly -f arrayReplace diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/package.nix new file mode 100644 index 00000000000000..c72af2e17377d2 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/package.nix @@ -0,0 +1,22 @@ +{ + callPackages, + lib, + makeSetupHook', + isDeclaredArray, + isDeclaredMap, + occursInMapKeys, +}: +makeSetupHook' { + name = "arrayReplace"; + script = ./arrayReplace.bash; + nativeBuildInputs = [ + isDeclaredArray + isDeclaredMap + occursInMapKeys + ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Replaces all occurrences of a value in an array with other value(s)"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/tests.nix new file mode 100644 index 00000000000000..1b3e5b9dd361cf --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/tests.nix @@ -0,0 +1,102 @@ +# NOTE: Tests related to arrayReplace go here. +{ + arrayReplace, + lib, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs removeAttrs; + inherit (testers) testEqualArrayOrMap; + check = + args: + (testEqualArrayOrMap ( + (removeAttrs args [ "delimiter" ]) + // { + script = '' + set -eu + nixLog "running arrayReplace with valuesArray and valuesMap to populate actualArray" + arrayReplace valuesArray valuesMap "$delimiter" actualArray + ''; + } + )).overrideAttrs + (prevAttrs: { + nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ arrayReplace ]; + inherit (args) delimiter; + }); +in +recurseIntoAttrs { + empty-with-string = check { + name = "empty"; + valuesArray = [ ]; + valuesMap = { }; + delimiter = " "; + expectedArray = [ ]; + }; + singleton = check { + name = "singleton"; + valuesArray = [ "apple" ]; + valuesMap = { }; + delimiter = " "; + expectedArray = [ "apple" ]; + }; + singleton-made-empty = check { + name = "singleton-made-empty"; + valuesArray = [ "apple" ]; + valuesMap = { + "apple" = ""; + }; + delimiter = " "; + expectedArray = [ ]; + }; + singleton-made-singleton = check { + name = "singleton-made-singleton"; + valuesArray = [ "apple" ]; + valuesMap = { + "apple" = "bee"; + }; + delimiter = " "; + expectedArray = [ "bee" ]; + }; + singleton-none-matching = check { + name = "singleton-none-matching"; + valuesArray = [ "apple" ]; + valuesMap = { + "bee" = "cat"; + }; + delimiter = " "; + expectedArray = [ "apple" ]; + }; + # preserves-duplicates = check { + # name = "preserves-duplicates"; + # valuesArray = [ + # "apple" + # "bee" + # "apple" + # ]; + # valuesToRemoveArray = [ "bee" ]; + # expectedArray = [ + # "apple" + # "apple" + # ]; + # }; + # order-of-first-array-preserved = check { + # name = "order-of-first-array-preserved"; + # valuesArray = [ + # "apple" + # "cat" + # "bee" + # "dog" + # "apple" + # "bee" + # ]; + # valuesToRemoveArray = [ + # "bee" + # "apple" + # ]; + # expectedArray = [ + # "cat" + # "dog" + # ]; + # }; + # TODO: Negative tests. +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/arraysAreEqual.bash b/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/arraysAreEqual.bash new file mode 100644 index 00000000000000..819b345474c0a4 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/arraysAreEqual.bash @@ -0,0 +1,31 @@ +# shellcheck shell=bash + +arraysAreEqual() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: arraysAreEqual inputArr1Ref inputArr2Ref" + exit 1 + fi + + local -rn inputArr1Ref="$1" + local -rn inputArr2Ref="$2" + + if ! isDeclaredArray "${!inputArr1Ref}"; then + nixErrorLog "first arugment inputArr1Ref must be an array reference" + exit 1 + fi + + if ! isDeclaredArray "${!inputArr2Ref}"; then + nixErrorLog "second arugment inputArr2Ref must be an array reference" + exit 1 + fi + + if [[ ${#inputArr1Ref[@]} -ne ${#inputArr2Ref[@]} || ${inputArr1Ref[*]@K} != "${inputArr2Ref[*]@K}" ]]; then + return 1 + fi + + return 0 +} + +# Prevent re-declaration +readonly -f arraysAreEqual diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/package.nix new file mode 100644 index 00000000000000..039f4ce32f6cfc --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/package.nix @@ -0,0 +1,16 @@ +{ + callPackages, + isDeclaredArray, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "arraysAreEqual"; + script = ./arraysAreEqual.bash; + nativeBuildInputs = [ isDeclaredArray ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Tests if two arrays are equal"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/tests.nix new file mode 100644 index 00000000000000..680316eaf4b98c --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/arraysAreEqual/tests.nix @@ -0,0 +1,75 @@ +# NOTE: Tests related to arrayReplace go here. +{ + arraysAreEqual, + lib, + runCommand, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs; + inherit (testers) testBuildFailure'; + check = + args: + runCommand "test-arraysAreEqual-${args.name}" + { + __structuredAttrs = true; + strictDeps = true; + nativeBuildInputs = [ arraysAreEqual ]; + inherit (args) valuesArray1 valuesArray2; + } + '' + set -eu + nixLog "running arraysAreEqual with valuesArray1 and valuesArray2" + if arraysAreEqual valuesArray1 valuesArray2; then + nixLog "arraysAreEqual returned successfully" + touch $out + else + nixErrorLog "arraysAreEqual returned unsuccessfully" + exit 1 + fi + ''; +in +recurseIntoAttrs { + empty-with-string = check { + name = "empty"; + valuesArray1 = [ ]; + valuesArray2 = [ ]; + }; + singleton = check { + name = "singleton"; + valuesArray1 = [ "apple" ]; + valuesArray2 = [ "apple" ]; + }; + preserves-duplicates = check { + name = "preserves-duplicates"; + valuesArray1 = [ + "apple" + "bee" + "apple" + ]; + valuesArray2 = [ + "apple" + "bee" + "apple" + ]; + }; + different = testBuildFailure' { + name = "different"; + drv = check { + name = "different"; + valuesArray1 = [ + "apple" + "bee" + "cat" + ]; + valuesArray2 = [ + "apple" + "bee" + "dog" + ]; + }; + expectedBuilderLogEntries = [ + "ERROR: genericBuild: arraysAreEqual returned unsuccessfully" + ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/computeFrequencyMap.bash b/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/computeFrequencyMap.bash new file mode 100644 index 00000000000000..ef7d5d439a4460 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/computeFrequencyMap.bash @@ -0,0 +1,42 @@ +# shellcheck shell=bash + +# computeFrequencyMap +# Produces a frequency map of the elements in an array. +# +# Arguments: +# - inputArrRef: a reference to an array (not mutated) +# - outputMapRef: a reference to an associative array (mutated) +# +# Returns 0. +computeFrequencyMap() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: computeFrequencyMap inputArrRef outputMapRef" + exit 1 + fi + + local -rn inputArrRef="$1" + local -rn outputMapRef="$2" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "first arugment inputArrRef must be an array reference" + exit 1 + fi + + if ! isDeclaredMap "${!outputMapRef}"; then + nixErrorLog "second arugment outputMapRef must be an associative array reference" + exit 1 + fi + + local -i numTimesSeen + local entry + for entry in "${inputArrRef[@]}"; do + numTimesSeen=$((${outputMapRef["$entry"]-0} + 1)) + outputMapRef["$entry"]=$numTimesSeen + done + + return 0 +} + +# Prevent re-declaration +readonly -f computeFrequencyMap diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/package.nix new file mode 100644 index 00000000000000..c43df5595b3d02 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/package.nix @@ -0,0 +1,20 @@ +{ + callPackages, + isDeclaredArray, + isDeclaredMap, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "computeFrequencyMap"; + script = ./computeFrequencyMap.bash; + nativeBuildInputs = [ + isDeclaredArray + isDeclaredMap + ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Computes the frequency of each element in an array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/tests.nix new file mode 100644 index 00000000000000..29c63fd4a06e68 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/computeFrequencyMap/tests.nix @@ -0,0 +1,174 @@ +# NOTE: Tests related to computeFrequencyMap go here. +{ + computeFrequencyMap, + lib, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs; + inherit (testers) testBuildFailure' testEqualArrayOrMap; + check = + args: + (testEqualArrayOrMap ( + args + // { + script = '' + set -eu + nixLog "running computeFrequencyMap with valuesArray to populate actualMap" + computeFrequencyMap valuesArray actualMap + ''; + } + )).overrideAttrs + (prevAttrs: { + nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ computeFrequencyMap ]; + }); +in +recurseIntoAttrs { + empty = check { + name = "empty"; + valuesArray = [ ]; + expectedMap = { }; + }; + singleton = check { + name = "singleton"; + valuesArray = [ "apple" ]; + expectedMap.apple = 1; + }; + twoUnique = check { + name = "twoUnique"; + valuesArray = [ + "apple" + "bee" + ]; + expectedMap = { + apple = 1; + bee = 1; + }; + }; + oneDuplicate = check { + name = "oneDuplicate"; + valuesArray = [ + "apple" + "apple" + ]; + expectedMap.apple = 2; + }; + oneUniqueOneDuplicate = check { + name = "oneUniqueOneDuplicate"; + valuesArray = [ + "bee" + "apple" + "bee" + ]; + expectedMap = { + apple = 1; + bee = 2; + }; + }; + failMissingKeyWithEmpty = testBuildFailure' { + drv = check { + name = "failMissingKeyWithEmpty"; + valuesArray = [ ]; + expectedMap.apple = 1; + }; + expectedBuilderLogEntries = [ + "ERROR: assertEqualMap: maps differ in length: expectedMap has length 1 but actualMap has length 0" + "ERROR: assertEqualMap: maps differ at key 'apple': expectedMap has value '1' but actualMap has no such key" + ]; + }; + failIncorrectFrequency = testBuildFailure' { + drv = check { + name = "failIncorrectFrequency"; + valuesArray = [ + "apple" + "bee" + "apple" + ]; + expectedMap = { + apple = 1; + bee = 1; + }; + }; + expectedBuilderLogEntries = [ + "ERROR: assertEqualMap: maps differ at key 'apple': expectedMap has value '1' but actualMap has value '2'" + ]; + }; + failMissingKeyWithNonEmpty = testBuildFailure' { + drv = check { + name = "failMissingKeyWithNonEmpty"; + valuesArray = [ + "cat" + "apple" + "bee" + ]; + expectedMap = { + apple = 1; + bee = 1; + }; + }; + expectedBuilderLogEntries = [ + "ERROR: assertEqualMap: maps differ in length: expectedMap has length 2 but actualMap has length 3" + "ERROR: assertEqualMap: maps differ at key 'cat': expectedMap has no such key but actualMap has value '1'" + ]; + }; + failFirstArgumentIsString = testBuildFailure' { + drv = check { + name = "failFirstArgumentIsString"; + valuesArray = "apple"; + expectedMap = { }; + }; + expectedBuilderLogEntries = [ + "ERROR: computeFrequencyMap: first arugment inputArrRef must be an array reference" + ]; + }; + failFirstArgumentIsMap = testBuildFailure' { + drv = check { + name = "failFirstArgumentIsMap"; + valuesArray.apple = 1; + expectedMap = { }; + }; + expectedBuilderLogEntries = [ + "ERROR: computeFrequencyMap: first arugment inputArrRef must be an array reference" + ]; + }; + failSecondArgumentIsArray = testBuildFailure' { + drv = + (check { + name = "failSecondArgumentIsArray"; + valuesArray = [ ]; + expectedMap = { }; + }).overrideAttrs + (prevAttrs: { + script = + '' + nixLog "unsetting and re-declaring actualMap to be an array" + unset actualMap + declare -ag actualMap=() + '' + + prevAttrs.script; + }); + expectedBuilderLogEntries = [ + "ERROR: computeFrequencyMap: second arugment outputMapRef must be an associative array reference" + ]; + }; + failSecondArgumentIsString = testBuildFailure' { + drv = + (check { + name = "failSecondArgumentIsString"; + valuesArray = [ ]; + expectedMap = { }; + }).overrideAttrs + (prevAttrs: { + script = + '' + nixLog "unsetting and re-declaring actualMap to be a string" + unset actualMap + declare -g actualMap="hello!" + '' + + prevAttrs.script; + }); + expectedBuilderLogEntries = [ + "ERROR: computeFrequencyMap: second arugment outputMapRef must be an associative array reference" + ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/deduplicateArray.bash b/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/deduplicateArray.bash new file mode 100644 index 00000000000000..f0e454149a2311 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/deduplicateArray.bash @@ -0,0 +1,60 @@ +# shellcheck shell=bash + +# deduplicateArray +# Deduplicates an array. If outputMapRef is provided, it will contain the frequency of each element in the input array. +# +# Arguments: +# - inputArrRef: a reference to an array (not mutated) +# - outputArrRef: a reference to an array (mutated) +# - outputMapRef: a reference to an associative array (mutated, optional) +# +# Returns 0. +deduplicateArray() { + if (($# != 2 && $# != 3)); then + nixErrorLog "expected two or three arguments!" + nixErrorLog "usage: deduplicateArray inputArrRef outputArrRef [outputMapRef]" + exit 1 + fi + + local -rn inputArrRef="$1" + # shellcheck disable=SC2178 + # don't warn about outputArrRef being used as an array because it is an array. + local -rn outputArrRef="$2" + # shellcheck disable=SC2034 + # outputMap is used in outputMapRef + local -A outputMap=() + # shellcheck disable=SC2178 + # don't warn about outputMapRef being used as an array because it is an array. + local -rn outputMapRef="${3:-outputMap}" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "first arugment inputArrRef must be an array reference" + exit 1 + fi + + if ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "second arugment outputArrRef must be an array reference" + exit 1 + fi + + if ! isDeclaredMap "${!outputMapRef}"; then + nixErrorLog "third arugment outputMapRef must be an associative array reference when present" + exit 1 + fi + + local -i numTimesSeen + local entry + for entry in "${inputArrRef[@]}"; do + numTimesSeen=$((${outputMapRef["$entry"]-0} + 1)) + outputMapRef["$entry"]=$numTimesSeen + + if ((numTimesSeen <= 1)); then + outputArrRef+=("$entry") + fi + done + + return 0 +} + +# Prevent re-declaration +readonly -f deduplicateArray diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/package.nix new file mode 100644 index 00000000000000..f7c73a3713f217 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/package.nix @@ -0,0 +1,20 @@ +{ + callPackages, + isDeclaredArray, + isDeclaredMap, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "deduplicateArray"; + script = ./deduplicateArray.bash; + nativeBuildInputs = [ + isDeclaredArray + isDeclaredMap + ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Removes duplicate elements from an array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/tests.nix new file mode 100644 index 00000000000000..6d9dd1339261aa --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/deduplicateArray/tests.nix @@ -0,0 +1,122 @@ +# NOTE: Tests related to deduplicateArray go here. +{ + deduplicateArray, + lib, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs; + inherit (testers) testBuildFailure' testEqualArrayOrMap; + check = + args: + (testEqualArrayOrMap ( + args + // { + script = '' + set -eu + nixLog "running deduplicateArray with valuesArray to populate actualArray" + deduplicateArray valuesArray actualArray + ''; + } + )).overrideAttrs + (prevAttrs: { + nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ deduplicateArray ]; + }); +in +recurseIntoAttrs { + empty = check { + name = "empty"; + valuesArray = [ ]; + expectedArray = [ ]; + }; + singleton = check { + name = "singleton"; + valuesArray = [ "apple" ]; + expectedArray = [ "apple" ]; + }; + allUniqueOrderPreserved = check { + name = "allUniqueOrderPreserved"; + valuesArray = [ + "apple" + "bee" + ]; + expectedArray = [ + "apple" + "bee" + ]; + }; + oneDuplicate = check { + name = "oneDuplicate"; + valuesArray = [ + "apple" + "apple" + ]; + expectedArray = [ + "apple" + ]; + }; + oneUniqueOrderPreserved = check { + name = "oneUniqueOrderPreserved"; + valuesArray = [ + "bee" + "apple" + "bee" + ]; + expectedArray = [ + "bee" + "apple" + ]; + }; + duplicatesWithSpacesAndLineBreaks = check { + name = "duplicatesWithSpacesAndLineBreaks"; + valuesArray = [ + "dog" + "bee" + '' + line + break + '' + "cat" + "zebra" + "bee" + "cat" + "elephant" + "dog with spaces" + '' + line + break + '' + ]; + expectedArray = [ + "dog" + "bee" + '' + line + break + '' + "cat" + "zebra" + "elephant" + "dog with spaces" + ]; + }; + failNoDeduplication = testBuildFailure' { + drv = check { + name = "failNoDeduplication"; + valuesArray = [ + "bee" + "apple" + "bee" + ]; + expectedArray = [ + "bee" + "apple" + "bee" + ]; + }; + expectedBuilderLogEntries = [ + "ERROR: assertEqualArray: arrays differ in length: expectedArray has length 3 but actualArray has length 2" + "ERROR: assertEqualArray: arrays differ at index 2: expectedArray has value 'bee' but actualArray has no such index" + ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/getElfFiles.bash b/pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/getElfFiles.bash new file mode 100644 index 00000000000000..9476aaedc86c34 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/getElfFiles.bash @@ -0,0 +1,44 @@ +# shellcheck shell=bash + +# Populate outputArrRef with the ELF files in the given path. +getElfFiles() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: getElfFiles path outputArrRef" + exit 1 + fi + + local -r path="$1" + # shellcheck disable=SC2178 + local -rn outputArrRef="$2" + + if [[ ! -d $path ]]; then + nixErrorLog "path $path is not a directory" + exit 1 + elif ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "first arugment outputArrRef must be an array reference" + exit 1 + fi + + # TODO: does `-type f` prevent inclusion of symlinks? + local file + # shellcheck disable=SC2154 + while IFS= read -r -d $'\0' file; do + if ! isELF "$file"; then + nixLog "excluding $file because it's not an ELF file" + continue + fi + + # NOTE: Since the input is sorted, the output is sorted by virtue of us iterating over it in order. + nixLog "including $file" + outputArrRef+=("$file") + + # NOTE from sort manpage: The locale specified by the environment affects sort order. Set LC_ALL=C to get the + # traditional sort order that uses native byte values. + done < <(find "$path" -type f -print0 | LC_ALL=C sort --stable --zero-terminated) + + return 0 +} + +# Prevent re-declaration +readonly -f getElfFiles diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/package.nix new file mode 100644 index 00000000000000..deb72cc8eeb432 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/getElfFiles/package.nix @@ -0,0 +1,20 @@ +{ + isDeclaredArray, + lib, + makeSetupHook', + patchelf, +}: +makeSetupHook' { + name = "getElfFiles"; + script = ./getElfFiles.bash; + nativeBuildInputs = [ + isDeclaredArray + patchelf + ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Populates a reference to an array with paths to ELF files in a given directory"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/getMapKeys.bash b/pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/getMapKeys.bash new file mode 100644 index 00000000000000..37a2afc4088255 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/getMapKeys.bash @@ -0,0 +1,33 @@ +# shellcheck shell=bash + +# Returns a sorted array of the keys of inputMapRef. +getMapKeys() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: getMapKeys inputMapRef outputArrRef" + exit 1 + fi + + local -rn inputMapRef="$1" + # shellcheck disable=SC2178 + # Don't warn about outputArrRef being used as an array because it is an array. + local -rn outputArrRef="$2" + + if ! isDeclaredMap "${!inputMapRef}"; then + nixErrorLog "first arugment inputMapRef must be an associative array reference" + exit 1 + fi + + if ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "second arugment outputArrRef must be an array reference" + exit 1 + fi + + # TODO: Should we hide mutation from the caller? + outputArrRef=("${!inputMapRef[@]}") + sortArray "${!outputArrRef}" "${!outputArrRef}" + return 0 +} + +# Prevent re-declaration +readonly -f getMapKeys diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/package.nix new file mode 100644 index 00000000000000..c223316d4eee02 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/getMapKeys/package.nix @@ -0,0 +1,22 @@ +{ + isDeclaredArray, + isDeclaredMap, + lib, + makeSetupHook', + sortArray, +}: +makeSetupHook' { + name = "getMapKeys"; + script = ./getMapKeys.bash; + nativeBuildInputs = [ + isDeclaredArray + isDeclaredMap + sortArray + ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Gets the indices of an associative array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/getRunpathEntries.bash b/pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/getRunpathEntries.bash new file mode 100644 index 00000000000000..b558c6ad292cd5 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/getRunpathEntries.bash @@ -0,0 +1,36 @@ +# shellcheck shell=bash + +# Populate outputArrRef with the runpath entries of path. +# NOTE: This function does not check if path is a valid ELF file. +getRunpathEntries() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: getRunpathEntries path outputArrRef" + exit 1 + fi + + local -r path="$1" + # shellcheck disable=SC2178 + local -rn outputArrRef="$2" + + if [[ ! -f $path ]]; then + nixErrorLog "path $path does not exist" + exit 1 + elif ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "second arugment outputArrRef must be an array reference" + exit 1 + fi + + local -r runpath="$(patchelf --print-rpath "$path")" + + if [[ -z $runpath ]]; then + outputArrRef=() + else + mapfile -d ':' -t "${!outputArrRef}" < <(echo -n "$runpath") + fi + + return 0 +} + +# Prevent re-declaration +readonly -f getRunpathEntries diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/package.nix new file mode 100644 index 00000000000000..0fc44856438ca2 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/getRunpathEntries/package.nix @@ -0,0 +1,20 @@ +{ + isDeclaredArray, + lib, + makeSetupHook', + patchelf, +}: +makeSetupHook' { + name = "getRunpathEntries"; + script = ./getRunpathEntries.bash; + nativeBuildInputs = [ + isDeclaredArray + patchelf + ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Populates a reference to an array with the runpath entries of a given file"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/isDeclaredArray.bash b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/isDeclaredArray.bash new file mode 100644 index 00000000000000..9b8d17c9356544 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/isDeclaredArray.bash @@ -0,0 +1,11 @@ +# shellcheck shell=bash + +# Tests if an array is declared. +# NOTE: We must dereference the name ref to get the type of the underlying variable. +isDeclaredArray() { + # shellcheck disable=SC2034 + local -nr arrayRef="$1" && [[ ${!arrayRef@a} =~ a ]] +} + +# Prevent re-declaration +readonly -f isDeclaredArray diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/package.nix new file mode 100644 index 00000000000000..f637155cc8569e --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredArray/package.nix @@ -0,0 +1,14 @@ +{ + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "isDeclaredArray"; + script = ./isDeclaredArray.bash; + # TODO(@connorbaker): Add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if an array is declared"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/isDeclaredMap.bash b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/isDeclaredMap.bash new file mode 100644 index 00000000000000..e46d25c34e30e5 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/isDeclaredMap.bash @@ -0,0 +1,11 @@ +# shellcheck shell=bash + +# Tests if a map is declared. +# NOTE: We must dereference the name ref to get the type of the underlying variable. +isDeclaredMap() { + # shellcheck disable=SC2034 + local -nr mapRef="$1" && [[ ${!mapRef@a} =~ A ]] +} + +# Prevent re-declaration +readonly -f isDeclaredMap diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/package.nix new file mode 100644 index 00000000000000..615758ad54b434 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/isDeclaredMap/package.nix @@ -0,0 +1,14 @@ +{ + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "isDeclaredMap"; + script = ./isDeclaredMap.bash; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if an associative array is declared"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/mapIsSubmap.bash b/pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/mapIsSubmap.bash new file mode 100644 index 00000000000000..cfbdca7f8c1c23 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/mapIsSubmap.bash @@ -0,0 +1,32 @@ +# shellcheck shell=bash + +mapIsSubmap() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: mapIsSubmap submapRef supermapRef" + exit 1 + fi + + local -rn submapRef="$1" + local -rn supermapRef="$2" + + if ! isDeclaredMap "${!submapRef}"; then + nixErrorLog "first arugment submapRef must be an associative array reference" + exit 1 + fi + + if ! isDeclaredMap "${!supermapRef}"; then + nixErrorLog "second arugment supermapRef must be an associative array reference" + exit 1 + fi + + local subMapKey + for subMapKey in "${!submapRef[@]}"; do + [[ ${submapRef["$subMapKey"]} != "${supermapRef["$subMapKey"]}" ]] && return 1 + done + + return 0 +} + +# Prevent re-declaration +readonly -f mapIsSubmap diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/package.nix new file mode 100644 index 00000000000000..f3e482f24131cf --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/mapIsSubmap/package.nix @@ -0,0 +1,16 @@ +{ + isDeclaredMap, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "mapIsSubmap"; + script = ./mapIsSubmap.bash; + nativeBuildInputs = [ isDeclaredMap ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if a map is a submap of another map"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/mapsAreEqual.bash b/pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/mapsAreEqual.bash new file mode 100644 index 00000000000000..5d16b71818fecc --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/mapsAreEqual.bash @@ -0,0 +1,33 @@ +# shellcheck shell=bash + +mapsAreEqual() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: mapsAreEqual inputMap1Ref inputMap2Ref" + exit 1 + fi + + local -rn inputMap1Ref="$1" + local -rn inputMap2Ref="$2" + + if ! isDeclaredMap "${!inputMap1Ref}"; then + nixErrorLog "first arugment inputMap1Ref must be an associative array reference" + exit 1 + fi + + if ! isDeclaredMap "${!inputMap2Ref}"; then + nixErrorLog "second arugment inputMap2Ref must be an associative array reference" + exit 1 + fi + + if ((${#inputMap1Ref[@]} != ${#inputMap2Ref[@]})) || + ! mapIsSubmap "${!inputMap1Ref}" "${!inputMap2Ref}" || + ! mapIsSubmap "${!inputMap2Ref}" "${!inputMap1Ref}"; then + return 1 + fi + + return 0 +} + +# Prevent re-declaration +readonly -f mapsAreEqual diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/package.nix new file mode 100644 index 00000000000000..e59631d2378ab3 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/mapsAreEqual/package.nix @@ -0,0 +1,20 @@ +{ + isDeclaredMap, + lib, + makeSetupHook', + mapIsSubmap, +}: +makeSetupHook' { + name = "mapsAreEqual"; + script = ./mapsAreEqual.bash; + nativeBuildInputs = [ + isDeclaredMap + mapIsSubmap + ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if two maps are equal"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/occursInArray.bash b/pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/occursInArray.bash new file mode 100644 index 00000000000000..1f0ca3e8f458c3 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/occursInArray.bash @@ -0,0 +1,28 @@ +# shellcheck shell=bash + +# Returns 0 if inputElem occurs in inputArrRef, 1 otherwise. +occursInArray() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: occursInArray inputElem inputArrRef" + exit 1 + fi + + local -r inputElem="$1" + local -rn inputArrRef="$2" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "second arugment inputArrRef must be an array reference" + exit 1 + fi + + local entry + for entry in "${inputArrRef[@]}"; do + [[ $entry == "$inputElem" ]] && return 0 + done + + return 1 +} + +# Prevent re-declaration +readonly -f occursInArray diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/package.nix new file mode 100644 index 00000000000000..9bae0dcf3092e3 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursInArray/package.nix @@ -0,0 +1,16 @@ +{ + isDeclaredArray, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "occursInArray"; + script = ./occursInArray.bash; + nativeBuildInputs = [ isDeclaredArray ]; + # TODO(@connorbaker): Add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if an element occurs in an array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/occursInMapKeys.bash b/pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/occursInMapKeys.bash new file mode 100644 index 00000000000000..20668c5d627b80 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/occursInMapKeys.bash @@ -0,0 +1,30 @@ +# shellcheck shell=bash + +# Returns 0 if inputElem occurs in the keys of inputMapRef, 1 otherwise. +# NOTE: This is O(n) in the size of the map, but allows testing for keys with values which +# are empty strings (normally considered unset). +occursInMapKeys() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: occursInMapKeys inputElem inputMapRef" + exit 1 + fi + + local -r inputElem="$1" + local -rn inputMapRef="$2" + + if ! isDeclaredMap "${!inputMapRef}"; then + nixErrorLog "second arugment inputMapRef must be an associative array reference" + exit 1 + fi + + # shellcheck disable=SC2034 + # keys is used in getMapKeys + local -a keys + getMapKeys "${!inputMapRef}" keys + occursInArray "$inputElem" keys + return $? # Return the result of occursInArray +} + +# Prevent re-declaration +readonly -f occursInMapKeys diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/package.nix new file mode 100644 index 00000000000000..b5219f7484b4bc --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursInMapKeys/package.nix @@ -0,0 +1,22 @@ +{ + isDeclaredMap, + getMapKeys, + lib, + makeSetupHook', + occursInArray, +}: +makeSetupHook' { + name = "occursInMapKeys"; + script = ./occursInMapKeys.bash; + nativeBuildInputs = [ + isDeclaredMap + getMapKeys + occursInArray + ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if a value occurs in the keys of a map"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/occursOnlyOrAfterInArray.bash b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/occursOnlyOrAfterInArray.bash new file mode 100644 index 00000000000000..bf89f5bff3d0a6 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/occursOnlyOrAfterInArray.bash @@ -0,0 +1,51 @@ +# shellcheck shell=bash + +# Returns 0 if inputElem1 occurs after inputElem2 in inputArrRef or if inputElem1 occurs and inputElem2 does not. +# Returns 1 otherwise. +occursOnlyOrAfterInArray() { + if (($# != 3)); then + nixErrorLog "expected three arguments!" + nixErrorLog "usage: occursOnlyOrAfterInArray inputElem1 inputElem2 inputArrRef" + exit 1 + fi + + local -r inputElem1="$1" + local -r inputElem2="$2" + local -rn inputArrRef="$3" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "third arugment inputArrRef must be an array reference" + exit 1 + fi + + if [[ $inputElem1 == "$inputElem2" ]]; then + nixErrorLog "inputElem1 and inputElem2 must be different" + exit 1 + fi + + local -i seenInputElem1=0 + local -i seenInputElem2=0 + local entry + for entry in "${inputArrRef[@]}"; do + if [[ $entry == "$inputElem1" ]]; then + # If we've already seen inputElem2, then inputElem1 occurs after inputElem2 and we can return success. + ((seenInputElem2)) && return 0 + # Otherwise, we've seen inputElem1 and are waiting to see if inputElem2 occurs. + seenInputElem1=1 + elif [[ $entry == "$inputElem2" ]]; then + # Since we've seen inputElem2, we can return failure if we've already seen inputElem1. + ((seenInputElem1)) && return 1 + # Otherwise, we've seen inputElem2 and are waiting to see if inputElem1 occurs. + seenInputElem2=1 + fi + done + + # Due to the structure of the return statements, when we exit the for loop, we know that at most one of the + # input elements has been seen. + # If we've seen inputElem1, then we know that inputElem2 didn't occur, so we return success. + # If we haven't seen inputElem1, it doesn't matter if we've seen inputElem2 or not -- we return failure. + return $((1 - seenInputElem1)) +} + +# Prevent re-declaration +readonly -f occursOnlyOrAfterInArray diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/package.nix new file mode 100644 index 00000000000000..2aaed1276d9333 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrAfterInArray/package.nix @@ -0,0 +1,16 @@ +{ + isDeclaredArray, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "occursOnlyOrAfterInArray"; + script = ./occursOnlyOrAfterInArray.bash; + nativeBuildInputs = [ isDeclaredArray ]; + # TODO(@connorbaker): add tests + # passthru.tests = callPackages ./tests.nix {}; + meta = { + description = "Tests if an element occurs only or after a given element in an array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/occursOnlyOrBeforeInArray.bash b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/occursOnlyOrBeforeInArray.bash new file mode 100644 index 00000000000000..000c432d15a42d --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/occursOnlyOrBeforeInArray.bash @@ -0,0 +1,38 @@ +# shellcheck shell=bash + +# Returns 0 if inputElem1 occurs before inputElem2 in inputArrRef or if inputElem1 occurs and inputElem2 does not. +# Returns 1 otherwise. +occursOnlyOrBeforeInArray() { + if (($# != 3)); then + nixErrorLog "expected three arguments!" + nixErrorLog "usage: occursOnlyOrBeforeInArray inputElem1 inputElem2 inputArrRef" + exit 1 + fi + + local -r inputElem1="$1" + local -r inputElem2="$2" + local -rn inputArrRef="$3" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "third arugment inputArrRef must be an array reference" + exit 1 + fi + + if [[ $inputElem1 == "$inputElem2" ]]; then + nixErrorLog "inputElem1 and inputElem2 must be different" + exit 1 + fi + + local entry + for entry in "${inputArrRef[@]}"; do + # Early return on finding inputElem1 + [[ $entry == "$inputElem1" ]] && return 0 + # Stop searching if we find inputElem2 + [[ $entry == "$inputElem2" ]] && break + done + + return 1 +} + +# Prevent re-declaration +readonly -f occursOnlyOrBeforeInArray diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/package.nix new file mode 100644 index 00000000000000..5447fdc886d945 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/package.nix @@ -0,0 +1,16 @@ +{ + callPackages, + isDeclaredArray, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "occursOnlyOrBeforeInArray"; + script = ./occursOnlyOrBeforeInArray.bash; + nativeBuildInputs = [ isDeclaredArray ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Tests if an element occurs only or before a given index in an array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/tests.nix new file mode 100644 index 00000000000000..023b17aee45d3c --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/occursOnlyOrBeforeInArray/tests.nix @@ -0,0 +1,206 @@ +# NOTE: Tests related to occursOnlyOrBeforeInArray go here. +{ + occursOnlyOrBeforeInArray, + lib, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs; + inherit (testers) runCommand; + + check = + { + name, + value1, + value2, + valuesArray, + shouldPass, + }: + runCommand { + inherit + name + value1 + value2 + valuesArray + shouldPass + ; + strictDeps = true; + __structuredAttrs = true; + nativeBuildInputs = [ occursOnlyOrBeforeInArray ]; + script = '' + set -eu + nixLog "using value1: ''${value1@Q}" + nixLog "using value2: ''${value2@Q}" + nixLog "using valuesArray: $(declare -p valuesArray)" + nixLog "using shouldPass: $shouldPass" + nixLog "running occursOnlyOrBeforeInArray with value1 value2 valuesArray" + + if occursOnlyOrBeforeInArray "$value1" "$value2" valuesArray; then + if ((shouldPass)); then + nixLog "Test passed as expected" + touch $out + else + nixErrorLog "Test passed but should have failed!" + exit 1 + fi + else + if ((shouldPass)); then + nixErrorLog "Test failed but should have passed!" + exit 1 + else + nixLog "Test failed as expected" + touch $out + fi + fi + ''; + }; +in +recurseIntoAttrs { + emptyArray = check { + name = "emptyArray"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ ]; + shouldPass = false; + }; + emptyStringForValue1 = check { + name = "emptyString"; + value1 = ""; + value2 = "bee"; + valuesArray = [ "" ]; + shouldPass = true; + }; + singleton = check { + name = "singleton"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ "apple" ]; + shouldPass = true; + }; + occursBefore = check { + name = "occursBefore"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ + "apple" + "bee" + ]; + shouldPass = true; + }; + occursOnly = check { + name = "occursOnly"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ "apple" ]; + shouldPass = true; + }; + occursAfter = check { + name = "occursAfter"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ + "bee" + "apple" + ]; + shouldPass = false; + }; + occursBeforeAlmostAtEnd = check { + name = "occursBeforeAlmostAtEnd"; + value1 = "apple"; + value2 = "cat"; + valuesArray = [ + "bee" + "apple" + "cat" + ]; + shouldPass = true; + }; + value1DoesntMatchStringWithPrefix = check { + name = "value1DoesntMatchStringWithPrefix"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ + "apple with spaces" + "bee" + ]; + shouldPass = false; + }; + value1DoesntMatchStringWithSuffix = check { + name = "value1DoesntMatchStringWithSuffix"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ + "apple in a tree" + "bee" + ]; + shouldPass = false; + }; + value2DoesntMatchStringWithPrefix = check { + name = "value2DoesntMatchStringWithPrefix"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ + "bee with spaces" + "apple" + ]; + shouldPass = true; + }; + value2DoesntMatchStringWithSuffix = check { + name = "value2DoesntMatchStringWithSuffix"; + value1 = "apple"; + value2 = "bee"; + valuesArray = [ + "bee in a tree" + "apple" + ]; + shouldPass = true; + }; + value1HasLineBreakOccursBefore = check { + name = "value1HasLineBreakOccursBefore"; + value1 = '' + apple + + up + + high + ''; + value2 = "bee"; + valuesArray = [ + "cat" + '' + apple + + up + + high + '' + ''line break '' + "bee" + ]; + shouldPass = true; + }; + value1HasLineBreakOccursAfter = check { + name = "value1HasLineBreakOccursAfter"; + value1 = '' + apple + + up + + high + ''; + value2 = "bee"; + valuesArray = [ + "cat" + "bee" + '' + apple + + up + + high + '' + ''line break '' + ]; + shouldPass = false; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/package.nix b/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/package.nix new file mode 100644 index 00000000000000..88085b3582a254 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/package.nix @@ -0,0 +1,16 @@ +{ + callPackages, + isDeclaredArray, + lib, + makeSetupHook', +}: +makeSetupHook' { + name = "sortArray"; + script = ./sortArray.bash; + nativeBuildInputs = [ isDeclaredArray ]; + passthru.tests = callPackages ./tests.nix { }; + meta = { + description = "Sorts an array"; + maintainers = [ lib.maintainers.connorbaker ]; + }; +} diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/sortArray.bash b/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/sortArray.bash new file mode 100644 index 00000000000000..221a9374ac8330 --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/sortArray.bash @@ -0,0 +1,41 @@ +# shellcheck shell=bash + +# TODO: Would it be a mistake to provided an occursInSortedArray? + +# Sorts inputArrRef and stores the result in outputArrRef. +# TODO: Is it safe for inputArrRef and outputArrRef to be the same? +sortArray() { + if (($# != 2)); then + nixErrorLog "expected two arguments!" + nixErrorLog "usage: sortArray inputArrRef outputArrRef" + exit 1 + fi + + local -rn inputArrRef="$1" + local -rn outputArrRef="$2" + + if ! isDeclaredArray "${!inputArrRef}"; then + nixErrorLog "first arugment inputArrRef must be an array reference" + exit 1 + fi + + if ! isDeclaredArray "${!outputArrRef}"; then + nixErrorLog "second arugment outputArrRef must be an array reference" + exit 1 + fi + + # Early return for empty array, as empty array will expand to nothing, but printf will still see it as an argument, + # producing an empty string. + if ((${#inputArrRef[@]} == 0)); then + outputArrRef=() + return 0 + fi + + # NOTE from sort manpage: The locale specified by the environment affects sort order. Set LC_ALL=C to get the + # traditional sort order that uses native byte values. + mapfile -d $'\0' -t "${!outputArrRef}" < <(printf '%s\0' "${inputArrRef[@]}" | LC_ALL=C sort --stable --zero-terminated) + return 0 +} + +# Prevent re-declaration +readonly -f sortArray diff --git a/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/tests.nix b/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/tests.nix new file mode 100644 index 00000000000000..cd1a0c8a601a8a --- /dev/null +++ b/pkgs/build-support/setup-hooks/arrayUtilities/sortArray/tests.nix @@ -0,0 +1,102 @@ +# NOTE: Tests related to sortArray go here. +{ + lib, + sortArray, + testers, +}: +let + inherit (lib.attrsets) recurseIntoAttrs; + inherit (testers) testEqualArrayOrMap; + check = + args: + (testEqualArrayOrMap ( + args + // { + script = '' + set -eu + nixLog "running sortArray with valuesArray to populate actualArray" + sortArray valuesArray actualArray + ''; + } + )).overrideAttrs + (prevAttrs: { + nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ sortArray ]; + }); +in +recurseIntoAttrs { + empty = check { + name = "empty"; + valuesArray = [ ]; + expectedArray = [ ]; + }; + singleton = check { + name = "singleton"; + valuesArray = [ "apple" ]; + expectedArray = [ "apple" ]; + }; + oneDuplicate = check { + name = "oneDuplicate"; + valuesArray = [ + "apple" + "apple" + ]; + expectedArray = [ + "apple" + "apple" + ]; + }; + oneUnique = check { + name = "oneUnique"; + valuesArray = [ + "bee" + "apple" + "bee" + ]; + expectedArray = [ + "apple" + "bee" + "bee" + ]; + }; + duplicatesWithSpacesAndLineBreaks = check { + name = "duplicatesWithSpacesAndLineBreaks"; + valuesArray = [ + "dog" + "bee" + '' + line + break + '' + "cat" + "zebra" + "bee" + "cat" + "elephant" + "dog with spaces" + '' + line + break + '' + ]; + expectedArray = [ + "bee" + "bee" + "cat" + "cat" + "dog" + "dog with spaces" + "elephant" + # NOTE: lead whitespace is removed, so the following entries start with `l`. + '' + line + break + '' + '' + line + break + '' + "zebra" + ]; + }; + # TODO: Negative tests. +} diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index fceea9cdbba5c8..12f41694523583 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -205,6 +205,16 @@ with pkgs; sourceGuard = pkgs.sourceGuard.passthru.tests; + # Accumulate all passthru.tests from arrayUtilities into a single attribute set. + arrayUtilities = recurseIntoAttrs ( + lib.concatMapAttrs ( + name: value: + lib.optionalAttrs (value ? passthru.tests) { + ${name} = value.passthru.tests; + } + ) arrayUtilities + ); + srcOnly = callPackage ../build-support/src-only/tests.nix { }; systemd = callPackage ./systemd { }; diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 0c29393bed25d7..05ae7e457ff598 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -163,6 +163,17 @@ with pkgs; __flattenIncludeHackHook = callPackage ../build-support/setup-hooks/flatten-include-hack { }; + arrayUtilities = lib.makeScope newScope ( + finalArrayUtilities: + recurseIntoAttrs { + callPackages = lib.callPackagesWith (pkgs // finalArrayUtilities); + } + // lib.packagesFromDirectoryRecursive { + inherit (finalArrayUtilities) callPackage; + directory = ../build-support/setup-hooks/arrayUtilities; + } + ); + addBinToPathHook = callPackage ( { makeSetupHook }: makeSetupHook {