cli: deploy: Don't assume the Python interpreter

Portage may be installed for a different interpreter than
/usr/bin/python.  Stop assuming that's the interpreter we should use
for Portage, and instead detect it by searching for the emerge
launcher in /usr/lib/python-exec.

BUG=b:291125423
TEST="cros deploy" a package onto a device with Portage installed for
     Python 3.8
TEST=unit tests

Change-Id: I640ad922b45bf2d56a2a6678e96225456bb13b82
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4685754
Auto-Submit: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: Alex Klein <saklein@chromium.org>
Commit-Queue: Alex Klein <saklein@chromium.org>
Tested-by: Jack Rosenthal <jrosenth@chromium.org>
diff --git a/cli/deploy.py b/cli/deploy.py
index 253b882..90166db 100644
--- a/cli/deploy.py
+++ b/cli/deploy.py
@@ -17,6 +17,7 @@
 import logging
 import os
 from pathlib import Path
+import re
 import tempfile
 from typing import Dict, List, NamedTuple, Set, Tuple
 
@@ -455,6 +456,36 @@
 
         return db
 
+    def _get_portage_interpreter(
+        self, device: remote_access.RemoteDevice
+    ) -> str:
+        """Get the Python interpreter that should be used for Portage.
+
+        Args:
+            device: The device to find the interpreter on.
+
+        Returns:
+            The executable that should be used for Python.
+        """
+        result = device.agent.RemoteSh(
+            "ls -1 /usr/lib/python-exec/python*/emerge"
+        )
+        emerge_bins = [Path(x) for x in result.stdout.splitlines()]
+        if not emerge_bins:
+            raise self.VartreeError(
+                "No suitable Python interpreter found for Portage."
+            )
+
+        # If Portage is installed for multiple Python versions, prefer the
+        # interpreter with the highest version.
+        def _parse_version(name):
+            match = re.fullmatch(r"python(\d+)\.(\d+)", name)
+            if match:
+                return tuple(int(x) for x in match.groups())
+            return (0, 0)
+
+        return max((x.parent.name for x in emerge_bins), key=_parse_version)
+
     def _InitTargetVarDB(
         self,
         device: remote_access.RemoteDevice,
@@ -464,9 +495,10 @@
     ) -> None:
         """Initializes a dictionary of packages installed on |device|."""
         get_vartree_script = self._GetVartreeSnippet(root)
+        python = self._get_portage_interpreter(device)
         try:
             result = device.agent.RemoteSh(
-                ["python"], remote_sudo=True, input=get_vartree_script
+                [python], remote_sudo=True, input=get_vartree_script
             )
         except cros_build_lib.RunCommandError as e:
             logging.error("Cannot get target vartree:\n%s", e.stderr)