refactor: use bazel-skylib for creating hub repo aliases (#1855)

With this PR the code becomes more maintainable and easier to inspect.
Since bazel-skylib is already a dependency of rules_python, this is
a backwards compatible change.

Skipping the CHANGELOG notes because it should not be an externally
visible change.

Work towards #735.
diff --git a/python/private/render_pkg_aliases.bzl b/python/private/render_pkg_aliases.bzl
index e38f133..5851baf 100644
--- a/python/private/render_pkg_aliases.bzl
+++ b/python/private/render_pkg_aliases.bzl
@@ -42,7 +42,8 @@
         *,
         name,
         default_version,
-        aliases):
+        aliases,
+        **kwargs):
     """Render an alias for common targets."""
     if len(aliases) == 1 and not aliases[0].version:
         alias = aliases[0]
@@ -56,27 +57,36 @@
     # whls that are based on a specific version of Python.
     selects = {}
     no_match_error = "_NO_MATCH_ERROR"
-    default = None
     for alias in sorted(aliases, key = lambda x: x.version):
         actual = "@{repo}//:{name}".format(repo = alias.repo, name = name)
-        selects[alias.config_setting] = actual
+        selects.setdefault(actual, []).append(alias.config_setting)
         if alias.version == default_version:
-            default = actual
+            selects[actual].append("//conditions:default")
             no_match_error = None
 
-    if default:
-        selects["//conditions:default"] = default
-
     return render.alias(
         name = name,
         actual = render.select(
-            selects,
+            {
+                tuple(sorted(
+                    conditions,
+                    # Group `is_python` and other conditions for easier reading
+                    # when looking at the generated files.
+                    key = lambda condition: ("is_python" not in condition, condition),
+                )): target
+                for target, conditions in sorted(selects.items())
+            },
             no_match_error = no_match_error,
+            # This key_repr is used to render selects.with_or keys
+            key_repr = lambda x: repr(x[0]) if len(x) == 1 else render.tuple(x),
+            name = "selects.with_or",
         ),
+        **kwargs
     )
 
 def _render_common_aliases(*, name, aliases, default_version = None):
     lines = [
+        """load("@bazel_skylib//lib:selects.bzl", "selects")""",
         """package(default_visibility = ["//visibility:public"])""",
     ]
 
diff --git a/python/private/text_util.bzl b/python/private/text_util.bzl
index 78f62be..dade9cb 100644
--- a/python/private/text_util.bzl
+++ b/python/private/text_util.bzl
@@ -35,18 +35,18 @@
         ")",
     ])
 
-def _render_dict(d, *, value_repr = repr):
+def _render_dict(d, *, key_repr = repr, value_repr = repr):
     return "\n".join([
         "{",
         _indent("\n".join([
-            "{}: {},".format(repr(k), value_repr(v))
+            "{}: {},".format(key_repr(k), value_repr(v))
             for k, v in d.items()
         ])),
         "}",
     ])
 
-def _render_select(selects, *, no_match_error = None, value_repr = repr):
-    dict_str = _render_dict(selects, value_repr = value_repr) + ","
+def _render_select(selects, *, no_match_error = None, key_repr = repr, value_repr = repr, name = "select"):
+    dict_str = _render_dict(selects, key_repr = key_repr, value_repr = value_repr) + ","
 
     if no_match_error:
         args = "\n".join([
@@ -62,7 +62,7 @@
             "",
         ])
 
-    return "select({})".format(args)
+    return "{}({})".format(name, args)
 
 def _render_list(items):
     if not items:
@@ -80,10 +80,27 @@
         "]",
     ])
 
+def _render_tuple(items, *, value_repr = repr):
+    if not items:
+        return "tuple()"
+
+    if len(items) == 1:
+        return "({},)".format(value_repr(items[0]))
+
+    return "\n".join([
+        "(",
+        _indent("\n".join([
+            "{},".format(value_repr(item))
+            for item in items
+        ])),
+        ")",
+    ])
+
 render = struct(
     alias = _render_alias,
     dict = _render_dict,
     indent = _indent,
     list = _render_list,
     select = _render_select,
+    tuple = _render_tuple,
 )
