summary refs log tree commit diff
diff options
context:
space:
mode:
authorSilvan Mosberger <contact@infinisil.com>2021-01-29 20:22:12 +0100
committerGitHub <noreply@github.com>2021-01-29 20:22:12 +0100
commitaa48e205a2e0516ff4b96c79eeb666aaaccd418c (patch)
tree4e8f6542985139c8e498057dcbdbfd6fea0c3648
parent417b19df406ae6106d80a6c551787cd13d0299dd (diff)
parent577d58a8e01aead5eb237d39a6d7df1fb7aa00c4 (diff)
Merge pull request #110787 from tfc/cartesian-product
lib/attrsets: add cartesianProductOfSets function
-rw-r--r--doc/functions/library/attrsets.xml39
-rw-r--r--lib/attrsets.nix19
-rw-r--r--lib/default.nix2
-rw-r--r--lib/lists.nix4
-rw-r--r--lib/tests/misc.nix67
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix6
-rw-r--r--nixos/tests/predictable-interface-names.nix8
-rw-r--r--pkgs/os-specific/solo5/default.nix9
8 files changed, 142 insertions, 12 deletions
diff --git a/doc/functions/library/attrsets.xml b/doc/functions/library/attrsets.xml
index 3c5823c25891c..7ef0d16624c82 100644
--- a/doc/functions/library/attrsets.xml
+++ b/doc/functions/library/attrsets.xml
@@ -1711,4 +1711,43 @@ recursiveUpdate
   </example>
  </section>
 
+ <section xml:id="function-library-lib.attrsets.cartesianProductOfSets">
+  <title><function>lib.attrsets.cartesianProductOfSets</function></title>
+
+  <subtitle><literal>cartesianProductOfSets :: AttrSet -> [ AttrSet ]</literal>
+  </subtitle>
+
+  <xi:include href="./locations.xml" xpointer="lib.attrsets.cartesianProductOfSets" />
+
+  <para>
+    Return the cartesian product of attribute set value combinations.
+  </para>
+
+  <variablelist>
+   <varlistentry>
+    <term>
+     <varname>set</varname>
+    </term>
+    <listitem>
+     <para>
+      An attribute set with attributes that carry lists of values.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <example xml:id="function-library-lib.attrsets.cartesianProductOfSets-example">
+   <title>Creating the cartesian product of a list of attribute values</title>
+<programlisting><![CDATA[
+cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; }
+=> [
+     { a = 1; b = 10; }
+     { a = 1; b = 20; }
+     { a = 2; b = 10; }
+     { a = 2; b = 20; }
+   ]
+]]></programlisting>
+  </example>
+ </section>
+
 </section>
