This patch is meant to ensure that console entry point scripts will
always generate on Python 3.7 installations where we may not have access
to importlib.metadata. By running it from a separate process *inside*
the venv, we can be assured to have access to setuptools and by
extension pkg_resources as a fallback.

It isn't strictly necessary to do this for Python 3.8+, which will
always have importlib.metadata available.

Signed-off-by: John Snow <js...@redhat.com>
---
 python/scripts/mkvenv.py | 99 ++++++++++++++++++++++++++++++++++------
 1 file changed, 85 insertions(+), 14 deletions(-)

diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
index 2172774403..4daa652f12 100644
--- a/python/scripts/mkvenv.py
+++ b/python/scripts/mkvenv.py
@@ -11,6 +11,8 @@
 Commands:
   command     Description
     create    create a venv
+    post_init
+              post-venv initialization
 
 --------------------------------------------------
 
@@ -23,6 +25,15 @@
   -h, --help  show this help message and exit
   --gen GEN   Regenerate console_scripts for given packages, if found.
 
+--------------------------------------------------
+
+usage: mkvenv post_init [-h] [--gen GEN] [--binpath BINPATH]
+
+options:
+  -h, --help         show this help message and exit
+  --gen GEN          Regenerate console_scripts for given packages, if found.
+  --binpath BINPATH  Path where console script shims should be generated
+
 """
 
 # Copyright (C) 2022-2023 Red Hat, Inc.
@@ -59,6 +70,7 @@
 # Do not add any mandatory dependencies from outside the stdlib:
 # This script *must* be usable standalone!
 
+DirType = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
 logger = logging.getLogger("mkvenv")
 
 
@@ -89,23 +101,42 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
         self.script_packages = kwargs.pop("script_packages", ())
         super().__init__(*args, **kwargs)
 
-        # The EnvBuilder class is cute and toggles this setting off
-        # before post_setup, but we need it to decide if we want to
-        # generate shims or not.
-        self._system_site_packages = self.system_site_packages
+        # Make the context available post-creation:
+        self._context: Optional[SimpleNamespace] = None
+
+    def ensure_directories(self, env_dir: DirType) -> SimpleNamespace:
+        logger.debug("ensure_directories(env_dir=%s)", env_dir)
+        self._context = super().ensure_directories(env_dir)
+        return self._context
+
+    def create(self, env_dir: DirType) -> None:
+        logger.debug("create(env_dir=%s)", env_dir)
+        super().create(env_dir)
+        assert self._context is not None
+        self.post_post_setup(self._context)
 
     def post_setup(self, context: SimpleNamespace) -> None:
         logger.debug("post_setup(...)")
-
-        # Generate console_script entry points for system packages:
-        if self._system_site_packages:
-            generate_console_scripts(
-                context.env_exe, context.bin_path, self.script_packages
-            )
-
         # print the python executable to stdout for configure.
         print(context.env_exe)
 
+    def post_post_setup(self, context: SimpleNamespace) -> None:
+        """
+        The final, final hook. Enter the venv and run commands inside of it.
+        """
+        args = [
+            context.env_exe,
+            __file__,
+            "post_init",
+            "--binpath",
+            context.bin_path,
+        ]
+        if self.system_site_packages:
+            scripts = ",".join(self.script_packages)
+            if scripts:
+                args += ["--gen", scripts]
+        subprocess.run(args, check=True)
+
 
 def need_ensurepip() -> bool:
     """
@@ -359,6 +390,13 @@ def generate_console_scripts(
     """
     Generate script shims for console_script entry points in @packages.
     """
+    logger.debug(
+        "generate_console_scripts(python_path=%s, bin_path=%s, packages=%s)",
+        python_path,
+        bin_path,
+        packages,
+    )
+
     if not packages:
         return
 
@@ -392,6 +430,17 @@ def _get_entry_points() -> Iterator[Dict[str, str]]:
         logger.debug("wrote '%s'", script_path)
 
 
+def post_venv_setup(bin_path: str, packages: Sequence[str] = ()) -> None:
+    """
+    This is intended to be run *inside the venv* after it is created.
+    """
+    python_path = sys.executable
+    logger.debug(
+        "post_venv_setup(bin_path=%s, packages=%s)", bin_path, packages
+    )
+    generate_console_scripts(python_path, bin_path, packages)
+
+
 def _add_create_subcommand(subparsers: Any) -> None:
     subparser = subparsers.add_parser("create", help="create a venv")
     subparser.add_argument(
@@ -408,6 +457,24 @@ def _add_create_subcommand(subparsers: Any) -> None:
     )
 
 
+def _add_post_init_subcommand(subparsers: Any) -> None:
+    subparser = subparsers.add_parser(
+        "post_init", help="post-venv initialization"
+    )
+    subparser.add_argument(
+        "--gen",
+        type=str,
+        action="append",
+        help="Regenerate console_scripts for given packages, if found.",
+    )
+    subparser.add_argument(
+        "--binpath",
+        type=str,
+        action="store",
+        help="Path where console script shims should be generated",
+    )
+
+
 def main() -> int:
     """CLI interface to make_qemu_venv. See module docstring."""
     if os.environ.get("DEBUG") or os.environ.get("GITLAB_CI"):
@@ -429,19 +496,23 @@ def main() -> int:
     )
 
     _add_create_subcommand(subparsers)
+    _add_post_init_subcommand(subparsers)
 
     args = parser.parse_args()
+    script_packages = []
+    for element in args.gen or ():
+        script_packages.extend(element.split(","))
+
     try:
         if args.command == "create":
-            script_packages = []
-            for element in args.gen or ():
-                script_packages.extend(element.split(","))
             make_venv(
                 args.target,
                 system_site_packages=True,
                 clear=True,
                 script_packages=script_packages,
             )
+        if args.command == "post_init":
+            post_venv_setup(args.binpath, script_packages)
         logger.debug("mkvenv.py %s: exiting", args.command)
     except Ouch as exc:
         print("\n*** Ouch! ***\n", file=sys.stderr)
-- 
2.39.2


Reply via email to