Repository: pypa/virtualenv

Issue:
Use the builtin plugin classes unless another plugin class is specifically asked for.
**What's the problem this feature will solve?**

I have a private plugin for virtualenv where I add an additional discovery class. This discovery class adds a new argument to the command line parser that is required but only when the discovery class is chosen. However I'm seeing an issue where using virtualenv via the command line as normal is now asking for this argument. The reason seems to be that virtualenv is picking a default discovery class but in a non-deterministic way and sometimes the additional discovery class is chosen as the default discovery class and so the argument is required. The default class is chosen depending on which entry point is discovered first. I believe entry points give no guarantees about order of discovery.

The order of entry points discovery seems to change in different installs of virtualenv and the plugin, rather than changing in the same environment between different invocations of virtualenv.

I believe the problem will be the same for creators, seeders, and activators as well.

**Describe the solution you'd like**

I would expect the builtin discovery class to be chosen as the default discovery class unless explicitly set otherwise.

**Alternative Solutions**

These classes could have a priority set at the class level. The builtin classes would have a priority set such that a plugin class could opt to set it's priority lower or higher than the builtins. virtualenv would then order these classes by their priority. Classes would be allowed to have the same priority with the understanding that the order of classes with the same priority value would be non-deterministic.



Discussion:
A PR achieving this would be welcomed and reviewed 👍🏻 

Resolving patch:
diff --git a/src/virtualenv/run/plugin/discovery.py b/src/virtualenv/run/plugin/discovery.py
--- a/src/virtualenv/run/plugin/discovery.py
+++ b/src/virtualenv/run/plugin/discovery.py
@@ -13,10 +13,13 @@ def get_discover(parser, args):
         title="discovery",
         description="discover and provide a target interpreter",
     )
+    choices = _get_default_discovery(discover_types)
+    # prefer the builtin if present, otherwise fallback to first defined type
+    choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1)
     discovery_parser.add_argument(
         "--discovery",
-        choices=_get_default_discovery(discover_types),
-        default=next(i for i in discover_types.keys()),
+        choices=choices,
+        default=next(iter(choices)),
         required=False,
         help="interpreter discovery method",
     )


Test patch:
diff --git a/tests/unit/config/cli/test_parser.py b/tests/unit/config/cli/test_parser.py
--- a/tests/unit/config/cli/test_parser.py
+++ b/tests/unit/config/cli/test_parser.py
@@ -44,3 +44,14 @@ def test_reset_app_data_does_not_conflict_clear():
     session_via_cli(["--clear", "venv"], options=options)
     assert options.clear is True
     assert options.reset_app_data is False
+
+
+def test_builtin_discovery_class_preferred(mocker):
+    mocker.patch(
+        "virtualenv.run.plugin.discovery._get_default_discovery",
+        return_value=["pluginA", "pluginX", "builtin", "Aplugin", "Xplugin"],
+    )
+
+    options = VirtualEnvOptions()
+    session_via_cli(["venv"], options=options)
+    assert options.discovery == "builtin"