diff --git a/tests/pip_hub_repository/render_pkg_aliases/render_pkg_aliases_test.bzl b/tests/pip_hub_repository/render_pkg_aliases/render_pkg_aliases_test.bzl
index c61e5ef..ddc9da7 100644
--- a/tests/pip_hub_repository/render_pkg_aliases/render_pkg_aliases_test.bzl
+++ b/tests/pip_hub_repository/render_pkg_aliases/render_pkg_aliases_test.bzl
@@ -65,6 +65,8 @@
 
     want_key = "foo/BUILD.bazel"
     want_content = """\
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
 package(default_visibility = ["//visibility:public"])
 
 alias(
@@ -108,6 +110,8 @@
 
     want_key = "bar_baz/BUILD.bazel"
     want_content = """\
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
 package(default_visibility = ["//visibility:public"])
 
 alias(
@@ -117,40 +121,48 @@
 
 alias(
     name = "pkg",
-    actual = select(
+    actual = selects.with_or(
         {
-            "//:my_config_setting": "@pypi_32_bar_baz//:pkg",
-            "//conditions:default": "@pypi_32_bar_baz//:pkg",
+            (
+                "//:my_config_setting",
+                "//conditions:default",
+            ): "@pypi_32_bar_baz//:pkg",
         },
     ),
 )
 
 alias(
     name = "whl",
-    actual = select(
+    actual = selects.with_or(
         {
-            "//:my_config_setting": "@pypi_32_bar_baz//:whl",
-            "//conditions:default": "@pypi_32_bar_baz//:whl",
+            (
+                "//:my_config_setting",
+                "//conditions:default",
+            ): "@pypi_32_bar_baz//:whl",
         },
     ),
 )
 
 alias(
     name = "data",
-    actual = select(
+    actual = selects.with_or(
         {
-            "//:my_config_setting": "@pypi_32_bar_baz//:data",
-            "//conditions:default": "@pypi_32_bar_baz//:data",
+            (
+                "//:my_config_setting",
+                "//conditions:default",
+            ): "@pypi_32_bar_baz//:data",
         },
     ),
 )
 
 alias(
     name = "dist_info",
-    actual = select(
+    actual = selects.with_or(
         {
-            "//:my_config_setting": "@pypi_32_bar_baz//:dist_info",
-            "//conditions:default": "@pypi_32_bar_baz//:dist_info",
+            (
+                "//:my_config_setting",
+                "//conditions:default",
+            ): "@pypi_32_bar_baz//:dist_info",
         },
     ),
 )"""
@@ -178,6 +190,8 @@
 
     want_key = "bar_baz/BUILD.bazel"
     want_content = """\
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
 package(default_visibility = ["//visibility:public"])
 
 _NO_MATCH_ERROR = \"\"\"\\
@@ -206,7 +220,7 @@
 
 alias(
     name = "pkg",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:pkg",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:pkg",
@@ -217,7 +231,7 @@
 
 alias(
     name = "whl",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:whl",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:whl",
@@ -228,7 +242,7 @@
 
 alias(
     name = "data",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:data",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:data",
@@ -239,7 +253,7 @@
 
 alias(
     name = "dist_info",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:dist_info",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:dist_info",
@@ -273,6 +287,8 @@
 
     want_key = "bar_baz/BUILD.bazel"
     want_content = """\
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
 package(default_visibility = ["//visibility:public"])
 
 _NO_MATCH_ERROR = \"\"\"\\
@@ -301,7 +317,7 @@
 
 alias(
     name = "pkg",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:pkg",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:pkg",
@@ -312,7 +328,7 @@
 
 alias(
     name = "whl",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:whl",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:whl",
@@ -323,7 +339,7 @@
 
 alias(
     name = "data",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:data",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:data",
@@ -334,7 +350,7 @@
 
 alias(
     name = "dist_info",
-    actual = select(
+    actual = selects.with_or(
         {
             "@@//python/config_settings:is_python_3.1": "@pypi_31_bar_baz//:dist_info",
             "@@//python/config_settings:is_python_3.2": "@pypi_32_bar_baz//:dist_info",
diff --git a/tests/private/text_util/render_tests.bzl b/tests/private/text_util/render_tests.bzl
index 7c3dddf..14967a9 100644
--- a/tests/private/text_util/render_tests.bzl
+++ b/tests/private/text_util/render_tests.bzl
@@ -54,6 +54,25 @@
 
 _tests.append(_test_render_alias)
 
+def _test_render_tuple_dict(env):
+    got = render.dict(
+        {
+            ("foo", "bar"): "baz",
+            ("foo",): "bar",
+        },
+        key_repr = render.tuple,
+    )
+    env.expect.that_str(got).equals("""\
+{
+    (
+        "foo",
+        "bar",
+    ): "baz",
+    ("foo",): "bar",
+}""")
+
+_tests.append(_test_render_tuple_dict)
+
 def render_test_suite(name):
     """Create the test suite.