diff --git a/lib/attrsets.nix b/lib/attrsets.nix
index d91d7a0cd47e6..0ce3aaeca452d 100644
--- a/lib/attrsets.nix
+++ b/lib/attrsets.nix
@@ -183,6 +183,24 @@ rec {
     else
       [];
 
+  /* Return the cartesian product of attribute set value combinations.
+
+    Example:
+      cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; }
+      => [
+           { a = 1; b = 10; }
+           { a = 1; b = 20; }
+           { a = 2; b = 10; }
+           { a = 2; b = 20; }
+         ]
+  */
+  cartesianProductOfSets = attrsOfLists:
+    lib.foldl' (listOfAttrs: attrName:
+      concatMap (attrs:
+        map (listValue: attrs // { ${attrName} = listValue; }) attrsOfLists.${attrName}
+      ) listOfAttrs
+    ) [{}] (attrNames attrsOfLists);
+
 
   /* Utility function that creates a {name, value} pair as expected by
      builtins.listToAttrs.
@@ -493,5 +511,4 @@ rec {
   zipWithNames = zipAttrsWithNames;
   zip = builtins.trace
     "lib.zip is deprecated, use lib.zipAttrsWith instead" zipAttrsWith;
-
 }
diff --git a/lib/default.nix b/lib/default.nix
index 803f1f765647e..50320669e2804 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -78,7 +78,7 @@ let
       zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
       recursiveUpdate matchAttrs overrideExisting getOutput getBin
       getLib getDev getMan chooseDevOutputs zipWithNames zip
-      recurseIntoAttrs dontRecurseIntoAttrs;
+      recurseIntoAttrs dontRecurseIntoAttrs cartesianProductOfSets;
     inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1
       concatMap flatten remove findSingle findFirst any all count
       optional optionals toList range partition zipListsWith zipLists
diff --git a/lib/lists.nix b/lib/lists.nix
index 06cee2eb112ab..56af4d9daa186 100644
--- a/lib/lists.nix
+++ b/lib/lists.nix
@@ -629,7 +629,9 @@ rec {
       crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]]
       => [ "13" "14" "23" "24" ]
   */
-  crossLists = f: foldl (fs: args: concatMap (f: map f args) fs) [f];
+  crossLists = builtins.trace
+    "lib.crossLists is deprecated, use lib.cartesianProductOfSets instead"
+    (f: foldl (fs: args: concatMap (f: map f args) fs) [f]);
 
 
   /* Remove duplicate elements from the list. O(n^2) complexity.
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 35a5801c724fe..0d249968402d9 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -660,4 +660,71 @@ runTests {
     expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
   };
 
+  testCartesianProductOfEmptySet = {
+    expr = cartesianProductOfSets {};
+    expected = [ {} ];
+  };
+
+  testCartesianProductOfOneSet = {
+    expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
+    expected = [ { a = 1; } { a = 2; } { a = 3; } ];
+  };
+
+  testCartesianProductOfTwoSets = {
+    expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
+    expected = [
+      { a = 1; b = 10; }
+      { a = 1; b = 20; }
+    ];
+  };
+
+  testCartesianProductOfTwoSetsWithOneEmpty = {
+    expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
+    expected = [ ];
+  };
+
+  testCartesianProductOfThreeSets = {
+    expr = cartesianProductOfSets {
+      a = [   1   2   3 ];
+      b = [  10  20  30 ];
+      c = [ 100 200 300 ];
+    };
+    expected = [
+      { a = 1; b = 10; c = 100; }
+      { a = 1; b = 10; c = 200; }
+      { a = 1; b = 10; c = 300; }
+
+      { a = 1; b = 20; c = 100; }
+      { a = 1; b = 20; c = 200; }
+      { a = 1; b = 20; c = 300; }
+
+      { a = 1; b = 30; c = 100; }
+      { a = 1; b = 30; c = 200; }
+      { a = 1; b = 30; c = 300; }
+
+      { a = 2; b = 10; c = 100; }
+      { a = 2; b = 10; c = 200; }
+      { a = 2; b = 10; c = 300; }
+
+      { a = 2; b = 20; c = 100; }
+      { a = 2; b = 20; c = 200; }
+      { a = 2; b = 20; c = 300; }
+
+      { a = 2; b = 30; c = 100; }
+      { a = 2; b = 30; c = 200; }
+      { a = 2; b = 30; c = 300; }
+
+      { a = 3; b = 10; c = 100; }
+      { a = 3; b = 10; c = 200; }
+      { a = 3; b = 10; c = 300; }
+
+      { a = 3; b = 20; c = 100; }
+      { a = 3; b = 20; c = 200; }
+      { a = 3; b = 20; c = 300; }
+
+      { a = 3; b = 30; c = 100; }
+      { a = 3; b = 30; c = 200; }
+      { a = 3; b = 30; c = 300; }
+    ];
+  };
 }
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 6945a241f92fc..9fdbe753dad50 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -444,8 +444,8 @@ in
       in
         # We will generate every possible pair of WM and DM.
         concatLists (
-          crossLists
-            (dm: wm: let
+            builtins.map
+            ({dm, wm}: let
               sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
               script = xsession dm wm;
               desktopNames = if dm ? desktopNames
@@ -472,7 +472,7 @@ in
                   providedSessions = [ sessionName ];
                 })
             )
-            [dms wms]
+            (cartesianProductOfSets { dm = dms; wm = wms; })
           );
 
     # Make xsessions and wayland sessions available in XDG_DATA_DIRS
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index bab091d57acf1..c0b472638a14d 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -5,7 +5,11 @@
 
 let
   inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
-in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
+  testCombinations = pkgs.lib.cartesianProductOfSets {
+    predictable = [true false];
+    withNetworkd = [true false];
+  };
+in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: {
   name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
        + pkgs.lib.optionalString withNetworkd "Networkd";
   value = makeTest {
@@ -30,4 +34,4 @@ in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
       machine.${if predictable then "fail" else "succeed"}("ip link show eth0")
     '';
   };
-}) [[true false] [true false]])
+}) testCombinations)
diff --git a/pkgs/os-specific/solo5/default.nix b/pkgs/os-specific/solo5/default.nix
index 2dbeca98051d9..19d1aa3b5ebe1 100644
--- a/pkgs/os-specific/solo5/default.nix
+++ b/pkgs/os-specific/solo5/default.nix
@@ -50,10 +50,11 @@ in stdenv.mkDerivation {
     homepage = "https://github.com/solo5/solo5";
     license = licenses.isc;
     maintainers = [ maintainers.ehmry ];
-    platforms = lib.crossLists (arch: os: "${arch}-${os}") [
-      [ "aarch64" "x86_64" ]
-      [ "freebsd" "genode" "linux" "openbsd" ]
-    ];
+    platforms = builtins.map ({arch, os}: "${arch}-${os}")
+      (cartesianProductOfSets {
+        arch = [ "aarch64" "x86_64" ];
+        os = [ "freebsd" "genode" "linux" "openbsd" ];
+      });
   };
 
 }